Source code for watertap.core.zero_order_sito

###############################################################################
# WaterTAP Copyright (c) 2021, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory, Oak Ridge National
# Laboratory, National Renewable Energy Laboratory, and National Energy
# Technology Laboratory (subject to receipt of any required approvals from
# the U.S. Dept. of Energy). All rights reserved.
#
# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license
# information, respectively. These files are also available online at the URL
# "https://github.com/watertap-org/watertap/"
#
###############################################################################
"""
This module contains the base class for all zero order single inlet-two outlet
(SITO) unit models.
"""
from idaes.core import UnitModelBlockData, useDefault
from idaes.core.util.config import is_physical_parameter_block
import idaes.logger as idaeslog
from idaes.core.util import get_solver
import idaes.core.util.scaling as iscale
from idaes.core.util.exceptions import ConfigurationError

from pyomo.common.config import ConfigBlock, ConfigValue, In
from pyomo.environ import NonNegativeReals, Var, units as pyunits

# Some more inforation about this module
__author__ = "Andrew Lee"


[docs]class SITOBaseData(UnitModelBlockData): """ Standard base class for single inlet-two outlet unit models. This class is intended to be used for creating derived model classes and cannot be instantiated by itself. When creating derived classes, developers must set the '_has_deltaP_treated` and `_has_deltaP_byproduct` attributes on the model before calling `super().build()`. These attributes determine whether the deltaP terms for the treated and byproduct streams are added to the model and included in the pressure constraints. """ CONFIG = ConfigBlock() CONFIG.declare('dynamic', ConfigValue( domain=In([False]), default=False, description='Dynamic model flag - must be False', doc='''All zero-order models are steady-state only''')) CONFIG.declare('has_holdup', ConfigValue( default=False, domain=In([False]), description='Holdup construction flag - must be False', doc='''Zero order models do not include holdup''')) CONFIG.declare('property_package', ConfigValue( default=useDefault, domain=is_physical_parameter_block, description='Property package to use for control volume', doc='''Property parameter object used to define property calculations, **default** - useDefault. **Valid values:** { **useDefault** - use default package from parent model or flowsheet, **PhysicalParameterObject** - a PhysicalParameterBlock object.}''')) CONFIG.declare('property_package_args', ConfigBlock( implicit=True, description='Arguments to use for constructing property packages', doc='''A ConfigBlock with arguments to be passed to a property block(s) and used when constructing these, **default** - None. **Valid values:** {see property package for documentation.}'''))
[docs] def build(self): super().build() # Check that derived class has implemented flags for pressure change if not hasattr(self, "_has_deltaP_treated"): raise NotImplementedError( f"{self.name} derived class has not been implemented " f"_has_deltaP_treated.") if not hasattr(self, "_has_deltaP_byproduct"): raise NotImplementedError( f"{self.name} derived class has not been implemented " f"_has_deltaP_byproduct.") # Check that property package meets requirements if self.config.property_package.phase_list != ["Liq"]: raise ConfigurationError( f"{self.name} configured with invalid property package. " f"Zero-order models only support property packages with a " f"single phase named 'Liq'.") if (not hasattr(self.config.property_package, "solvent_set") or self.config.property_package.solvent_set != ["H2O"]): raise ConfigurationError( f"{self.name} configured with invalid property package. " f"Zero-order models only support property packages which " f"include 'H2O' as the only Solvent.") if not hasattr(self.config.property_package, "solute_set"): raise ConfigurationError( f"{self.name} configured with invalid property package. " f"Zero-order models require property packages to declare all " f"dissolved species as Solutes.") if (len(self.config.property_package.solute_set) != len(self.config.property_package.component_list)-1): raise ConfigurationError( f"{self.name} configured with invalid property package. " f"Zero-order models only support `H2O` as a solvent and all " f"other species as Solutes.") # Get units metadata units_meta = \ self.config.property_package.get_metadata().get_derived_units # Create state blocks for inlet and outlets tmp_dict = dict(**self.config.property_package_args) tmp_dict["has_phase_equilibrium"] = False tmp_dict["defined_state"] = True self.properties_in = self.config.property_package.build_state_block( self.flowsheet().time, doc="Material properties at inlet", default=tmp_dict) tmp_dict_2 = dict(**tmp_dict) tmp_dict_2["defined_state"] = False self.properties_treated = \ self.config.property_package.build_state_block( self.flowsheet().time, doc="Material properties of treated water", default=tmp_dict_2) self.properties_byproduct = \ self.config.property_package.build_state_block( self.flowsheet().time, doc="Material properties of byproduct stream", default=tmp_dict_2) # Create Ports self.add_port("inlet", self.properties_in, doc="Inlet port") self.add_port("treated", self.properties_treated, doc="Treated water outlet port") self.add_port("byproduct", self.properties_byproduct, doc="Byproduct outlet port") # Add performance variables self.recovery_vol = Var( self.flowsheet().time, initialize=0.8, domain=NonNegativeReals, units=pyunits.dimensionless, bounds=(1E-8, 1.0000001), doc='Volumetric recovery fraction of water in the treated stream') self.removal_mass_solute = Var( self.flowsheet().time, self.config.property_package.solute_set, domain=NonNegativeReals, initialize=0.01, units=pyunits.dimensionless, doc='Solute removal fraction on a mass basis') if self._has_deltaP_treated: self.deltaP_treated = Var( self.flowsheet().time, initialize=0, units=units_meta('pressure'), doc='Pressure change between inlet and treated outlet') if self._has_deltaP_byproduct: self.deltaP_byproduct = Var( self.flowsheet().time, initialize=0, units=units_meta('pressure'), doc='Pressure change between inlet and byproduct outlet') # Add performance constraints # Water recovery @self.Constraint(self.flowsheet().time, doc='Water recovery equation') def water_recovery_equation(b, t): return (b.recovery_vol[t] * b.properties_in[t].flow_vol == b.properties_treated[t].flow_vol) # Flow balance @self.Constraint(self.flowsheet().time, doc='Overall flow balance') def flow_balance(b, t): return (b.properties_in[t].flow_vol == b.properties_treated[t].flow_vol + b.properties_byproduct[t].flow_vol) # Solute removal @self.Constraint(self.flowsheet().time, self.config.property_package.solute_set, doc='Solute removal equations') def solute_removal_equation(b, t, j): return (b.removal_mass_solute[t, j] * b.properties_in[t].conc_mass_comp[j] == (1 - b.recovery_vol[t]) * b.properties_byproduct[t].conc_mass_comp[j]) # Solute concentration of treated stream @self.Constraint(self.flowsheet().time, self.config.property_package.solute_set, doc='Constraint for solute concentration in treated ' 'stream.') def solute_treated_equation(b, t, j): return ((1 - b.removal_mass_solute[t, j]) * b.properties_in[t].conc_mass_comp[j] == b.recovery_vol[t] * b.properties_treated[t].conc_mass_comp[j]) # Pressure drop @self.Constraint(self.flowsheet().time, doc='Treated stream pressure equation') def treated_pressure_constraint(b, t): if self._has_deltaP_treated: dp = b.deltaP_treated[t] else: dp = 0 return (b.properties_in[t].pressure + dp == b.properties_treated[t].pressure) @self.Constraint(self.flowsheet().time, doc='Byproduct stream pressure equation') def byproduct_pressure_constraint(b, t): if self._has_deltaP_byproduct: dp = b.deltaP_byproduct[t] else: dp = 0 return (b.properties_in[t].pressure + dp == b.properties_byproduct[t].pressure) # Temperature equality @self.Constraint(self.flowsheet().time, doc='Treated stream temperature equality') def treated_temperature_equality(b, t): return (b.properties_in[t].temperature == b.properties_treated[t].temperature) @self.Constraint(self.flowsheet().time, doc='Byproduct stream temperature equality') def byproduct_temperature_equality(b, t): return (b.properties_in[t].temperature == b.properties_byproduct[t].temperature)
[docs] def initialize(blk, state_args=None, outlvl=idaeslog.NOTSET, solver=None, optarg=None): ''' Initialization routine for single inlet-two outlet unit models. Keyword Arguments: state_args : a dict of arguments to be passed to the property package(s) to provide an initial state for initialization (see documentation of the specific property package) (default = {}). outlvl : sets output level of initialization routine optarg : solver options dictionary object (default=None, use default solver options) solver : str indicating which solver to use during initialization (default = None, use default IDAES solver) Returns: None ''' if optarg is None: optarg = {} # Set solver options init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit") solver_obj = get_solver(solver, optarg) # Get initial guesses for inlet if none provided if state_args is None: state_args = {} state_dict = ( blk.properties_in[ blk.flowsheet().time.first()] .define_port_members()) for k in state_dict.keys(): if state_dict[k].is_indexed(): state_args[k] = {} for m in state_dict[k].keys(): state_args[k][m] = state_dict[k][m].value else: state_args[k] = state_dict[k].value # --------------------------------------------------------------------- # Initialize control volume block flags = blk.properties_in.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args, hold_state=True ) blk.properties_treated.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args, hold_state=False ) blk.properties_byproduct.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args, hold_state=False ) init_log.info_high('Initialization Step 1 Complete.') # --------------------------------------------------------------------- # Solve unit with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: results = solver_obj.solve(blk, tee=slc.tee) init_log.info_high( "Initialization Step 2 {}.".format(idaeslog.condition(results)) ) # --------------------------------------------------------------------- # Release Inlet state blk.properties_in.release_state(flags, outlvl) init_log.info('Initialization Complete: {}' .format(idaeslog.condition(results)))
def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() for t, v in self.water_recovery_equation.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].flow_vol, default=1, warning=True, hint=" for water recovery")) for t, v in self.flow_balance.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].flow_vol, default=1, warning=False)) # would just be a duplicate of above for (t, j), v in self.solute_removal_equation.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].flow_mass_comp[j], default=1, warning=True, hint=" for solute removal")) for (t, j), v in self.solute_treated_equation.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].flow_mass_comp[j], default=1, warning=False)) # would just be a duplicate of above for t, v in self.treated_pressure_constraint.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].pressure, default=1e-5, warning=True, hint=" for treated pressure constraint")) for t, v in self.byproduct_pressure_constraint.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].pressure, default=1e-5, warning=False)) # would just be a duplicate of above for t, v in self.treated_temperature_equality.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].temperature, default=1e-2, warning=True, hint=" for treated temperature equality")) for t, v in self.byproduct_temperature_equality.items(): iscale.constraint_scaling_transform( v, iscale.get_scaling_factor( self.properties_in[t].temperature, default=1e-2, warning=False)) # would just be a duplicate of above