Example #1
0
 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
     }))
Example #2
0
 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}))
Example #3
0
 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)
Example #4
0
 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
Example #5
0
 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)
Example #6
0
 def test__str(self):
     P = NTParameterSet(self.example)
     as_string = str(P)
     self.assertIsInstance(as_string, str)
     self.assertEqual(P, NTParameterSet(as_string))
Example #7
0
 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")
Example #8
0
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)
Example #9
0
    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