Source code for biosteam.evaluation._parameter

# -*- coding: utf-8 -*-
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
# Copyright (C) 2020-2023, Yoel Cortes-Pena <yoelcortes@gmail.com>
# 
# This module is under the UIUC open-source license. See 
# github.com/BioSTEAMDevelopmentGroup/biosteam/blob/master/LICENSE.txt
# for license details.
"""
"""
__all__ = ('Parameter',)

from ._variable import Variable
from ..utils import format_title
import biosteam as bst
from warnings import warn
from inspect import signature

try:
    from chaospy import distributions as shape
except:
    warn('chaospy not installed; cannot use automation variables for uncertainty and optimization', RuntimeWarning, stacklevel=2)
else:
    # Fix compatibility with new chaospy version
    import chaospy as cp
    version_components = cp.__version__.split('.')
    CP_MAJOR, CP_MINOR = int(version_components[0]), int(version_components[1])
    CP4 = (CP_MAJOR, CP_MINOR) >= (4, 0)
    if CP4:
        def save_repr_init(f):
            defaults = list(signature(f).parameters.values())[1:]
            defaults = {i.name: i.default for i in defaults}
            def init(self, *args, **kwargs):
                if not hasattr(self, '_repr'):
                    self._repr = params = defaults.copy()
                    for i, j in zip(params, args): params[i] = j
                    params.update(kwargs)
                f(self, *args, **kwargs)
            return init
        
        shapes = cp.distributions
        Distribution = cp.distributions.Distribution
        baseshapes = set([i for i in cp.distributions.baseclass.__dict__.values()
                          if isinstance(i, type) and issubclass(i, Distribution)])
        for i in shapes.__dict__.values():
            if isinstance(i, type) and issubclass(i, Distribution) and i not in baseshapes:
                i.__init__ = save_repr_init(i.__init__)
        del save_repr_init, shapes, baseshapes, Distribution, i
    del version_components, CP_MAJOR, CP_MINOR, CP4

[docs] class Parameter(Variable): """ Create a Parameter object that, when called, runs the setter and the simulate functions. Parameters ---------- name : str Name of parameter. setter : function Should set the parameter. element : object Element associated to parameter. system : System System associated to parameter. distribution : chaospy.Dist Parameter distribution. units : str Units of parameter. baseline : float Baseline value of parameter. bounds : tuple[float, float] Lower and upper bounds of parameter. coupled : str Whether parameter is coupled to the system's mass and energy balances. This allows a ConvergenceModel to predict it's impact on recycle loops. Defaults to False. hook : Callable Should return the new parameter value given the sample. """ __slots__ = ('setter', 'system', 'distribution', 'baseline', 'bounds', 'coupled', 'hook', 'description', 'active', 'last_value') def __init__(self, name, setter, element, system, distribution, units, baseline, bounds, coupled, hook, description): if not name: name, *_ = signature(setter).parameters.keys() super().__init__(format_title(name), units, element) self.setter = setter.setter if isinstance(setter, Parameter) else setter self.system = system if isinstance(distribution, str): match distribution.lower(): case 'triangle' | 'triangular': distribution = shape.Triangle(bounds[0], baseline, bounds[1]) case 'uniform': distribution = shape.Uniform(*bounds) case _: raise ValueError(f"invalid distribution {distribution!r}; distribution must be either 'triangular' or 'uniform'") elif bounds is None: if distribution: bounds = (distribution.lower[0], distribution.upper[0]) elif not distribution: distribution = shape.Uniform(*bounds) if bounds and baseline is None: baseline = 0.5 * (bounds[0] + bounds[1]) self.distribution = distribution self.baseline = baseline self.bounds = bounds self.coupled = coupled self.hook = hook self.description = description self.active = True self.last_value = None @classmethod def sort_parameters(cls, parameters): if not parameters: return try: system, = set([i.system for i in parameters]) except: raise ValueError('all parameters must have the same system to sort') if system is None: return unit_path = system.units length = len(unit_path) def key(parameter): if parameter.coupled: unit = parameter.unit if unit: return unit_path.index(unit) return length parameters.sort(key=key) @property def unit(self): """Unit operation directly associated to parameter.""" element = self.element if isinstance(element, bst.Unit): return element elif isinstance(element, bst.Stream): return element._sink @property def subsystem(self): """Subsystem directly associated to parameter.""" system = self.system if not system: return None else: unit = self.unit return system._downstream_system(unit) if unit else system
[docs] def simulate(self, **dyn_sim_kwargs): """Simulate parameter.""" if self.coupled: subsystem = self.subsystem if not subsystem: raise RuntimeError('no system to simulate') self.subsystem.simulate(**dyn_sim_kwargs) else: unit = self.unit if hasattr(unit, '_reevaluate'): unit._reevaluate()
def __call__(self, value): if self.hook: value = self.hook(value) self.setter(value) self.last_value = value self.simulate() def _info(self): return (f'{type(self).__name__}: {self.name}\n' + (f' element: {self.element_name}' if self.element else '') + (f' system: {self.system}\n' if self.system else '') + (f' units: {self.units}\n' if self.units else '') + (f' distribution: {self.distribution}\n' if self.distribution else ''))