Source code for emat.scope.parameter


import numbers
from typing import Collection, Any, Mapping
import numpy

from ..workbench.em_framework import parameters as workbench_param
from scipy import stats
from scipy.stats._distn_infrastructure import rv_frozen

from ..util import distributions, DistributionTypeError, DistributionFreezeError
from ..util import make_rv_frozen, rv_frozen_as_dict
from .names import ShortnameMixin, TaggableMixin

def standardize_parameter_type(original_type):
    """Standardize parameter type descriptions

    Args:
        original_type (str): The original type

    Returns:
        str: The standarized type name
    """
    original_type = original_type.lower()
    if 'unc' in original_type:
        return 'uncertainty'
    if 'lev' in original_type:
        return 'lever'
    elif 'con' in original_type or 'fix' in original_type:
        return 'constant'
    raise ValueError('cannot decipher parameter ptype')

def standardize_data_type(original_type):
    dtype = str(original_type).lower()
    dtype = {
        'float': 'real',
        'float32': 'real',
        'float64': 'real',
        'floating': 'real',
        'double': 'real',
        'integer': 'int',
        'int32': 'int',
        'int64': 'int',
        'long': 'int',
        'boolean': 'bool',
        'category': 'cat',
        'categorical': 'cat',
    }.get(dtype, dtype)
    return dtype


def _get_bounds_from_dist(dist):
    ppf_zero = 0
    try:
        if isinstance(dist.dist, stats.rv_discrete):
            # ppf at actual zero for rv_discrete gives lower bound - 1
            # due to a quirk in the scipy.stats implementation
            # so we use the smallest positive float instead
            ppf_zero = 5e-324
    except AttributeError:
        pass
    lower_bound = dist.ppf(ppf_zero)
    upper_bound = dist.ppf(1.0)
    return lower_bound, upper_bound

def _get_lower_bound_from_dist(dist):
    ppf_zero = 0
    try:
        if isinstance(dist.dist, stats.rv_discrete):
            # ppf at actual zero for rv_discrete gives lower bound - 1
            # due to a quirk in the scipy.stats implementation
            # so we use the smallest positive float instead
            ppf_zero = 5e-324
    except AttributeError:
        pass
    lower_bound = dist.ppf(ppf_zero)
    return lower_bound

def _get_upper_bound_from_dist(dist):
    upper_bound = dist.ppf(1.0)
    return upper_bound

