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 ._feature import Feature
from ..utils import format_title
import biosteam as bst
from chaospy import distributions as shape
from inspect import signature

[docs] class Parameter(Feature): """ 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. kind : str * 'design': Parameter only affects unit operation design. * 'coupled': Parameter affects mass and energy balances. * 'isolated': Parameter does not affect the system in any way. hook : Callable Should return the new parameter value given the sample. scale : float, optional The sample is multiplied by the scale before setting. """ __slots__ = ('setter', 'system', 'distribution', 'baseline', 'bounds', 'kind', 'hook', 'description', 'scale') def __init__(self, name, setter, element, system, distribution, units, baseline, bounds, kind, hook, description, scale): if not name: name, *_ = signature(setter).parameters.keys() super().__init__(format_title(name), units, element) if kind is None: kind = 'isolated' self.setter = setter.setter if isinstance(setter, Parameter) else setter self.system = system if not bounds: 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.kind = kind self.hook = hook self.description = description self.scale = scale @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.kind == '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.""" kind = self.kind if kind in ('design', 'cost'): unit = self.unit if not unit: raise RuntimeError(f'no unit to run {kind} algorithm') unit._reevaluate() elif kind == 'coupled': subsystem = self.subsystem if not subsystem: raise RuntimeError(f'no system to run {kind} algorithm') self.subsystem.simulate(**dyn_sim_kwargs) elif kind == 'isolated': pass else: raise RuntimeError(f"invalid parameter kind '{kind}'")
def __call__(self, value): if self.hook: value = self.hook(value) self.setter(value if self.scale is None else value * self.scale) 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 ''))