Source code for watertap.unit_models.stoichiometric_reactor

#################################################################################
# WaterTAP Copyright (c) 2020-2024, 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/"
#################################################################################

# Import Pyomo libraries
from pyomo.environ import (
    Set,
    Var,
    Param,
    Suffix,
    NonNegativeReals,
    Reals,
    Reference,
    TransformationFactory,
    units as pyunits,
)
from pyomo.network import Arc
from pyomo.common.config import ConfigBlock, ConfigValue, In

# Import IDAES cores
from idaes.core import (
    ControlVolume0DBlock,
    declare_process_block_class,
    MaterialBalanceType,
    EnergyBalanceType,
    MomentumBalanceType,
    UnitModelBlockData,
    useDefault,
    MaterialFlowBasis,
)
from idaes.core.util.initialization import propagate_state
from idaes.models.unit_models import Separator
from idaes.models.unit_models.separator import (
    SplittingType,
    EnergySplittingType,
)
from watertap.core.solvers import get_solver
from idaes.core.util.tables import create_stream_table_dataframe
from idaes.core.util.config import is_physical_parameter_block
import idaes.core.util.scaling as iscale
import idaes.logger as idaeslog
from watertap.costing.unit_models.stoichiometric_reactor import (
    cost_stoichiometric_reactor,
)

__author__ = "Tim Bartholomew, Alexander V. Dudchenko"

_log = idaeslog.getLogger(__name__)


