Source code for skpar.core.parameters

"""***Parameters***

Module for handling parameters in the context of optimisation.

The following assumptions are made:

    * parameters have no meaning to the optimiser engine
    * for the optimiser, parameters are just a list of values to be manipulated
    * the user must create an OrderedDict of parameter name:value pairs;
      the OrderedDict eliminates automatically duplicate definitions of parameters,
      yielding the minimum possible number of degrees of freedom for the optimiser
    * the OrderedDictionary is built by parsing an skdefs.template file that
      contains parameter-strings (conveying the name, initial value and range).
    * skdefs.template is parsed by functions looking for a given format from which
      parameter-strings are extracted
    * the Class Parameter is initialised from a parameter-string.
    * a reduced skdefs.template is obtained, by removing the initial value and range
      from the input skdefs.template which is ready for creating the final skdefs
      file by string.format(dict(zip(ParNames,Parvalues)) substitution.
"""
import os.path
from skpar.core.utils import get_logger

LOGGER = get_logger("__name__")


[docs]def get_parameters(userinp): """Parse user input for definitions of parameters. The definitions should be of the form ('name', 'optionally_something'). The optional part could in principle be one or more str/float/int. """ params = [] for pardef in userinp: try: # Due to yaml/json representation, each parameter definition is a # dictionary on its own, with one item. Using (key, val), we # extract the one and only item in this dict. ((pname, pdef),) = pardef.items() except AttributeError: # pardef cannot be itemised => assume just pname pname = pardef pdef = "" try: parstring = " ".join( [ pname, ] + pdef.split() ) except AttributeError: # pdef turns out to not be a string try: # assume it is a list of floats parstring = " ".join( [ pname, ] + ["{}".format(v) for v in pdef] ) except TypeError: # pdef not iterable; assume it is a single float parstring = " ".join([pname, str(pdef)]) # initialise from string newpar = Parameter(parstring) params.append(newpar) return params
[docs]class Parameter(object): """ A parameter object, that is initialised from a string. ParameterName InitialValue MinValue Maxvalue [ParameterType] ParameterName MinValue Maxvalue [ParameterType] ParameterName InitialValue [ParameterType] ParameterName [ParameterType] ParameterName must be alphanumeric, allowing _ too. Iinit/Min/MaxValue can be integer or float ParameterType is optional (float by default), and indicated by either 'i'(int) or 'f'(float) White space separation is mandatory. """ # permitted parameter types typedict = {"i": int, "f": float} def __init_from_kwargs(self, name, **kwargs): """ Look for one obligatory argument -- name, and the rest is optional. """ self.name = name for key in ["value", "minv", "maxv"]: _val = kwargs.get(key, None) setattr(self, key, _val) def __init_from_string(self, parameterstring): """ assume S is a string of one of the following forms: ParName initial min max partype ParName min max partype ParName initial partype ParName partype Permit only 'i' or 'f' for partype and make it optional """ # take away spaces, check format consistency and get type assert isinstance(parameterstring, str) words = parameterstring.split() if len(words) > 1: if words[-1] in list(Parameter.typedict.keys()): self.ptype = Parameter.typedict[words[-1]] words = words[:-1] else: self.ptype = float # extract data, converting values to appropriate type self.name = words[0] # the conversion from words to float to type allows one to do # %(name,1.0,2.0,3.0)i; otherwise map(int,'3.0') gives valueerror floats = list(map(float, words[1:])) try: self.value, self.minv, self.maxv = list(map(self.ptype, floats)) except ValueError: try: self.minv, self.maxv = list(map(self.ptype, floats)) self.value = 0.0 except ValueError: # note at this stage value is a single item, not a list self.value = list(map(self.ptype, floats))[0] self.minv = None self.maxv = None except: print("Parameter string not understood: {}".format(parstring)) except: print("Parameter string not understood: {}".format(parstring)) else: self.ptype = float self.name = words[0] self.value = None self.minv = None self.maxv = None def __init__(self, string, **kwargs): """wrapper init method The whole thing became patchy. It needs a careful revision. """ if ( string.split() == [ string, ] and kwargs ): self.__init_from_kwargs(string, **kwargs) else: self.__init_from_string(string) def __repr__(self): return "Parameter {name} = {val}, range=[{minv},{maxv}]".format( name=self.name, val=self.value, minv=self.minv, maxv=self.maxv )
[docs]def substitute_template(parameters, parnames, templatefile, resultfile): """Substitute a template with actual parameter values. Args: parameters (list): Parameter list, items being either floats or objects with .value and .name attributes. parnames (list): If `parameters` is a list of floats, then parnames is the list of corresponding names. templatefile (str): Name of template file with substitution patterns. resultfile (str): Name of file to contain the substituted result. """ with open(templatefile, "r") as fin: template = fin.read() try: pardict = dict([(p.name, p.value) for p in parameters]) except AttributeError: pardict = dict([(name, val) for (name, val) in zip(parnames, parameters)]) updated = update_template(template, pardict) with open(resultfile, "w") as fout: fout.write(updated)
[docs]def update_template(template, pardict): """Makes variable substitution in a template. Args: template (str): Template with old style Python format strings. pardict (dict): Dictionary of parameters to substitute. Returns: str: String with substituted content. """ return template % pardict
[docs]def update_parameters(workroot, templates, parameters, parnames=None): """Update relevant templates with new parameter values. Args: workroot (str): Root working directory, template names are relative to this directory. templates (list): List of ascii filenames containing placeholders for inserting parameter values. The place holders must follow the old string formatting of Python: %(ParameterName)ParameterType. parameters (list): Either a list of floats, or a list of objects (each having .value and .name attributes) parnames (list): If `parameters` is a list of floats, then parnames is the list of corresponding names. """ # Overwrite parnames with the names of the parameter objects, if available try: parnames = [p.name for p in parameters] except AttributeError: # parameters is a list of floats; double check! assert all([isinstance(p, (float, int)) for p in parameters]), "{}".format( parameters ) # test consistency if parnames is given (may be None) if parnames is not None: assert len(parameters) == len(parnames) except TypeError: assert parameters is None parnames = None # Prepare the values to write try: parvalues = [p.value for p in parameters] except AttributeError: parvalues = parameters except TypeError: assert parameters is None parvalues = None # Udpate (fill in) template files if templates: for ftempl in templates: assert parnames is not None fin = os.path.normpath(os.path.join(workroot, ftempl)) path, templname = os.path.split(fin) name = templname.replace("template.", "") fout = os.path.join(path, name) substitute_template(parvalues, parnames, fin, fout)