Source code for idaes.core.initialization.general_hierarchical

#################################################################################
# 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-2023 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.
#################################################################################
"""
Initializer class for implementing Hierarchical initialization routines for
IDAES models with standard forms (e.g. units with 1 control volume)
"""
from pyomo.environ import Block
from pyomo.common.config import ConfigDict, ConfigValue

from idaes.core.initialization.initializer_base import ModularInitializerBase
from idaes.core.solvers import get_solver
from idaes.core.util import to_json, from_json, StoreSpec
from idaes.core.util.exceptions import InitializationError
import idaes.logger as idaeslog

__author__ = "Andrew Lee"


[docs]class SingleControlVolumeUnitInitializer(ModularInitializerBase): """ This is a general purpose Initializer object for unit models which have a single Control Volume named 'control_volume'. For details of the initialization routine, please see the documentation. """ CONFIG = ModularInitializerBase.CONFIG() CONFIG.declare( "solver", ConfigValue( default=None, # TODO: Can we add a square problem solver as the default here? # At the moment there is an issue with the scipy solvers not supporting the tee argument. description="Solver to use for initialization", ), ) CONFIG.declare( "solver_options", ConfigDict( implicit=True, description="Dict of options to pass to solver", ), ) def __init__(self, **kwargs): super().__init__(**kwargs) self._solver = None def initialization_routine( self, model: Block, plugin_initializer_args: dict = None, copy_inlet_state: bool = False, ): """ Common initialization routine for models with one control volume. Args: model: Pyomo Block to be initialized plugin_initializer_args: dict-of-dicts containing arguments to be passed to plug-in Initializers. Keys should be submodel components. copy_inlet_state: bool (default=False). Whether to copy inlet state to other sttes or not. Copying will generally be faster, but inlet states may not contain all properties required elsewhere. Returns: Pyomo solver results object """ if not hasattr(model, "control_volume"): raise TypeError( f"Model {model.name} does not appear to be a standard form unit model. " f"Please use an Initializer specific to the model being initialized." ) if plugin_initializer_args is None: plugin_initializer_args = {} # Get logger _log = self.get_logger(model) # Prepare plug-ins for initialization sub_initializers, plugin_initializer_args = self._prepare_plugins( model, plugin_initializer_args, _log ) # Initialize model and sub-models results = self._initialize_submodels( model, plugin_initializer_args, copy_inlet_state, sub_initializers, _log ) # Solve full model including plug-ins results = self._solve_full_model(model, _log, results) # Clean up plug-ins self._cleanup(model, plugin_initializer_args, sub_initializers, _log) return results def _prepare_plugins(self, model, plugin_initializer_args, logger): sub_initializers = {} plugin_initializer_args = dict(plugin_initializer_args) for sm in model.initialization_order: if sm is not model: # Get initializers for plug-ins sub_initializers[sm] = self.get_submodel_initializer(sm)() if sm not in plugin_initializer_args.keys(): plugin_initializer_args[sm] = {} # Call prepare method for plug-ins sub_initializers[sm].plugin_prepare(sm, **plugin_initializer_args[sm]) logger.info_high("Step 1: preparation complete.") return sub_initializers, plugin_initializer_args def _initialize_submodels( self, model, plugin_initializer_args, copy_inlet_state, sub_initializers, logger ): results = None for sm in model.initialization_order: if sm is model: results = self._initialize_main_model(model, copy_inlet_state, logger) else: sub_initializers[sm].plugin_initialize( sm, **plugin_initializer_args[sm] ) if results is None: raise InitializationError( f"Main model ({model.name}) was not initialized (no results returned). " f"This is likely due to an error in the model.initialization_order." ) logger.info_high("Step 2: sub-model initialization complete.") return results def _initialize_main_model(self, model, copy_inlet_state, logger): # Initialize properties try: # Guess a 0-D control volume self._init_props_0D(model, copy_inlet_state) except AttributeError: # Assume it must be a 1-D control volume self._init_props_1D(model) logger.info_high("Step 2a: properties initialization complete") # Initialize reactions if they exist if hasattr(model.control_volume, "reactions"): self._init_rxns(model) logger.info_high("Step 2b: reactions initialization complete") # Solve main model solve_log = idaeslog.getSolveLogger( model.name, self.config.output_level, tag="unit" ) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = self._get_solver().solve(model, tee=slc.tee) logger.info_high(f"Step 2c: model {idaeslog.condition(results)}.") return results def _init_props_0D(self, model, copy_inlet_state): # Initialize inlet properties - inlet state should already be fixed prop_init = self.get_submodel_initializer(model.control_volume.properties_in) if prop_init is not None: prop_init.initialize( model.control_volume.properties_in, solver=self.config.solver, optarg=self.config.solver_options, outlvl=self.config.output_level, ) if not copy_inlet_state: # Just in case the user set a different initializer for the outlet prop_init = self.get_submodel_initializer( model.control_volume.properties_out ) if prop_init is not None: prop_init.initialize( model.control_volume.properties_out, solver=self.config.solver, optarg=self.config.solver_options, outlvl=self.config.output_level, ) else: # Map solution from inlet properties to outlet properties state = to_json( model.control_volume.properties_in, wts=StoreSpec().value(), return_dict=True, ) from_json( model.control_volume.properties_out, sd=state, wts=StoreSpec().value(only_not_fixed=True), ) def _init_props_1D(self, model): prop_init = self.get_submodel_initializer(model.control_volume.properties) if prop_init is not None: prop_init.initialize( model.control_volume.properties, solver=self.config.solver, optarg=self.config.solver_options, outlvl=self.config.output_level, ) def _init_rxns(self, model): rxn_init = self.get_submodel_initializer(model.control_volume.reactions) if rxn_init is not None: rxn_init.initialize( model.control_volume.reactions, solver=self.config.solver, optarg=self.config.solver_options, outlvl=self.config.output_level, ) def _solve_full_model(self, model, logger, results): # Check to see if there are any plug-ins if len(model.initialization_order) > 1: # Solve model with plugins solve_log = idaeslog.getSolveLogger( model.name, self.config.output_level, tag="unit" ) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = self._get_solver().solve(model, tee=slc.tee) logger.info_high( f"Step 3: full model initialization {idaeslog.condition(results)}." ) return results def _cleanup(self, model, plugin_initializer_args, sub_initializers, logger): for sm in reversed(model.initialization_order): if sm is not model: sub_initializers[sm].plugin_finalize(sm, **plugin_initializer_args[sm]) logger.info_high("Step 4: clean up completed.") def _get_solver(self): if self._solver is None: self._solver = get_solver(self.config.solver, self.config.solver_options) return self._solver