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)