[docs]def make_parameter( name, ptype='constant', desc='missing description', min=None, max=None, dist=None, default=None, corr=None, address=None, dtype='infer', values=None, resolution=None, shortname=None, abbrev=None, ): """ Factory method to build a Parameter or Constant for a model. This function will create an object of the appropriate (sub)class for the parameter or constant. Args: name (str): A name for this parameter. The name must be a `str` and ideally a valid Python identifier (i.e., begins with a letter or underscore, contains only letters, numerals, and underscores). ptype (str, default 'constant'): The type for this parameter, one of {'constant', 'uncertainty', 'lever'}. min (numeric, optional): The minimum value for this parameter. max (numeric, optional): The maximum value for this parameter. dist (str or Mapping or rv_frozen, optional): A definition of a distribution to use for this parameter. Can be specified just as the name of the distribution when that distribution is parameterized only by the min and max (e.g., 'uniform'). If the distribution requires other parameters, this argument should be a Mapping, with keys including 'name' for the name of the distribution, as well as giving one or more named distributional parameters as appropriate. Or, just pass a rv_frozen object directly (see scipy.stats). The distribution defined here is of primary use for uncertainty parameters, as the features of defined uncertainty distributions can be used to derive probability distributions on outputs. However, distributions can also be used for policy lever parameters to guide the development of appropriate experimental designs. default (Any, optional): A default value for this parameter. The default value is used as the actual value for constant parameters. It is also used during univariate sensitivity testing as the value for this parameter when other parameters are being evaluated at non-default values. corr (dict, optional): A correlation definition that relates this parameter to others. Only applicable for uncertainty parameters. address (Any, optional): The address to use to access this parameter in the model. This is an implementation-specific detail. For example, in an Excel-based model, the address could be a sheet and cell reference given as a string. dtype (str, default 'infer'): A dtype for this parameter, one of {'cat', 'int', 'real', 'bool'} or some sub-class variant or specialization thereof (e.g., int64). values (Collection, optional): A collection of possible values, relevant only for categorical parameters. resolution (Collection, optional): A collection of possible particular values, used to set the possible values considered when sampling with factorial-based designs. shortname (str, optional): A shorter name, especially useful when the name of this parameter is a long strings that may not display neatly in figures. abbrev (Mapping, optional): A set of abbreviations used for values, especially useful when the names of values are long strings that may not display neatly in figures. Returns: Parameter or Constant """ # Convert dist to a Mapping if it is just a string if isinstance(dist, str): dist = {'name': dist} # Default correlation is an empty list. corr = corr if corr is not None else [] # Standardize the dtype to a lowercase string of # correct type dtype = standardize_data_type(dtype) if dtype == 'infer': if values is not None: if set(values) == {True, False}: dtype = 'bool' else: dtype = 'cat' elif max is True and min is False: dtype = 'bool' elif isinstance(min, numbers.Integral) and isinstance(max, numbers.Integral): dtype = 'int' elif isinstance(min, numbers.Real) and isinstance(max, numbers.Real): dtype = 'real' elif dist is not None and isinstance(dist, Mapping): try: make_rv_frozen(**dist, discrete=True) except DistributionTypeError: dtype = 'real' else: dtype = 'int' else: raise ValueError(f'cannot infer dtype for {name}, give it explicitly') if dtype not in ('cat', 'int', 'real', 'bool'): message = f"invalid dtype {dtype} for parameter {name}, must be 'cat', 'int', 'real', or 'bool'" if 'unc' in dtype: message += "\n(to set this parameter as an uncertainty, set `ptype` to 'uncertainty', not `dtype`)" elif 'lev' in dtype: message += "\n(to set this parameter as a lever, set `ptype` to 'lever', not `dtype`)" elif 'con' in dtype: message += "\n(to set this parameter as a constant, set `ptype` to 'constant', not `dtype`)" raise ValueError(message) # Data checks if dist is not None and not isinstance(dist, Mapping) and not isinstance(dist, rv_frozen): raise TypeError(f'dist must be a dict or rv_frozen for {name}, not {type(dist)}') if dist is None: dist_ = {} rv_gen = None elif isinstance(dist, rv_frozen): dist_ = {'name': dist.dist.name} dist_.update(dist.kwds) rv_gen = dist else: dist_ = dist rv_gen = None dist_for_maker = dist_.copy() if min is not None: dist_for_maker['min'] = min if max is not None: dist_for_maker['max'] = max if dtype=='bool': dist_for_maker['min'] = 0 dist_for_maker['max'] = 1 elif dtype=='cat': dist_for_maker['min'] = 0 dist_for_maker['max'] = (len(values)-1) if values is not None else 0 ptype = standardize_parameter_type(ptype) if ptype is 'constant': if dist_.get('name') is None: dist_['name'] = 'constant' if dist_.get('name') != 'constant': raise ValueError(f'constant cannot have non-constant distribution for {name}') rv_gen_tentative = None else: # If inferred dtype is int but distribution is discrete, promote to real try: rv_gen_tentative = rv_gen or make_rv_frozen(**dist_for_maker, discrete=True) except DistributionTypeError: if dtype == 'int': dtype = 'real' rv_gen_tentative = rv_gen or make_rv_frozen(**dist_for_maker, discrete=False) except DistributionFreezeError as err: raise DistributionFreezeError(f'on freeze for {name}') from err if dtype == 'bool': if min is None: min = False if min != False: raise ValueError(f'min of bool must be False for {name}') if max is None: max = True if max != True: raise ValueError(f'max of bool must be True for {name}') values = [False, True] if dtype in {'int', 'real'}: if dist_.get('name') == 'constant': if min is None and default is not None: min = default if max is None and default is not None: max = default if rv_gen_tentative is not None: min, max = _get_bounds_from_dist(rv_gen_tentative) if min is None: raise ValueError(f'min of {dtype} is required for {name}') if max is None: raise ValueError(f'max of {dtype} is required for {name}') # Create the Parameter if ptype == 'constant': if dtype == 'cat': p = Constant(name, default, desc=desc, address=address, dtype='cat') else: p = Constant(name, default, desc=desc, address=address) elif dtype == 'cat': p = CategoricalParameter( name, values, default=default, desc=desc, address=address, ptype=ptype, corr=corr, abbrev=abbrev, shortname=shortname, ) elif dtype == 'int': rv_gen = rv_gen or make_rv_frozen(**dist_for_maker, discrete=True) if rv_gen is None: raise ValueError(f'failed to make {name} ({ptype}) from {dist_}') # p = Constant(name, default, desc=desc, address=address) else: p = IntegerParameter( name, lower_bound=min, upper_bound=max, resolution=resolution, default=default, dist=rv_gen, dist_def=dist_, desc=desc, address=address, ptype=ptype, corr=corr, abbrev=abbrev, shortname=shortname, ) elif dtype == 'real': rv_gen = rv_gen or make_rv_frozen(**dist_for_maker) if rv_gen is None: raise ValueError(f'failed to make {name} ({ptype}) from {dist_}') # p = Constant(name, default, desc=desc, address=address) else: p = RealParameter( name, lower_bound=min, upper_bound=max, resolution=resolution, default=default, dist=rv_gen, dist_def=dist_, desc=desc, address=address, ptype=ptype, corr=corr, abbrev=abbrev, shortname=shortname, ) elif dtype == 'bool': rv_gen = rv_gen or make_rv_frozen(**dist_for_maker, discrete=True) if rv_gen is None: raise ValueError(f'failed to make {name} ({ptype}) from {dist_}') # p = Constant(name, default, desc=desc, address=address) else: p = BooleanParameter( name, default=default, dist=rv_gen, dist_def=dist_, desc=desc, address=address, ptype=ptype, corr=corr, abbrev=abbrev, shortname=shortname, ) else: raise ValueError(f"invalid dtype {dtype}") return p
[docs]class Constant(workbench_param.Constant): ptype = 'constant' """str: Parameter type, for compatibility with Parameter.""" def __init__(self, name, value, desc="", address=None, dtype=None): if value is None: raise ValueError("Constant.value cannot be None") workbench_param.Constant.__init__( self, name, value, ) self.desc = desc """str: Human readable description of this constant, for reference only""" self.address = address """ Any: The address to use to access this parameter in the model. This is an implementation-specific detail. For example, in an Excel-based model, the address could be a sheet and cell reference given as a string. """ if dtype is None: dtype = numpy.asarray(value).dtype dtype = standardize_data_type(dtype) self.dtype = dtype """str: The dtype for the value, as a string.""" @property def default(self): """Read-only alias for value""" return self.value @property def values(self): """list: The value as a one-item list""" return [self.value,] def __eq__(self, other): try: if self.address != other.address: return False if self.dtype != other.dtype: return False except AttributeError: return False return super().__eq__(other) def __ne__(self, other): return not self.__eq__(other)
[docs]class Parameter(workbench_param.Parameter, ShortnameMixin, TaggableMixin): dtype = None def __init__( self, name, dist, *, lower_bound=None, upper_bound=None, resolution=None, default=None, variable_name=None, pff=False, desc="", address=None, ptype=None, corr=None, dist_def=None, shortname=None, abbrev=None, tags=None, ): # The default constructor for ema_workbench parameters uses no distribution # But for EMAT, we want to always define a distribution explicitly # for clarity. if dist is None and (lower_bound is None or upper_bound is None): raise ValueError("must give lower_bound and upper_bound, or dist") if dist is None: from scipy.stats import uniform dist = uniform(lower_bound, upper_bound-lower_bound) if isinstance(dist, str): dist = {'name':dist} if isinstance(dist, Mapping): dist = dict(**dist) if lower_bound is not None: dist['min'] = lower_bound if upper_bound is not None: dist['max'] = upper_bound dist = make_rv_frozen(**dist) # We extract and set the lower and upper bounds here, # in order to use the default constructor from the workbench. ppf_zero = 0 if isinstance(dist.dist, stats.rv_discrete): # @UndefinedVariable # ppf at actual zero for rv_discrete gives lower bound - 1 # due to a quirk in the scipy.stats implementation # so we use the smallest positive float instead ppf_zero = 5e-324 lower_bound = dist.ppf(ppf_zero) upper_bound = dist.ppf(1.0) if self.dtype == 'int': lower_bound = int(lower_bound) upper_bound = int(upper_bound) workbench_param.Parameter.__init__( self, name=name, lower_bound=lower_bound, upper_bound=upper_bound, resolution=resolution, default=default, variable_name=variable_name, pff=pff, ) self.dist = dist self.desc = desc """str: Human readable description of this parameter, for reference only""" self.address = address """ Any: The address to use to access this parameter in the model. This is an implementation-specific detail. For example, in an Excel-based model, the address could be a sheet and cell reference given as a string. """ self.ptype = standardize_parameter_type(ptype) if ptype is not None else None """str: Parameter type, one of {'constant', 'uncertainty', 'lever'}""" self.corr = corr if corr is not None else [] """Dict: A correlation definition. Key give names of other parameters, values give correlation.""" self.dist_def = dict(dist_def) if dist_def is not None else {} """Dict: The arguments that define the underlying distribution.""" self._shortname = shortname self.abbrev = abbrev or {} """Dict: Abbreviations used for long attribute names in figures.""" if tags: if isinstance(tags, str): tags = [tags] for tag in tags: self.add_tag(tag) @property def min(self): return self.lower_bound @property def max(self): return self.upper_bound def __eq__(self, other): try: if type(self) != type(other): return False if self.address != other.address: return False if self.dtype != other.dtype: return False if self.ptype != other.ptype: return False if self.corr != other.corr: return False if self.distdef != other.distdef: print(f"distdef not equal: {self.distdef} != {other.distdef}") return False except AttributeError: return False if not isinstance(self, other.__class__): return False self_keys = set(self.__dict__.keys()) other_keys = set(other.__dict__.keys()) if self_keys - other_keys: return False else: for key in self_keys: if key == 'dist_def': continue if key != 'dist': if getattr(self, key) != getattr(other, key): return False else: # name, parameters self_dist = getattr(self, key) other_dist = getattr(other, key) if self_dist.dist.name != other_dist.dist.name: return False if self_dist.args != other_dist.args: return False else: return True @property def distdef(self): result = rv_frozen_as_dict(self.dist, self.min, self.max) return result @property def dist_description(self): result = self.dist_def if isinstance(result, str): result = {'name':result} if result.get('name') == 'pert': if 'gamma' in result: return f"Pert Distribution (min={self.min}, peak={result.get('peak')}, max={self.max}, gamma={result.get('gamma')})" return f"Pert Distribution (min={self.min}, peak={result.get('peak')}, max={self.max})" if result.get('name') == 'uniform' or len(result)==0: return f"Uniform Distribution (min={self.min}, max={self.max})" if result.get('name') == 'triangle': return f"Trianglar Distribution (min={self.min}, peak={result.get('peak')}, max={self.max})" return str(result) def __repr__(self): return f"<emat.{self.__class__.__name__} '{self.name}'>" def _hash_it(self, ha=None): from ..util.hasher import hash_it return hash_it( self.name, self.dist_def, self.resolution, self.address, self.dtype, self.pff, tuple(self.variable_name), self.shortname, self.ptype, self.corr, ha=ha, )
[docs] def get_abbrev(self, name): """Get an abbreviated name if available.""" try: return self.abbrev.get(name, name) except AttributeError: return name
def set_abbrev(self, values=None, **kwargs): if values is None: values = {} try: self.abbrev.update(values, **kwargs) except AttributeError: self.abbrev = {} self.abbrev.update(values, **kwargs)
[docs]class RealParameter(Parameter, workbench_param.RealParameter): dtype = 'real' def __init__(self, name, *, lower_bound=None, upper_bound=None, resolution=None, default=None, variable_name=None, pff=False, dist=None, dist_def=None, desc="", address=None, ptype=None, corr=None, shortname=None, abbrev=None): if dist is None and (lower_bound is None or upper_bound is None): raise ValueError("must give lower_bound and upper_bound, or dist") if dist is None: from scipy.stats import uniform dist = uniform(lower_bound, upper_bound-lower_bound) Parameter.__init__( self, name, dist=dist, resolution=resolution, default=default, variable_name=variable_name, pff=pff, desc=desc, address=address, ptype=ptype, corr=corr, dist_def=dist_def, shortname=shortname, abbrev=abbrev, ) @property def min(self): return float(super().lower_bound) @property def max(self): return float(super().upper_bound)
[docs]class IntegerParameter(Parameter, workbench_param.IntegerParameter): dtype = 'int' def __init__(self, name, *, lower_bound=None, upper_bound=None, resolution=None, default=None, variable_name=None, pff=False, dist=None, dist_def=None, desc="", address=None, ptype=None, corr=None, shortname=None, abbrev=None): if dist is None and (lower_bound is None or upper_bound is None): raise ValueError("must give lower_bound and upper_bound, or dist") if dist is None: from scipy.stats import randint dist = randint(lower_bound, upper_bound+1) Parameter.__init__( self, name, dist=dist, resolution=resolution, default=default, variable_name=variable_name, pff=pff, desc=desc, address=address, ptype=ptype, corr=corr, dist_def=dist_def, shortname=shortname, abbrev=abbrev, ) if self.resolution is not None: for entry in self.resolution: if not isinstance(entry, numbers.Integral): raise ValueError(('all entries in resolution should be ' 'integers')) @property def min(self): return int(super().lower_bound) @property def max(self): return int(super().upper_bound)
[docs]class BooleanParameter(Parameter, workbench_param.BooleanParameter): dtype = 'bool' def __init__(self, name, *, lower_bound=None, upper_bound=None, resolution=None, default=None, variable_name=None, pff=False, dist=None, dist_def=None, desc="", address=None, ptype=None, corr=None, shortname=None, abbrev=None): Parameter.__init__( self, name, dist=dist, resolution=resolution, default=default, variable_name=variable_name, pff=pff, desc=desc, address=address, ptype=ptype, corr=corr, dist_def=dist_def, shortname=shortname, abbrev=abbrev, ) cats = [workbench_param.create_category(cat) for cat in [False, True]] self._categories = workbench_param.NamedObjectMap(workbench_param.Category) self.categories = cats self.resolution = [i for i in range(len(self.categories))] self.multivalue = False @property def values(self): """List: The possible discrete values.""" return [False, True] @property def min(self): return False @property def max(self): return True
[docs]class CategoricalParameter(Parameter, workbench_param.CategoricalParameter): dtype = 'cat' def __init__(self, name, categories, *, default=None, variable_name=None, pff=False, multivalue=False, desc="", address=None, ptype=None, corr=None, dist=None, singleton_ok=False, shortname=None, abbrev=None): lower_bound = 0 upper_bound = len(categories) - 1 from scipy.stats import randint dist = randint(lower_bound, upper_bound+1) if upper_bound == 0 and not singleton_ok: raise ValueError('there should be more than 1 category') Parameter.__init__( self, name, dist=dist, resolution=None, default=default, variable_name=variable_name, pff=pff, desc=desc, address=address, ptype=ptype, corr=corr, shortname=shortname, abbrev=abbrev, ) cats = [workbench_param.create_category(cat) for cat in categories] self._categories = workbench_param.NamedObjectMap(workbench_param.Category) self.categories = cats self.resolution = [i for i in range(len(self.categories))] self.multivalue = multivalue @property def values(self): """List: The possible discrete values.""" return list(i.value for i in self.categories) @property def min(self): """None: Categorical parameters are not characterized by a lower bound.""" return None @property def max(self): """None: Categorical parameters are not characterized by an upper bound.""" return None @property def distdef(self): """None: Categorical parameters distribution is not implemented.""" return None @property def dist_description(self): return "Uniform Distribution"
############# class OLD_Parameter: """ Definitions for a particular input for a model. Args: name (str): A name for this parameter. The name must be a `str` and ideally a valid Python identifier (i.e., begins with a letter or underscore, contains only letters, numerals, and underscores). ptype (str, default 'constant'): The type for this parameter, one of {'constant', 'uncertainty', 'lever'}. min (numeric, optional): The minimum value for this parameter. max (numeric, optional): The maximum value for this parameter. dist (str or Mapping, optional): A definition of a distribution to use for this parameter, which is only relevant for uncertainty parameters. Can be specified just as the name of the distribution when that distribution is parameterized only by the min and max (e.g., 'uniform'). If the distribution requires other parameters, this argument should be a Mapping, with keys including 'name' for the name of the distribution, as well as giving one or more named distributional parameters as appropriate. default (Any, optional): A default value for this parameter. The default value is used as the actual value for constant parameters. It is also used during univariate sensitivity testing as the value for this parameter when other parameters are being evaluated at non-default values. corr (dict, optional): A correlation definition that relates this parameter to others. Only applicable for uncertainty parameters. address (Any, optional): The address to use to access this parameter in the model. This is an implementation-specific detail. For example, in an Excel-based model, the address could be a sheet and cell reference given as a string. dtype (str, default 'infer'): A dtype for this parameter, one of {'cat', 'int', 'real', 'bool'} or some sub-class variant or specialization thereof (e.g., int64). values (Collection, optional): A collection of possible values, relevant only for categorical parameters. resolution (Collection, optional): A collection of possible particular values, used to set the possible values considered when sampling with factorial-based designs. """ def __init__( self, name, ptype='constant', desc='missing description', min=None, max=None, dist=None, default=None, corr=None, address=None, dtype='infer', values=None, resolution=None, ): self.name = name """str: Parameter name, used to identify parameter.""" self.ptype = standardize_parameter_type(ptype) """str: Parameter type, one of {'constant', 'uncertainty', 'lever'}""" self.desc = desc """str: Human readable description of this parameter, for reference only""" self.min = min """numeric: Lower bound for this parameter, or None""" self.max = max """numeric: Upper bound for this parameter, or None""" if isinstance(dist, str): dist = {'name': dist} self.dist = dist """Dict: definition of a distribution to use for this parameter""" self.default = default """A default value for this parameter, used for constants or in univariate sensitivity testing""" self.corr = corr if corr is not None else [] self.address = address self.dtype = standardize_data_type(dtype) self.values = values self.resolution = resolution if self.dtype == 'infer': if self.values is not None: if set(self.values) == {True, False}: self.dtype = 'bool' else: self.dtype = 'cat' elif self.max is True: self.dtype = 'bool' elif isinstance(self.max, numbers.Integral): self.dtype = 'int' elif isinstance(self.max, numbers.Real): self.dtype = 'real' else: self.dtype = 'bool' if self.dtype not in ('cat','int','real','bool'): raise ValueError(f"invalid dtype {self.dtype}") # Data checks if self.dist is not None and not isinstance(self.dist, Mapping): raise TypeError(f'dist must be a dict for {self.name}, not {type(self.dist)}') if self.ptype is 'constant': if self.dist is None: self.dist = {'name': 'constant'} if self.dist.get('name') != 'constant': raise ValueError(f'constant cannot have non-constant distribution for {self.name}') if self.dtype == 'bool': if self.min is None: self.min = False if self.min != False: raise ValueError(f'min of bool must be False for {self.name}') if self.max is None: self.max = True if self.max != True: raise ValueError(f'max of bool must be True for {self.name}') self.values = [False, True] if self.dtype in {'int','real'}: if self.dist is not None and self.dist.get('name') == 'constant': if self.min is None and self.default is not None: self.min = self.default if self.max is None and self.default is not None: self.max = self.default if self.min is None: raise ValueError(f'min of {self.dtype} is required for {self.name}') if self.max is None: raise ValueError(f'max of {self.dtype} is required for {self.name}') def get_parameter(self): """Get an ema_workbench.Parameter from this emat.Parameter. This method returns an :class:`ema_workbench.Parameter <ema_workbench.em_framework.parameters.Parameter>` of the correct type for this :class:`emat.Parameter`. This will be one of: * :class:`Constant <ema_workbench.em_framework.parameters.Constant>` * :class:`CategoricalParameter <ema_workbench.em_framework.parameters.CategoricalParameter>` * :class:`IntegerParameter <ema_workbench.em_framework.parameters.IntegerParameter>` * :class:`RealParameter <ema_workbench.em_framework.parameters.RealParameter>` * :class:`BooleanParameter <ema_workbench.em_framework.parameters.BooleanParameter>` """ if self.ptype == 'constant': return Constant(self.name, self.default) elif self.dtype == 'cat': return CategoricalParameter(self.name, self.values, default=self.default,) elif self.dtype == 'int': return IntegerParameter( self.name, self.min, self.max, resolution=self.resolution, default=self.default, ) elif self.dtype == 'real': if self.dist is not None and len(self.dist) > 0: _d = self.dist.copy() distname = _d.pop('name', 'uniform') if distname == 'uniform': _d['loc'] = self.min _d['scale'] = self.max - self.min elif distname == 'triangle': _d['lower_bound'] = self.min _d['upper_bound'] = self.max elif distname == 'pert': _d['lower_bound'] = self.min _d['upper_bound'] = self.max dist_gen = getattr(distributions, distname) dist = dist_gen(**_d) else: dist = None return RealParameter( self.name, self.min, self.max, resolution=self.resolution, default=self.default, dist=dist, ) elif self.dtype == 'bool': return BooleanParameter(self.name, default=self.default) else: raise ValueError(f"invalid dtype {self.dtype}") def __repr__(self): classname = self.__class__.__name__ name = self.name rep = f"{classname}('{name}', dtype={self.dtype}, ptype='{self.ptype}'" rep += ')' return rep def __eq__(self, other): keys = {'name', 'ptype', 'desc', 'dtype', 'default', 'min', 'max', 'dist', 'corr', 'values', 'address', 'resolution'} for k in keys: if getattr(self, k) != getattr(other, k): return False return True