def test_diff(self): P1 = NTParameterSet(self.example) P2 = NTParameterSet({ "y": { "a": -2, "b": [4, 5, 6], "c": 55, }, "x": 2.9, "z": 100, "mylabel": "Dodge City", "have_horse": True }) self.assertEqual(P1.diff(P2), ({ 'y': { 'c': 5 }, 'mylabel': 'camelot', 'have_horse': False }, { 'y': { 'c': 55 }, 'mylabel': 'Dodge City', 'have_horse': True }))
def test_diff(self): P1 = NTParameterSet(self.example) P2 = NTParameterSet({ "y": { "a": -2, "b": [4, 5, 6], "c": 55, }, "x": 2.9, "z": 100, "mylabel": "Dodge City", "have_horse": True}) self.assertEqual(P1.diff(P2), ({'y': {'c': 5}, 'mylabel': 'camelot', 'have_horse': False}, {'y': {'c': 55}, 'mylabel': 'Dodge City', 'have_horse': True}))
def test__pop(self): P = NTParameterSet(self.example) self.assertEqual(P.pop('x'), 2.9) self.assertEqual(P.pop('have_horse'), False) self.assertEqual(P.pop('y'), {"a": -2, "b": [4, 5, 6], "c": 5}) self.assertEqual(P.as_dict(), {'z': 100, 'mylabel': 'camelot'}) self.assertEqual(P.pop('foo', 42), 42) self.assertEqual(P.pop('foo', None), None)
def desc(self): desc = ParameterSet({ 'input type': 'Random variable', 'generator': self.gen, 'module': self.module, # Module where constructor is defined 'frozen': self.frozen, }) if self.frozen: if None in (self.args, self.kwds): warn( "Cannot produce a valid description for a frozen " "distribution if it doesn't not save `args` and `kwds` " "attributes (this happens for multivariate distributions)." ) desc.frozen = 'invalid' # Will make valid_desc return False else: desc.args = self.rv.args desc.kwds = self.rv.kwds return desc
def test__str(self): P = NTParameterSet(self.example) as_string = str(P) self.assertIsInstance(as_string, str) self.assertEqual(P, NTParameterSet(as_string))
def test__json_should_be_accepted(self): P = NTParameterSet(json.dumps(self.example)) self.assertEqual(P.y.a, -2) self.assertEqual(P.y.b, [4, 5, 6]) self.assertEqual(P.x, 2.9) self.assertEqual(P.mylabel, "camelot")
def describe(v): """ Provides a single method, `describe`, for producing a unique and reproducible description of a variable. Description of sequences is intentionally not a one-to-one. From the point of view of parameters, all sequences are the same: they are either sequences of values we iterate over, or arrays used in vector operations. Both these uses are supported by by `ndarray`, so we treate sequences as follows: - For description (this function), all sequences are converted lists. This has a clean and compact string representation which is compatible with JSON. - When interpreting saved parameters, all sequences (which should all be lists) are converted to `ndarray`. Similarly, all mappings are converted to `ParameterSet`. This function is essentially one big if-else statement. """ if isinstance(v, PlainArg) or v is None: return v elif isinstance(v, Sequence): r = [describe(u) for u in v] # OK, so it seems that Sumatra is ok with lists of dicts, but I leave # this here in case I need it later. Goes with "arg" test for Mappings # if not all(isinstance(u, PlainArg+(list,)) for u in r): # # Sumatra only supports (nested) lists of plain args # # -> Convert the list into a ParameterSet # r = ParameterSet({f'arg{i}': u for i,u in enumerate(r)}) return r elif isinstance(v, np.ndarray): return v.tolist() elif isinstance(v, Mapping): # Covers ParameterSetBase # I think Sumatra only supports strings as keys r = ParameterSet({str(k): describe(u) for k, u in v.items()}) for k in r.keys(): # if k[:3].lower() == "arg": # warn(f"Mapping keys beginning with 'arg', such as {k}, " # "are reserved by `smttask`.") # break if k.lower() == "type": warn("The mapping key 'type' is reserved by Sumatra and will " "prevent the web interface from displaying the " "parameters.") return r elif isinstance(v, Iterable): warn(f"Attempting to describe an iterable of type {type(v)}. Only " "Sequences (list, tuple) and ndarrays are properly supported.") return v # elif isinstance(v, File): # return v.desc elif isinstance(v, DataFile): return File.get_desc(v.full_path) # elif isinstance(v, (Task, StatelessFunction, File)): elif hasattr(v, 'desc'): return v.desc elif isinstance(v, type): s = repr(v) if '<locals>' in s: warn( f"Type {s} is dynamically generated and thus not reproducible." ) return s # scipy.stats Distribution types # elif isinstance(v, # (_mv.multi_rv_generic, _mv.multi_rv_frozen)): # if isinstance(v, _mv.multivariate_normal_gen): # return "multivariate_normal" # elif isinstance(v, _mv.multivariate_normal_frozen): # return f"multivariate_normal(mean={v.mean()}, cov={v.cov()})" # else: # warn(dist_warning.format(type(v))) # return repr(v) # elif isinstance(v, _mv.multi_rv_frozen): # if isinstance(v, _mv.multivariate_normal_gen): # return f"multivariate_normal)" # else: # warn(dist_warning.format(type(v))) # return repr(v) else: warn("Task was not tested on inputs of type {}. " "Please make sure task digests are unique " "and reproducible.".format(type(v))) return repr(v)
def _merge_params_and_taskinputs(cls, params, taskinputs): """ params: arguments passed as a dictionary to constructor As a special case, if a task has only one input, it does not need to be wrapped in a dict (i.e. `params` can be the value itself). taskinputs: arguments passed directly as keywords to constructor This function does the following: + Merge dictionary and keyword arguments. Keyword arguments take precedence. + Check that all arguments required by task `run()` signature are provided. + Retrieve any missing argument values from the defaults in `run()` signature. + Cast every input to its expected type. If an input defines multiple allowable types, the left-most one takes precedence. """ if params is None: params = {} elif isinstance(params, str): params = build_parameters(params) elif isinstance(params, dict): params = ParameterSet(params) else: if len(cls.inputs) == 1: # For tasks with only one input, don't require dict θname, θtype = next(iter(cls.inputs.items())) if len(taskinputs) > 0: raise TypeError(f"Argument given by name {θname} " "and position.") # if not isinstance(taskinputs, θtype): # # Cast to correct type # taskinputs = cast(taskinputs, θtype) taskinputs = ParameterSet({θname: taskinputs}) else: raise ValueError("`params` should be either a dictionary " "or a path to a parameter file, however it " "is of type {}.".format(type(params))) taskinputs = {**params, **taskinputs} sigparams = inspect.signature(cls._run).parameters required_inputs = [ p.name for p in sigparams.values() if p.default is inspect._empty ] default_inputs = { p.name: p.default for p in sigparams.values() if p.default is not inspect._empty } if type(cls.__dict__['_run']) is not staticmethod: # instance and class methods already provide 'self' or 'cls' firstarg = required_inputs.pop(0) # Only allowing 'self' and 'cls' ensures we don't accidentally # remove true input arguments assert firstarg in ("self", "cls") if not all((p in taskinputs) for p in required_inputs): raise TypeError("Missing required inputs '{}'.".format( set(required_inputs).difference(taskinputs))) # Add default inputs so they are recorded as task arguments taskinputs = {**default_inputs, **taskinputs} # Finally, cast all task inputs for name, θ in taskinputs.items(): θtype = cls.inputs[name] if isinstance(θ, LazyCastTypes): # Can't cast e.g. tasks: they haven't been executed yet continue elif not isinstance(θ, θtype): taskinputs[name] = cast(θ, θtype, 'input') return taskinputs