Source code for idaes.models.unit_models.skeleton_model

#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved.  Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""
The basic functionality is to allow a user to add any model of choice that
can interface with other IDAES unit models that leverage the ControlVolume
blocks. This is done by letting the user create ports and populate with
members that match with state variables defined in an IDAES unit model.
Also, a default initialize method is provided with a flexibility to pass a
callback from the user.
"""

from pyomo.network import Port
from pyomo.common.config import ConfigValue, In

from idaes.core.base.process_base import declare_process_block_class, ProcessBlockData
from idaes.core.util.exceptions import ConfigurationError
from idaes.core.util.model_statistics import degrees_of_freedom
from idaes.core.solvers import get_solver
import idaes.logger as idaeslog
from idaes.core.initialization import BlockTriangularizationInitializer

__author__ = "Jaffer Ghouse"

# Set up logger
_log = idaeslog.getLogger(__name__)


[docs] @declare_process_block_class("SkeletonUnitModel") class SkeletonUnitModelData(ProcessBlockData): """ This is the class for a skeleton unit model. This will provide a user with a generic skeleton to add a custom or a surrogate unit model when control volumes are not required. """ # Set default initializer # TODO: For now, use Block Triangularization as the default default_initializer = BlockTriangularizationInitializer # This is a staticmethod that will be the default callable set for the # initializer flag in the config block. @staticmethod def _default_initializer( model, opt=None, init_log=None, solve_log=None, initial_guess=None ): with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(model, tee=slc.tee) init_log.info( "Initialization completed using default method {}.".format( idaeslog.condition(res) ) ) # Create Class ConfigBlock CONFIG = ProcessBlockData.CONFIG() CONFIG.declare( "dynamic", ConfigValue( default=False, domain=In([False]), description="Dynamic model flag", doc="""Model does not support dynamics or holdup automatically. The variables declared in this unit will need to be indexed with the flowsheet time domain to facilitate connection with other unit models.""", ), ) CONFIG.declare( "initializer", ConfigValue( # Note: staticmethod is unbound, extract with __func__ default=_default_initializer.__func__, description="Initializer to be used", doc="""Flag to set the callback from user for initialization. Default is set to use the default_initialize method.""", ), ) # pylint: disable=W0235
[docs] def build(self): """ General build method for SkeletonUnitModelBlockData. Inheriting models should call `super().build`. Args: None Returns: None """ super().build()
[docs] def add_ports(self, name, member_dict, doc=None): """ This is a method to build Port objects in the SkeletonUnitModel and populate them with appropriate port members as specified. User can add as many inlet and outlet ports as required. Keyword Args: name : name to use for Port object. dict : dictionary containing variables to be added to the port doc : doc string for Port object Returns: A Pyomo Port object and associated components. """ # Validate that member_dict is a dict if not isinstance(member_dict, dict): raise ConfigurationError( "member_dict should be a dictionary " "with the keys being the name assigned(strings) and " "values being the variable objects declared in the model." ) # Create empty Port p = Port(noruleinit=True, doc=doc) # Add port object to model setattr(self, name, p) # Populate port and map names to actual variables as defined for k in member_dict.keys(): p.add(member_dict[k], name=k)
[docs] def fix_initialization_states(self): """ Attempts to fix inlet states by iterating over all Ports and looking for "inlet" in the name. Users may need to fix inlets manually if they use names which do not include "inlet". Returns: None """ for p in self.component_objects(Port, descend_into=False): if "inlet" in p.name: # Assume name is correct and fix all variables inside p.fix()
# TODO : Work out how to make this work with new UnitModel initialization
[docs] def initialize( self, outlvl=idaeslog.NOTSET, solver=None, optarg=None, initial_guess=None ): """Initialize method for the SkeletonUnitModel. If a custom function is provided via the initializer argument in the config block, then, this method will use it. If no custom function is provided, the _default_initializer method is used. Irrespective of which method is being used (default or custom), the expectation is that the degrees of freedom is zero at the start of initialization. Args: outlvl (idaes logger, optional): Set idaes logger level. Defaults to idaeslog.NOTSET. solver (string, optional): solver to be used for solve. Defaults to None but if None, ipopt from IDAES is used. optarg ({dict}, optional): solver arguments. Defaults to None but if None, default args for ipopt from IDAES is used. initial_guess ({dict}, optional): initial guess that is passed to custom initialize. Not used for the default initialize method. Raises: ConfigurationError: If degrees of freedom is not zero at the start of initialization. """ # Set solver options init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") opt = get_solver(solver=solver, options=optarg) if degrees_of_freedom(self) == 0: self.config.initializer( self, opt=opt, init_log=init_log, solve_log=solve_log, initial_guess=initial_guess, ) else: raise ConfigurationError( "Degrees of freedom is not zero during start of " "initialization. Fix/unfix appropriate number of variables " "to result in zero degrees of freedom for initialization." )