[docs]@declare_process_block_class("StoichiometricReactor") class StoichiometricReactorData(UnitModelBlockData): """ StoichiometricReactor - users must provide equations for the solids formed """ CONFIG = ConfigBlock() CONFIG.declare( "dynamic", ConfigValue( domain=In([False]), default=False, description="Dynamic model flag - must be False", doc="""Indicates whether this model will be dynamic or not, **default** = False. NF units do not support dynamic behavior.""", ), ) CONFIG.declare( "has_holdup", ConfigValue( default=False, domain=In([False]), description="Holdup construction flag - must be False", doc="""Indicates whether holdup terms should be constructed or not. **default** - False. NF units do not have defined volume, thus this must be False.""", ), ) CONFIG.declare( "material_balance_type", ConfigValue( default=MaterialBalanceType.useDefault, domain=In(MaterialBalanceType), description="Material balance construction flag", doc="""Indicates what type of mass balance should be constructed, **default** - MaterialBalanceType.useDefault. **Valid values:** { **MaterialBalanceType.useDefault - refer to property package for default balance type **MaterialBalanceType.none** - exclude material balances, **MaterialBalanceType.componentPhase** - use phase component balances, **MaterialBalanceType.componentTotal** - use total component balances, **MaterialBalanceType.elementTotal** - use total element balances, **MaterialBalanceType.total** - use total material balance.}""", ), ) CONFIG.declare( "energy_balance_type", ConfigValue( default=EnergyBalanceType.useDefault, domain=In(EnergyBalanceType), description="Energy balance construction flag", doc="""Indicates what type of energy balance should be constructed, **default** - EnergyBalanceType.useDefault. **Valid values:** { **EnergyBalanceType.useDefault - refer to property package for default balance type **EnergyBalanceType.none** - exclude energy balances, **EnergyBalanceType.enthalpyTotal** - single enthalpy balance for material, **EnergyBalanceType.enthalpyPhase** - enthalpy balances for each phase, **EnergyBalanceType.energyTotal** - single energy balance for material, **EnergyBalanceType.energyPhase** - energy balances for each phase.}""", ), ) CONFIG.declare( "momentum_balance_type", ConfigValue( default=MomentumBalanceType.pressureTotal, domain=In(MomentumBalanceType), description="Momentum balance construction flag", doc="""Indicates what type of momentum balance should be constructed, **default** - MomentumBalanceType.pressureTotal. **Valid values:** { **MomentumBalanceType.none** - exclude momentum balances, **MomentumBalanceType.pressureTotal** - single pressure balance for material, **MomentumBalanceType.pressurePhase** - pressure balances for each phase, **MomentumBalanceType.momentumTotal** - single momentum balance for material, **MomentumBalanceType.momentumPhase** - momentum balances for each phase.}""", ), ) CONFIG.declare( "has_pressure_change", ConfigValue( default=False, domain=In([True, False]), description="Pressure change term construction flag", doc="""Indicates whether terms for pressure change should be constructed, **default** - False. **Valid values:** { **True** - include pressure change terms, **False** - exclude pressure change terms.}""", ), ) 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.}""", ), ) CONFIG.declare( "reagent", ConfigValue( default={}, domain=dict, description="Specification of reagents used in StoichiometricReactor process", doc="""A dict of reagents used in the StoichiometricReactor process including their molecular weights, and dissolution stoichiometric coefficients for the components defined in the property package in the following format: .. code-block:: python { "reagent_name_1": { "mw": (value, units), "density_reagent": (value, units), "dissolution_stoichiometric": { "component_name_1": stoichiometric_coeff, "component_name_2": stoichiometric_coeff, } }, "reagent_name_2": { "mw": (value, units), "density_reagent": (value, units), "dissolution_stoichiometric": { "component_name_1": stoichiometric_coeff, "component_name_2": stoichiometric_coeff, } }, } """, ), ) CONFIG.declare( "precipitate", ConfigValue( default={}, domain=dict, description="Specification of precipitates formed in StoichiometricReactor process", doc=""" A dict of precipitates formed in the StoichiometricReactor process including their molecular weights, and precipitation stoichiometric coefficients for the components defined in the property package in the following format: .. code-block:: python { "precipitate_name_1": { "mw": (value, units), "precipitation_stoichiometric": { "component_name_1": stoichiometric_coeff, "component_name_2": stoichiometric_coeff, } }, "precipitate_name_2": { "mw": (value, units), "precipitation_stoichiometric": { "component_name_1": stoichiometric_coeff, "component_name_2": stoichiometric_coeff, } }, } """, ), )
[docs] def build(self): # Call UnitModel.build to setup dynamics super().build() self.scaling_factor = Suffix(direction=Suffix.EXPORT) units_meta = self.config.property_package.get_metadata().get_derived_units if len(self.config.reagent.keys()): self.reagent_list = Set(initialize=self.config.reagent.keys()) self.has_dissolution_reaction = True else: self.has_dissolution_reaction = False if len(self.config.precipitate.keys()): self.precipitate_list = Set(initialize=self.config.precipitate.keys()) self.has_precipitation_reaction = True else: self.has_precipitation_reaction = False if ( self.has_dissolution_reaction == False and self.has_precipitation_reaction == False ): raise TypeError( "Stoichiometric chemical reactor requires that at least reagents or precipitants are provided" ) # Add unit parameters if self.has_dissolution_reaction: self.mw_reagent = Var( self.reagent_list, units=pyunits.kg / pyunits.mol, doc="Molecular weight of reagents", ) self.density_reagent = Var( self.reagent_list, initialize=1000, units=units_meta("mass") / units_meta("volume"), doc="Density of reagents", ) for r in self.reagent_list: self.mw_reagent[r].fix( pyunits.convert( self.config.reagent[r]["mw"], to_units=pyunits.kg / pyunits.mol, ) ) if self.config.reagent[r].get("density_reagent") is not None: self.density_reagent[r].fix( pyunits.convert( self.config.reagent[r].get("density_reagent"), to_units=(units_meta("mass") / units_meta("volume")), ) ) else: _log.warning( "User did not specify density for reagent {}, using 1kg/L".format( r ) ) self.density_reagent[r].fix( pyunits.convert( 1 * pyunits.kg / pyunits.liter, to_units=(units_meta("mass") / units_meta("volume")), ) ) self.dissolution_stoich_comp = Param( self.reagent_list, self.config.property_package.component_list, initialize=0, mutable=True, doc="Dissolution stoichiometric coefficients for components in property package", ) for r in self.reagent_list: for j in self.config.reagent[r]["dissolution_stoichiometric"].keys(): self.dissolution_stoich_comp[r, j] = self.config.reagent[r][ "dissolution_stoichiometric" ][j] # Add unit variables self.reagent_dose = Var( self.reagent_list, initialize=1, domain=NonNegativeReals, units=units_meta("mass") / units_meta("volume"), doc="reagent dose", ) self.flow_mass_reagent = Var( self.reagent_list, initialize=1e-3, domain=NonNegativeReals, units=units_meta("mass") / units_meta("time"), doc="Mass flowrate of reagent", ) self.flow_vol_reagent = Var( self.reagent_list, initialize=1e-3, domain=NonNegativeReals, units=units_meta("volume") / units_meta("time"), doc="Volume flowrate of reagent", ) self.dissolution_reaction_generation_comp = Var( self.flowsheet().config.time, self.config.property_package.component_list, domain=Reals, initialize=0, units=units_meta("mass") / units_meta("time"), doc="Mass of component generated from dissolution", ) if self.has_precipitation_reaction: self.waste_mass_frac_precipitate = Var( initialize=0.2, bounds=(0.0, 1), units=pyunits.dimensionless, doc="Solid mass fraction in sludge", ) self.mw_precipitate = Var( self.precipitate_list, units=pyunits.kg / pyunits.mol, doc="Molecular weight of precipitate", ) for p in self.precipitate_list: self.mw_precipitate[p].fix( pyunits.convert( self.config.precipitate[p]["mw"], to_units=pyunits.kg / pyunits.mol, ) ) self.precipitation_stoich_comp = Param( self.precipitate_list, self.config.property_package.component_list, initialize=0, mutable=True, doc="Precipitation stoichiometric coefficients for components in property package", ) for p in self.precipitate_list: for j in self.config.precipitate[p][ "precipitation_stoichiometric" ].keys(): self.precipitation_stoich_comp[p, j] = self.config.precipitate[p][ "precipitation_stoichiometric" ][j] self.flow_mass_precipitate = Var( self.precipitate_list, initialize=1e-3, domain=NonNegativeReals, units=units_meta("mass") / units_meta("time"), doc="Mass flowrate of precipitate", ) self.conc_mass_precipitate = Var( self.precipitate_list, initialize=1, domain=NonNegativeReals, units=units_meta("mass") / units_meta("volume"), doc="Mass concentration of precipitate", ) self.precipitation_reaction_generation_comp = Var( self.flowsheet().config.time, self.config.property_package.component_list, domain=Reals, initialize=0, units=units_meta("mass") / units_meta("time"), doc="Mass of component generated from precipitation", ) if self.has_dissolution_reaction: # Build control volume for dissolution reactor self.dissolution_reactor = ControlVolume0DBlock( dynamic=False, has_holdup=False, property_package=self.config.property_package, property_package_args=self.config.property_package_args, ) self.dissolution_reactor.add_state_blocks(has_phase_equilibrium=False) self.dissolution_reactor.add_material_balances( balance_type=self.config.material_balance_type, has_mass_transfer=True, ) @self.dissolution_reactor.Constraint( self.flowsheet().config.time, doc="isothermal energy balance for reactor", ) def eq_isothermal_dissolution(b, t): return b.properties_in[t].temperature == b.properties_out[t].temperature self.dissolution_reactor.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change, ) if ( self.config.has_pressure_change is True and self.config.momentum_balance_type != "none" ): self.deltaP = Reference(self.dissolution_reactor.deltaP) if self.has_precipitation_reaction: self.precipitation_reactor = ControlVolume0DBlock( dynamic=False, has_holdup=False, property_package=self.config.property_package, property_package_args=self.config.property_package_args, ) self.precipitation_reactor.add_state_blocks(has_phase_equilibrium=False) self.precipitation_reactor.add_material_balances( balance_type=self.config.material_balance_type, has_mass_transfer=True, ) @self.precipitation_reactor.Constraint( self.flowsheet().config.time, doc="isothermal energy balance for reactor", ) def eq_isothermal_precipitation(b, t): return b.properties_in[t].temperature == b.properties_out[t].temperature self.precipitation_reactor.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change, ) # References for control volume # pressure change if ( self.config.has_pressure_change is True and self.config.momentum_balance_type != "none" ): self.deltaP = Reference(self.precipitation_reactor.deltaP) # Build IDAES separator self.separator = Separator( property_package=self.config.property_package, outlet_list=["treated", "waste"], split_basis=SplittingType.phaseFlow, energy_split_basis=EnergySplittingType.equal_temperature, ) # Add ports if self.has_dissolution_reaction: # Add Ports self.add_inlet_port(name="inlet", block=self.dissolution_reactor) if self.has_precipitation_reaction: self.add_port( name="dissolution_reactor_outlet", block=self.dissolution_reactor.properties_out, ) self.add_port( name="precipitation_reactor_inlet", block=self.precipitation_reactor.properties_in, ) self.add_port( name="precipitation_reactor_outlet", block=self.precipitation_reactor.properties_out, ) self.add_port(name="outlet", block=self.separator.treated_state) self.add_port(name="waste", block=self.separator.waste_state) self.dissolution_to_precipitation_reactor = Arc( source=self.dissolution_reactor_outlet, destination=self.precipitation_reactor_inlet, ) self.precipitation_reactor_separator_arc = Arc( source=self.precipitation_reactor_outlet, destination=self.separator.inlet, ) TransformationFactory("network.expand_arcs").apply_to(self) else: self.add_port( name="outlet", block=self.dissolution_reactor.properties_out ) elif self.has_precipitation_reaction: self.add_inlet_port( name="inlet", block=self.precipitation_reactor, ) self.add_port( name="precipitation_reactor_outlet", block=self.precipitation_reactor.properties_out, ) self.add_port(name="outlet", block=self.separator.treated_state) self.add_port(name="waste", block=self.separator.waste_state) self.precipitation_reactor_separator_arc = Arc( source=self.precipitation_reactor_outlet, destination=self.separator.inlet, ) TransformationFactory("network.expand_arcs").apply_to(self) # Dissolution_reactor performance equations if self.has_dissolution_reaction: @self.Constraint( self.flowsheet().config.time, self.config.property_package.phase_list, self.config.property_package.component_list, doc="Mass balance with reaction terms", ) def eq_dissolution_mass_balance(b, t, p, j): if ( b.config.property_package.config.material_flow_basis == MaterialFlowBasis.mass ): return ( b.dissolution_reactor.mass_transfer_term[t, p, j] == b.dissolution_reaction_generation_comp[t, j] ) elif ( b.config.property_package.config.material_flow_basis == MaterialFlowBasis.molar ): return ( b.dissolution_reactor.mass_transfer_term[t, p, j] * b.config.property_package.mw_comp[j] # == b.dissolution_reaction_generation_comp[t, j] ) @self.Constraint( self.flowsheet().config.time, self.config.property_package.component_list, doc="Mass generation from dissolution", ) def eq_dissolution_reaction_generation(b, t, j): return b.dissolution_reaction_generation_comp[t, j] == sum( b.flow_mass_reagent[r] * b.dissolution_stoich_comp[r, j] * b.config.property_package.mw_comp[j] / b.mw_reagent[r] for r in self.reagent_list ) @self.Constraint( self.flowsheet().config.time, self.reagent_list, doc="Reagent mass flow", ) def eq_flow_mass_reagent(b, t, r): return ( b.flow_mass_reagent[r] == b.reagent_dose[r] * b.dissolution_reactor.properties_in[t].flow_vol_phase["Liq"] ) @self.Constraint( self.flowsheet().config.time, self.reagent_list, doc="Reagent mass flow", ) def eq_flow_vol_reagent(b, t, r): return ( b.flow_mass_reagent[r] == b.flow_vol_reagent[r] * b.density_reagent[r] ) if self.has_precipitation_reaction: @self.Constraint( self.flowsheet().config.time, self.config.property_package.phase_list, self.config.property_package.component_list, doc="Mass balance with reaction terms", ) def eq_precipitation_mass_balance(b, t, p, j): if ( b.config.property_package.config.material_flow_basis == MaterialFlowBasis.mass ): return ( b.precipitation_reactor.mass_transfer_term[t, p, j] == b.precipitation_reaction_generation_comp[t, j] ) elif ( b.config.property_package.config.material_flow_basis == MaterialFlowBasis.molar ): return ( b.precipitation_reactor.mass_transfer_term[t, p, j] * b.config.property_package.mw_comp[j] # == b.precipitation_reaction_generation_comp[t, j] ) @self.Constraint( self.flowsheet().config.time, self.config.property_package.component_list, doc="Mass generation from precipitation", ) def eq_precipitation_reaction_generation(b, t, j): return b.precipitation_reaction_generation_comp[t, j] == -sum( b.flow_mass_precipitate[p] * b.precipitation_stoich_comp[p, j] * b.config.property_package.mw_comp[j] / b.mw_precipitate[p] for p in self.precipitate_list ) @self.Constraint( self.flowsheet().config.time, self.precipitate_list, doc="Precipitate mass flow", ) def eq_flow_mass_precipitate(b, t, p): return ( b.flow_mass_precipitate[p] == b.conc_mass_precipitate[p] * b.precipitation_reactor.properties_out[t].flow_vol_phase["Liq"] ) @self.Constraint( self.flowsheet().config.time, doc="Mass generation from precipitation", ) def eq_waste_split(b, t): return b.waste_mass_frac_precipitate * ( sum(b.flow_mass_precipitate[p] for p in b.precipitate_list) + sum( b.separator.waste_state[t].flow_mass_phase_comp["Liq", j] for j in b.config.property_package.component_list ) ) == sum(b.flow_mass_precipitate[p] for p in b.precipitate_list)
[docs] def initialize_build( self, state_args=None, outlvl=idaeslog.NOTSET, solver=None, optarg=None ): """ General wrapper for pressure changer initialization routines 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) solver : str indicating which solver to use during initialization (default = None) Returns: None """ init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") opt = get_solver(solver, optarg) # --------------------------------------------------------------------- # Initialize dissolution reactor if constructed if self.has_dissolution_reaction: disolve_flags = self.dissolution_reactor.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args, ) init_log.info_high("Dissolution reactor Step 1 Complete.") if self.has_precipitation_reaction: # if we have dossolution reactor propgate state to precip reactor if self.has_dissolution_reaction: propagate_state(self.dissolution_to_precipitation_reactor) precip_flags = self.precipitation_reactor.initialize( outlvl=outlvl, optarg=optarg, solver=solver, state_args=state_args, ) if self.has_dissolution_reaction: self.precipitation_reactor.release_state(precip_flags, outlvl) init_log.info_high("Precipitation reactor Step 1 Complete.") propagate_state(self.precipitation_reactor_separator_arc) self.separator.split_fraction[0, "waste", "Liq"].fix( 0.01 ) # guess split fraction as 1% self.separator.initialize( outlvl=outlvl, optarg=optarg, solver=solver, ) self.separator.split_fraction[0, "waste", "Liq"].unfix() init_log.info_high("Initialization Step 2 Complete.") # --------------------------------------------------------------------- # Solve unit with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(self, tee=slc.tee) init_log.info_high("Initialization Step 3 {}.".format(idaeslog.condition(res))) # --------------------------------------------------------------------- # Release Inlet state if self.has_dissolution_reaction: self.dissolution_reactor.release_state(disolve_flags, outlvl) else: self.precipitation_reactor.release_state(precip_flags, outlvl) init_log.info("Initialization Complete: {}".format(idaeslog.condition(res)))
def _get_performance_contents(self, time_point=0): var_dict = {} expr_dict = {} for r in self.reagent_list: var_dict["Reagent dose - " + r] = self.reagent_dose[r] var_dict["Reagent flow - " + r] = self.flow_mass_reagent[r] for p in self.precipitate_list: var_dict["Precipitate flow - " + p] = self.flow_mass_precipitate[p] var_dict["Precipitate conc - " + p] = self.conc_mass_precipitate[p] return {"vars": var_dict, "exprs": expr_dict} def _get_stream_table_contents(self, time_point=0): return create_stream_table_dataframe( { "Inlet": self.inlet, "Reactor outlet": self.reactor_outlet, "Outlet": self.outlet, "Waste": self.waste, }, time_point=time_point, ) def calculate_scaling_factors(self): super().calculate_scaling_factors() # setting scaling factors for variables # these variables should have user input, if not there will be a warning if self.has_dissolution_reaction: for r in self.reagent_list: if iscale.get_scaling_factor(self.reagent_dose[r]) is None: if iscale.get_scaling_factor(self.flow_mass_reagent[r]) is not None: # scale reagent_dose based on flow_mass_reagent sf = iscale.get_scaling_factor( self.flow_mass_reagent[r] ) / iscale.get_scaling_factor( self.dissolution_reactor.properties_in[0].flow_vol_phase[ "Liq" ] ) iscale.set_scaling_factor(self.reagent_dose[r], sf) else: # default scaling for reagent_dose with warning sf = iscale.get_scaling_factor( self.reagent_dose[r], default=1, warning=True ) iscale.set_scaling_factor(self.reagent_dose[r], sf) if iscale.get_scaling_factor(self.flow_mass_reagent[r]) is None: sf = iscale.get_scaling_factor( self.dissolution_reactor.properties_in[0].flow_vol_phase["Liq"] ) sf = iscale.get_scaling_factor(self.reagent_dose[r]) * sf iscale.set_scaling_factor(self.flow_mass_reagent[r], sf) if iscale.get_scaling_factor(self.flow_vol_reagent[r]) is None: sf = iscale.get_scaling_factor(self.flow_mass_reagent[r]) print(sf, self.density_reagent[r].value) sf = sf / self.density_reagent[r].value iscale.set_scaling_factor(self.flow_vol_reagent[r], sf) for (t, j), v in self.dissolution_reaction_generation_comp.items(): if iscale.get_scaling_factor(v) is None: sf = iscale.get_scaling_factor( self.dissolution_reactor.properties_in[ t ].get_material_flow_terms("Liq", j) ) iscale.set_scaling_factor(v, sf) for (t, p, j), con in self.eq_dissolution_mass_balance.items(): sf = iscale.get_scaling_factor( self.dissolution_reaction_generation_comp[t, j] ) iscale.constraint_scaling_transform(con, sf) for (t, j), con in self.eq_dissolution_reaction_generation.items(): sf = iscale.get_scaling_factor( self.dissolution_reaction_generation_comp[t, j] ) iscale.constraint_scaling_transform(con, sf) for (t, r), con in self.eq_flow_mass_reagent.items(): sf = iscale.get_scaling_factor( self.flow_mass_reagent[r], default=1, warning=True ) iscale.constraint_scaling_transform(con, sf) for (t, r), con in self.eq_flow_vol_reagent.items(): sf = iscale.get_scaling_factor( self.flow_vol_reagent[r], default=1, warning=True ) iscale.constraint_scaling_transform(con, sf) for r in self.reagent_list: if iscale.get_scaling_factor(self.flow_mass_reagent[r]) is None: # scale flow_mass_reagent based on reagent_dose sf = iscale.get_scaling_factor( self.reagent_dose[r] ) * iscale.get_scaling_factor( self.dissolution_reactor.properties_in[0].flow_vol_phase["Liq"] ) iscale.set_scaling_factor(self.flow_mass_reagent[r], sf) if self.has_precipitation_reaction: for p in self.precipitate_list: if iscale.get_scaling_factor(self.flow_mass_precipitate[p]) is None: if ( iscale.get_scaling_factor(self.conc_mass_precipitate[p]) is not None ): # scale flow_mass_precipitate based on conc_mass_precipitate sf = iscale.get_scaling_factor( self.conc_mass_precipitate[p] ) * iscale.get_scaling_factor( self.precipitation_reactor.properties_out[0].flow_vol_phase[ "Liq" ] ) iscale.set_scaling_factor(self.flow_mass_precipitate[p], sf) else: # default scaling for reagent_dose with warning sf = iscale.get_scaling_factor( self.flow_mass_precipitate[p], default=1e3, warning=True ) iscale.set_scaling_factor(self.flow_mass_precipitate[p], sf) for p in self.precipitate_list: if iscale.get_scaling_factor(self.conc_mass_precipitate[p]) is None: sf = iscale.get_scaling_factor( self.flow_mass_precipitate[p] ) / iscale.get_scaling_factor( self.precipitation_reactor.properties_out[0].flow_vol_phase[ "Liq" ] ) iscale.set_scaling_factor(self.conc_mass_precipitate[p], sf) for (t, j), v in self.precipitation_reaction_generation_comp.items(): if iscale.get_scaling_factor(v) is None: sf = iscale.get_scaling_factor( self.precipitation_reactor.properties_in[ t ].get_material_flow_terms("Liq", j) ) iscale.set_scaling_factor(v, sf) for (t, p, j), con in self.eq_precipitation_mass_balance.items(): sf = iscale.get_scaling_factor( self.precipitation_reaction_generation_comp[t, j] ) iscale.constraint_scaling_transform(con, sf) for (t, j), con in self.eq_precipitation_reaction_generation.items(): sf = iscale.get_scaling_factor( self.precipitation_reaction_generation_comp[t, j] ) iscale.constraint_scaling_transform(con, sf) for (t, p), con in self.eq_flow_mass_precipitate.items(): sf = iscale.get_scaling_factor(self.flow_mass_precipitate[p]) iscale.constraint_scaling_transform(con, sf) sf = ( sum( iscale.get_scaling_factor(self.flow_mass_precipitate[p]) ** -1 for p in self.precipitate_list ) ** -1 ) iscale.constraint_scaling_transform(self.eq_waste_split[0], sf) @property def default_costing_method(self): return cost_stoichiometric_reactor