From 753144099ebc4bf4abfc0a83cbab90d82b4de536 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 22 Nov 2024 11:11:06 -0500 Subject: [PATCH 01/46] Create initial files for ADM1 scalers --- .../anaerobic_digestion/adm1_properties.py | 34 +++++++++++++++++++ .../anaerobic_digestion/adm1_reactions.py | 32 +++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py index 184f0914f7..26bc723a6c 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py @@ -34,6 +34,7 @@ from idaes.core.util.initialization import fix_state_vars, revert_state_vars import idaes.logger as idaeslog import idaes.core.util.scaling as iscale +from idaes.core.scaling import CustomScalerBase # Some more information about this module __author__ = "Alejandro Garciadiego, Adam Atia, Xinhong Liu" @@ -247,6 +248,37 @@ def release_state(self, flags, outlvl=idaeslog.NOTSET): init_log.info("State Released.") +class ADM1PropertiesScaler(CustomScalerBase): + """ + Scaler for the Anaerobic Digestion Model No.1 property package. + Flow and temperature are scaled by the default value (if no user input provided), and + pressure is scaled assuming an order of magnitude of 1e5 Pa. + """ + + UNIT_SCALING_FACTORS = { + # "QuantityName: (reference units, scaling factor) + "Pressure": (pyo.units.Pa, 1e-5), + } + + DEFAULT_SCALING_FACTORS = { + "flow_vol": 1e5, + "temperature": 1e-2, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + self.scale_variable_by_default(model.temperature, overwrite=overwrite) + self.scale_variable_by_default(model.flow_vol, overwrite=overwrite) + self.scale_variable_by_units(model.pressure, overwrite=overwrite) + + # There are currently no constraints in this model + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + pass + + @declare_process_block_class("ADM1StateBlock", block_class=_ADM1StateBlock) class ADM1StateBlockData(StateBlockData): """ @@ -254,6 +286,8 @@ class ADM1StateBlockData(StateBlockData): reaction system. """ + default_scaler = ADM1PropertiesScaler + def build(self): """ Callable method for Block construction diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py index 5f6e7de77f..07b3692714 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py @@ -1159,6 +1159,38 @@ def define_metadata(cls, obj): ) +class ADM1ReactionScaler(CustomScalerBase): + """ + Scaler for the Anaerobic Digestion Model No.1 reaction package. + Variables are scaled by nominal order of magnitude, and constraints + using the inverse maximum scheme. + """ + + # TODO: Revisit this scaling factor + DEFAULT_SCALING_FACTORS = {"reaction_rate": 1e2} + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + + if model.is_property_constructed("reaction_rate"): + for j in model.reaction_rate.values(): + self.scale_variable_by_default(j, overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + # TODO: Revisit this scaling methodology + # Consider scale_constraint_by_default or scale_constraints_by_jacobian_norm + if model.is_property_constructed("rate_expression"): + for j in model.rate_expression.values(): + self.scale_constraint_by_nominal_value( + j, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + class _ADM1ReactionBlock(ReactionBlockBase): """ This Class contains methods which should be applied to Reaction Blocks as a From 059803e96125f0f60fff2370c16c96906fc5d08a Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 22 Nov 2024 15:52:52 -0500 Subject: [PATCH 02/46] Add new scaler objects --- .../anaerobic_digestion/adm1_properties.py | 4 +- .../adm1_properties_vapor.py | 43 ++++++- .../anaerobic_digestion/adm1_reactions.py | 110 ++++++++++++++++-- 3 files changed, 145 insertions(+), 12 deletions(-) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py index 26bc723a6c..a3258fd8bd 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py @@ -257,12 +257,12 @@ class ADM1PropertiesScaler(CustomScalerBase): UNIT_SCALING_FACTORS = { # "QuantityName: (reference units, scaling factor) - "Pressure": (pyo.units.Pa, 1e-5), + "Pressure": (pyo.units.Pa, 1e-6), } DEFAULT_SCALING_FACTORS = { "flow_vol": 1e5, - "temperature": 1e-2, + "temperature": 1e-1, } def variable_scaling_routine( diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py index 50cd540d72..bd31485cdd 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py @@ -34,6 +34,7 @@ from idaes.core.util.initialization import fix_state_vars, revert_state_vars import idaes.logger as idaeslog import idaes.core.util.scaling as iscale +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme # Some more information about this module __author__ = "Alejandro Garciadiego, Xinhong Liu" @@ -217,6 +218,44 @@ def release_state(self, flags, outlvl=idaeslog.NOTSET): init_log.info("State Released.") +class ADM1_vaporPropertiesScaler(CustomScalerBase): + """ + Scaler for the vapor Anaerobic Digestion Model No.1 property package. + Flow and temperature are scaled by the default value (if no user input provided), and + pressure is scaled assuming an order of magnitude of 1e5 Pa. + """ + + UNIT_SCALING_FACTORS = { + # "QuantityName: (reference units, scaling factor) + "Pressure": (pyo.units.Pa, 1e-3), + } + + DEFAULT_SCALING_FACTORS = { + "flow_vol": 1e5, + "temperature": 1e-1, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + self.scale_variable_by_default(model.temperature, overwrite=overwrite) + self.scale_variable_by_default(model.flow_vol, overwrite=overwrite) + self.scale_variable_by_units(model.pressure, overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + # TODO: Revisit this scaling methodologies + # Consider other schemes, scale_constraint_by_default, or scale_constraints_by_jacobian_norm + if model.is_property_constructed("pressure_sat"): + for j in model._pressure_sat.values(): + self.scale_constraint_by_nominal_value( + j, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("ADM1_vaporStateBlock", block_class=_ADM1_vaporStateBlock) class ADM1_vaporStateBlockData(StateBlockData): """ @@ -224,6 +263,8 @@ class ADM1_vaporStateBlockData(StateBlockData): reaction system. """ + default_scaler = ADM1_vaporPropertiesScaler + def build(self): """ Callable method for Block construction @@ -413,8 +454,6 @@ def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() - # No constraints in this model as yet, just need to set scaling factors - # for expressions sf_F = iscale.get_scaling_factor(self.flow_vol, default=1e2, warning=True) sf_T = iscale.get_scaling_factor(self.temperature, default=1e-2, warning=True) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py index 07b3692714..5623983918 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py @@ -41,6 +41,7 @@ import idaes.logger as idaeslog import idaes.core.util.scaling as iscale from idaes.core.util.math import smooth_max +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme # Some more information about this module __author__ = "Adam Atia, Alejandro Garciadiego, Xinhong Liu" @@ -1162,16 +1163,22 @@ def define_metadata(cls, obj): class ADM1ReactionScaler(CustomScalerBase): """ Scaler for the Anaerobic Digestion Model No.1 reaction package. - Variables are scaled by nominal order of magnitude, and constraints - using the inverse maximum scheme. + + Variables are scaled by their default scaling factor (if no user input provided), and constraints + are scaled using the inverse maximum scheme. """ # TODO: Revisit this scaling factor - DEFAULT_SCALING_FACTORS = {"reaction_rate": 1e2} + DEFAULT_SCALING_FACTORS = { + "reaction_rate": 1e2, + "I": 1e1, + } def variable_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None ): + for r in model.params.rate_reaction_idx: + self.scale_variable_by_default(model.I[r], overwrite=overwrite) if model.is_property_constructed("reaction_rate"): for j in model.reaction_rate.values(): @@ -1180,8 +1187,8 @@ def variable_scaling_routine( def constraint_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None ): - # TODO: Revisit this scaling methodology - # Consider scale_constraint_by_default or scale_constraints_by_jacobian_norm + # TODO: Revisit these scaling methodologies + # Consider other schemes, scale_constraint_by_default, or scale_constraints_by_jacobian_norm if model.is_property_constructed("rate_expression"): for j in model.rate_expression.values(): self.scale_constraint_by_nominal_value( @@ -1189,6 +1196,91 @@ def constraint_scaling_routine( scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) + if model.is_property_constructed("Dissociation"): + self.scale_constraint_by_nominal_value( + model.Dissociation, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("CO2_acid_base_equilibrium"): + self.scale_constraint_by_nominal_value( + model.CO2_acid_base_equilibrium, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("IN_acid_base_equilibrium"): + self.scale_constraint_by_nominal_value( + model.IN_acid_base_equilibrium, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("pH_calc"): + self.scale_constraint_by_nominal_value( + model.pH_calc, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_va"): + self.scale_constraint_by_nominal_value( + model.concentration_of_va, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_bu"): + self.scale_constraint_by_nominal_value( + model.concentration_of_bu, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_pro"): + self.scale_constraint_by_nominal_value( + model.concentration_of_pro, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_ac"): + self.scale_constraint_by_nominal_value( + model.concentration_of_ac, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_hco3"): + self.scale_constraint_by_nominal_value( + model.concentration_of_hco3, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_nh3"): + self.scale_constraint_by_nominal_value( + model.concentration_of_nh3, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_co2"): + self.scale_constraint_by_nominal_value( + model.concentration_of_co2, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("concentration_of_nh4"): + self.scale_constraint_by_nominal_value( + model.concentration_of_nh4, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("S_H_cons"): + self.scale_constraint_by_nominal_value( + model.S_H_cons, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if model.is_property_constructed("I_fun"): + for r in model.params.rate_reaction_idx: + self.scale_constraint_by_nominal_value( + model.I_fun[r], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) class _ADM1ReactionBlock(ReactionBlockBase): @@ -1197,6 +1289,8 @@ class _ADM1ReactionBlock(ReactionBlockBase): whole, rather than individual elements of indexed Reaction Blocks. """ + default_scaler = ADM1ReactionScaler + def initialize(self, outlvl=idaeslog.NOTSET, **kwargs): """ Initialization routine for reaction package. @@ -1801,9 +1895,6 @@ def rate_expression_rule(b, r): self.del_component(self.rate_expression) raise - for i, c in self.rates.items(): - iscale.set_scaling_factor(self.reaction_rate[i], 1 / c) - iscale.set_scaling_factor(self.I, 1e1) iscale.set_scaling_factor(self.conc_mass_va, 1e2) iscale.set_scaling_factor(self.conc_mass_bu, 1e2) @@ -1825,6 +1916,9 @@ def get_reaction_rate_basis(self): def calculate_scaling_factors(self): super().calculate_scaling_factors() + for i, c in self.rates.items(): + iscale.set_scaling_factor(self.reaction_rate[i], 1 / c) + for i, c in self.rate_expression.items(): iscale.constraint_scaling_transform( c, From a0921cda742b1420d3a6834958c09e00891860a2 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 22 Nov 2024 15:53:22 -0500 Subject: [PATCH 03/46] Update tests --- .../tests/test_adm1_reaction.py | 208 ++++++++++++++++++ .../tests/test_adm1_thermo.py | 57 ++++- .../tests/test_adm1_vapor_thermo.py | 56 +++++ 3 files changed, 320 insertions(+), 1 deletion(-) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py index 521d57ab56..c3b7c6c2ea 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py @@ -27,6 +27,7 @@ check_optimal_termination, ConcreteModel, Constraint, + Suffix, value, Var, log10, @@ -49,6 +50,7 @@ from watertap.property_models.unit_specific.anaerobic_digestion.adm1_reactions import ( ADM1ReactionParameterBlock, ADM1ReactionBlock, + ADM1ReactionScaler, ) # ----------------------------------------------------------------------------- @@ -65,6 +67,10 @@ def model(self): return model + @pytest.mark.unit + def test_config(self, model): + assert len(model.rparams.config) == 2 + @pytest.mark.unit def test_build(self, model): assert model.rparams.reaction_block_class is ADM1ReactionBlock @@ -428,6 +434,208 @@ def check_units(self, model): assert_units_consistent(model) +class TestADM1ReactionScaler(object): + @pytest.mark.unit + def test_variable_scaling_routine(self): + model = ConcreteModel() + model.pparams = ADM1ParameterBlock() + model.rparams = ADM1ReactionParameterBlock(property_package=model.pparams) + + model.props = model.pparams.build_state_block([1]) + model.rxns = model.rparams.build_reaction_block([1], state_block=model.props) + + # Trigger build of reaction properties + model.rxns[1].reaction_rate + + scaler = model.rxns[1].default_scaler() + assert isinstance(scaler, ADM1ReactionScaler) + + scaler.variable_scaling_routine(model.rxns[1]) + + assert isinstance(model.rxns[1].scaling_factor, Suffix) + + sfx = model.rxns[1].scaling_factor + assert len(sfx) == 52 + assert sfx[model.rxns[1].I] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R1"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R2"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R3"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R4"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R5"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R6"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R7"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R8"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R9"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R10"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R11"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R12"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R13"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R14"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R15"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R16"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R17"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R18"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R19"]] == pytest.approx(1e2, rel=1e-8) + + @pytest.mark.unit + def test_constraint_scaling_routine(self): + model = ConcreteModel() + model.pparams = ADM1ParameterBlock() + model.rparams = ADM1ReactionParameterBlock(property_package=model.pparams) + + model.props = model.pparams.build_state_block([1]) + model.rxns = model.rparams.build_reaction_block([1], state_block=model.props) + + # Trigger build of reaction properties + model.rxns[1].reaction_rate + + scaler = model.rxns[1].default_scaler() + assert isinstance(scaler, ADM1ReactionScaler) + + scaler.constraint_scaling_routine(model.rxns[1]) + + assert isinstance(model.rxns[1].scaling_factor, Suffix) + + sfx = model.rxns[1].scaling_factor + assert len(sfx) == 84 + assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx( + 5.59910414e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx( + 3.09119011e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R3"]] == pytest.approx( + 8.424599832e4, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R4"]] == pytest.approx( + 2.93083236e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R5"]] == pytest.approx( + 2.93772033e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R6"]] == pytest.approx( + 8.4245998e4, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R7"]] == pytest.approx( + 3.13971743e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx( + 3.99201597e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R9"]] == pytest.approx( + 3.09597523e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R10"]] == pytest.approx( + 3.79362671e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R11"]] == pytest.approx( + 8.1967213e4, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R12"]] == pytest.approx( + 2.39005736e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R13"]] == pytest.approx( + 1.0271158587e7, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R14"]] == pytest.approx( + 3.663003663e6, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R15"]] == pytest.approx( + 1.7771459037e7, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R16"]] == pytest.approx( + 1.00010001e7, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R17"]] == pytest.approx( + 3.1456432841e7, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R18"]] == pytest.approx( + 5.678591709e6, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R19"]] == pytest.approx( + 1.3625834582e7, rel=1e-8 + ) + assert sfx[model.rxns[1].Dissociation] == pytest.approx(1.4873815e-3, rel=1e-8) + assert sfx[model.rxns[1].CO2_acid_base_equilibrium] == pytest.approx( + 1.087426448e-2, rel=1e-8 + ) + assert sfx[model.rxns[1].IN_acid_base_equilibrium] == pytest.approx( + 1.60001205e-3, rel=1e-8 + ) + assert sfx[model.rxns[1].pH_calc] == pytest.approx(0.2, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_va] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_bu] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_pro] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_ac] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_hco3] == pytest.approx( + 0.3333333333, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_nh3] == pytest.approx( + 0.3333333333, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_co2] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_nh4] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].S_H_cons] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R1"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R2"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R3"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R4"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R5"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R6"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R7"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R8"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R9"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R10"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R11"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R12"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R13"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R14"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R15"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R16"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R17"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R18"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R19"]] == pytest.approx(1, rel=1e-8) + + @pytest.mark.unit + def test_scale_model(self): + model = ConcreteModel() + model.pparams = ADM1ParameterBlock() + model.rparams = ADM1ReactionParameterBlock(property_package=model.pparams) + + model.props = model.pparams.build_state_block([1]) + model.rxns = model.rparams.build_reaction_block([1], state_block=model.props) + + # Trigger build of reaction properties + model.rxns[1].reaction_rate + + scaler = model.rxns[1].default_scaler() + assert isinstance(scaler, ADM1ReactionScaler) + + scaler.scale_model(model.rxns[1]) + + assert isinstance(model.rxns[1].scaling_factor, Suffix) + + sfx = model.rxns[1].scaling_factor + assert len(sfx) == 103 + assert sfx[model.rxns[1].reaction_rate["R1"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R2"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R3"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R4"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R5"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R6"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R7"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R8"]] == pytest.approx(1e2, rel=1e-8) + + assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R3"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R4"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R5"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R6"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R7"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx(1e2, rel=1e-8) + + class TestReactor: @pytest.fixture(scope="class") def model(self): diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py index 0c1322dd8a..53108c36e0 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py @@ -16,7 +16,7 @@ import pytest -from pyomo.environ import ConcreteModel, Param, value, Var +from pyomo.environ import ConcreteModel, Param, Suffix, value, Var from pyomo.util.check_units import assert_units_consistent from idaes.core import MaterialBalanceType, EnergyBalanceType, MaterialFlowBasis @@ -24,6 +24,7 @@ from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties import ( ADM1ParameterBlock, ADM1StateBlock, + ADM1PropertiesScaler, ) from idaes.core.util.model_statistics import ( fixed_variables_set, @@ -111,6 +112,8 @@ def model(self): @pytest.mark.unit def test_build(self, model): + assert model.props[1].default_scaler is ADM1PropertiesScaler + assert isinstance(model.props[1].flow_vol, Var) assert value(model.props[1].flow_vol) == 1 @@ -298,3 +301,55 @@ def test_initialize(self, model): @pytest.mark.unit def check_units(self, model): assert_units_consistent(model) + + +class TestADM1PropertiesScaler: + @pytest.mark.unit + def test_variable_scaling_routine(self): + model = ConcreteModel() + model.params = ADM1ParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ADM1PropertiesScaler) + + scaler.variable_scaling_routine(model.props[1]) + + sfx = model.props[1].scaling_factor + assert len(sfx) == 30 + assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) + assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) + assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) + + @pytest.mark.unit + def test_constraint_scaling_routine(self): + model = ConcreteModel() + model.params = ADM1ParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ADM1PropertiesScaler) + + scaler.constraint_scaling_routine(model.props[1]) + + @pytest.mark.unit + def test_scale_model(self): + model = ConcreteModel() + model.params = ADM1ParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ADM1PropertiesScaler) + + scaler.scale_model(model.props[1]) + + assert isinstance(model.props[1].scaling_factor, Suffix) + + sfx = model.props[1].scaling_factor + assert len(sfx) == 30 + assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) + assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) + assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py index 31de35737d..da891b0f0e 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py @@ -19,6 +19,7 @@ from pyomo.environ import ( ConcreteModel, Param, + Suffix, value, Var, check_optimal_termination, @@ -31,6 +32,7 @@ from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties_vapor import ( ADM1_vaporParameterBlock, ADM1_vaporStateBlock, + ADM1_vaporPropertiesScaler, ) from idaes.core.util.model_statistics import ( fixed_variables_set, @@ -90,6 +92,8 @@ def model(self): @pytest.mark.unit def test_build(self, model): + assert model.props[1].default_scaler is ADM1_vaporPropertiesScaler + assert isinstance(model.props[1].flow_vol, Var) assert value(model.props[1].flow_vol) == 1 @@ -257,3 +261,55 @@ def test_pressures(self, model): @pytest.mark.unit def check_units(self, model): assert_units_consistent(model) + + +class TestADM1_vaporPropertiesScaler: + @pytest.mark.unit + def test_variable_scaling_routine(self): + model = ConcreteModel() + model.params = ADM1_vaporParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ADM1_vaporPropertiesScaler) + + scaler.variable_scaling_routine(model.props[1]) + + sfx = model.props[1].scaling_factor + assert len(sfx) == 12 + assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) + assert sfx[model.props[1].pressure] == pytest.approx(1e-3, rel=1e-8) + assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) + + @pytest.mark.unit + def test_constraint_scaling_routine(self): + model = ConcreteModel() + model.params = ADM1_vaporParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ADM1_vaporPropertiesScaler) + + scaler.constraint_scaling_routine(model.props[1]) + + @pytest.mark.unit + def test_scale_model(self): + model = ConcreteModel() + model.params = ADM1_vaporParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ADM1_vaporPropertiesScaler) + + scaler.scale_model(model.props[1]) + + assert isinstance(model.props[1].scaling_factor, Suffix) + + sfx = model.props[1].scaling_factor + assert len(sfx) == 16 + assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) + assert sfx[model.props[1].pressure] == pytest.approx(1e-3, rel=1e-8) + assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) From 367f18c475d3ebe1693b87786f138acb61d99da3 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 22 Nov 2024 16:08:17 -0500 Subject: [PATCH 04/46] Carry over ASM1 scaler updates --- .../activated_sludge/asm1_properties.py | 41 +++++- .../activated_sludge/asm1_reactions.py | 39 +++++- .../tests/test_asm1_reaction.py | 121 ++++++++++++++++++ .../tests/test_asm1_thermo.py | 57 ++++++++- 4 files changed, 252 insertions(+), 6 deletions(-) diff --git a/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py b/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py index 2a2c183686..81e82d1926 100644 --- a/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py +++ b/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py @@ -20,7 +20,6 @@ from idaes.core import ( declare_process_block_class, MaterialFlowBasis, - PhysicalParameterBlock, StateBlockData, StateBlock, MaterialBalanceType, @@ -32,7 +31,9 @@ ) from idaes.core.util.model_statistics import degrees_of_freedom from idaes.core.util.initialization import fix_state_vars, revert_state_vars +from idaes.core.scaling import CustomScalerBase import idaes.logger as idaeslog +from idaes.core.base.property_base import PhysicalParameterBlock import idaes.core.util.scaling as iscale # Some more information about this module @@ -191,12 +192,46 @@ def define_metadata(cls, obj): ) +class ASM1PropertiesScaler(CustomScalerBase): + """ + Scaler for the Activated Sludge Model No.1 property package. + + Flow and temperature are scaled by the default value (if no user input provided), and + pressure is scaled assuming an order of magnitude of 1e5 Pa. + """ + + UNIT_SCALING_FACTORS = { + # "QuantityName: (reference units, scaling factor) + "Pressure": (pyo.units.Pa, 1e-6), + } + + DEFAULT_SCALING_FACTORS = { + "flow_vol": 1e1, + "temperature": 1e-1, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + self.scale_variable_by_default(model.temperature, overwrite=overwrite) + self.scale_variable_by_default(model.flow_vol, overwrite=overwrite) + self.scale_variable_by_units(model.pressure, overwrite=overwrite) + + # There are currently no constraints in this model + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + pass + + class _ASM1StateBlock(StateBlock): """ This Class contains methods which should be applied to Property Blocks as a whole, rather than individual elements of indexed Property Blocks. """ + default_scaler = ASM1PropertiesScaler + def initialize( self, state_args=None, @@ -292,7 +327,7 @@ def release_state(self, flags, outlvl=idaeslog.NOTSET): @declare_process_block_class("ASM1StateBlock", block_class=_ASM1StateBlock) class ASM1StateBlockData(StateBlockData): """ - StateBlock for calculating thermophysical proeprties associated with the ASM1 + StateBlock for calculating thermophysical properties associated with the ASM1 reaction system. """ @@ -306,7 +341,7 @@ def build(self): self.flow_vol = pyo.Var( initialize=1.0, domain=pyo.NonNegativeReals, - doc="Total volumentric flowrate", + doc="Total volumetric flowrate", units=pyo.units.m**3 / pyo.units.s, ) self.pressure = pyo.Var( diff --git a/watertap/property_models/unit_specific/activated_sludge/asm1_reactions.py b/watertap/property_models/unit_specific/activated_sludge/asm1_reactions.py index c550c29251..f6f3e18274 100644 --- a/watertap/property_models/unit_specific/activated_sludge/asm1_reactions.py +++ b/watertap/property_models/unit_specific/activated_sludge/asm1_reactions.py @@ -34,9 +34,9 @@ ) from idaes.core.util.misc import add_object_reference from idaes.core.util.exceptions import BurntToast -import idaes.core.util.scaling as iscale import idaes.logger as idaeslog - +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme +import idaes.core.util.scaling as iscale # Some more information about this module __author__ = "Andrew Lee, Xinhong Liu, Adam Atia" @@ -330,12 +330,47 @@ def define_metadata(cls, obj): ) +class ASM1ReactionScaler(CustomScalerBase): + """ + Scaler for the Activated Sludge Model No.1 reaction package. + + Variables are scaled by their default scaling factor (if no user input provided), and constraints + are scaled using the inverse maximum scheme. + """ + + # TODO: Revisit this scaling factor + DEFAULT_SCALING_FACTORS = {"reaction_rate": 1e2} + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + + if model.is_property_constructed("reaction_rate"): + for j in model.reaction_rate.values(): + self.scale_variable_by_default(j, overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + # TODO: Revisit this scaling methodology + # Consider scale_constraint_by_default or scale_constraints_by_jacobian_norm + if model.is_property_constructed("rate_expression"): + for j in model.rate_expression.values(): + self.scale_constraint_by_nominal_value( + j, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + class _ASM1ReactionBlock(ReactionBlockBase): """ This Class contains methods which should be applied to Reaction Blocks as a whole, rather than individual elements of indexed Reaction Blocks. """ + default_scaler = ASM1ReactionScaler + def initialize(self, outlvl=idaeslog.NOTSET, **kwargs): """ Initialization routine for reaction package. diff --git a/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_reaction.py b/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_reaction.py index 467bf38810..d6f2dbae4c 100644 --- a/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_reaction.py +++ b/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_reaction.py @@ -20,6 +20,7 @@ check_optimal_termination, ConcreteModel, Constraint, + Suffix, units, value, Var, @@ -38,6 +39,7 @@ from watertap.property_models.unit_specific.activated_sludge.asm1_reactions import ( ASM1ReactionParameterBlock, ASM1ReactionBlock, + ASM1ReactionScaler, ) # ----------------------------------------------------------------------------- @@ -54,6 +56,10 @@ def model(self): return model + @pytest.mark.unit + def test_config(self, model): + assert len(model.rparams.config) == 2 + @pytest.mark.unit def test_build(self, model): assert model.rparams.reaction_block_class is ASM1ReactionBlock @@ -161,6 +167,121 @@ def check_units(self, model): assert_units_consistent(model) +class TestASM1ReactionScaler(object): + @pytest.mark.unit + def test_variable_scaling_routine(self): + model = ConcreteModel() + model.pparams = ASM1ParameterBlock() + model.rparams = ASM1ReactionParameterBlock(property_package=model.pparams) + + model.props = model.pparams.build_state_block([1]) + model.rxns = model.rparams.build_reaction_block([1], state_block=model.props) + + # Trigger build of reaction properties + model.rxns[1].reaction_rate + + scaler = model.rxns[1].default_scaler() + assert isinstance(scaler, ASM1ReactionScaler) + + scaler.variable_scaling_routine(model.rxns[1]) + + assert isinstance(model.rxns[1].scaling_factor, Suffix) + + sfx = model.rxns[1].scaling_factor + assert len(sfx) == 8 + assert sfx[model.rxns[1].reaction_rate["R1"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R2"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R3"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R4"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R5"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R6"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R7"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R8"]] == pytest.approx(1e2, rel=1e-8) + + @pytest.mark.unit + def test_constraint_scaling_routine(self): + model = ConcreteModel() + model.pparams = ASM1ParameterBlock() + model.rparams = ASM1ReactionParameterBlock(property_package=model.pparams) + + model.props = model.pparams.build_state_block([1]) + model.rxns = model.rparams.build_reaction_block([1], state_block=model.props) + + # Trigger build of reaction properties + model.rxns[1].reaction_rate + + scaler = model.rxns[1].default_scaler() + assert isinstance(scaler, ASM1ReactionScaler) + + scaler.constraint_scaling_routine(model.rxns[1]) + + assert isinstance(model.rxns[1].scaling_factor, Suffix) + + sfx = model.rxns[1].scaling_factor + assert len(sfx) == 8 + assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx( + 2.380752e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx( + 1.49540985e8, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R3"]] == pytest.approx( + 1.75226112e6, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R4"]] == pytest.approx( + 2.88e6, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R5"]] == pytest.approx( + 1.728e7, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R6"]] == pytest.approx( + 1.728e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R7"]] == pytest.approx( + 3.174336e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx(1, rel=1e-8) + + @pytest.mark.unit + def test_scale_model(self): + model = ConcreteModel() + model.pparams = ASM1ParameterBlock() + model.rparams = ASM1ReactionParameterBlock(property_package=model.pparams) + + model.props = model.pparams.build_state_block([1]) + model.rxns = model.rparams.build_reaction_block([1], state_block=model.props) + + # Trigger build of reaction properties + model.rxns[1].reaction_rate + + scaler = model.rxns[1].default_scaler() + assert isinstance(scaler, ASM1ReactionScaler) + + scaler.scale_model(model.rxns[1]) + + assert isinstance(model.rxns[1].scaling_factor, Suffix) + + sfx = model.rxns[1].scaling_factor + assert len(sfx) == 16 + assert sfx[model.rxns[1].reaction_rate["R1"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R2"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R3"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R4"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R5"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R6"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R7"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R8"]] == pytest.approx(1e2, rel=1e-8) + + assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R3"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R4"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R5"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R6"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R7"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx(1e2, rel=1e-8) + + class TestReactor: @pytest.fixture(scope="class") def model(self): diff --git a/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py b/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py index bc8dd6312b..cbb6e825e8 100644 --- a/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py +++ b/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py @@ -15,13 +15,14 @@ """ import pytest -from pyomo.environ import ConcreteModel, Expression, Param, units, value, Var +from pyomo.environ import ConcreteModel, Expression, Param, units, value, Var, Suffix from pyomo.util.check_units import assert_units_consistent from idaes.core import MaterialBalanceType, EnergyBalanceType, MaterialFlowBasis from watertap.property_models.unit_specific.activated_sludge.asm1_properties import ( ASM1ParameterBlock, ASM1StateBlock, + ASM1PropertiesScaler, ) from idaes.core.util.model_statistics import ( fixed_variables_set, @@ -130,6 +131,8 @@ def model(self): @pytest.mark.unit def test_build(self, model): + assert model.props[1].default_scaler is ASM1PropertiesScaler + assert isinstance(model.props[1].flow_vol, Var) assert value(model.props[1].flow_vol) == 1 @@ -341,3 +344,55 @@ def test_expressions(self, model): assert value(model.props[1].BOD5["raw"]) == 0.096 * 0.65 / 0.25 assert value(model.props[1].TKN) == pytest.approx(0.328, rel=1e-3) assert value(model.props[1].Total_N) == pytest.approx(0.428, rel=1e-3) + + +class TestASM1PropertiesScaler: + @pytest.mark.unit + def test_variable_scaling_routine(self): + model = ConcreteModel() + model.params = ASM1ParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ASM1PropertiesScaler) + + scaler.variable_scaling_routine(model.props[1]) + + sfx = model.props[1].scaling_factor + assert len(sfx) == 16 + assert sfx[model.props[1].flow_vol] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) + assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) + + @pytest.mark.unit + def test_constraint_scaling_routine(self): + model = ConcreteModel() + model.params = ASM1ParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ASM1PropertiesScaler) + + scaler.constraint_scaling_routine(model.props[1]) + + @pytest.mark.unit + def test_scale_model(self): + model = ConcreteModel() + model.params = ASM1ParameterBlock() + + model.props = model.params.build_state_block([1], defined_state=False) + + scaler = model.props[1].default_scaler() + assert isinstance(scaler, ASM1PropertiesScaler) + + scaler.scale_model(model.props[1]) + + assert isinstance(model.props[1].scaling_factor, Suffix) + + sfx = model.props[1].scaling_factor + assert len(sfx) == 16 + assert sfx[model.props[1].flow_vol] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) + assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) From 40ab286dd59795f282a1858cd61a1d3a00de5aa4 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 25 Nov 2024 13:22:18 -0500 Subject: [PATCH 05/46] Create first draft of AD Scaler object --- watertap/unit_models/anaerobic_digester.py | 219 ++++++++++++++++++++- 1 file changed, 218 insertions(+), 1 deletion(-) diff --git a/watertap/unit_models/anaerobic_digester.py b/watertap/unit_models/anaerobic_digester.py index a297b6dcee..72b345cbe5 100644 --- a/watertap/unit_models/anaerobic_digester.py +++ b/watertap/unit_models/anaerobic_digester.py @@ -67,18 +67,235 @@ from idaes.core.util.constants import Constants from idaes.core.util.exceptions import ConfigurationError, InitializationError from idaes.core.util.tables import create_stream_table_dataframe +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from watertap.costing.unit_models.anaerobic_digester import cost_anaerobic_digester __author__ = "Alejandro Garciadiego, Andrew Lee, Xinhong Liu" +class ADScaler(CustomScalerBase): + """ + Default modular scaler for anaerobic digester. + + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "volume": 1e-2, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.liquid_phase.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.liquid_phase.properties_out, + source_state=model.liquid_phase.properties_in, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.liquid_phase.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.liquid_phase.reactions, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scaling control volume variables + # TODO: Revisit this scaling factor & the addition of other scaling factors + self.scale_variable_by_default(model.liquid_phase.volume, overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.liquid_phase.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.liquid_phase.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.liquid_phase.reactions, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale control volume constraints + for c in model.liquid_phase.component_data_objects( + Constraint, descend_into=False + ): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + # Scale unit level constraints + if hasattr(model, "rate_reaction_constraint"): + for c in model.rate_reaction_constraint.values(): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "unit_material_balance"): + for c in model.unit_material_balance.values(): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "ad_performance_eqn"): + for c in model.ad_performance_eqn.values(): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + # TODO: See if these can be scaled in a for loop like the control volume constraints + if hasattr(model, "CO2_Henrys_law"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "Ch4_Henrys_law"): + self.scale_constraint_by_nominal_value( + model.Ch4_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "H2_Henrys_law"): + self.scale_constraint_by_nominal_value( + model.H2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "outlet_P"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "Sh2_conc"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "Sch4_conc"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "Sco2_conc"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "flow_vol_vap"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "ad_total_volume"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "AD_retention_time"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "unit_temperature_equality"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "unit_pressure_balance"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "unit_enthalpy_balance"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + if hasattr(model, "unit_electricity_consumption"): + self.scale_constraint_by_nominal_value( + model.CO2_Henrys_law, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("AD") class ADData(UnitModelBlockData): """ AD Unit Model Class """ + default_scaler = ADScaler + CONFIG = UnitModelBlockData.CONFIG() CONFIG.declare( @@ -366,7 +583,7 @@ def build(self): ) # --------------------------------------------------------------------- - # Check flow basis is compatable + # Check flow basis is compatible # TODO : Could add code to convert flow bases, but not now t_init = self.flowsheet().time.first() if ( From 03e970a3d2b5af782e96a279d78f9577d47908b8 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Wed, 27 Nov 2024 14:20:30 -0500 Subject: [PATCH 06/46] Move iscale scaling to calculate_scaling_factors --- .../activated_sludge/asm1_properties.py | 10 +++--- .../anaerobic_digestion/adm1_properties.py | 16 ++++----- .../tests/test_adm1_reaction.py | 36 +++++++++++-------- .../tests/test_adm1_thermo.py | 4 +-- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py b/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py index 81e82d1926..06401ba5bd 100644 --- a/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py +++ b/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py @@ -505,11 +505,6 @@ def _Total_N(self): doc="Total Nitrogen", ) - iscale.set_scaling_factor(self.flow_vol, 1e1) - iscale.set_scaling_factor(self.temperature, 1e-1) - iscale.set_scaling_factor(self.pressure, 1e-6) - iscale.set_scaling_factor(self.conc_mass_comp, 1e1) - def get_material_flow_terms(self, p, j): return self.material_flow_expression[j] @@ -553,6 +548,11 @@ def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() + iscale.set_scaling_factor(self.flow_vol, 1e1) + iscale.set_scaling_factor(self.temperature, 1e-1) + iscale.set_scaling_factor(self.pressure, 1e-6) + iscale.set_scaling_factor(self.conc_mass_comp, 1e1) + # No constraints in this model as yet, just need to set scaling factors # for expressions sf_F = iscale.get_scaling_factor(self.flow_vol, default=1e2, warning=True) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py index a3258fd8bd..15f8a79422 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties.py @@ -424,14 +424,6 @@ def energy_density_expression(self): rule=energy_density_expression, doc="Energy density term" ) - iscale.set_scaling_factor(self.flow_vol, 1e5) - iscale.set_scaling_factor(self.temperature, 1e-1) - iscale.set_scaling_factor(self.pressure, 1e-6) - iscale.set_scaling_factor(self.conc_mass_comp, 1e2) - iscale.set_scaling_factor(self.conc_mass_comp["S_h2"], 1e5) - iscale.set_scaling_factor(self.anions, 1e2) - iscale.set_scaling_factor(self.cations, 1e2) - def get_material_flow_terms(self, p, j): return self.material_flow_expression[j] @@ -477,6 +469,14 @@ def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() + iscale.set_scaling_factor(self.flow_vol, 1e5) + iscale.set_scaling_factor(self.temperature, 1e-1) + iscale.set_scaling_factor(self.pressure, 1e-6) + iscale.set_scaling_factor(self.conc_mass_comp, 1e2) + iscale.set_scaling_factor(self.conc_mass_comp["S_h2"], 1e5) + iscale.set_scaling_factor(self.anions, 1e2) + iscale.set_scaling_factor(self.cations, 1e2) + # No constraints in this model as yet, just need to set scaling factors # for expressions sf_F = iscale.get_scaling_factor(self.flow_vol, default=1, warning=True) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py index c3b7c6c2ea..0bdc6c9c4f 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py @@ -499,10 +499,10 @@ def test_constraint_scaling_routine(self): sfx = model.rxns[1].scaling_factor assert len(sfx) == 84 assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx( - 5.59910414e5, rel=1e-8 + 5.574193548e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx( - 3.09119011e5, rel=1e-8 + 3.0857142857e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R3"]] == pytest.approx( 8.424599832e4, rel=1e-8 @@ -538,7 +538,7 @@ def test_constraint_scaling_routine(self): 1.0271158587e7, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R14"]] == pytest.approx( - 3.663003663e6, rel=1e-8 + 3.66101694915e6, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R15"]] == pytest.approx( 1.7771459037e7, rel=1e-8 @@ -547,34 +547,42 @@ def test_constraint_scaling_routine(self): 1.00010001e7, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R17"]] == pytest.approx( - 3.1456432841e7, rel=1e-8 + 3.0857142857e7, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R18"]] == pytest.approx( 5.678591709e6, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R19"]] == pytest.approx( - 1.3625834582e7, rel=1e-8 + 1.35e7, rel=1e-8 ) - assert sfx[model.rxns[1].Dissociation] == pytest.approx(1.4873815e-3, rel=1e-8) + assert sfx[model.rxns[1].Dissociation] == pytest.approx(3.10210344e-2, rel=1e-8) assert sfx[model.rxns[1].CO2_acid_base_equilibrium] == pytest.approx( - 1.087426448e-2, rel=1e-8 + 6.83928318e-2, rel=1e-8 ) assert sfx[model.rxns[1].IN_acid_base_equilibrium] == pytest.approx( - 1.60001205e-3, rel=1e-8 + 4.69507548e-2, rel=1e-8 ) assert sfx[model.rxns[1].pH_calc] == pytest.approx(0.2, rel=1e-8) - assert sfx[model.rxns[1].concentration_of_va] == pytest.approx(1e2, rel=1e-8) - assert sfx[model.rxns[1].concentration_of_bu] == pytest.approx(1e2, rel=1e-8) - assert sfx[model.rxns[1].concentration_of_pro] == pytest.approx(1e2, rel=1e-8) - assert sfx[model.rxns[1].concentration_of_ac] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_va] == pytest.approx( + 83.3333333333, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_bu] == pytest.approx( + 76.92307692, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_pro] == pytest.approx(62.5, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_ac] == pytest.approx(5, rel=1e-8) assert sfx[model.rxns[1].concentration_of_hco3] == pytest.approx( 0.3333333333, rel=1e-8 ) assert sfx[model.rxns[1].concentration_of_nh3] == pytest.approx( 0.3333333333, rel=1e-8 ) - assert sfx[model.rxns[1].concentration_of_co2] == pytest.approx(1e1, rel=1e-8) - assert sfx[model.rxns[1].concentration_of_nh4] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_co2] == pytest.approx( + 6.66666666667, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_nh4] == pytest.approx( + 7.692307692, rel=1e-8 + ) assert sfx[model.rxns[1].S_H_cons] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R1"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R2"]] == pytest.approx(1, rel=1e-8) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py index 53108c36e0..2ea6afcc90 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_thermo.py @@ -317,7 +317,7 @@ def test_variable_scaling_routine(self): scaler.variable_scaling_routine(model.props[1]) sfx = model.props[1].scaling_factor - assert len(sfx) == 30 + assert len(sfx) == 3 assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) @@ -349,7 +349,7 @@ def test_scale_model(self): assert isinstance(model.props[1].scaling_factor, Suffix) sfx = model.props[1].scaling_factor - assert len(sfx) == 30 + assert len(sfx) == 3 assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) From 863e6b301860242181808615f66f585b662506bc Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Wed, 27 Nov 2024 14:53:19 -0500 Subject: [PATCH 07/46] Move more iscale scaling to calculate_scaling_factors --- .../adm1_properties_vapor.py | 16 ++--- .../anaerobic_digestion/adm1_reactions.py | 12 ++-- .../tests/test_adm1_reaction.py | 61 ++++++++++++------- .../tests/test_adm1_vapor_thermo.py | 4 +- 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py index bd31485cdd..e044d9b7e7 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py @@ -405,14 +405,6 @@ def energy_density_expression(self): rule=energy_density_expression, doc="Energy density term" ) - iscale.set_scaling_factor(self.flow_vol, 1e5) - iscale.set_scaling_factor(self.temperature, 1e-1) - iscale.set_scaling_factor(self.pressure, 1e-3) - iscale.set_scaling_factor(self.conc_mass_comp, 1e2) - iscale.set_scaling_factor(self.conc_mass_comp["S_h2"], 1e3) - iscale.set_scaling_factor(self.pressure_sat, 1e-3) - iscale.set_scaling_factor(self.pressure_sat["S_h2"], 1e-2) - def get_material_flow_terms(self, p, j): return self.material_flow_expression[j] @@ -454,6 +446,14 @@ def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() + iscale.set_scaling_factor(self.flow_vol, 1e5) + iscale.set_scaling_factor(self.temperature, 1e-1) + iscale.set_scaling_factor(self.pressure, 1e-3) + iscale.set_scaling_factor(self.conc_mass_comp, 1e2) + iscale.set_scaling_factor(self.conc_mass_comp["S_h2"], 1e3) + iscale.set_scaling_factor(self.pressure_sat, 1e-3) + iscale.set_scaling_factor(self.pressure_sat["S_h2"], 1e-2) + sf_F = iscale.get_scaling_factor(self.flow_vol, default=1e2, warning=True) sf_T = iscale.get_scaling_factor(self.temperature, default=1e-2, warning=True) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py index 5623983918..4b8eaf66ab 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_reactions.py @@ -1895,6 +1895,12 @@ def rate_expression_rule(b, r): self.del_component(self.rate_expression) raise + def get_reaction_rate_basis(self): + return MaterialFlowBasis.mass + + def calculate_scaling_factors(self): + super().calculate_scaling_factors() + iscale.set_scaling_factor(self.I, 1e1) iscale.set_scaling_factor(self.conc_mass_va, 1e2) iscale.set_scaling_factor(self.conc_mass_bu, 1e2) @@ -1910,12 +1916,6 @@ def rate_expression_rule(b, r): iscale.set_scaling_factor(self.pK_a_IN, 1e0) iscale.set_scaling_factor(self.pH, 1e0) - def get_reaction_rate_basis(self): - return MaterialFlowBasis.mass - - def calculate_scaling_factors(self): - super().calculate_scaling_factors() - for i, c in self.rates.items(): iscale.set_scaling_factor(self.reaction_rate[i], 1 / c) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py index 0bdc6c9c4f..dc07ecdb81 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py @@ -446,6 +446,7 @@ def test_variable_scaling_routine(self): # Trigger build of reaction properties model.rxns[1].reaction_rate + model.rxns[1].I scaler = model.rxns[1].default_scaler() assert isinstance(scaler, ADM1ReactionScaler) @@ -455,8 +456,26 @@ def test_variable_scaling_routine(self): assert isinstance(model.rxns[1].scaling_factor, Suffix) sfx = model.rxns[1].scaling_factor - assert len(sfx) == 52 - assert sfx[model.rxns[1].I] == pytest.approx(1e1, rel=1e-8) + assert len(sfx) == 38 + assert sfx[model.rxns[1].I["R1"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R2"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R3"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R4"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R5"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R6"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R7"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R8"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R9"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R10"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R11"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R12"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R13"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R14"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R15"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R16"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R17"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R18"]] == pytest.approx(1e1, rel=1e-8) + assert sfx[model.rxns[1].I["R19"]] == pytest.approx(1e1, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R1"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R2"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R3"]] == pytest.approx(1e2, rel=1e-8) @@ -497,7 +516,7 @@ def test_constraint_scaling_routine(self): assert isinstance(model.rxns[1].scaling_factor, Suffix) sfx = model.rxns[1].scaling_factor - assert len(sfx) == 84 + assert len(sfx) == 51 assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx( 5.574193548e5, rel=1e-8 ) @@ -511,25 +530,25 @@ def test_constraint_scaling_routine(self): 2.93083236e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R5"]] == pytest.approx( - 2.93772033e5, rel=1e-8 + 2.9257142857e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R6"]] == pytest.approx( 8.4245998e4, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R7"]] == pytest.approx( - 3.13971743e5, rel=1e-8 + 3.024242424e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx( - 3.99201597e5, rel=1e-8 + 3.69767443395e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R9"]] == pytest.approx( 3.09597523e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R10"]] == pytest.approx( - 3.79362671e5, rel=1e-8 + 3.44175824176e5, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R11"]] == pytest.approx( - 8.1967213e4, rel=1e-8 + 2.4868421053e4, rel=1e-8 ) assert sfx[model.rxns[1].rate_expression["R12"]] == pytest.approx( 2.39005736e5, rel=1e-8 @@ -562,7 +581,7 @@ def test_constraint_scaling_routine(self): assert sfx[model.rxns[1].IN_acid_base_equilibrium] == pytest.approx( 4.69507548e-2, rel=1e-8 ) - assert sfx[model.rxns[1].pH_calc] == pytest.approx(0.2, rel=1e-8) + assert sfx[model.rxns[1].pH_calc] == pytest.approx(0.1428571429, rel=1e-8) assert sfx[model.rxns[1].concentration_of_va] == pytest.approx( 83.3333333333, rel=1e-8 ) @@ -572,10 +591,10 @@ def test_constraint_scaling_routine(self): assert sfx[model.rxns[1].concentration_of_pro] == pytest.approx(62.5, rel=1e-8) assert sfx[model.rxns[1].concentration_of_ac] == pytest.approx(5, rel=1e-8) assert sfx[model.rxns[1].concentration_of_hco3] == pytest.approx( - 0.3333333333, rel=1e-8 + 0.1428571429, rel=1e-8 ) assert sfx[model.rxns[1].concentration_of_nh3] == pytest.approx( - 0.3333333333, rel=1e-8 + 0.1081081081, rel=1e-8 ) assert sfx[model.rxns[1].concentration_of_co2] == pytest.approx( 6.66666666667, rel=1e-8 @@ -583,19 +602,19 @@ def test_constraint_scaling_routine(self): assert sfx[model.rxns[1].concentration_of_nh4] == pytest.approx( 7.692307692, rel=1e-8 ) - assert sfx[model.rxns[1].S_H_cons] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].S_H_cons] == pytest.approx(7.1428571429, rel=1e-8) assert sfx[model.rxns[1].I_fun["R1"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R2"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R3"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R4"]] == pytest.approx(1, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R5"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R6"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R7"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R8"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R9"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R10"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R11"]] == pytest.approx(10, rel=1e-8) - assert sfx[model.rxns[1].I_fun["R12"]] == pytest.approx(10, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R5"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R6"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R7"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R8"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R9"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R10"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R11"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R12"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R13"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R14"]] == pytest.approx(1, rel=1e-8) assert sfx[model.rxns[1].I_fun["R15"]] == pytest.approx(1, rel=1e-8) @@ -624,7 +643,7 @@ def test_scale_model(self): assert isinstance(model.rxns[1].scaling_factor, Suffix) sfx = model.rxns[1].scaling_factor - assert len(sfx) == 103 + assert len(sfx) == 89 assert sfx[model.rxns[1].reaction_rate["R1"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R2"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R3"]] == pytest.approx(1e2, rel=1e-8) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py index da891b0f0e..859b65c7a4 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py @@ -277,7 +277,7 @@ def test_variable_scaling_routine(self): scaler.variable_scaling_routine(model.props[1]) sfx = model.props[1].scaling_factor - assert len(sfx) == 12 + assert len(sfx) == 3 assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) assert sfx[model.props[1].pressure] == pytest.approx(1e-3, rel=1e-8) assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) @@ -309,7 +309,7 @@ def test_scale_model(self): assert isinstance(model.props[1].scaling_factor, Suffix) sfx = model.props[1].scaling_factor - assert len(sfx) == 16 + assert len(sfx) == 7 assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) assert sfx[model.props[1].pressure] == pytest.approx(1e-3, rel=1e-8) assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) From 01427e9004201263468f69cd5e887b0f38b582d2 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 29 Nov 2024 06:34:26 -0500 Subject: [PATCH 08/46] Move iscale functionality in AD to calculate_scaling_factors --- watertap/unit_models/anaerobic_digester.py | 72 +- .../tests/test_anaerobic_digester.py | 863 +++++++++++++++++- 2 files changed, 893 insertions(+), 42 deletions(-) diff --git a/watertap/unit_models/anaerobic_digester.py b/watertap/unit_models/anaerobic_digester.py index 72b345cbe5..7e43589f97 100644 --- a/watertap/unit_models/anaerobic_digester.py +++ b/watertap/unit_models/anaerobic_digester.py @@ -129,7 +129,9 @@ def variable_scaling_routine( # Scaling control volume variables # TODO: Revisit this scaling factor & the addition of other scaling factors - self.scale_variable_by_default(model.liquid_phase.volume, overwrite=overwrite) + self.scale_variable_by_default( + model.liquid_phase.volume[0], overwrite=overwrite + ) def constraint_scaling_routine( self, model, overwrite: bool = False, submodel_scalers: dict = None @@ -179,13 +181,6 @@ def constraint_scaling_routine( ) # Scale unit level constraints - if hasattr(model, "rate_reaction_constraint"): - for c in model.rate_reaction_constraint.values(): - self.scale_constraint_by_nominal_value( - c, - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) if hasattr(model, "unit_material_balance"): for c in model.unit_material_balance.values(): self.scale_constraint_by_nominal_value( @@ -204,85 +199,86 @@ def constraint_scaling_routine( # TODO: See if these can be scaled in a for loop like the control volume constraints if hasattr(model, "CO2_Henrys_law"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.CO2_Henrys_law[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "Ch4_Henrys_law"): self.scale_constraint_by_nominal_value( - model.Ch4_Henrys_law, + model.Ch4_Henrys_law[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "H2_Henrys_law"): self.scale_constraint_by_nominal_value( - model.H2_Henrys_law, + model.H2_Henrys_law[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "outlet_P"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.outlet_P[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "Sh2_conc"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.Sh2_conc[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "Sch4_conc"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.Sch4_conc[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "Sco2_conc"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.Sco2_conc[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "flow_vol_vap"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.flow_vol_vap[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "ad_total_volume"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.ad_total_volume[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "AD_retention_time"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.AD_retention_time[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) + # TODO: Might be able to remove temperature, pressure, and enthalpy balances if hasattr(model, "unit_temperature_equality"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.unit_temperature_equality[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "unit_pressure_balance"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.unit_pressure_balance[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "unit_enthalpy_balance"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.unit_enthalpy_balance[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) if hasattr(model, "unit_electricity_consumption"): self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law, + model.unit_electricity_consumption[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) @@ -1009,22 +1005,6 @@ def rule_electricity_consumption(self, t): # Set references to balance terms at unit level self.heat_duty = Reference(self.liquid_phase.heat[:]) - iscale.set_scaling_factor(self.KH_co2, 1e2) - iscale.set_scaling_factor(self.KH_ch4, 1e2) - iscale.set_scaling_factor(self.KH_h2, 1e2) - iscale.set_scaling_factor(self.hydraulic_retention_time, 1e-6) - iscale.set_scaling_factor(self.volume_AD, 1e-2) - iscale.set_scaling_factor(self.volume_vapor, 1e-2) - iscale.set_scaling_factor(self.liquid_phase.rate_reaction_generation, 1e4) - iscale.set_scaling_factor(self.liquid_phase.mass_transfer_term, 1e2) - iscale.set_scaling_factor(self.liquid_phase.heat, 1e0) - iscale.set_scaling_factor(self.liquid_phase.rate_reaction_extent, 1e4) - iscale.set_scaling_factor(self.liquid_phase.enthalpy_transfer, 1e0) - iscale.set_scaling_factor(self.liquid_phase.volume, 1e-2) - iscale.set_scaling_factor(self.electricity_consumption, 1e0) - for i, c in self.ad_performance_eqn.items(): - iscale.constraint_scaling_transform(c, 1e2) - def _get_stream_table_contents(self, time_point=0): return create_stream_table_dataframe( { @@ -1053,6 +1033,22 @@ def calculate_scaling_factors(self): & self.liquid_phase.properties_out.component_list ) + iscale.set_scaling_factor(self.KH_co2, 1e2) + iscale.set_scaling_factor(self.KH_ch4, 1e2) + iscale.set_scaling_factor(self.KH_h2, 1e2) + iscale.set_scaling_factor(self.hydraulic_retention_time, 1e-6) + iscale.set_scaling_factor(self.volume_AD, 1e-2) + iscale.set_scaling_factor(self.volume_vapor, 1e-2) + iscale.set_scaling_factor(self.liquid_phase.rate_reaction_generation, 1e4) + iscale.set_scaling_factor(self.liquid_phase.mass_transfer_term, 1e2) + iscale.set_scaling_factor(self.liquid_phase.heat, 1e0) + iscale.set_scaling_factor(self.liquid_phase.rate_reaction_extent, 1e4) + iscale.set_scaling_factor(self.liquid_phase.enthalpy_transfer, 1e0) + iscale.set_scaling_factor(self.liquid_phase.volume, 1e-2) + iscale.set_scaling_factor(self.electricity_consumption, 1e0) + for i, c in self.ad_performance_eqn.items(): + iscale.constraint_scaling_transform(c, 1e2) + # TODO: improve this later; for now, this resolved some scaling issues for modified adm1 test file if "S_IP" in self.config.liquid_property_package.component_list: sf = iscale.get_scaling_factor( diff --git a/watertap/unit_models/tests/test_anaerobic_digester.py b/watertap/unit_models/tests/test_anaerobic_digester.py index 5852602435..5e0fb761f2 100644 --- a/watertap/unit_models/tests/test_anaerobic_digester.py +++ b/watertap/unit_models/tests/test_anaerobic_digester.py @@ -19,25 +19,34 @@ Department of Industrial Electrical Engineering and Automation, Lund University, Lund, Sweden, pp.1-35. """ +import pytest from pyomo.environ import ( ConcreteModel, + Suffix, + TransformationFactory, ) from idaes.core import ( FlowsheetBlock, ) - +from idaes.core.util.scaling import ( + get_jacobian, + jacobian_cond, +) from watertap.core.solvers import get_solver -from watertap.unit_models.anaerobic_digester import AD +from watertap.unit_models.anaerobic_digester import AD, ADScaler from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties import ( ADM1ParameterBlock, + ADM1PropertiesScaler, ) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties_vapor import ( ADM1_vaporParameterBlock, + ADM1_vaporPropertiesScaler, ) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_reactions import ( ADM1ReactionParameterBlock, + ADM1ReactionScaler, ) from watertap.unit_models.tests.unit_test_harness import UnitTestHarness @@ -51,6 +60,7 @@ # ----------------------------------------------------------------------------- +# TODO: Refine testing once iscale functionality has been deprecated/removed def build(): m = ConcreteModel() m.fs = FlowsheetBlock(dynamic=False) @@ -105,13 +115,13 @@ def build(): m.fs.unit.volume_vapor.fix(300) m.fs.unit.liquid_outlet.temperature.fix(308.15) + iscale.calculate_scaling_factors(m.fs.unit) + # Set scaling factors for badly scaled variables iscale.set_scaling_factor( m.fs.unit.liquid_phase.mass_transfer_term[0, "Liq", "S_h2"], 1e7 ) - iscale.calculate_scaling_factors(m.fs.unit) - return m @@ -238,3 +248,848 @@ def configure(self): } return m + + +class TestADScaler: + @pytest.fixture + def model(self): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.props = ADM1ParameterBlock() + m.fs.props_vap = ADM1_vaporParameterBlock() + m.fs.rxn_props = ADM1ReactionParameterBlock(property_package=m.fs.props) + + m.fs.unit = AD( + liquid_property_package=m.fs.props, + vapor_property_package=m.fs.props_vap, + reaction_package=m.fs.rxn_props, + has_heat_transfer=True, + has_pressure_change=False, + ) + + # Set the operating conditions + m.fs.unit.inlet.flow_vol.fix(170 / 24 / 3600) + m.fs.unit.inlet.temperature.fix(308.15) + m.fs.unit.inlet.pressure.fix(101325) + + m.fs.unit.inlet.conc_mass_comp[0, "S_su"].fix(0.01) + m.fs.unit.inlet.conc_mass_comp[0, "S_aa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_fa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_va"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_bu"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_pro"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_ac"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_h2"].fix(1e-8) + m.fs.unit.inlet.conc_mass_comp[0, "S_ch4"].fix(1e-5) + m.fs.unit.inlet.conc_mass_comp[0, "S_IC"].fix(0.48) + m.fs.unit.inlet.conc_mass_comp[0, "S_IN"].fix(0.14) + m.fs.unit.inlet.conc_mass_comp[0, "S_I"].fix(0.02) + + m.fs.unit.inlet.conc_mass_comp[0, "X_c"].fix(2) + m.fs.unit.inlet.conc_mass_comp[0, "X_ch"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_pr"].fix(20) + m.fs.unit.inlet.conc_mass_comp[0, "X_li"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_su"].fix(0.0) + m.fs.unit.inlet.conc_mass_comp[0, "X_aa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_fa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_c4"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_pro"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_ac"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_h2"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_I"].fix(25) + + m.fs.unit.inlet.cations[0].fix(0.04) + m.fs.unit.inlet.anions[0].fix(0.02) + + m.fs.unit.volume_liquid.fix(3400) + m.fs.unit.volume_vapor.fix(300) + m.fs.unit.liquid_outlet.temperature.fix(308.15) + + return m + + @pytest.mark.component + def test_variable_scaling_routine(self, model): + scaler = model.fs.unit.default_scaler() + + assert isinstance(scaler, ADScaler) + + scaler.variable_scaling_routine(model.fs.unit) + + # Inlet state + sfx_in = model.fs.unit.liquid_phase.properties_in[0].scaling_factor + assert isinstance(sfx_in, Suffix) + assert len(sfx_in) == 3 + assert sfx_in[ + model.fs.unit.liquid_phase.properties_in[0].flow_vol + ] == pytest.approx(1e5, rel=1e-8) + assert sfx_in[ + model.fs.unit.liquid_phase.properties_in[0].pressure + ] == pytest.approx(1e-6, rel=1e-8) + assert sfx_in[ + model.fs.unit.liquid_phase.properties_in[0].temperature + ] == pytest.approx(1e-1, rel=1e-8) + + # Outlet state - should be the same as the inlet + sfx_out = model.fs.unit.liquid_phase.properties_out[0].scaling_factor + assert isinstance(sfx_out, Suffix) + assert len(sfx_out) == 3 + assert sfx_out[ + model.fs.unit.liquid_phase.properties_out[0].flow_vol + ] == pytest.approx(1e5, rel=1e-8) + assert sfx_out[ + model.fs.unit.liquid_phase.properties_out[0].pressure + ] == pytest.approx(1e-6, rel=1e-8) + assert sfx_out[ + model.fs.unit.liquid_phase.properties_out[0].temperature + ] == pytest.approx(1e-1, rel=1e-8) + + # Reaction block + sfx_rxn = model.fs.unit.liquid_phase.reactions[0].scaling_factor + assert isinstance(sfx_rxn, Suffix) + assert len(sfx_rxn) == 38 + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R1"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R2"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R3"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R4"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R5"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R6"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R7"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R8"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R9"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R10"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R11"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R12"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R13"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R14"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R15"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R16"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R17"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R18"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R19"] + ] == pytest.approx(1e2, rel=1e-8) + + # Check that unit model has scaling factors + sfx_cv = model.fs.unit.liquid_phase.scaling_factor + assert isinstance(sfx_cv, Suffix) + assert len(sfx_cv) == 1 + assert sfx_cv[model.fs.unit.liquid_phase.volume[0]] == pytest.approx( + 1e-2, rel=1e-3 + ) + + # + @pytest.mark.component + def test_constraint_scaling_routine(self, model): + scaler = model.fs.unit.default_scaler() + + assert isinstance(scaler, ADScaler) + + scaler.constraint_scaling_routine(model.fs.unit) + + sfx_out = model.fs.unit.liquid_phase.properties_out[0].scaling_factor + assert isinstance(sfx_out, Suffix) + assert len(sfx_out) == 0 + + sfx_rxn = model.fs.unit.liquid_phase.reactions[0].scaling_factor + assert isinstance(sfx_rxn, Suffix) + assert len(sfx_rxn) == 51 + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R1"] + ] == pytest.approx(5.574193548e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R2"] + ] == pytest.approx(3.0857142857e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R3"] + ] == pytest.approx(8.424599832e4, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R4"] + ] == pytest.approx(2.93083236e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R5"] + ] == pytest.approx(2.9257142857e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R6"] + ] == pytest.approx(8.4245998e4, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R7"] + ] == pytest.approx(3.024242424e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R8"] + ] == pytest.approx(3.697674433395e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R9"] + ] == pytest.approx(3.09597523e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R10"] + ] == pytest.approx(3.44175824176e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R11"] + ] == pytest.approx(2.4868421053e4, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R12"] + ] == pytest.approx(2.39005736e5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R13"] + ] == pytest.approx(1.0271158587e7, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R14"] + ] == pytest.approx(3.6610169492e6, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R15"] + ] == pytest.approx(1.7771459037e7, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R16"] + ] == pytest.approx(1.00010001e7, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R17"] + ] == pytest.approx(3.08571428571e7, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R18"] + ] == pytest.approx(5.678591709e6, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R19"] + ] == pytest.approx(1.35e7, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].Dissociation + ] == pytest.approx(3.10210344e-2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].CO2_acid_base_equilibrium + ] == pytest.approx(6.83928318e-2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].IN_acid_base_equilibrium + ] == pytest.approx(4.69507548e-2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].pH_calc + ] == pytest.approx(0.1428571429, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_va + ] == pytest.approx(83.33333333, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_bu + ] == pytest.approx(76.923076923, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_pro + ] == pytest.approx(62.5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_ac + ] == pytest.approx(5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_hco3 + ] == pytest.approx(0.142857143, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_nh3 + ] == pytest.approx(0.10810810811, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_co2 + ] == pytest.approx(6.6666666667, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_nh4 + ] == pytest.approx(7.6923076923, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].S_H_cons + ] == pytest.approx(7.14285714, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R1"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R2"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R3"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R4"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R5"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R6"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R7"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R8"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R9"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R10"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R11"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R12"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R13"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R14"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R15"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R16"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R17"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R18"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R19"] + ] == pytest.approx(1, rel=1e-8) + + # Check that unit model has scaling factors + sfx_cv = model.fs.unit.liquid_phase.scaling_factor + assert isinstance(model.fs.unit.liquid_phase.scaling_factor, Suffix) + assert len(sfx_cv) == 56 + assert sfx_cv[ + model.fs.unit.liquid_phase.enthalpy_balances[0.0] + ] == pytest.approx(7.783208078e-10, abs=1e-8) + assert sfx_cv[ + model.fs.unit.liquid_phase.pressure_balance[0.0] + ] == pytest.approx(9.869232667e-6, rel=1e-8) + for ( + c + ) in model.fs.unit.liquid_phase.rate_reaction_stoichiometry_constraint.values(): + assert sfx_cv[c] == pytest.approx(1, rel=1e-8) + + sfx_unit = model.fs.unit.scaling_factor + assert isinstance(sfx_unit, Suffix) + assert len(sfx_unit) == 59 + assert sfx_unit[model.fs.unit.CO2_Henrys_law[0]] == pytest.approx( + 1.277154575e-1, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Ch4_Henrys_law[0]] == pytest.approx( + 1.479435417e-1, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.H2_Henrys_law[0]] == pytest.approx( + 1.38666123e-1, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.outlet_P[0]] == pytest.approx( + 9.8692326672e-6, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Sh2_conc[0]] == pytest.approx( + 5.524296675192e5, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Sch4_conc[0]] == pytest.approx( + 2.310160428, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Sco2_conc[0]] == pytest.approx( + 1.069518717, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.flow_vol_vap[0]] == pytest.approx(1, rel=1e-8) + assert sfx_unit[model.fs.unit.ad_total_volume[0]] == pytest.approx( + 2.702702703e-4, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.AD_retention_time[0]] == pytest.approx( + 5.3178178178e-7, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.unit_temperature_equality[0]] == pytest.approx( + 3.2451728e-3, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.unit_enthalpy_balance[0]] == pytest.approx( + 7.783208078e-10, abs=1e-8 + ) + assert sfx_unit[model.fs.unit.unit_electricity_consumption[0]] == pytest.approx( + 4.214223e-2, rel=1e-8 + ) + + @pytest.mark.component + def test_scale_model(self, model): + scaler = model.fs.unit.default_scaler() + + assert isinstance(scaler, ADScaler) + + scaler.scale_model(model.fs.unit) + + # Inlet state + sfx_in = model.fs.unit.liquid_phase.properties_in[0].scaling_factor + assert isinstance(sfx_in, Suffix) + assert len(sfx_in) == 3 + assert sfx_in[ + model.fs.unit.liquid_phase.properties_in[0].flow_vol + ] == pytest.approx(1e5, rel=1e-8) + assert sfx_in[ + model.fs.unit.liquid_phase.properties_in[0].pressure + ] == pytest.approx(1e-6, rel=1e-8) + assert sfx_in[ + model.fs.unit.liquid_phase.properties_in[0].temperature + ] == pytest.approx(1e-1, rel=1e-8) + + # Outlet state - should be the same as the inlet + sfx_out = model.fs.unit.liquid_phase.properties_out[0].scaling_factor + assert isinstance(sfx_out, Suffix) + assert len(sfx_out) == 3 + assert sfx_out[ + model.fs.unit.liquid_phase.properties_out[0].flow_vol + ] == pytest.approx(1e5, rel=1e-8) + assert sfx_out[ + model.fs.unit.liquid_phase.properties_out[0].pressure + ] == pytest.approx(1e-6, rel=1e-8) + assert sfx_out[ + model.fs.unit.liquid_phase.properties_out[0].temperature + ] == pytest.approx(1e-1, rel=1e-8) + + # Reaction block + sfx_rxn = model.fs.unit.liquid_phase.reactions[0].scaling_factor + assert isinstance(sfx_rxn, Suffix) + assert len(sfx_rxn) == 89 + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R1"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R2"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R3"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R4"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R5"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R6"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R7"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R8"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R9"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R10"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R11"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R12"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R13"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R14"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R15"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R16"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R17"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R18"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0].reaction_rate["R19"] + ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R1"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R2"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R3"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R4"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R5"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R6"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R7"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R8"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R9"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R10"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R11"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R12"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R13"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R14"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R15"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R16"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R17"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R18"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R19"] + ] == pytest.approx(100, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].Dissociation + ] == pytest.approx(3.10210344e-2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].CO2_acid_base_equilibrium + ] == pytest.approx(6.83928318e-2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].IN_acid_base_equilibrium + ] == pytest.approx(4.69507548e-2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].pH_calc + ] == pytest.approx(0.142857143, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_va + ] == pytest.approx(83.33333333, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_bu + ] == pytest.approx(76.923076923, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_pro + ] == pytest.approx(62.5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_ac + ] == pytest.approx(5, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_hco3 + ] == pytest.approx(0.142857143, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_nh3 + ] == pytest.approx(0.10810810811, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_co2 + ] == pytest.approx(6.6666666667, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].concentration_of_nh4 + ] == pytest.approx(7.6923076923, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].S_H_cons + ] == pytest.approx(7.14285714, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R1"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R2"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R3"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R4"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R5"] + ] == pytest.approx(1.000769231, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R6"] + ] == pytest.approx(1.000769231, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R7"] + ] == pytest.approx(1.046804615, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R8"] + ] == pytest.approx(1.023786923, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R9"] + ] == pytest.approx(1.023786923, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R10"] + ] == pytest.approx(1.066534066, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R11"] + ] == pytest.approx(3.280299145, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R12"] + ] == pytest.approx(1.000769231, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R13"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R14"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R15"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R16"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R17"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R18"] + ] == pytest.approx(1, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I_fun["R19"] + ] == pytest.approx(1, rel=1e-8) + + # Check that unit model has scaling factors + sfx_cv = model.fs.unit.liquid_phase.scaling_factor + assert isinstance(sfx_cv, Suffix) + assert len(sfx_cv) == 57 + assert sfx_cv[ + model.fs.unit.liquid_phase.enthalpy_balances[0.0] + ] == pytest.approx(3.95570105e-7, rel=1e-8) + assert sfx_cv[ + model.fs.unit.liquid_phase.pressure_balance[0.0] + ] == pytest.approx(1e-6, rel=1e-8) + for ( + c + ) in model.fs.unit.liquid_phase.rate_reaction_stoichiometry_constraint.values(): + assert sfx_cv[c] == pytest.approx(1, rel=1e-8) + + sfx_unit = model.fs.unit.scaling_factor + assert isinstance(sfx_unit, Suffix) + assert len(sfx_unit) == 59 + assert sfx_unit[model.fs.unit.CO2_Henrys_law[0]] == pytest.approx( + 0.127715457, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Ch4_Henrys_law[0]] == pytest.approx( + 0.147943542, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.H2_Henrys_law[0]] == pytest.approx( + 0.138666123, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.outlet_P[0]] == pytest.approx( + 9.8692326672e-6, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Sh2_conc[0]] == pytest.approx( + 5.52429667519e5, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Sch4_conc[0]] == pytest.approx( + 2.310160428, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.Sco2_conc[0]] == pytest.approx( + 1.069518717, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.flow_vol_vap[0]] == pytest.approx(1, rel=1e-8) + assert sfx_unit[model.fs.unit.ad_total_volume[0]] == pytest.approx( + 2.7027027027e-4, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.AD_retention_time[0]] == pytest.approx( + 5.3178178178e-7, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.unit_temperature_equality[0]] == pytest.approx( + 3.2451728e-3, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.unit_enthalpy_balance[0]] == pytest.approx( + 3.95570105e-7, rel=1e-8 + ) + assert sfx_unit[model.fs.unit.unit_electricity_consumption[0]] == pytest.approx( + 4.214223e-2, rel=1e-8 + ) + + # TODO: Remove test once iscale is deprecated + @pytest.mark.integration + def test_example_case_iscale(self): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.props = ADM1ParameterBlock() + m.fs.props_vap = ADM1_vaporParameterBlock() + m.fs.rxn_props = ADM1ReactionParameterBlock(property_package=m.fs.props) + + m.fs.unit = AD( + liquid_property_package=m.fs.props, + vapor_property_package=m.fs.props_vap, + reaction_package=m.fs.rxn_props, + has_heat_transfer=True, + has_pressure_change=False, + ) + + # Set the operating conditions + m.fs.unit.inlet.flow_vol.fix(170 / 24 / 3600) + m.fs.unit.inlet.temperature.fix(308.15) + m.fs.unit.inlet.pressure.fix(101325) + + m.fs.unit.inlet.conc_mass_comp[0, "S_su"].fix(0.01) + m.fs.unit.inlet.conc_mass_comp[0, "S_aa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_fa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_va"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_bu"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_pro"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_ac"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_h2"].fix(1e-8) + m.fs.unit.inlet.conc_mass_comp[0, "S_ch4"].fix(1e-5) + m.fs.unit.inlet.conc_mass_comp[0, "S_IC"].fix(0.48) + m.fs.unit.inlet.conc_mass_comp[0, "S_IN"].fix(0.14) + m.fs.unit.inlet.conc_mass_comp[0, "S_I"].fix(0.02) + + m.fs.unit.inlet.conc_mass_comp[0, "X_c"].fix(2) + m.fs.unit.inlet.conc_mass_comp[0, "X_ch"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_pr"].fix(20) + m.fs.unit.inlet.conc_mass_comp[0, "X_li"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_su"].fix(0.0) + m.fs.unit.inlet.conc_mass_comp[0, "X_aa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_fa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_c4"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_pro"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_ac"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_h2"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_I"].fix(25) + + m.fs.unit.inlet.cations[0].fix(0.04) + m.fs.unit.inlet.anions[0].fix(0.02) + + m.fs.unit.volume_liquid.fix(3400) + m.fs.unit.volume_vapor.fix(300) + m.fs.unit.liquid_outlet.temperature.fix(308.15) + + iscale.calculate_scaling_factors(m.fs.unit) + + # Set scaling factors for badly scaled variables + iscale.set_scaling_factor( + m.fs.unit.liquid_phase.mass_transfer_term[0, "Liq", "S_h2"], 1e7 + ) + + # Check condition number to confirm scaling + sm = TransformationFactory("core.scale_model").create_using(m, rename=False) + jac, _ = get_jacobian(sm, scaled=False) + assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( + 4.184434e12, rel=1e-3 + ) + + @pytest.mark.integration + def test_example_case_scaler_scaling(self): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.props = ADM1ParameterBlock() + m.fs.props_vap = ADM1_vaporParameterBlock() + m.fs.rxn_props = ADM1ReactionParameterBlock(property_package=m.fs.props) + + m.fs.unit = AD( + liquid_property_package=m.fs.props, + vapor_property_package=m.fs.props_vap, + reaction_package=m.fs.rxn_props, + has_heat_transfer=True, + has_pressure_change=False, + ) + + # Set the operating conditions + m.fs.unit.inlet.flow_vol.fix(170 / 24 / 3600) + m.fs.unit.inlet.temperature.fix(308.15) + m.fs.unit.inlet.pressure.fix(101325) + + m.fs.unit.inlet.conc_mass_comp[0, "S_su"].fix(0.01) + m.fs.unit.inlet.conc_mass_comp[0, "S_aa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_fa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_va"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_bu"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_pro"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_ac"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_h2"].fix(1e-8) + m.fs.unit.inlet.conc_mass_comp[0, "S_ch4"].fix(1e-5) + m.fs.unit.inlet.conc_mass_comp[0, "S_IC"].fix(0.48) + m.fs.unit.inlet.conc_mass_comp[0, "S_IN"].fix(0.14) + m.fs.unit.inlet.conc_mass_comp[0, "S_I"].fix(0.02) + + m.fs.unit.inlet.conc_mass_comp[0, "X_c"].fix(2) + m.fs.unit.inlet.conc_mass_comp[0, "X_ch"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_pr"].fix(20) + m.fs.unit.inlet.conc_mass_comp[0, "X_li"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_su"].fix(0.0) + m.fs.unit.inlet.conc_mass_comp[0, "X_aa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_fa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_c4"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_pro"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_ac"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_h2"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_I"].fix(25) + + m.fs.unit.inlet.cations[0].fix(0.04) + m.fs.unit.inlet.anions[0].fix(0.02) + + m.fs.unit.volume_liquid.fix(3400) + m.fs.unit.volume_vapor.fix(300) + m.fs.unit.liquid_outlet.temperature.fix(308.15) + + # TODO: Figure out how to implement vapor scaler - may need manual scaling + # May be the case that vapor scaler can't do anything since no control volume + # vapor_scaler = ADM1_vaporPropertiesScaler() + # vapor_scaler.scale_model(m.fs.props_vap) + + scaler = ADScaler() + scaler.scale_model( + m.fs.unit, + submodel_scalers={ + m.fs.unit.liquid_phase.properties_in: ADM1PropertiesScaler, + m.fs.unit.liquid_phase.properties_out: ADM1PropertiesScaler, + m.fs.unit.liquid_phase.reactions: ADM1ReactionScaler, + }, + ) + + # Check condition number to confirm scaling + sm = TransformationFactory("core.scale_model").create_using(m, rename=False) + jac, _ = get_jacobian(sm, scaled=False) + assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( + 9.43861834e16, rel=1e-3 + ) From 9b624d9f86926575d6e4d92da69bff82482f65c8 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 29 Nov 2024 06:59:08 -0500 Subject: [PATCH 09/46] Address pylint issue --- watertap/unit_models/tests/test_anaerobic_digester.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/watertap/unit_models/tests/test_anaerobic_digester.py b/watertap/unit_models/tests/test_anaerobic_digester.py index 5e0fb761f2..4e0037cdf2 100644 --- a/watertap/unit_models/tests/test_anaerobic_digester.py +++ b/watertap/unit_models/tests/test_anaerobic_digester.py @@ -42,7 +42,6 @@ ) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties_vapor import ( ADM1_vaporParameterBlock, - ADM1_vaporPropertiesScaler, ) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_reactions import ( ADM1ReactionParameterBlock, @@ -1072,11 +1071,6 @@ def test_example_case_scaler_scaling(self): m.fs.unit.volume_vapor.fix(300) m.fs.unit.liquid_outlet.temperature.fix(308.15) - # TODO: Figure out how to implement vapor scaler - may need manual scaling - # May be the case that vapor scaler can't do anything since no control volume - # vapor_scaler = ADM1_vaporPropertiesScaler() - # vapor_scaler.scale_model(m.fs.props_vap) - scaler = ADScaler() scaler.scale_model( m.fs.unit, From 07aabdd0459a7e441c12d233c50f9c8b2f8a4f08 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 29 Nov 2024 17:47:34 -0500 Subject: [PATCH 10/46] Undo changes to adm1 vapor property package --- .../adm1_properties_vapor.py | 59 ++++--------------- .../tests/test_adm1_vapor_thermo.py | 56 ------------------ 2 files changed, 10 insertions(+), 105 deletions(-) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py index e044d9b7e7..50cd540d72 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py @@ -34,7 +34,6 @@ from idaes.core.util.initialization import fix_state_vars, revert_state_vars import idaes.logger as idaeslog import idaes.core.util.scaling as iscale -from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme # Some more information about this module __author__ = "Alejandro Garciadiego, Xinhong Liu" @@ -218,44 +217,6 @@ def release_state(self, flags, outlvl=idaeslog.NOTSET): init_log.info("State Released.") -class ADM1_vaporPropertiesScaler(CustomScalerBase): - """ - Scaler for the vapor Anaerobic Digestion Model No.1 property package. - Flow and temperature are scaled by the default value (if no user input provided), and - pressure is scaled assuming an order of magnitude of 1e5 Pa. - """ - - UNIT_SCALING_FACTORS = { - # "QuantityName: (reference units, scaling factor) - "Pressure": (pyo.units.Pa, 1e-3), - } - - DEFAULT_SCALING_FACTORS = { - "flow_vol": 1e5, - "temperature": 1e-1, - } - - def variable_scaling_routine( - self, model, overwrite: bool = False, submodel_scalers: dict = None - ): - self.scale_variable_by_default(model.temperature, overwrite=overwrite) - self.scale_variable_by_default(model.flow_vol, overwrite=overwrite) - self.scale_variable_by_units(model.pressure, overwrite=overwrite) - - def constraint_scaling_routine( - self, model, overwrite: bool = False, submodel_scalers: dict = None - ): - # TODO: Revisit this scaling methodologies - # Consider other schemes, scale_constraint_by_default, or scale_constraints_by_jacobian_norm - if model.is_property_constructed("pressure_sat"): - for j in model._pressure_sat.values(): - self.scale_constraint_by_nominal_value( - j, - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - - @declare_process_block_class("ADM1_vaporStateBlock", block_class=_ADM1_vaporStateBlock) class ADM1_vaporStateBlockData(StateBlockData): """ @@ -263,8 +224,6 @@ class ADM1_vaporStateBlockData(StateBlockData): reaction system. """ - default_scaler = ADM1_vaporPropertiesScaler - def build(self): """ Callable method for Block construction @@ -405,6 +364,14 @@ def energy_density_expression(self): rule=energy_density_expression, doc="Energy density term" ) + iscale.set_scaling_factor(self.flow_vol, 1e5) + iscale.set_scaling_factor(self.temperature, 1e-1) + iscale.set_scaling_factor(self.pressure, 1e-3) + iscale.set_scaling_factor(self.conc_mass_comp, 1e2) + iscale.set_scaling_factor(self.conc_mass_comp["S_h2"], 1e3) + iscale.set_scaling_factor(self.pressure_sat, 1e-3) + iscale.set_scaling_factor(self.pressure_sat["S_h2"], 1e-2) + def get_material_flow_terms(self, p, j): return self.material_flow_expression[j] @@ -446,14 +413,8 @@ def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() - iscale.set_scaling_factor(self.flow_vol, 1e5) - iscale.set_scaling_factor(self.temperature, 1e-1) - iscale.set_scaling_factor(self.pressure, 1e-3) - iscale.set_scaling_factor(self.conc_mass_comp, 1e2) - iscale.set_scaling_factor(self.conc_mass_comp["S_h2"], 1e3) - iscale.set_scaling_factor(self.pressure_sat, 1e-3) - iscale.set_scaling_factor(self.pressure_sat["S_h2"], 1e-2) - + # No constraints in this model as yet, just need to set scaling factors + # for expressions sf_F = iscale.get_scaling_factor(self.flow_vol, default=1e2, warning=True) sf_T = iscale.get_scaling_factor(self.temperature, default=1e-2, warning=True) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py index 859b65c7a4..31de35737d 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_vapor_thermo.py @@ -19,7 +19,6 @@ from pyomo.environ import ( ConcreteModel, Param, - Suffix, value, Var, check_optimal_termination, @@ -32,7 +31,6 @@ from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties_vapor import ( ADM1_vaporParameterBlock, ADM1_vaporStateBlock, - ADM1_vaporPropertiesScaler, ) from idaes.core.util.model_statistics import ( fixed_variables_set, @@ -92,8 +90,6 @@ def model(self): @pytest.mark.unit def test_build(self, model): - assert model.props[1].default_scaler is ADM1_vaporPropertiesScaler - assert isinstance(model.props[1].flow_vol, Var) assert value(model.props[1].flow_vol) == 1 @@ -261,55 +257,3 @@ def test_pressures(self, model): @pytest.mark.unit def check_units(self, model): assert_units_consistent(model) - - -class TestADM1_vaporPropertiesScaler: - @pytest.mark.unit - def test_variable_scaling_routine(self): - model = ConcreteModel() - model.params = ADM1_vaporParameterBlock() - - model.props = model.params.build_state_block([1], defined_state=False) - - scaler = model.props[1].default_scaler() - assert isinstance(scaler, ADM1_vaporPropertiesScaler) - - scaler.variable_scaling_routine(model.props[1]) - - sfx = model.props[1].scaling_factor - assert len(sfx) == 3 - assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) - assert sfx[model.props[1].pressure] == pytest.approx(1e-3, rel=1e-8) - assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) - - @pytest.mark.unit - def test_constraint_scaling_routine(self): - model = ConcreteModel() - model.params = ADM1_vaporParameterBlock() - - model.props = model.params.build_state_block([1], defined_state=False) - - scaler = model.props[1].default_scaler() - assert isinstance(scaler, ADM1_vaporPropertiesScaler) - - scaler.constraint_scaling_routine(model.props[1]) - - @pytest.mark.unit - def test_scale_model(self): - model = ConcreteModel() - model.params = ADM1_vaporParameterBlock() - - model.props = model.params.build_state_block([1], defined_state=False) - - scaler = model.props[1].default_scaler() - assert isinstance(scaler, ADM1_vaporPropertiesScaler) - - scaler.scale_model(model.props[1]) - - assert isinstance(model.props[1].scaling_factor, Suffix) - - sfx = model.props[1].scaling_factor - assert len(sfx) == 7 - assert sfx[model.props[1].flow_vol] == pytest.approx(1e5, rel=1e-8) - assert sfx[model.props[1].pressure] == pytest.approx(1e-3, rel=1e-8) - assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) From 7b7328280b5d9844007ade5f2c2ff732d5ede8d0 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 2 Dec 2024 11:37:33 -0500 Subject: [PATCH 11/46] Update tests --- .../anaerobic_digester/ADM1_flowsheet.py | 3 +++ .../modified_ADM1_flowsheet_with_translators.py | 3 ++- .../activated_sludge/tests/test_asm1_thermo.py | 4 ++-- .../tests/test_modified_adm1_reaction.py | 4 ++++ watertap/unit_models/tests/test_clarifier.py | 4 ++-- watertap/unit_models/tests/test_cstr.py | 12 ++++++++---- .../unit_models/tests/test_cstr_injection.py | 15 +++++++++++---- .../unit_models/tests/test_thickener_unit.py | 16 ++++++++++++++++ 8 files changed, 48 insertions(+), 13 deletions(-) diff --git a/watertap/flowsheets/anaerobic_digester/ADM1_flowsheet.py b/watertap/flowsheets/anaerobic_digester/ADM1_flowsheet.py index 67a72b76fb..acfeb41b37 100644 --- a/watertap/flowsheets/anaerobic_digester/ADM1_flowsheet.py +++ b/watertap/flowsheets/anaerobic_digester/ADM1_flowsheet.py @@ -16,6 +16,7 @@ units, ) from idaes.core import FlowsheetBlock +import idaes.core.util.scaling as iscale from watertap.core.solvers import get_solver import idaes.logger as idaeslog from watertap.unit_models.anaerobic_digester import AD @@ -89,6 +90,8 @@ def build_flowsheet(): m.fs.R1.liquid_outlet.temperature.fix(308.15 * pyo.units.K) + iscale.calculate_scaling_factors(m) + # TO DO: Fix initialization m.fs.R1.initialize(outlvl=idaeslog.INFO_HIGH) diff --git a/watertap/flowsheets/anaerobic_digester/modified_ADM1_flowsheet_with_translators.py b/watertap/flowsheets/anaerobic_digester/modified_ADM1_flowsheet_with_translators.py index 2aff490232..c7a2c4fe40 100644 --- a/watertap/flowsheets/anaerobic_digester/modified_ADM1_flowsheet_with_translators.py +++ b/watertap/flowsheets/anaerobic_digester/modified_ADM1_flowsheet_with_translators.py @@ -293,6 +293,8 @@ def set_operating_conditions(m, bio_P=False): m.fs.AD.volume_vapor.fix(300) m.fs.AD.liquid_outlet.temperature.fix(308.15) + iscale.calculate_scaling_factors(m.fs) + def scale_variables(m): for var in m.fs.component_data_objects(pyo.Var, descend_into=True): if "flow_vol" in var.name: @@ -306,7 +308,6 @@ def scale_variables(m): # Apply scaling scale_variables(m) - iscale.calculate_scaling_factors(m.fs) def initialize_system(m): diff --git a/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py b/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py index cbb6e825e8..8ac88d0afc 100644 --- a/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py +++ b/watertap/property_models/unit_specific/activated_sludge/tests/test_asm1_thermo.py @@ -360,7 +360,7 @@ def test_variable_scaling_routine(self): scaler.variable_scaling_routine(model.props[1]) sfx = model.props[1].scaling_factor - assert len(sfx) == 16 + assert len(sfx) == 3 assert sfx[model.props[1].flow_vol] == pytest.approx(1e1, rel=1e-8) assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) @@ -392,7 +392,7 @@ def test_scale_model(self): assert isinstance(model.props[1].scaling_factor, Suffix) sfx = model.props[1].scaling_factor - assert len(sfx) == 16 + assert len(sfx) == 3 assert sfx[model.props[1].flow_vol] == pytest.approx(1e1, rel=1e-8) assert sfx[model.props[1].pressure] == pytest.approx(1e-6, rel=1e-8) assert sfx[model.props[1].temperature] == pytest.approx(1e-1, rel=1e-8) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py index ee17f4a9d3..1f53ace10c 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py @@ -622,6 +622,10 @@ def model(self): m.fs.unit.liquid_phase.properties_in[0].TSS m.fs.unit.liquid_phase.properties_out[0].TSS + # TODO: Add additional scaling factors? + iscale.calculate_scaling_factors(m) + iscale.set_scaling_factor(m.fs.unit.liquid_phase.heat, 1e-6) + return m @pytest.mark.unit diff --git a/watertap/unit_models/tests/test_clarifier.py b/watertap/unit_models/tests/test_clarifier.py index 3d80b0e3d0..4f04dd54bb 100644 --- a/watertap/unit_models/tests/test_clarifier.py +++ b/watertap/unit_models/tests/test_clarifier.py @@ -96,6 +96,8 @@ def build(): m.fs.unit.split_fraction[0, "effluent", "X_ND"].fix(0.5192) m.fs.unit.split_fraction[0, "effluent", "S_ALK"].fix(0.993) + iscale.calculate_scaling_factors(m.fs.unit) + # Set scaling factors for badly scaled variables iscale.set_scaling_factor( m.fs.unit.underflow_state[0.0].conc_mass_comp["X_BA"], 1e3 @@ -110,8 +112,6 @@ def build(): iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].conc_mass_comp["S_O"], 1e7) iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].conc_mass_comp["S_NO"], 1e7) - iscale.calculate_scaling_factors(m.fs.unit) - return m diff --git a/watertap/unit_models/tests/test_cstr.py b/watertap/unit_models/tests/test_cstr.py index ba53472386..d12aa9b801 100644 --- a/watertap/unit_models/tests/test_cstr.py +++ b/watertap/unit_models/tests/test_cstr.py @@ -92,6 +92,8 @@ def build(): m.fs.unit.heat_duty.fix(0) m.fs.unit.deltaP.fix(0) + iscale.calculate_scaling_factors(m.fs.unit) + iscale.set_scaling_factor( m.fs.unit.control_volume.properties_out[0.0].conc_mol_comp["H2O"], 1e-5 ) @@ -99,8 +101,6 @@ def build(): m.fs.unit.control_volume.properties_out[0.0].pressure, 1e-6 ) - iscale.calculate_scaling_factors(m.fs.unit) - return m @@ -375,10 +375,14 @@ def configure(self): m.fs.costing.add_LCOW(m.fs.unit.control_volume.properties_out[0].flow_vol) m.objective = Objective(expr=m.fs.costing.LCOW) - iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-7) - iscale.calculate_scaling_factors(m.fs.unit) + iscale.set_scaling_factor( + m.fs.unit.control_volume.properties_out[0.0].conc_mass_comp["S_O"], 1e6 + ) + + iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-7) + self.unit_solutions[m.fs.unit.costing.capital_cost] = 566989.10 self.unit_solutions[m.fs.costing.LCOW] = 0.002127 diff --git a/watertap/unit_models/tests/test_cstr_injection.py b/watertap/unit_models/tests/test_cstr_injection.py index dde7766b68..a488f8d2e2 100644 --- a/watertap/unit_models/tests/test_cstr_injection.py +++ b/watertap/unit_models/tests/test_cstr_injection.py @@ -279,10 +279,10 @@ def configure(self): m.fs.costing.add_LCOW(m.fs.unit.control_volume.properties_out[0].flow_vol) m.objective = Objective(expr=m.fs.costing.LCOW) - iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-2) - iscale.calculate_scaling_factors(m.fs.unit) + iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-2) + m.fs.unit.initialize() solver.solve(m) @@ -334,6 +334,10 @@ class TestCSTR_injection_ASM1(UnitTestHarness): def configure(self): m = build_ASM1() + iscale.set_scaling_factor( + m.fs.unit.control_volume.properties_out[0].conc_mass_comp["X_P"], 1e5 + ) + self.unit_solutions[m.fs.unit.outlet.pressure[0]] = 101325.0 self.unit_solutions[m.fs.unit.outlet.temperature[0]] = 308.15 self.unit_solutions[m.fs.unit.outlet.conc_mass_comp[0, "S_O"]] = 6.258e-3 @@ -382,10 +386,13 @@ def configure(self): m.fs.costing.add_LCOW(m.fs.unit.control_volume.properties_out[0].flow_vol) m.objective = Objective(expr=m.fs.costing.LCOW) - iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-7) - iscale.calculate_scaling_factors(m.fs.unit) + iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-7) + iscale.set_scaling_factor( + m.fs.unit.control_volume.properties_out[0].conc_mass_comp["X_P"], 1e5 + ) + m.fs.unit.initialize() solver.solve(m) diff --git a/watertap/unit_models/tests/test_thickener_unit.py b/watertap/unit_models/tests/test_thickener_unit.py index 454fd559ad..d64cb3ae00 100644 --- a/watertap/unit_models/tests/test_thickener_unit.py +++ b/watertap/unit_models/tests/test_thickener_unit.py @@ -89,6 +89,22 @@ def build_ASM1(): m.fs.unit.hydraulic_retention_time.fix() m.fs.unit.diameter.fix() + # Set scaling factors for badly scaled variables + iscale.set_scaling_factor(m.fs.unit.underflow_state[0.0].flow_vol, 1e4) + iscale.set_scaling_factor(m.fs.unit.underflow_state[0.0].pressure, 1e-6) + iscale.set_scaling_factor(m.fs.unit.underflow_state[0.0].conc_mass_comp["S_S"], 1e4) + iscale.set_scaling_factor( + m.fs.unit.underflow_state[0.0].conc_mass_comp["S_NH"], 1e4 + ) + iscale.set_scaling_factor( + m.fs.unit.underflow_state[0.0].conc_mass_comp["S_ND"], 1e4 + ) + iscale.set_scaling_factor(m.fs.unit.overflow_state[0.0].pressure, 1e-6) + iscale.set_scaling_factor(m.fs.unit.overflow_state[0.0].conc_mass_comp["S_S"], 1e4) + iscale.set_scaling_factor(m.fs.unit.overflow_state[0.0].conc_mass_comp["S_NH"], 1e4) + iscale.set_scaling_factor(m.fs.unit.overflow_state[0.0].conc_mass_comp["S_ND"], 1e4) + iscale.set_scaling_factor(m.fs.unit.overflow_state[0.0].conc_mass_comp["X_ND"], 1e4) + return m From ff3a127c3d979bfb57391f59bf740cacbdb0484c Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 2 Dec 2024 11:39:36 -0500 Subject: [PATCH 12/46] Trying to resolve BSM2P issues --- .../BSM2_P_extension.py | 70 ++++++++++--------- watertap/unit_models/anaerobic_digester.py | 1 - 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 3923203a16..50be541c5b 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -373,11 +373,6 @@ def build(bio_P=False): doc="Dissolved oxygen concentration at equilibrium", ) - m.fs.aerobic_reactors = (m.fs.R5, m.fs.R6, m.fs.R7) - for R in m.fs.aerobic_reactors: - iscale.set_scaling_factor(R.KLa, 1e-2) - iscale.set_scaling_factor(R.hydraulic_retention_time[0], 1e-3) - @m.fs.R5.Constraint(m.fs.time, doc="Mass transfer constraint for R3") def mass_transfer_R5(self, t): return pyo.units.convert( @@ -526,6 +521,8 @@ def set_operating_conditions(m, bio_P=False): m.fs.thickener.hydraulic_retention_time.fix(86400 * pyo.units.s) m.fs.thickener.diameter.fix(10 * pyo.units.m) + iscale.calculate_scaling_factors(m) + def scale_variables(m): for var in m.fs.component_data_objects(pyo.Var, descend_into=True): if "flow_vol" in var.name: @@ -537,39 +534,44 @@ def scale_variables(m): if "conc_mass_comp" in var.name: iscale.set_scaling_factor(var, 1e1) - for unit in ("R1", "R2", "R3", "R4"): - block = getattr(m.fs, unit) - iscale.set_scaling_factor(block.hydraulic_retention_time, 1e-3) - - for unit in ("R1", "R2", "R3", "R4", "R5", "R6", "R7"): - block = getattr(m.fs, unit) - iscale.set_scaling_factor( - block.control_volume.reactions[0.0].rate_expression, 1e3 - ) - iscale.set_scaling_factor(block.cstr_performance_eqn, 1e3) - iscale.set_scaling_factor( - block.control_volume.rate_reaction_stoichiometry_constraint, 1e3 - ) - iscale.set_scaling_factor(block.control_volume.material_balances, 1e3) - - iscale.set_scaling_factor(m.fs.AD.KH_co2, 1e1) - iscale.set_scaling_factor(m.fs.AD.KH_ch4, 1e1) - iscale.set_scaling_factor(m.fs.AD.KH_h2, 1e2) + m.fs.aerobic_reactors = (m.fs.R5, m.fs.R6, m.fs.R7) + for R in m.fs.aerobic_reactors: + iscale.set_scaling_factor(R.KLa, 1e-2) + iscale.set_scaling_factor(R.hydraulic_retention_time[0], 1e-3) - if bio_P: - iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) - iscale.constraint_scaling_transform( - m.fs.AD.liquid_phase.enthalpy_balances[0], 1e-6 - ) - else: - iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) - iscale.constraint_scaling_transform( - m.fs.AD.liquid_phase.enthalpy_balances[0], 1e-3 - ) + # for unit in ("R1", "R2", "R3", "R4"): + # block = getattr(m.fs, unit) + # iscale.set_scaling_factor(block.hydraulic_retention_time, 1e-3) + # + # for unit in ("R1", "R2", "R3", "R4", "R5", "R6", "R7"): + # block = getattr(m.fs, unit) + # iscale.set_scaling_factor( + # block.control_volume.reactions[0.0].rate_expression, 1e3 + # ) + # iscale.set_scaling_factor(block.cstr_performance_eqn, 1e3) + # iscale.set_scaling_factor( + # block.control_volume.rate_reaction_stoichiometry_constraint, 1e3 + # ) + # iscale.set_scaling_factor(block.control_volume.material_balances, 1e3) + # + # iscale.set_scaling_factor(m.fs.AD.KH_co2, 1e1) + # iscale.set_scaling_factor(m.fs.AD.KH_ch4, 1e1) + # iscale.set_scaling_factor(m.fs.AD.KH_h2, 1e2) + # + # if bio_P: + # iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) + # iscale.constraint_scaling_transform( + # m.fs.AD.liquid_phase.enthalpy_balances[0], 1e-6 + # ) + # else: + # iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) + # iscale.constraint_scaling_transform( + # m.fs.AD.liquid_phase.enthalpy_balances[0], 1e-3 + # ) # Apply scaling scale_variables(m) - iscale.calculate_scaling_factors(m) + # iscale.calculate_scaling_factors(m) def initialize_system(m, bio_P=False, solver=None): diff --git a/watertap/unit_models/anaerobic_digester.py b/watertap/unit_models/anaerobic_digester.py index 7e43589f97..322fffb097 100644 --- a/watertap/unit_models/anaerobic_digester.py +++ b/watertap/unit_models/anaerobic_digester.py @@ -1041,7 +1041,6 @@ def calculate_scaling_factors(self): iscale.set_scaling_factor(self.volume_vapor, 1e-2) iscale.set_scaling_factor(self.liquid_phase.rate_reaction_generation, 1e4) iscale.set_scaling_factor(self.liquid_phase.mass_transfer_term, 1e2) - iscale.set_scaling_factor(self.liquid_phase.heat, 1e0) iscale.set_scaling_factor(self.liquid_phase.rate_reaction_extent, 1e4) iscale.set_scaling_factor(self.liquid_phase.enthalpy_transfer, 1e0) iscale.set_scaling_factor(self.liquid_phase.volume, 1e-2) From f6203ea13ae36c0ab24076c774a9c38ed4c3d41c Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 6 Dec 2024 10:58:15 -0500 Subject: [PATCH 13/46] Carry over latest changes from other scaler PRs --- .../activated_sludge/ASM1_flowsheet.py | 9 +++ .../BSM2.py | 26 +++++-- .../test/test_full_WRRF_with_ASM1_ADM1.py | 12 ++-- .../activated_sludge/asm1_properties.py | 5 -- .../tests/test_adm1_reaction.py | 69 +++++++++++++++++++ watertap/unit_models/tests/test_clarifier.py | 6 +- watertap/unit_models/tests/test_cstr.py | 9 +-- .../unit_models/tests/test_cstr_injection.py | 7 +- .../tests/test_translator_asm1_adm1.py | 3 + 9 files changed, 121 insertions(+), 25 deletions(-) diff --git a/watertap/flowsheets/activated_sludge/ASM1_flowsheet.py b/watertap/flowsheets/activated_sludge/ASM1_flowsheet.py index 8508c3f545..31568403ab 100644 --- a/watertap/flowsheets/activated_sludge/ASM1_flowsheet.py +++ b/watertap/flowsheets/activated_sludge/ASM1_flowsheet.py @@ -246,6 +246,15 @@ def mass_transfer_R4(self, t): assert degrees_of_freedom(m) == 0 # Apply scaling + for var in m.fs.component_data_objects(pyo.Var, descend_into=True): + if "flow_vol" in var.name: + iscale.set_scaling_factor(var, 1e1) + if "temperature" in var.name: + iscale.set_scaling_factor(var, 1e-1) + if "pressure" in var.name: + iscale.set_scaling_factor(var, 1e-6) + if "conc_mass_comp" in var.name: + iscale.set_scaling_factor(var, 1e1) iscale.calculate_scaling_factors(m.fs) # Initialize flowsheet diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py index 701c19f593..9d5fb13457 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py @@ -10,7 +10,7 @@ # "https://github.com/watertap-org/watertap/" ################################################################################# """ -Flowsheet example full Water Resource Recovery Facility +Flowsheet example full Water Resource Recovery Facility (WRRF; a.k.a., wastewater treatment plant) with ASM1 and ADM1. The flowsheet follows the same formulation as benchmark simulation model no.2 (BSM2) @@ -46,7 +46,7 @@ ADM1_vaporParameterBlock, ) -from idaes.core import FlowsheetBlock, UnitModelCostingBlock +from idaes.core import FlowsheetBlock, UnitModelCostingBlock, UnitModelBlockData from idaes.models.unit_models import ( Feed, Mixer, @@ -277,8 +277,6 @@ def build(): pyo.TransformationFactory("network.expand_arcs").apply_to(m) - iscale.calculate_scaling_factors(m.fs) - # keep handy all the mixers m.mixers = (m.fs.MX1, m.fs.MX2, m.fs.MX3, m.fs.MX4, m.fs.MX6) @@ -387,6 +385,18 @@ def set_operating_conditions(m): for mx in m.mixers: mx.pressure_equality_constraints[0.0, 2].deactivate() + for var in m.fs.component_data_objects(pyo.Var, descend_into=True): + if "flow_vol" in var.name: + iscale.set_scaling_factor(var, 1e1) + if "temperature" in var.name: + iscale.set_scaling_factor(var, 1e-1) + if "pressure" in var.name: + iscale.set_scaling_factor(var, 1e-6) + if "conc_mass_comp" in var.name: + iscale.set_scaling_factor(var, 1e1) + + iscale.calculate_scaling_factors(m) + def initialize_system(m): # Initialize flowsheet @@ -494,10 +504,14 @@ def add_costing(m): m.fs.objective = pyo.Objective(expr=m.fs.costing.LCOW) iscale.set_scaling_factor(m.fs.costing.LCOW, 1e3) - iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-7) iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) - iscale.calculate_scaling_factors(m.fs) + for block in m.fs.component_objects(pyo.Block, descend_into=True): + if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): + iscale.set_scaling_factor(block.costing.capital_cost, 1e-6) + + iscale.constraint_scaling_transform(m.fs.DU.costing.capital_cost_constraint, 1e-6) + iscale.constraint_scaling_transform(m.fs.RADM.costing.capital_cost_constraint, 1e-6) def setup_optimization(m, reactor_volume_equalities=False): diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_full_WRRF_with_ASM1_ADM1.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_full_WRRF_with_ASM1_ADM1.py index 890e39cc19..bca64346c8 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_full_WRRF_with_ASM1_ADM1.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_full_WRRF_with_ASM1_ADM1.py @@ -143,12 +143,12 @@ def test_optimization(self, system_frame): assert degrees_of_freedom(system_frame) == 10 # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.3497531, rel=1e-5) + assert value(m.fs.costing.LCOW) == pytest.approx(0.349772203, rel=1e-5) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 17441736.89749642, rel=1e-5 + 17379540.339857, rel=1e-5 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 629780.1104274583, rel=1e-5 + 636129.6209807, rel=1e-5 ) @@ -258,10 +258,10 @@ def test_optimization(self, system_frame): assert degrees_of_freedom(system_frame) == 8 # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.3497531, rel=1e-5) + assert value(m.fs.costing.LCOW) == pytest.approx(0.349560273, rel=1e-5) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 17441740.61915915, rel=1e-5 + 17370674.42102, rel=1e-5 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 629779.9546967598, rel=1e-5 + 635577.7320509, rel=1e-5 ) diff --git a/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py b/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py index 06401ba5bd..1650daee06 100644 --- a/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py +++ b/watertap/property_models/unit_specific/activated_sludge/asm1_properties.py @@ -548,11 +548,6 @@ def calculate_scaling_factors(self): # Get default scale factors and do calculations from base classes super().calculate_scaling_factors() - iscale.set_scaling_factor(self.flow_vol, 1e1) - iscale.set_scaling_factor(self.temperature, 1e-1) - iscale.set_scaling_factor(self.pressure, 1e-6) - iscale.set_scaling_factor(self.conc_mass_comp, 1e1) - # No constraints in this model as yet, just need to set scaling factors # for expressions sf_F = iscale.get_scaling_factor(self.flow_vol, default=1e2, warning=True) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py index dc07ecdb81..700a63557d 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_adm1_reaction.py @@ -652,6 +652,16 @@ def test_scale_model(self): assert sfx[model.rxns[1].reaction_rate["R6"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R7"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].reaction_rate["R8"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R9"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R10"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R11"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R12"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R13"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R14"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R15"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R16"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R17"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].reaction_rate["R18"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx(1e2, rel=1e-8) @@ -661,6 +671,65 @@ def test_scale_model(self): assert sfx[model.rxns[1].rate_expression["R6"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].rate_expression["R7"]] == pytest.approx(1e2, rel=1e-8) assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R9"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R10"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R11"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R12"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R13"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R14"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R15"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R16"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R17"]] == pytest.approx(1e2, rel=1e-8) + assert sfx[model.rxns[1].rate_expression["R18"]] == pytest.approx(1e2, rel=1e-8) + + assert sfx[model.rxns[1].Dissociation] == pytest.approx(3.10210344e-2, rel=1e-8) + assert sfx[model.rxns[1].CO2_acid_base_equilibrium] == pytest.approx( + 6.83928318e-2, rel=1e-8 + ) + assert sfx[model.rxns[1].IN_acid_base_equilibrium] == pytest.approx( + 4.69507548e-2, rel=1e-8 + ) + assert sfx[model.rxns[1].pH_calc] == pytest.approx(0.1428571429, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_va] == pytest.approx( + 83.3333333333, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_bu] == pytest.approx( + 76.92307692, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_pro] == pytest.approx(62.5, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_ac] == pytest.approx(5, rel=1e-8) + assert sfx[model.rxns[1].concentration_of_hco3] == pytest.approx( + 0.1428571429, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_nh3] == pytest.approx( + 0.1081081081, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_co2] == pytest.approx( + 6.66666666667, rel=1e-8 + ) + assert sfx[model.rxns[1].concentration_of_nh4] == pytest.approx( + 7.692307692, rel=1e-8 + ) + assert sfx[model.rxns[1].S_H_cons] == pytest.approx(7.1428571429, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R1"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R2"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R3"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R4"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R5"]] == pytest.approx(1.00076923, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R6"]] == pytest.approx(1.00076923, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R7"]] == pytest.approx(1.046804615, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R8"]] == pytest.approx(1.02378692, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R9"]] == pytest.approx(1.02378692, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R10"]] == pytest.approx(1.06653407, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R11"]] == pytest.approx(3.280299145, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R12"]] == pytest.approx(1.00076923, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R13"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R14"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R15"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R16"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R17"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R18"]] == pytest.approx(1, rel=1e-8) + assert sfx[model.rxns[1].I_fun["R19"]] == pytest.approx(1, rel=1e-8) class TestReactor: diff --git a/watertap/unit_models/tests/test_clarifier.py b/watertap/unit_models/tests/test_clarifier.py index 4f04dd54bb..6cb5357113 100644 --- a/watertap/unit_models/tests/test_clarifier.py +++ b/watertap/unit_models/tests/test_clarifier.py @@ -96,9 +96,8 @@ def build(): m.fs.unit.split_fraction[0, "effluent", "X_ND"].fix(0.5192) m.fs.unit.split_fraction[0, "effluent", "S_ALK"].fix(0.993) - iscale.calculate_scaling_factors(m.fs.unit) - # Set scaling factors for badly scaled variables + iscale.set_scaling_factor(m.fs.unit.underflow_state[0.0].pressure, 1e-5) iscale.set_scaling_factor( m.fs.unit.underflow_state[0.0].conc_mass_comp["X_BA"], 1e3 ) @@ -107,11 +106,14 @@ def build(): iscale.set_scaling_factor( m.fs.unit.underflow_state[0.0].conc_mass_comp["S_NO"], 1e3 ) + iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].pressure, 1e-5) iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].conc_mass_comp["X_BA"], 1e7) iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].conc_mass_comp["X_P"], 1e7) iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].conc_mass_comp["S_O"], 1e7) iscale.set_scaling_factor(m.fs.unit.effluent_state[0.0].conc_mass_comp["S_NO"], 1e7) + iscale.calculate_scaling_factors(m.fs.unit) + return m diff --git a/watertap/unit_models/tests/test_cstr.py b/watertap/unit_models/tests/test_cstr.py index d12aa9b801..346ea4ecf1 100644 --- a/watertap/unit_models/tests/test_cstr.py +++ b/watertap/unit_models/tests/test_cstr.py @@ -92,8 +92,6 @@ def build(): m.fs.unit.heat_duty.fix(0) m.fs.unit.deltaP.fix(0) - iscale.calculate_scaling_factors(m.fs.unit) - iscale.set_scaling_factor( m.fs.unit.control_volume.properties_out[0.0].conc_mol_comp["H2O"], 1e-5 ) @@ -101,6 +99,8 @@ def build(): m.fs.unit.control_volume.properties_out[0.0].pressure, 1e-6 ) + iscale.calculate_scaling_factors(m.fs.unit) + return m @@ -136,6 +136,7 @@ def build_ASM1(): m.fs.unit.volume[0].fix(1000 * units.m**3) # Set scaling factors for badly scaled variables + iscale.set_scaling_factor(m.fs.unit.control_volume.properties_out[0].pressure, 1e-5) iscale.set_scaling_factor( m.fs.unit.control_volume.properties_out[0.0].conc_mass_comp["S_O"], 1e5 ) @@ -375,14 +376,14 @@ def configure(self): m.fs.costing.add_LCOW(m.fs.unit.control_volume.properties_out[0].flow_vol) m.objective = Objective(expr=m.fs.costing.LCOW) - iscale.calculate_scaling_factors(m.fs.unit) - iscale.set_scaling_factor( m.fs.unit.control_volume.properties_out[0.0].conc_mass_comp["S_O"], 1e6 ) iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-7) + iscale.calculate_scaling_factors(m.fs.unit) + self.unit_solutions[m.fs.unit.costing.capital_cost] = 566989.10 self.unit_solutions[m.fs.costing.LCOW] = 0.002127 diff --git a/watertap/unit_models/tests/test_cstr_injection.py b/watertap/unit_models/tests/test_cstr_injection.py index a488f8d2e2..31dcfb8bfc 100644 --- a/watertap/unit_models/tests/test_cstr_injection.py +++ b/watertap/unit_models/tests/test_cstr_injection.py @@ -155,6 +155,9 @@ def build_ASM1(): m.fs.unit.injection[0, "Liq", "S_O"].fix(2e-3) # Set scaling factors for badly scaled variables + iscale.set_scaling_factor( + m.fs.unit.control_volume.properties_out[0.0].pressure, 1e-5 + ) iscale.set_scaling_factor( m.fs.unit.control_volume.properties_out[0.0].conc_mass_comp["X_P"], 1e3 ) @@ -386,13 +389,13 @@ def configure(self): m.fs.costing.add_LCOW(m.fs.unit.control_volume.properties_out[0].flow_vol) m.objective = Objective(expr=m.fs.costing.LCOW) - iscale.calculate_scaling_factors(m.fs.unit) - iscale.set_scaling_factor(m.fs.unit.costing.capital_cost, 1e-7) iscale.set_scaling_factor( m.fs.unit.control_volume.properties_out[0].conc_mass_comp["X_P"], 1e5 ) + iscale.calculate_scaling_factors(m.fs.unit) + m.fs.unit.initialize() solver.solve(m) diff --git a/watertap/unit_models/translators/tests/test_translator_asm1_adm1.py b/watertap/unit_models/translators/tests/test_translator_asm1_adm1.py index 45a3252dda..6b060e27e3 100644 --- a/watertap/unit_models/translators/tests/test_translator_asm1_adm1.py +++ b/watertap/unit_models/translators/tests/test_translator_asm1_adm1.py @@ -23,6 +23,7 @@ from pyomo.environ import ConcreteModel, value, assert_optimal_termination, Param from idaes.core import FlowsheetBlock +import idaes.core.util.scaling as iscale from pyomo.environ import units @@ -134,6 +135,8 @@ def asmadm(self): m.fs.unit.inlet.conc_mass_comp[0, "X_ND"].fix(906.0933 * units.mg / units.liter) m.fs.unit.inlet.alkalinity.fix(7.1549 * units.mol / units.m**3) + iscale.calculate_scaling_factors(m) + return m @pytest.mark.build From de4ca5576777a5a179dd500063e095a1724942b4 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Tue, 10 Dec 2024 15:57:32 -0500 Subject: [PATCH 14/46] Update Jupyter notebook --- tutorials/BSM2.ipynb | 1284 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 1187 insertions(+), 97 deletions(-) diff --git a/tutorials/BSM2.ipynb b/tutorials/BSM2.ipynb index b9689f0a79..5260be590c 100644 --- a/tutorials/BSM2.ipynb +++ b/tutorials/BSM2.ipynb @@ -55,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "845b8ea4", "metadata": {}, "outputs": [], @@ -78,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "9c5f42f2", "metadata": {}, "outputs": [], @@ -86,16 +86,22 @@ "# Import anaerobic digester model\n", "from watertap.unit_models.anaerobic_digester import AD\n", "\n", - "# Import CSTR with oxygen injection model\n", - "from watertap.unit_models.cstr_injection import CSTR_Injection\n", + "# Import aeration tank model\n", + "from watertap.unit_models.aeration_tank import AerationTank, ElectricityConsumption\n", + "\n", + "# Import continuous stirred tank reactor model\n", + "from watertap.unit_models.cstr import CSTR\n", + "\n", + "# Import clarifier model\n", + "from watertap.unit_models.clarifier import Clarifier\n", "\n", "# Import BSM2 separator models \n", "from watertap.unit_models.thickener import Thickener\n", "from watertap.unit_models.dewatering import DewateringUnit\n", "\n", "# Import idaes unit models for separators and mixers and ASM models\n", + "from idaes.models.unit_models.mixer import MomentumMixingType\n", "from idaes.models.unit_models import (\n", - " CSTR,\n", " Feed,\n", " Mixer,\n", " Separator,\n", @@ -121,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "af7d8037-fa50-47a0-99f6-3b1ef3b45a36", "metadata": {}, "outputs": [], @@ -165,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "faba836b-eb59-4b4b-8726-6a921ca25073", "metadata": {}, "outputs": [], @@ -185,7 +191,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "b428d1a3-1a04-49ab-be91-a632d531dda9", "metadata": {}, "outputs": [], @@ -211,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "56611aeb-4c31-4dda-81cc-7dc2b45d0c6c", "metadata": {}, "outputs": [], @@ -220,7 +226,7 @@ "m.fs.FeedWater = Feed(property_package=m.fs.props_ASM1)\n", "# Mixer for feed water and recycled sludge\n", "m.fs.MX1 = Mixer(\n", - " property_package=m.fs.props_ASM1, inlet_list=[\"feed_water\", \"recycle\"]\n", + " property_package=m.fs.props_ASM1, inlet_list=[\"feed_water\", \"recycle\"], momentum_mixing_type=MomentumMixingType.equality,\n", ")\n", "# First reactor (anoxic) - standard CSTR\n", "m.fs.R1 = CSTR(\n", @@ -231,22 +237,28 @@ " property_package=m.fs.props_ASM1, reaction_package=m.fs.ASM1_rxn_props\n", ")\n", "# Third reactor (aerobic) - CSTR with injection\n", - "m.fs.R3 = CSTR_Injection(\n", - " property_package=m.fs.props_ASM1, reaction_package=m.fs.ASM1_rxn_props\n", + "m.fs.R3 = AerationTank(\n", + " property_package=m.fs.props_ASM1,\n", + " reaction_package=m.fs.ASM1_rxn_props,\n", + " electricity_consumption=ElectricityConsumption.calculated,\n", ")\n", "# Fourth reactor (aerobic) - CSTR with injection\n", - "m.fs.R4 = CSTR_Injection(\n", - " property_package=m.fs.props_ASM1, reaction_package=m.fs.ASM1_rxn_props\n", + "m.fs.R4 = AerationTank(\n", + " property_package=m.fs.props_ASM1,\n", + " reaction_package=m.fs.ASM1_rxn_props,\n", + " electricity_consumption=ElectricityConsumption.calculated,\n", ")\n", "# Fifth reactor (aerobic) - CSTR with injection\n", - "m.fs.R5 = CSTR_Injection(\n", - " property_package=m.fs.props_ASM1, reaction_package=m.fs.ASM1_rxn_props\n", + "m.fs.R5 = AerationTank(\n", + " property_package=m.fs.props_ASM1,\n", + " reaction_package=m.fs.ASM1_rxn_props,\n", + " electricity_consumption=ElectricityConsumption.calculated,\n", ")\n", "m.fs.SP5 = Separator(\n", " property_package=m.fs.props_ASM1, outlet_list=[\"underflow\", \"overflow\"]\n", ")\n", "# Clarifier\n", - "m.fs.CL1 = Separator(\n", + "m.fs.CL1 = Clarifier(\n", " property_package=m.fs.props_ASM1,\n", " outlet_list=[\"underflow\", \"effluent\"],\n", " split_basis=SplittingType.componentFlow,\n", @@ -259,7 +271,7 @@ ")\n", "# Mixing sludge recycle and R5 underflow\n", "m.fs.MX6 = Mixer(\n", - " property_package=m.fs.props_ASM1, inlet_list=[\"clarifier\", \"reactor\"]\n", + " property_package=m.fs.props_ASM1, inlet_list=[\"clarifier\", \"reactor\"], momentum_mixing_type=MomentumMixingType.equality\n", ")\n", "# Product Blocks\n", "m.fs.Treated = Product(property_package=m.fs.props_ASM1)\n", @@ -277,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "0435dc0d-c79e-42f4-a402-473e63e7f854", "metadata": {}, "outputs": [], @@ -309,7 +321,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "a4971fbe-6030-42da-87c8-afb98afab9d8", "metadata": {}, "outputs": [], @@ -319,30 +331,30 @@ "m.fs.FeedWater.temperature.fix(308.15 * pyo.units.K)\n", "m.fs.FeedWater.pressure.fix(1 * pyo.units.atm)\n", "m.fs.FeedWater.conc_mass_comp[0, \"S_I\"].fix(\n", - " 27.2262 * pyo.units.g / pyo.units.m**3\n", + " 27 * pyo.units.g / pyo.units.m**3\n", ")\n", "m.fs.FeedWater.conc_mass_comp[0, \"S_S\"].fix(\n", - " 58.1762 * pyo.units.g / pyo.units.m**3\n", + " 58 * pyo.units.g / pyo.units.m**3\n", ")\n", - "m.fs.FeedWater.conc_mass_comp[0, \"X_I\"].fix(92.499 * pyo.units.g / pyo.units.m**3)\n", + "m.fs.FeedWater.conc_mass_comp[0, \"X_I\"].fix(92 * pyo.units.g / pyo.units.m**3)\n", "m.fs.FeedWater.conc_mass_comp[0, \"X_S\"].fix(\n", - " 363.9435 * pyo.units.g / pyo.units.m**3\n", + " 363 * pyo.units.g / pyo.units.m**3\n", ")\n", "m.fs.FeedWater.conc_mass_comp[0, \"X_BH\"].fix(\n", - " 50.6833 * pyo.units.g / pyo.units.m**3\n", + " 50 * pyo.units.g / pyo.units.m**3\n", ")\n", "m.fs.FeedWater.conc_mass_comp[0, \"X_BA\"].fix(0 * pyo.units.g / pyo.units.m**3)\n", "m.fs.FeedWater.conc_mass_comp[0, \"X_P\"].fix(0 * pyo.units.g / pyo.units.m**3)\n", "m.fs.FeedWater.conc_mass_comp[0, \"S_O\"].fix(0 * pyo.units.g / pyo.units.m**3)\n", "m.fs.FeedWater.conc_mass_comp[0, \"S_NO\"].fix(0 * pyo.units.g / pyo.units.m**3)\n", "m.fs.FeedWater.conc_mass_comp[0, \"S_NH\"].fix(\n", - " 23.8595 * pyo.units.g / pyo.units.m**3\n", + " 23 * pyo.units.g / pyo.units.m**3\n", ")\n", "m.fs.FeedWater.conc_mass_comp[0, \"S_ND\"].fix(\n", - " 5.6516 * pyo.units.g / pyo.units.m**3\n", + " 5 * pyo.units.g / pyo.units.m**3\n", ")\n", "m.fs.FeedWater.conc_mass_comp[0, \"X_ND\"].fix(\n", - " 16.1298 * pyo.units.g / pyo.units.m**3\n", + " 16 * pyo.units.g / pyo.units.m**3\n", ")\n", "m.fs.FeedWater.alkalinity.fix(7 * pyo.units.mol / pyo.units.m**3)" ] @@ -357,17 +369,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "de03c3db-0cd1-4404-8010-aac6663c9611", "metadata": {}, "outputs": [], "source": [ "# Reactor sizing\n", - "m.fs.R1.volume.fix(1500 * pyo.units.m**3)\n", - "m.fs.R2.volume.fix(1500 * pyo.units.m**3)\n", - "m.fs.R3.volume.fix(3000 * pyo.units.m**3)\n", - "m.fs.R4.volume.fix(3000 * pyo.units.m**3)\n", - "m.fs.R5.volume.fix(3000 * pyo.units.m**3)" + "m.fs.R1.volume.fix(1000 * pyo.units.m**3)\n", + "m.fs.R2.volume.fix(1000 * pyo.units.m**3)\n", + "m.fs.R3.volume.fix(1333 * pyo.units.m**3)\n", + "m.fs.R4.volume.fix(1333 * pyo.units.m**3)\n", + "m.fs.R5.volume.fix(1333 * pyo.units.m**3)" ] }, { @@ -380,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "ad067482-32a8-487d-b248-8049fd55d877", "metadata": {}, "outputs": [], @@ -393,9 +405,12 @@ " m.fs.R4.injection[:, :, j].fix(0)\n", " m.fs.R5.injection[:, :, j].fix(0)\n", "# Then set injections rates for O2\n", - "m.fs.R3.outlet.conc_mass_comp[:, \"S_O\"].fix(0.46635e-3)\n", - "m.fs.R4.outlet.conc_mass_comp[:, \"S_O\"].fix(1.4284e-3)\n", - "m.fs.R5.outlet.conc_mass_comp[:, \"S_O\"].fix(1.3748e-3)" + "m.fs.R3.outlet.conc_mass_comp[:, \"S_O\"].fix(1.72e-3)\n", + "m.fs.R4.outlet.conc_mass_comp[:, \"S_O\"].fix(2.43e-3)\n", + "m.fs.R5.outlet.conc_mass_comp[:, \"S_O\"].fix(4.49e-4)\n", + "# Oxygen concentration in reactors 3 and 4 is governed by mass transfer\n", + "m.fs.R3.KLa = 7.6\n", + "m.fs.R4.KLa = 5.7" ] }, { @@ -408,44 +423,45 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "deae8720-3c7a-4bf1-ae4f-bbf6f4757eeb", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[None]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Set fraction of outflow from reactor 5 that goes to recycle\n", "m.fs.SP5.split_fraction[:, \"underflow\"].fix(0.6)\n", "\n", "# Clarifier\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"H2O\"].fix(20640.7791 / (20640.7791 + 20648))\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_I\"].fix(20640.7791 / (20640.7791 + 20648))\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_S\"].fix(20640.7791 / (20640.7791 + 20648))\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"X_I\"].fix(\n", - " 5.9191 * 20640.7791 / (5.9191 * 20640.7791 + 3036.2175 * 20648)\n", - ")\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"X_S\"].fix(\n", - " 0.12329 * 20640.7791 / (0.12329 * 20640.7791 + 63.2392 * 20648)\n", - ")\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"X_BH\"].fix(0.00193)\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"X_BA\"].fix(0.00193)\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"X_P\"].fix(0.00193)\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_O\"].fix(20640.7791 / (20640.7791 + 20648))\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_NO\"].fix(\n", - " 20640.7791 / (20640.7791 + 20648)\n", - ")\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_NH\"].fix(\n", - " 20640.7791 / (20640.7791 + 20648)\n", - ")\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_ND\"].fix(\n", - " 20640.7791 / (20640.7791 + 20648)\n", - ")\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"X_ND\"].fix(0.00193)\n", - "m.fs.CL1.split_fraction[0, \"effluent\", \"S_ALK\"].fix(\n", - " 20640.7791 / (20640.7791 + 20648)\n", - ")\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"H2O\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_I\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_S\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"X_I\"].fix(0.00187)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"X_S\"].fix(0.00187)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"X_BH\"].fix(0.00187)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"X_BA\"].fix(0.00187)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"X_P\"].fix(0.00187)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_O\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_NO\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_NH\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_ND\"].fix(0.48956)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"X_ND\"].fix(0.00187)\n", + "m.fs.CL1.split_fraction[0, \"effluent\", \"S_ALK\"].fix(0.48956)\n", + "\n", + "m.fs.CL1.surface_area.fix(1500 * pyo.units.m**2)\n", "\n", "# Sludge purge separator\n", - "m.fs.SP6.split_fraction[:, \"recycle\"].fix(20648 / 20948)" + "m.fs.SP6.split_fraction[:, \"recycle\"].fix(0.985)" ] }, { @@ -458,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "7330b47f-7a11-4465-92da-a4961eac90a5", "metadata": {}, "outputs": [], @@ -481,7 +497,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "b01a4461-c122-4ef9-9534-b3aa92be2672", "metadata": {}, "outputs": [], @@ -505,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "9a24bacb-1010-4a67-bb11-a258e6336348", "metadata": {}, "outputs": [], @@ -537,12 +553,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "d2836b17-bcb4-4d76-877a-5f8894870113", "metadata": {}, "outputs": [], "source": [ - "m.fs.CL = Separator(\n", + "m.fs.CL = Clarifier(\n", " property_package=m.fs.props_ASM1,\n", " outlet_list=[\"underflow\", \"effluent\"],\n", " split_basis=SplittingType.componentFlow,\n", @@ -552,13 +568,13 @@ "m.fs.DU = DewateringUnit(property_package=m.fs.props_ASM1)\n", "\n", "m.fs.MX2 = Mixer(\n", - " property_package=m.fs.props_ASM1, inlet_list=[\"feed_water1\", \"recycle1\"]\n", + " property_package=m.fs.props_ASM1, inlet_list=[\"feed_water1\", \"recycle1\"], momentum_mixing_type=MomentumMixingType.equality\n", ")\n", "m.fs.MX3 = Mixer(\n", - " property_package=m.fs.props_ASM1, inlet_list=[\"feed_water2\", \"recycle2\"]\n", + " property_package=m.fs.props_ASM1, inlet_list=[\"feed_water2\", \"recycle2\"], momentum_mixing_type=MomentumMixingType.equality\n", ")\n", "m.fs.MX4 = Mixer(\n", - " property_package=m.fs.props_ASM1, inlet_list=[\"thickener\", \"clarifier\"]\n", + " property_package=m.fs.props_ASM1, inlet_list=[\"thickener\", \"clarifier\"], momentum_mixing_type=MomentumMixingType.equality\n", ")" ] }, @@ -572,7 +588,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "33894140-176d-4620-a474-e6865f0ac2ba", "metadata": {}, "outputs": [], @@ -604,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "6344ee3c-18d9-4c86-b248-82dd810dc2f5", "metadata": {}, "outputs": [], @@ -625,13 +641,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "982691fd", "metadata": {}, "outputs": [], "source": [ "# Dewatering unit\n", - "m.fs.DU.hydraulic_retention_time.fix(1800 * pyo.units.s)" + "m.fs.DU.hydraulic_retention_time.fix(1800 * pyo.units.s)\n", + "m.fs.DU.energy_electric_flow_vol_inlet[0] = 0.069 * pyo.units.kWh / pyo.units.m**3" ] }, { @@ -644,8 +661,8 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "dfb8d9a3", + "execution_count": 19, + "id": "2bbde06e-4b2d-4802-81a1-faf4461d0ef1", "metadata": {}, "outputs": [], "source": [ @@ -664,7 +681,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "7a2d57fb-e9e9-4475-a299-bc0b9a9405ea", "metadata": {}, "outputs": [], @@ -689,13 +706,690 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "a115aa26-1f30-4187-823e-bf8cbb8d4c5d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.FeedWater.properties[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX1.feed_water_state[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX1.recycle_state[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX1.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.volume\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.volume\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.volume\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:15 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.volume\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.volume\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R1]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R2]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R3]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R4]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R5]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R6]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R7]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.rate_reaction_extent[0.0,R8]\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP5.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP5.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP5.overflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL1.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL1.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL1.effluent_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP6.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP6.recycle_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP6.waste_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX6.clarifier_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX6.reactor_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX6.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.Treated.properties[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.P1.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.P1.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.P1.control_volume.work\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.asm_adm.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.adm_asm.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL.effluent_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.TU.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.TU.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.TU.overflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.DU.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.DU.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.DU.overflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX2.feed_water1_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX2.recycle1_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX2.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX3.feed_water2_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX3.recycle2_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX3.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX4.thickener_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX4.clarifier_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX4.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX1.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R1.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R2.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R3.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R4.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.R5.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP5.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP5.overflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL1.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP5.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX6.reactor_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL1.effluent_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.Treated.properties[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL1.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP6.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP6.recycle_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX6.clarifier_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX6.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.P1.control_volume.properties_in[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.P1.control_volume.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX1.recycle_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.SP6.waste_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.TU.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.TU.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX4.thickener_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.TU.overflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX3.recycle2_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL.underflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX4.clarifier_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.adm_asm.properties_out[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.DU.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.DU.overflow_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX2.recycle1_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.FeedWater.properties[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX2.feed_water1_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX2.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX3.feed_water2_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX3.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.CL.effluent_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX1.feed_water_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.MX4.mixed_state[0.0].alkalinity\n", + "2024-12-05 15:29:16 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.asm_adm.properties_in[0.0].alkalinity\n" + ] + } + ], "source": [ - "# Apply scaling\n", - "iscale.calculate_scaling_factors(m.fs)" + "# Deactivate redundant constraints\n", + "m.mixers = (m.fs.MX1, m.fs.MX2, m.fs.MX3, m.fs.MX4, m.fs.MX6)\n", + "for mx in m.mixers:\n", + " mx.pressure_equality_constraints[0.0, 2].deactivate()\n", + "\n", + "# calculate and propagate scaling factors\n", + "iscale.calculate_scaling_factors(m.fs)\n", + "\n", + " " ] }, { @@ -709,10 +1403,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "id": "9bab4f00-afe2-4374-a39b-72f8f7307f1f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initialization Order\n", + "fs.R1\n", + "fs.R2\n", + "fs.R3\n", + "fs.DU\n", + "fs.MX2\n", + "fs.SP5\n", + "fs.CL1\n", + "fs.SP6\n", + "fs.MX6\n", + "fs.MX3\n", + "fs.CL\n", + "fs.MX1\n" + ] + } + ], "source": [ "# Initialize flowsheet\n", "# Apply sequential decomposition - 1 iteration should suffice\n", @@ -787,15 +1501,279 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "id": "a4cbb465-b499-41bf-9322-2b45464abf72", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1.control_volume: Initialization Complete\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R1: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.FeedWater.properties: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.FeedWater.properties: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.FeedWater: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.asm_adm.properties_out: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.asm_adm.properties_out: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.asm_adm.properties_in: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.asm_adm: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2.control_volume: Initialization Complete\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.R2: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM.liquid_phase.reactions: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM.liquid_phase.properties_out: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM.liquid_phase: Initialization Complete\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM.vapor_phase: State Released.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM.vapor_phase: Initialization Complete.\n", + "2024-12-05 15:29:16 [INFO] idaes.init.fs.RADM: Initialization Step 2 Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.RADM: Initialization Step 3 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.RADM.liquid_phase.properties_in: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.RADM: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3.control_volume: Initialization Complete\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R3: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.adm_asm.properties_out: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.adm_asm.properties_out: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.adm_asm.properties_in: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.adm_asm: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.DU.underflow_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.DU.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.DU.overflow_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.DU.overflow_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.DU: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.DU.mixed_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4.control_volume: Initialization Complete\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R4: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.MX2.mixed_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.MX2.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.MX2: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.MX2.feed_water1_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.MX2.recycle1_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5.control_volume: Initialization Complete\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.R5: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP5.underflow_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP5.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP5.overflow_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP5.overflow_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP5: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP5.mixed_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.CL1.underflow_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.CL1.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.CL1.effluent_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.CL1.effluent_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.CL1: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.CL1.mixed_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP6.recycle_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP6.recycle_state: Initialization Complete.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP6.waste_state: State Released.\n", + "2024-12-05 15:29:17 [INFO] idaes.init.fs.SP6.waste_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.SP6: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.SP6.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.Treated.properties: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.Treated.properties: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.Treated: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX6.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX6.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX6: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX6.clarifier_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX6.reactor_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.TU.underflow_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.TU.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.TU.overflow_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.TU.overflow_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.TU: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.TU.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX3.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX3.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX3: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX3.feed_water2_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX3.recycle2_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.P1.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.P1.control_volume: Initialization Complete\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.P1: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.P1: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.P1.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.P1: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.CL.underflow_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.CL.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.CL.effluent_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.CL.effluent_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.CL: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.CL.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX1.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX1.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX1: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX1.feed_water_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX1.recycle_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX4.mixed_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX4.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX4: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX4.thickener_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.MX4.clarifier_state: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.FeedWater.properties: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.FeedWater.properties: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.FeedWater: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.asm_adm.properties_out: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.asm_adm.properties_out: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.asm_adm.properties_in: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.asm_adm: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1.control_volume: Initialization Complete\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R1: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2.control_volume: Initialization Complete\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.R2: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM.liquid_phase.reactions: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM.liquid_phase.properties_out: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM.liquid_phase: Initialization Complete\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM.vapor_phase: State Released.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM.vapor_phase: Initialization Complete.\n", + "2024-12-05 15:29:18 [INFO] idaes.init.fs.RADM: Initialization Step 2 Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.RADM: Initialization Step 3 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.RADM.liquid_phase.properties_in: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.RADM: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3.control_volume: Initialization Complete\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R3: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.adm_asm.properties_out: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.adm_asm.properties_out: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.adm_asm.properties_in: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.adm_asm: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.DU.underflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.DU.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.DU.overflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.DU.overflow_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.DU: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.DU.mixed_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4.control_volume: Initialization Complete\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R4: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.MX2.mixed_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.MX2.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.MX2: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.MX2.feed_water1_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.MX2.recycle1_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5.control_volume.reactions: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5.control_volume: Initialization Complete\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.R5: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP5.underflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP5.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP5.overflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP5.overflow_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP5: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP5.mixed_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.CL1.underflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.CL1.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.CL1.effluent_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.CL1.effluent_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.CL1: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.CL1.mixed_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP6.recycle_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP6.recycle_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP6.waste_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP6.waste_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP6: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.SP6.mixed_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.TU.underflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.TU.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.TU.overflow_state: State Released.\n", + "2024-12-05 15:29:19 [INFO] idaes.init.fs.TU.overflow_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.TU: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.TU.mixed_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX6.mixed_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX6.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX6: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX6.clarifier_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX6.reactor_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX3.mixed_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX3.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX3: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX3.feed_water2_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX3.recycle2_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.P1.control_volume.properties_out: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.P1.control_volume: Initialization Complete\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.P1: Initialization Step 1 Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.P1: Initialization Step 2 optimal - Optimal Solution Found.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.P1.control_volume.properties_in: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.P1: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.CL.underflow_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.CL.underflow_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.CL.effluent_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.CL.effluent_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.CL: Initialization Step 2 Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.CL.mixed_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX1.mixed_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX1.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX1: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX1.feed_water_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX1.recycle_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX4.mixed_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX4.mixed_state: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX4: Initialization Complete: optimal - Optimal Solution Found\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX4.thickener_state: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.MX4.clarifier_state: State Released.\n", + "WARNING: Direct failed to converge in 1 iterations\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.Treated.properties: State Released.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.Treated.properties: Initialization Complete.\n", + "2024-12-05 15:29:20 [INFO] idaes.init.fs.Treated: Initialization Complete.\n" + ] + } + ], "source": [ "def function(unit):\n", " unit.initialize(outlvl=idaeslog.INFO_HIGH)\n", "\n", - "seq.run(m, function)" + "seq.run(m, function)\n", + "# Deactivate redundant constraints that the initializer reactivated\n", + "m.mixers = (m.fs.MX1, m.fs.MX2, m.fs.MX3, m.fs.MX4, m.fs.MX6)\n", + "for mx in m.mixers:\n", + " mx.pressure_equality_constraints[0.0, 2].deactivate()" ] }, { @@ -809,10 +1787,92 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "id": "e0d29703-d4b6-4749-84d8-b3e7739ed22f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ipopt-watertap: ipopt with user variable scaling and IDAES jacobian constraint scaling\n", + "Ipopt 3.13.2: tol=1e-08\n", + "constr_viol_tol=1e-08\n", + "acceptable_constr_viol_tol=1e-08\n", + "bound_relax_factor=0.0\n", + "honor_original_bounds=no\n", + "nlp_scaling_method=user-scaling\n", + "\n", + "\n", + "******************************************************************************\n", + "This program contains Ipopt, a library for large-scale nonlinear optimization.\n", + " Ipopt is released as open source code under the Eclipse Public License (EPL).\n", + " For more information visit http://projects.coin-or.org/Ipopt\n", + "\n", + "This version of Ipopt was compiled from source code available at\n", + " https://github.com/IDAES/Ipopt as part of the Institute for the Design of\n", + " Advanced Energy Systems Process Systems Engineering Framework (IDAES PSE\n", + " Framework) Copyright (c) 2018-2019. See https://github.com/IDAES/idaes-pse.\n", + "\n", + "This version of Ipopt was compiled using HSL, a collection of Fortran codes\n", + " for large-scale scientific computation. All technical papers, sales and\n", + " publicity material resulting from use of the HSL codes within IPOPT must\n", + " contain the following acknowledgement:\n", + " HSL, a collection of Fortran codes for large-scale scientific\n", + " computation. See http://www.hsl.rl.ac.uk.\n", + "******************************************************************************\n", + "\n", + "This is Ipopt version 3.13.2, running with linear solver ma27.\n", + "\n", + "Number of nonzeros in equality constraint Jacobian...: 3950\n", + "Number of nonzeros in inequality constraint Jacobian.: 0\n", + "Number of nonzeros in Lagrangian Hessian.............: 1822\n", + "\n", + "Total number of variables............................: 1286\n", + " variables with only lower bounds: 842\n", + " variables with lower and upper bounds: 124\n", + " variables with only upper bounds: 0\n", + "Total number of equality constraints.................: 1286\n", + "Total number of inequality constraints...............: 0\n", + " inequality constraints with only lower bounds: 0\n", + " inequality constraints with lower and upper bounds: 0\n", + " inequality constraints with only upper bounds: 0\n", + "\n", + "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", + " 0 0.0000000e+00 1.86e+01 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.45e+01 3.57e+02 -1.0 6.65e+01 - 5.37e-01 2.20e-01h 1\n", + " 2 0.0000000e+00 5.66e+01 9.54e+02 -1.0 5.31e+01 - 8.42e-01 6.51e-01h 1\n", + " 3 0.0000000e+00 1.41e+01 1.13e+03 -1.0 4.54e+01 - 9.04e-01 9.56e-01h 1\n", + " 4 0.0000000e+00 1.49e-01 6.63e+03 -1.0 2.35e+01 - 9.93e-01 9.90e-01h 1\n", + " 5 0.0000000e+00 1.50e-03 3.82e+03 -1.0 1.28e+00 - 1.00e+00 9.90e-01h 1\n", + " 6 0.0000000e+00 1.50e-05 1.79e+04 -1.7 1.43e-02 - 1.00e+00 9.90e-01h 1\n", + " 7 0.0000000e+00 1.50e-07 1.67e+06 -1.7 1.43e-04 - 1.00e+00 9.90e-01h 1\n", + " 8 0.0000000e+00 1.63e-09 1.67e+08 -1.7 1.43e-06 - 1.00e+00 9.90e-01h 1\n", + "\n", + "Number of Iterations....: 8\n", + "\n", + " (scaled) (unscaled)\n", + "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Constraint violation....: 1.1699586249065637e-11 1.6298145055770874e-09\n", + "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", + "Overall NLP error.......: 1.1699586249065637e-11 1.6298145055770874e-09\n", + "\n", + "\n", + "Number of objective function evaluations = 9\n", + "Number of objective gradient evaluations = 9\n", + "Number of equality constraint evaluations = 9\n", + "Number of inequality constraint evaluations = 0\n", + "Number of equality constraint Jacobian evaluations = 9\n", + "Number of inequality constraint Jacobian evaluations = 0\n", + "Number of Lagrangian Hessian evaluations = 8\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 0.041\n", + "Total CPU secs in NLP function evaluations = 0.003\n", + "\n", + "EXIT: Optimal Solution Found.\n" + ] + } + ], "source": [ "solver = get_solver()\n", "results = solver.solve(m, tee=True)" @@ -828,7 +1888,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "id": "c0bae126-fce0-4c01-8386-f33854787d9a", "metadata": {}, "outputs": [], @@ -847,10 +1907,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "id": "183c650b-5a5e-4672-a63c-ce4ae1f8383b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "====================================================================================\n", + "Unit : fs.Treated Time: 0.0\n", + "------------------------------------------------------------------------------------\n", + " Stream Table\n", + " Units Inlet \n", + " Volumetric Flowrate meter ** 3 / second 0.23889\n", + " Molar Alkalinity mole / meter ** 3 3.8096\n", + " Mass Concentration S_I kilogram / meter ** 3 0.061909\n", + " Mass Concentration S_S kilogram / meter ** 3 0.00087127\n", + " Mass Concentration X_I kilogram / meter ** 3 0.0054462\n", + " Mass Concentration X_S kilogram / meter ** 3 0.00020555\n", + " Mass Concentration X_BH kilogram / meter ** 3 0.010903\n", + " Mass Concentration X_BA kilogram / meter ** 3 0.00078876\n", + " Mass Concentration X_P kilogram / meter ** 3 0.0022565\n", + " Mass Concentration S_O kilogram / meter ** 3 0.00044900\n", + " Mass Concentration S_NO kilogram / meter ** 3 0.015456\n", + " Mass Concentration S_NH kilogram / meter ** 3 0.00091693\n", + " Mass Concentration S_ND kilogram / meter ** 3 0.00064661\n", + " Mass Concentration X_ND kilogram / meter ** 3 1.4159e-05\n", + " Temperature kelvin 308.15\n", + " Pressure pascal 1.0132e+05\n", + "====================================================================================\n" + ] + } + ], "source": [ "m.fs.Treated.report()" ] @@ -881,7 +1971,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.17" + "version": "3.11.5" } }, "nbformat": 4, From 2e9aaafa0e020e8afa8a7beaf9e3e6ec37e59a00 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Tue, 10 Dec 2024 15:58:06 -0500 Subject: [PATCH 15/46] Cut back on scaling and update tests --- .../BSM2_P_extension.py | 55 ++--- watertap/unit_models/anaerobic_digester.py | 105 ---------- .../tests/test_anaerobic_digester.py | 194 +++++++++--------- 3 files changed, 114 insertions(+), 240 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 50be541c5b..abdbea1b84 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -88,6 +88,10 @@ cost_circular_clarifier, cost_primary_clarifier, ) +from idaes.core.scaling.custom_scaler_base import ( + CustomScalerBase, + ConstraintScalingScheme, +) # Set up logger _log = idaeslog.getLogger(__name__) @@ -521,12 +525,12 @@ def set_operating_conditions(m, bio_P=False): m.fs.thickener.hydraulic_retention_time.fix(86400 * pyo.units.s) m.fs.thickener.diameter.fix(10 * pyo.units.m) - iscale.calculate_scaling_factors(m) + scaler = CustomScalerBase() def scale_variables(m): for var in m.fs.component_data_objects(pyo.Var, descend_into=True): if "flow_vol" in var.name: - iscale.set_scaling_factor(var, 1e0) + iscale.set_scaling_factor(var, 1e3) if "temperature" in var.name: iscale.set_scaling_factor(var, 1e-2) if "pressure" in var.name: @@ -534,44 +538,29 @@ def scale_variables(m): if "conc_mass_comp" in var.name: iscale.set_scaling_factor(var, 1e1) + def scale_constraints(m): + for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): + if "flow_vol_equality" in c.name: + scaler.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=True, + ) + m.fs.aerobic_reactors = (m.fs.R5, m.fs.R6, m.fs.R7) for R in m.fs.aerobic_reactors: iscale.set_scaling_factor(R.KLa, 1e-2) iscale.set_scaling_factor(R.hydraulic_retention_time[0], 1e-3) - # for unit in ("R1", "R2", "R3", "R4"): - # block = getattr(m.fs, unit) - # iscale.set_scaling_factor(block.hydraulic_retention_time, 1e-3) - # - # for unit in ("R1", "R2", "R3", "R4", "R5", "R6", "R7"): - # block = getattr(m.fs, unit) - # iscale.set_scaling_factor( - # block.control_volume.reactions[0.0].rate_expression, 1e3 - # ) - # iscale.set_scaling_factor(block.cstr_performance_eqn, 1e3) - # iscale.set_scaling_factor( - # block.control_volume.rate_reaction_stoichiometry_constraint, 1e3 - # ) - # iscale.set_scaling_factor(block.control_volume.material_balances, 1e3) - # - # iscale.set_scaling_factor(m.fs.AD.KH_co2, 1e1) - # iscale.set_scaling_factor(m.fs.AD.KH_ch4, 1e1) - # iscale.set_scaling_factor(m.fs.AD.KH_h2, 1e2) - # - # if bio_P: - # iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) - # iscale.constraint_scaling_transform( - # m.fs.AD.liquid_phase.enthalpy_balances[0], 1e-6 - # ) - # else: - # iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) - # iscale.constraint_scaling_transform( - # m.fs.AD.liquid_phase.enthalpy_balances[0], 1e-3 - # ) + if bio_P: + iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) + else: + iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) # Apply scaling scale_variables(m) - # iscale.calculate_scaling_factors(m) + scale_constraints(m) + iscale.calculate_scaling_factors(m) def initialize_system(m, bio_P=False, solver=None): @@ -974,7 +963,7 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main(bio_P=True) + m, results = main(bio_P=False) stream_table = create_stream_table_dataframe( { diff --git a/watertap/unit_models/anaerobic_digester.py b/watertap/unit_models/anaerobic_digester.py index 322fffb097..177f087f48 100644 --- a/watertap/unit_models/anaerobic_digester.py +++ b/watertap/unit_models/anaerobic_digester.py @@ -170,118 +170,13 @@ def constraint_scaling_routine( overwrite=overwrite, ) - # Scale control volume constraints - for c in model.liquid_phase.component_data_objects( - Constraint, descend_into=False - ): - self.scale_constraint_by_nominal_value( - c, - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - # Scale unit level constraints - if hasattr(model, "unit_material_balance"): - for c in model.unit_material_balance.values(): - self.scale_constraint_by_nominal_value( - c, - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "ad_performance_eqn"): - for c in model.ad_performance_eqn.values(): - self.scale_constraint_by_nominal_value( - c, - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - - # TODO: See if these can be scaled in a for loop like the control volume constraints - if hasattr(model, "CO2_Henrys_law"): - self.scale_constraint_by_nominal_value( - model.CO2_Henrys_law[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "Ch4_Henrys_law"): - self.scale_constraint_by_nominal_value( - model.Ch4_Henrys_law[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "H2_Henrys_law"): - self.scale_constraint_by_nominal_value( - model.H2_Henrys_law[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "outlet_P"): - self.scale_constraint_by_nominal_value( - model.outlet_P[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "Sh2_conc"): - self.scale_constraint_by_nominal_value( - model.Sh2_conc[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "Sch4_conc"): - self.scale_constraint_by_nominal_value( - model.Sch4_conc[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "Sco2_conc"): - self.scale_constraint_by_nominal_value( - model.Sco2_conc[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "flow_vol_vap"): - self.scale_constraint_by_nominal_value( - model.flow_vol_vap[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "ad_total_volume"): - self.scale_constraint_by_nominal_value( - model.ad_total_volume[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) if hasattr(model, "AD_retention_time"): self.scale_constraint_by_nominal_value( model.AD_retention_time[0], scheme=ConstraintScalingScheme.inverseMaximum, overwrite=overwrite, ) - # TODO: Might be able to remove temperature, pressure, and enthalpy balances - if hasattr(model, "unit_temperature_equality"): - self.scale_constraint_by_nominal_value( - model.unit_temperature_equality[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "unit_pressure_balance"): - self.scale_constraint_by_nominal_value( - model.unit_pressure_balance[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "unit_enthalpy_balance"): - self.scale_constraint_by_nominal_value( - model.unit_enthalpy_balance[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) - if hasattr(model, "unit_electricity_consumption"): - self.scale_constraint_by_nominal_value( - model.unit_electricity_consumption[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=overwrite, - ) @declare_process_block_class("AD") diff --git a/watertap/unit_models/tests/test_anaerobic_digester.py b/watertap/unit_models/tests/test_anaerobic_digester.py index 4e0037cdf2..509afd9c59 100644 --- a/watertap/unit_models/tests/test_anaerobic_digester.py +++ b/watertap/unit_models/tests/test_anaerobic_digester.py @@ -24,6 +24,7 @@ ConcreteModel, Suffix, TransformationFactory, + Var, ) from idaes.core import ( @@ -50,6 +51,7 @@ from watertap.unit_models.tests.unit_test_harness import UnitTestHarness import idaes.core.util.scaling as iscale +from idaes.core.scaling.scaling_base import ScalerBase # ----------------------------------------------------------------------------- # Get default solver for testing @@ -120,6 +122,7 @@ def build(): iscale.set_scaling_factor( m.fs.unit.liquid_phase.mass_transfer_term[0, "Liq", "S_h2"], 1e7 ) + iscale.set_scaling_factor(m.fs.unit.liquid_phase.heat[0], 1e3) return m @@ -583,61 +586,12 @@ def test_constraint_scaling_routine(self, model): model.fs.unit.liquid_phase.reactions[0.0].I_fun["R19"] ] == pytest.approx(1, rel=1e-8) - # Check that unit model has scaling factors - sfx_cv = model.fs.unit.liquid_phase.scaling_factor - assert isinstance(model.fs.unit.liquid_phase.scaling_factor, Suffix) - assert len(sfx_cv) == 56 - assert sfx_cv[ - model.fs.unit.liquid_phase.enthalpy_balances[0.0] - ] == pytest.approx(7.783208078e-10, abs=1e-8) - assert sfx_cv[ - model.fs.unit.liquid_phase.pressure_balance[0.0] - ] == pytest.approx(9.869232667e-6, rel=1e-8) - for ( - c - ) in model.fs.unit.liquid_phase.rate_reaction_stoichiometry_constraint.values(): - assert sfx_cv[c] == pytest.approx(1, rel=1e-8) - sfx_unit = model.fs.unit.scaling_factor assert isinstance(sfx_unit, Suffix) - assert len(sfx_unit) == 59 - assert sfx_unit[model.fs.unit.CO2_Henrys_law[0]] == pytest.approx( - 1.277154575e-1, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Ch4_Henrys_law[0]] == pytest.approx( - 1.479435417e-1, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.H2_Henrys_law[0]] == pytest.approx( - 1.38666123e-1, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.outlet_P[0]] == pytest.approx( - 9.8692326672e-6, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Sh2_conc[0]] == pytest.approx( - 5.524296675192e5, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Sch4_conc[0]] == pytest.approx( - 2.310160428, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Sco2_conc[0]] == pytest.approx( - 1.069518717, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.flow_vol_vap[0]] == pytest.approx(1, rel=1e-8) - assert sfx_unit[model.fs.unit.ad_total_volume[0]] == pytest.approx( - 2.702702703e-4, rel=1e-8 - ) + assert len(sfx_unit) == 1 assert sfx_unit[model.fs.unit.AD_retention_time[0]] == pytest.approx( 5.3178178178e-7, rel=1e-8 ) - assert sfx_unit[model.fs.unit.unit_temperature_equality[0]] == pytest.approx( - 3.2451728e-3, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.unit_enthalpy_balance[0]] == pytest.approx( - 7.783208078e-10, abs=1e-8 - ) - assert sfx_unit[model.fs.unit.unit_electricity_consumption[0]] == pytest.approx( - 4.214223e-2, rel=1e-8 - ) @pytest.mark.component def test_scale_model(self, model): @@ -890,61 +844,12 @@ def test_scale_model(self, model): model.fs.unit.liquid_phase.reactions[0.0].I_fun["R19"] ] == pytest.approx(1, rel=1e-8) - # Check that unit model has scaling factors - sfx_cv = model.fs.unit.liquid_phase.scaling_factor - assert isinstance(sfx_cv, Suffix) - assert len(sfx_cv) == 57 - assert sfx_cv[ - model.fs.unit.liquid_phase.enthalpy_balances[0.0] - ] == pytest.approx(3.95570105e-7, rel=1e-8) - assert sfx_cv[ - model.fs.unit.liquid_phase.pressure_balance[0.0] - ] == pytest.approx(1e-6, rel=1e-8) - for ( - c - ) in model.fs.unit.liquid_phase.rate_reaction_stoichiometry_constraint.values(): - assert sfx_cv[c] == pytest.approx(1, rel=1e-8) - sfx_unit = model.fs.unit.scaling_factor assert isinstance(sfx_unit, Suffix) - assert len(sfx_unit) == 59 - assert sfx_unit[model.fs.unit.CO2_Henrys_law[0]] == pytest.approx( - 0.127715457, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Ch4_Henrys_law[0]] == pytest.approx( - 0.147943542, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.H2_Henrys_law[0]] == pytest.approx( - 0.138666123, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.outlet_P[0]] == pytest.approx( - 9.8692326672e-6, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Sh2_conc[0]] == pytest.approx( - 5.52429667519e5, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Sch4_conc[0]] == pytest.approx( - 2.310160428, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.Sco2_conc[0]] == pytest.approx( - 1.069518717, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.flow_vol_vap[0]] == pytest.approx(1, rel=1e-8) - assert sfx_unit[model.fs.unit.ad_total_volume[0]] == pytest.approx( - 2.7027027027e-4, rel=1e-8 - ) + assert len(sfx_unit) == 1 assert sfx_unit[model.fs.unit.AD_retention_time[0]] == pytest.approx( 5.3178178178e-7, rel=1e-8 ) - assert sfx_unit[model.fs.unit.unit_temperature_equality[0]] == pytest.approx( - 3.2451728e-3, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.unit_enthalpy_balance[0]] == pytest.approx( - 3.95570105e-7, rel=1e-8 - ) - assert sfx_unit[model.fs.unit.unit_electricity_consumption[0]] == pytest.approx( - 4.214223e-2, rel=1e-8 - ) # TODO: Remove test once iscale is deprecated @pytest.mark.integration @@ -1013,7 +918,79 @@ def test_example_case_iscale(self): sm = TransformationFactory("core.scale_model").create_using(m, rename=False) jac, _ = get_jacobian(sm, scaled=False) assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( - 4.184434e12, rel=1e-3 + 3.8550325e12, rel=1e-3 + ) + + @pytest.mark.integration + def test_example_case_scaler_scaling_default(self): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.props = ADM1ParameterBlock() + m.fs.props_vap = ADM1_vaporParameterBlock() + m.fs.rxn_props = ADM1ReactionParameterBlock(property_package=m.fs.props) + + m.fs.unit = AD( + liquid_property_package=m.fs.props, + vapor_property_package=m.fs.props_vap, + reaction_package=m.fs.rxn_props, + has_heat_transfer=True, + has_pressure_change=False, + ) + + # Set the operating conditions + m.fs.unit.inlet.flow_vol.fix(170 / 24 / 3600) + m.fs.unit.inlet.temperature.fix(308.15) + m.fs.unit.inlet.pressure.fix(101325) + + m.fs.unit.inlet.conc_mass_comp[0, "S_su"].fix(0.01) + m.fs.unit.inlet.conc_mass_comp[0, "S_aa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_fa"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_va"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_bu"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_pro"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_ac"].fix(0.001) + m.fs.unit.inlet.conc_mass_comp[0, "S_h2"].fix(1e-8) + m.fs.unit.inlet.conc_mass_comp[0, "S_ch4"].fix(1e-5) + m.fs.unit.inlet.conc_mass_comp[0, "S_IC"].fix(0.48) + m.fs.unit.inlet.conc_mass_comp[0, "S_IN"].fix(0.14) + m.fs.unit.inlet.conc_mass_comp[0, "S_I"].fix(0.02) + + m.fs.unit.inlet.conc_mass_comp[0, "X_c"].fix(2) + m.fs.unit.inlet.conc_mass_comp[0, "X_ch"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_pr"].fix(20) + m.fs.unit.inlet.conc_mass_comp[0, "X_li"].fix(5) + m.fs.unit.inlet.conc_mass_comp[0, "X_su"].fix(0.0) + m.fs.unit.inlet.conc_mass_comp[0, "X_aa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_fa"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_c4"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_pro"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_ac"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_h2"].fix(0.010) + m.fs.unit.inlet.conc_mass_comp[0, "X_I"].fix(25) + + m.fs.unit.inlet.cations[0].fix(0.04) + m.fs.unit.inlet.anions[0].fix(0.02) + + m.fs.unit.volume_liquid.fix(3400) + m.fs.unit.volume_vapor.fix(300) + m.fs.unit.liquid_outlet.temperature.fix(308.15) + + scaler = ADScaler() + scaler.scale_model( + m.fs.unit, + submodel_scalers={ + m.fs.unit.liquid_phase.properties_in: ADM1PropertiesScaler, + m.fs.unit.liquid_phase.properties_out: ADM1PropertiesScaler, + m.fs.unit.liquid_phase.reactions: ADM1ReactionScaler, + }, + ) + + # Check condition number to confirm scaling + sm = TransformationFactory("core.scale_model").create_using(m, rename=False) + jac, _ = get_jacobian(sm, scaled=False) + assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( + 2.428479487421e12, rel=1e-3 ) @pytest.mark.integration @@ -1071,6 +1048,19 @@ def test_example_case_scaler_scaling(self): m.fs.unit.volume_vapor.fix(300) m.fs.unit.liquid_outlet.temperature.fix(308.15) + sb = ScalerBase() + + # Apply scaling to unscaled variables + for var in m.fs.component_data_objects(Var, descend_into=True): + if "conc_mass_comp" in var.name: + sb.set_variable_scaling_factor(var, 1e1) + if "conc_mol" in var.name: + sb.set_variable_scaling_factor(var, 1e2) + if "reaction_rate" in var.name: + sb.set_variable_scaling_factor(var, 1e6) + + sb.set_variable_scaling_factor(m.fs.unit.hydraulic_retention_time[0], 1e-6) + scaler = ADScaler() scaler.scale_model( m.fs.unit, @@ -1085,5 +1075,5 @@ def test_example_case_scaler_scaling(self): sm = TransformationFactory("core.scale_model").create_using(m, rename=False) jac, _ = get_jacobian(sm, scaled=False) assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( - 9.43861834e16, rel=1e-3 + 2.27372477638e11, rel=1e-3 ) From 9797a526bac2acc36640b6d8714a1f74df11dba8 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Wed, 11 Dec 2024 12:26:43 -0500 Subject: [PATCH 16/46] Improve scaling for bio_P=False configuration --- .../BSM2_P_extension.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index abdbea1b84..584e9de29f 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -556,6 +556,9 @@ def scale_constraints(m): iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) else: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) + iscale.set_scaling_factor( + m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 + ) # Apply scaling scale_variables(m) @@ -963,7 +966,7 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main(bio_P=False) + m, results = main(bio_P=True) stream_table = create_stream_table_dataframe( { From c7f05a6362ffe11201909efe447c85011b67d255 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Wed, 11 Dec 2024 21:10:14 -0500 Subject: [PATCH 17/46] Test changes to boi_P=False scaling --- .../BSM2_P_extension.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 584e9de29f..b58d7d7d2d 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -93,6 +93,8 @@ ConstraintScalingScheme, ) +from idaes.core.util import DiagnosticsToolbox + # Set up logger _log = idaeslog.getLogger(__name__) @@ -148,6 +150,17 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) + dt = DiagnosticsToolbox(m) + print("---Numerical Issues---") + dt.report_numerical_issues() + dt.display_variables_with_extreme_jacobians() + dt.display_constraints_with_extreme_jacobians() + + badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + print("---------------- badly_scaled_var_list ----------------") + for x in badly_scaled_var_list: + print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + return m, results @@ -554,6 +567,7 @@ def scale_constraints(m): if bio_P: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) + scale_constraints(m) else: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) iscale.set_scaling_factor( @@ -562,7 +576,6 @@ def scale_constraints(m): # Apply scaling scale_variables(m) - scale_constraints(m) iscale.calculate_scaling_factors(m) @@ -966,7 +979,7 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main(bio_P=True) + m, results = main(bio_P=False) stream_table = create_stream_table_dataframe( { From 3313ef20a75729c52b34ea3c882a067e7c90c6e5 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 12 Dec 2024 09:25:52 -0500 Subject: [PATCH 18/46] Try adjusting scaling again --- .../BSM2_P_extension.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index b58d7d7d2d..3070b8aa14 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -567,15 +567,12 @@ def scale_constraints(m): if bio_P: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) - scale_constraints(m) else: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) - iscale.set_scaling_factor( - m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 - ) # Apply scaling scale_variables(m) + scale_constraints(m) iscale.calculate_scaling_factors(m) From 9eb6984b77a21d9df50abd1cfac8fa2298acb4a0 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 12 Dec 2024 12:50:38 -0500 Subject: [PATCH 19/46] Add additional scaling for heat and enthalpy --- .../BSM2_P_extension.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 3070b8aa14..ce38f558df 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -94,6 +94,7 @@ ) from idaes.core.util import DiagnosticsToolbox +from idaes.core.scaling import report_scaling_factors # Set up logger _log = idaeslog.getLogger(__name__) @@ -161,6 +162,9 @@ def main(bio_P=False): for x in badly_scaled_var_list: print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + print("--- Scaling Factors ---") + report_scaling_factors(m, descend_into=True) + return m, results @@ -568,7 +572,15 @@ def scale_constraints(m): if bio_P: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) else: - iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e2) + iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) + scaler.scale_constraint_by_nominal_value( + m.fs.AD.liquid_phase.enthalpy_balances[0], + scheme=ConstraintScalingScheme.inverseMinimum, + overwrite=True, + ) + # iscale.set_scaling_factor( + # m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 + # ) # Apply scaling scale_variables(m) From 1e57393656ee64d8fa86d29b827e6613d0af6de7 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 12 Dec 2024 14:50:08 -0500 Subject: [PATCH 20/46] Use InverseMaximum scaling scheme --- .../BSM2_P_extension.py | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index ce38f558df..4c8aefc4a4 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -93,9 +93,6 @@ ConstraintScalingScheme, ) -from idaes.core.util import DiagnosticsToolbox -from idaes.core.scaling import report_scaling_factors - # Set up logger _log = idaeslog.getLogger(__name__) @@ -151,20 +148,6 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) - dt = DiagnosticsToolbox(m) - print("---Numerical Issues---") - dt.report_numerical_issues() - dt.display_variables_with_extreme_jacobians() - dt.display_constraints_with_extreme_jacobians() - - badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) - print("---------------- badly_scaled_var_list ----------------") - for x in badly_scaled_var_list: - print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") - - print("--- Scaling Factors ---") - report_scaling_factors(m, descend_into=True) - return m, results @@ -575,12 +558,9 @@ def scale_constraints(m): iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) scaler.scale_constraint_by_nominal_value( m.fs.AD.liquid_phase.enthalpy_balances[0], - scheme=ConstraintScalingScheme.inverseMinimum, + scheme=ConstraintScalingScheme.inverseMaximum, overwrite=True, ) - # iscale.set_scaling_factor( - # m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 - # ) # Apply scaling scale_variables(m) From f75f4b053d21189d49ced3b743e658de4648fa7c Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 12 Dec 2024 15:33:23 -0500 Subject: [PATCH 21/46] Additional scaling improvement --- .../BSM2_P_extension.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 4c8aefc4a4..2e634afcc2 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -93,6 +93,9 @@ ConstraintScalingScheme, ) +from idaes.core.util import DiagnosticsToolbox +from idaes.core.scaling import report_scaling_factors + # Set up logger _log = idaeslog.getLogger(__name__) @@ -148,6 +151,20 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) + dt = DiagnosticsToolbox(m) + print("---Numerical Issues---") + dt.report_numerical_issues() + dt.display_variables_with_extreme_jacobians() + dt.display_constraints_with_extreme_jacobians() + + badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + print("---------------- badly_scaled_var_list ----------------") + for x in badly_scaled_var_list: + print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + + print("--- Scaling Factors ---") + report_scaling_factors(m, descend_into=True) + return m, results @@ -556,6 +573,9 @@ def scale_constraints(m): iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) else: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) + iscale.set_scaling_factor( + m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e7 + ) scaler.scale_constraint_by_nominal_value( m.fs.AD.liquid_phase.enthalpy_balances[0], scheme=ConstraintScalingScheme.inverseMaximum, From 0dc4b4f4e321961bcedcbb707ea90b835f21c282 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 12 Dec 2024 17:05:13 -0500 Subject: [PATCH 22/46] Add rate expression constraint scaling --- .../BSM2_P_extension.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 2e634afcc2..48307d19a1 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -165,6 +165,10 @@ def main(bio_P=False): print("--- Scaling Factors ---") report_scaling_factors(m, descend_into=True) + print("---SVD---") + svd = dt.prepare_svd_toolbox() + svd.display_underdetermined_variables_and_constraints() + return m, results @@ -573,12 +577,17 @@ def scale_constraints(m): iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) else: iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) + scaler.scale_constraint_by_nominal_value( + m.fs.AD.liquid_phase.enthalpy_balances[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=True, + ) iscale.set_scaling_factor( m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e7 ) scaler.scale_constraint_by_nominal_value( - m.fs.AD.liquid_phase.enthalpy_balances[0], - scheme=ConstraintScalingScheme.inverseMaximum, + m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], + scheme=ConstraintScalingScheme.inverseRSS, overwrite=True, ) From 5dda788a3f021b0f66b749b17e27b15f4c3c4360 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 12 Dec 2024 19:28:33 -0500 Subject: [PATCH 23/46] Use inverseMinimum and update test --- .../BSM2_P_extension.py | 18 +- .../test/test_BSM2_P_extension.py | 218 +++++++++--------- 2 files changed, 118 insertions(+), 118 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 48307d19a1..b3a8dfa3ac 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -579,17 +579,17 @@ def scale_constraints(m): iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) scaler.scale_constraint_by_nominal_value( m.fs.AD.liquid_phase.enthalpy_balances[0], - scheme=ConstraintScalingScheme.inverseMaximum, - overwrite=True, - ) - iscale.set_scaling_factor( - m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e7 - ) - scaler.scale_constraint_by_nominal_value( - m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], - scheme=ConstraintScalingScheme.inverseRSS, + scheme=ConstraintScalingScheme.inverseMinimum, overwrite=True, ) + # iscale.set_scaling_factor( + # m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 + # ) + # scaler.scale_constraint_by_nominal_value( + # m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], + # scheme=ConstraintScalingScheme.inverseMaximum, + # overwrite=True, + # ) # Apply scaling scale_variables(m) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index f0eabc0af6..8cedb0c6f6 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -61,55 +61,55 @@ def test_solve(self, system_frame): 6.4300e-07, abs=1e-6 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.00027610, rel=1e-3 + 0.000268855, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( 0.057450, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.052060, rel=1e-3) + ) == pytest.approx(0.051475018, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.00017686, rel=1e-3) + ) == pytest.approx(0.000182758, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(0.0060483, rel=1e-3) + ) == pytest.approx(0.0091486, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.0076767, rel=1e-3) + ) == pytest.approx(0.00768617, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.65267, rel=1e-3) + ) == pytest.approx(0.634266, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( 0.36810, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.018654, rel=1e-3) + ) == pytest.approx(0.0187295, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.15148, rel=1e-3) + ) == pytest.approx(0.152977, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(0.00041597, rel=1e-3) + ) == pytest.approx(0.00043230, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.013371, rel=1e-3 + 0.01333278, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.012360, rel=1e-3 + 0.012626, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.011077, rel=1e-3) + ) == pytest.approx(0.00947097, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] - ) == pytest.approx(5.0389e-06, abs=1e-6) + ) == pytest.approx(1.3866e-06, abs=1e-6) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.0036998, rel=1e-3) + ) == pytest.approx(0.0035771, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.00021570, rel=1e-3 + 0.000210879, rel=1e-3 ) @pytest.mark.requires_idaes_solver @@ -118,103 +118,103 @@ def test_costing(self, system_frame): m = system_frame # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.468069, rel=1e-3) + assert value(m.fs.costing.LCOW) == pytest.approx(0.46694196, rel=1e-3) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 23935727.742, rel=1e-3 + 23878359.211, rel=1e-3 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 827635.25, rel=1e-3 + 825615.28, rel=1e-3 ) -class TestFullFlowsheetBioPTrue: - @pytest.mark.requires_idaes_solver - @pytest.fixture(scope="class") - def system_frame(self): - m, res = main(bio_P=True) - m.results = res - return m - - @pytest.mark.requires_idaes_solver - @pytest.mark.integration - def test_structure(self, system_frame): - assert_units_consistent(system_frame) - assert degrees_of_freedom(system_frame) == 0 - assert_optimal_termination(system_frame.results) - - @pytest.mark.requires_idaes_solver - @pytest.mark.component - def test_solve(self, system_frame): - m = system_frame - - assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( - 0.2422, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( - 6.6530e-07, abs=1e-6 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.00027824, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( - 0.057450, rel=1e-3 - ) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.050224, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.00019157, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(0.0055542, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.0076580, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.0026406, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( - 0.36984, rel=1e-3 - ) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.020860, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.15191, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(0.00038907, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.013578, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.012569, rel=1e-3 - ) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.012282, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] - ) == pytest.approx(6.6978e-06, abs=1e-6) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.0040285, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.00022424, rel=1e-3 - ) - - @pytest.mark.requires_idaes_solver - @pytest.mark.component - def test_costing(self, system_frame): - m = system_frame - - # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) - assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 24019261.867, rel=1e-3 - ) - assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 830582.94, rel=1e-3 - ) +# class TestFullFlowsheetBioPTrue: +# @pytest.mark.requires_idaes_solver +# @pytest.fixture(scope="class") +# def system_frame(self): +# m, res = main(bio_P=True) +# m.results = res +# return m +# +# @pytest.mark.requires_idaes_solver +# @pytest.mark.integration +# def test_structure(self, system_frame): +# assert_units_consistent(system_frame) +# assert degrees_of_freedom(system_frame) == 0 +# assert_optimal_termination(system_frame.results) +# +# @pytest.mark.requires_idaes_solver +# @pytest.mark.component +# def test_solve(self, system_frame): +# m = system_frame +# +# assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( +# 0.2422, rel=1e-3 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( +# 6.6530e-07, abs=1e-6 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( +# 0.00027824, rel=1e-3 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( +# 0.057450, rel=1e-3 +# ) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_N2"] +# ) == pytest.approx(0.050224, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] +# ) == pytest.approx(0.00019157, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] +# ) == pytest.approx(0.0055542, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_O2"] +# ) == pytest.approx(0.0076580, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] +# ) == pytest.approx(0.0026406, rel=1e-3) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( +# 0.36984, rel=1e-3 +# ) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] +# ) == pytest.approx(0.020860, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_IC"] +# ) == pytest.approx(0.15191, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] +# ) == pytest.approx(0.00038907, rel=1e-3) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( +# 0.013578, rel=1e-3 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( +# 0.012569, rel=1e-3 +# ) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] +# ) == pytest.approx(0.012282, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] +# ) == pytest.approx(6.6978e-06, abs=1e-6) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_PP"] +# ) == pytest.approx(0.0040285, rel=1e-3) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( +# 0.00022424, rel=1e-3 +# ) +# +# @pytest.mark.requires_idaes_solver +# @pytest.mark.component +# def test_costing(self, system_frame): +# m = system_frame +# +# # check costing +# assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) +# assert value(m.fs.costing.total_capital_cost) == pytest.approx( +# 24019261.867, rel=1e-3 +# ) +# assert value(m.fs.costing.total_operating_cost) == pytest.approx( +# 830582.94, rel=1e-3 +# ) From 07688ab8275c18db7d7a18a852e48c30f390df41 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 13 Dec 2024 09:27:37 -0500 Subject: [PATCH 24/46] Address merge conflicts --- .../test/test_BSM2_P_extension.py | 347 ++++++++++-------- 1 file changed, 197 insertions(+), 150 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index 8cedb0c6f6..eebc5b70da 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -10,14 +10,21 @@ # "https://github.com/watertap-org/watertap/" ################################################################################# """ -Tests for full Water Resource Recovery Facility with ASM2d and ADM1 +Tests for full Water Resource Recovery Facility (WRRF; a.k.a., wastewater treatment plant) flowsheet example with ASM1 and ADM1. The flowsheet follows the same formulation as benchmark simulation model no.2 (BSM2) but comprises different specifications for default values than BSM2. + +Verified against results from: + +Rosen, C. and Jeppsson, U., 2006. +Aspects on ADM1 Implementation within the BSM2 Framework. +Department of Industrial Electrical Engineering and Automation, Lund University, Lund, Sweden, pp.1-35. + """ # Some more information about this module -__author__ = "Chenyu Wang" +__author__ = "Alejandro Garciadiego, Xinhong Liu, Adam Atia, Marcus Holly" import pytest @@ -26,195 +33,235 @@ from idaes.core.util.model_statistics import degrees_of_freedom -from watertap.flowsheets.full_water_resource_recovery_facility.BSM2_P_extension import ( - main, -) -from watertap.core.solvers import get_solver +import watertap.flowsheets.full_water_resource_recovery_facility.BSM2 as BSM2 -solver = get_solver() - -class TestFullFlowsheetBioPFalse: - @pytest.mark.requires_idaes_solver +class TestFullFlowsheet: @pytest.fixture(scope="class") def system_frame(self): - m, res = main(bio_P=False) - m.results = res + m = BSM2.build() + BSM2.set_operating_conditions(m) + for mx in m.mixers: + mx.pressure_equality_constraints[0.0, 2].deactivate() + assert degrees_of_freedom(m) == 0 + assert_units_consistent(m) + BSM2.initialize_system(m) + for mx in m.mixers: + mx.pressure_equality_constraints[0.0, 2].deactivate() + assert degrees_of_freedom(m) == 0 + + m.results = BSM2.solve(m) + return m - @pytest.mark.requires_idaes_solver @pytest.mark.integration - def test_structure(self, system_frame): + def test_square_problem(self, system_frame): assert_units_consistent(system_frame) assert degrees_of_freedom(system_frame) == 0 assert_optimal_termination(system_frame.results) - @pytest.mark.requires_idaes_solver @pytest.mark.component - def test_solve(self, system_frame): + def test_square_solve(self, system_frame): m = system_frame assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( - 0.24219, rel=1e-3 + 0.23889, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( - 6.4300e-07, abs=1e-6 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.000268855, rel=1e-3 + assert value(m.fs.Treated.properties[0].alkalinity) == pytest.approx( + 3.8096e-3, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( - 0.057450, rel=1e-3 + 0.061909, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_S"]) == pytest.approx( + 0.00087127, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( + 0.0054462, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( + 0.00020555, rel=1e-3 ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.051475018, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_BH"] + ) == pytest.approx(0.010903, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.000182758, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_BA"] + ) == pytest.approx(0.00078876, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_P"]) == pytest.approx( + 0.0022565, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_O"]) == pytest.approx( + 0.000449, rel=1e-3 + ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(0.0091486, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["S_NO"] + ) == pytest.approx(0.0155, rel=1e-2) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.00768617, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["S_NH"] + ) == pytest.approx(0.00091693, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.634266, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( - 0.36810, rel=1e-3 - ) + m.fs.Treated.properties[0].conc_mass_comp["S_ND"] + ) == pytest.approx(0.00064661, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.0187295, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_ND"] + ) == pytest.approx(1.4159e-5, rel=1e-3) + + @pytest.mark.component + def test_costing(self, system_frame): + m = system_frame + + BSM2.add_costing(m) + m.fs.costing.initialize() + results = BSM2.solve(m) + + assert_optimal_termination(results) + + # check costing + assert value(m.fs.costing.LCOW) == pytest.approx(0.351097, rel=1e-3) + assert value(m.fs.costing.total_capital_cost) == pytest.approx( + 17443323.82075141, rel=1e-3 + ) + assert value(m.fs.costing.total_operating_cost) == pytest.approx( + 638749.398846816, rel=1e-3 + ) + + @pytest.mark.component + def test_display(self, system_frame): + m = system_frame + BSM2.display_results(m) + BSM2.display_costing(m) + BSM2.display_performance_metrics(m) + + @pytest.mark.requires_idaes_solver + @pytest.mark.component + def test_optimization(self, system_frame): + m = system_frame + BSM2.setup_optimization(system_frame, reactor_volume_equalities=False) + results = BSM2.solve(system_frame) + assert_optimal_termination(results) + assert degrees_of_freedom(system_frame) == 10 + + # check costing + assert value(m.fs.costing.LCOW) == pytest.approx(0.349772203, rel=1e-5) + assert value(m.fs.costing.total_capital_cost) == pytest.approx( + 17379540.339857, rel=1e-5 + ) + assert value(m.fs.costing.total_operating_cost) == pytest.approx( + 636129.6209807, rel=1e-5 + ) + + +class TestFullFlowsheet_with_equal_reactor_vols: + @pytest.fixture(scope="class") + def system_frame(self): + m = BSM2.build() + BSM2.set_operating_conditions(m) + for mx in m.mixers: + mx.pressure_equality_constraints[0.0, 2].deactivate() + assert degrees_of_freedom(m) == 0 + assert_units_consistent(m) + BSM2.initialize_system(m) + for mx in m.mixers: + mx.pressure_equality_constraints[0.0, 2].deactivate() + assert degrees_of_freedom(m) == 0 + + m.results = BSM2.solve(m) + + return m + + @pytest.mark.integration + def test_square_problem(self, system_frame): + assert_units_consistent(system_frame) + assert degrees_of_freedom(system_frame) == 0 + assert_optimal_termination(system_frame.results) + + @pytest.mark.component + def test_square_solve(self, system_frame): + m = system_frame + + assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( + 0.23889, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].alkalinity) == pytest.approx( + 3.8096e-3, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( + 0.061909, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_S"]) == pytest.approx( + 0.00087127, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( + 0.0054462, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( + 0.00020555, rel=1e-3 + ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.152977, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_BH"] + ) == pytest.approx(0.010903, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(0.00043230, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.01333278, rel=1e-3 + m.fs.Treated.properties[0].conc_mass_comp["X_BA"] + ) == pytest.approx(0.00078876, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_P"]) == pytest.approx( + 0.0022565, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.012626, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_O"]) == pytest.approx( + 0.000449, rel=1e-3 ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.00947097, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["S_NO"] + ) == pytest.approx(0.0155, rel=1e-2) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] - ) == pytest.approx(1.3866e-06, abs=1e-6) + m.fs.Treated.properties[0].conc_mass_comp["S_NH"] + ) == pytest.approx(0.00091693, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.0035771, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.000210879, rel=1e-3 - ) + m.fs.Treated.properties[0].conc_mass_comp["S_ND"] + ) == pytest.approx(0.00064661, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["X_ND"] + ) == pytest.approx(1.4159e-5, rel=1e-3) - @pytest.mark.requires_idaes_solver @pytest.mark.component def test_costing(self, system_frame): m = system_frame + BSM2.add_costing(m) + m.fs.costing.initialize() + results = BSM2.solve(m) + + assert_optimal_termination(results) + # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.46694196, rel=1e-3) + assert value(m.fs.costing.LCOW) == pytest.approx(0.351097, rel=1e-3) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 23878359.211, rel=1e-3 + 17443323.82075141, rel=1e-3 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 825615.28, rel=1e-3 + 638749.398846816, rel=1e-3 ) + @pytest.mark.component + def test_display(self, system_frame): + m = system_frame + BSM2.display_results(m) + BSM2.display_costing(m) -# class TestFullFlowsheetBioPTrue: -# @pytest.mark.requires_idaes_solver -# @pytest.fixture(scope="class") -# def system_frame(self): -# m, res = main(bio_P=True) -# m.results = res -# return m -# -# @pytest.mark.requires_idaes_solver -# @pytest.mark.integration -# def test_structure(self, system_frame): -# assert_units_consistent(system_frame) -# assert degrees_of_freedom(system_frame) == 0 -# assert_optimal_termination(system_frame.results) -# -# @pytest.mark.requires_idaes_solver -# @pytest.mark.component -# def test_solve(self, system_frame): -# m = system_frame -# -# assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( -# 0.2422, rel=1e-3 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( -# 6.6530e-07, abs=1e-6 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( -# 0.00027824, rel=1e-3 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( -# 0.057450, rel=1e-3 -# ) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_N2"] -# ) == pytest.approx(0.050224, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] -# ) == pytest.approx(0.00019157, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] -# ) == pytest.approx(0.0055542, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_O2"] -# ) == pytest.approx(0.0076580, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] -# ) == pytest.approx(0.0026406, rel=1e-3) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( -# 0.36984, rel=1e-3 -# ) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] -# ) == pytest.approx(0.020860, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_IC"] -# ) == pytest.approx(0.15191, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] -# ) == pytest.approx(0.00038907, rel=1e-3) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( -# 0.013578, rel=1e-3 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( -# 0.012569, rel=1e-3 -# ) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] -# ) == pytest.approx(0.012282, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] -# ) == pytest.approx(6.6978e-06, abs=1e-6) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_PP"] -# ) == pytest.approx(0.0040285, rel=1e-3) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( -# 0.00022424, rel=1e-3 -# ) -# -# @pytest.mark.requires_idaes_solver -# @pytest.mark.component -# def test_costing(self, system_frame): -# m = system_frame -# -# # check costing -# assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) -# assert value(m.fs.costing.total_capital_cost) == pytest.approx( -# 24019261.867, rel=1e-3 -# ) -# assert value(m.fs.costing.total_operating_cost) == pytest.approx( -# 830582.94, rel=1e-3 -# ) + @pytest.mark.requires_idaes_solver + @pytest.mark.component + def test_optimization(self, system_frame): + m = system_frame + BSM2.setup_optimization(system_frame, reactor_volume_equalities=True) + results = BSM2.solve(system_frame) + assert_optimal_termination(results) + assert degrees_of_freedom(system_frame) == 8 + + # check costing + assert value(m.fs.costing.LCOW) == pytest.approx(0.349560273, rel=1e-5) + assert value(m.fs.costing.total_capital_cost) == pytest.approx( + 17370674.42102, rel=1e-5 + ) + assert value(m.fs.costing.total_operating_cost) == pytest.approx( + 635577.7320509, rel=1e-5 + ) From 01ffa204ccd199f8fd28ce982ba8f24c9c3d20fd Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Sat, 14 Dec 2024 00:05:49 -0500 Subject: [PATCH 25/46] Clean up flowsheet --- .../BSM2_P_extension.py | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 8417988578..dba2c6a505 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -93,9 +93,6 @@ ConstraintScalingScheme, ) -from idaes.core.util import DiagnosticsToolbox -from idaes.core.scaling import report_scaling_factors - # Set up logger _log = idaeslog.getLogger(__name__) @@ -151,24 +148,6 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) - dt = DiagnosticsToolbox(m) - print("---Numerical Issues---") - dt.report_numerical_issues() - dt.display_variables_with_extreme_jacobians() - dt.display_constraints_with_extreme_jacobians() - - badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) - print("---------------- badly_scaled_var_list ----------------") - for x in badly_scaled_var_list: - print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") - - print("--- Scaling Factors ---") - report_scaling_factors(m, descend_into=True) - - print("---SVD---") - svd = dt.prepare_svd_toolbox() - svd.display_underdetermined_variables_and_constraints() - return m, results @@ -586,14 +565,6 @@ def scale_constraints(m): scheme=ConstraintScalingScheme.inverseMinimum, overwrite=True, ) - # iscale.set_scaling_factor( - # m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 - # ) - # scaler.scale_constraint_by_nominal_value( - # m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], - # scheme=ConstraintScalingScheme.inverseMaximum, - # overwrite=True, - # ) # Apply scaling scale_variables(m) From 10862769e8e026a798654cbc6a27ac70cb065d9f Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 16 Dec 2024 09:58:17 -0500 Subject: [PATCH 26/46] Update BSM2-P test --- .../test/test_BSM2_P_extension.py | 283 ++++++++---------- watertap/unit_models/anaerobic_digester.py | 1 - 2 files changed, 118 insertions(+), 166 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index eebc5b70da..e77125f1df 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -10,21 +10,14 @@ # "https://github.com/watertap-org/watertap/" ################################################################################# """ -Tests for full Water Resource Recovery Facility +Tests for full Water Resource Recovery Facility with ASM2d and ADM1 (WRRF; a.k.a., wastewater treatment plant) flowsheet example with ASM1 and ADM1. The flowsheet follows the same formulation as benchmark simulation model no.2 (BSM2) but comprises different specifications for default values than BSM2. - -Verified against results from: - -Rosen, C. and Jeppsson, U., 2006. -Aspects on ADM1 Implementation within the BSM2 Framework. -Department of Industrial Electrical Engineering and Automation, Lund University, Lund, Sweden, pp.1-35. - """ # Some more information about this module -__author__ = "Alejandro Garciadiego, Xinhong Liu, Adam Atia, Marcus Holly" +__author__ = "Chenyu Wang" import pytest @@ -33,235 +26,195 @@ from idaes.core.util.model_statistics import degrees_of_freedom -import watertap.flowsheets.full_water_resource_recovery_facility.BSM2 as BSM2 +from watertap.flowsheets.full_water_resource_recovery_facility.BSM2_P_extension import ( + main, +) +from watertap.core.solvers import get_solver +solver = get_solver() -class TestFullFlowsheet: + +class TestFullFlowsheetBioPFalse: + @pytest.mark.requires_idaes_solver @pytest.fixture(scope="class") def system_frame(self): - m = BSM2.build() - BSM2.set_operating_conditions(m) - for mx in m.mixers: - mx.pressure_equality_constraints[0.0, 2].deactivate() - assert degrees_of_freedom(m) == 0 - assert_units_consistent(m) - BSM2.initialize_system(m) - for mx in m.mixers: - mx.pressure_equality_constraints[0.0, 2].deactivate() - assert degrees_of_freedom(m) == 0 - - m.results = BSM2.solve(m) - + m, res = main(bio_P=False) + m.results = res return m + @pytest.mark.requires_idaes_solver @pytest.mark.integration - def test_square_problem(self, system_frame): + def test_structure(self, system_frame): assert_units_consistent(system_frame) assert degrees_of_freedom(system_frame) == 0 assert_optimal_termination(system_frame.results) + @pytest.mark.requires_idaes_solver @pytest.mark.component - def test_square_solve(self, system_frame): + def test_solve(self, system_frame): m = system_frame assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( - 0.23889, rel=1e-3 + 0.24219, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].alkalinity) == pytest.approx( - 3.8096e-3, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( + 6.4300e-07, abs=1e-6 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( - 0.061909, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_S"]) == pytest.approx( - 0.00087127, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( + 0.000268855, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.0054462, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( + 0.057450, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.00020555, rel=1e-3 + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_N2"] + ) == pytest.approx(0.051475018, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] + ) == pytest.approx(0.000182758, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] + ) == pytest.approx(0.0091486, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_O2"] + ) == pytest.approx(0.00768617, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] + ) == pytest.approx(0.634266, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( + 0.36810, rel=1e-3 ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_BH"] - ) == pytest.approx(0.010903, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] + ) == pytest.approx(0.0187295, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_IC"] + ) == pytest.approx(0.152977, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_BA"] - ) == pytest.approx(0.00078876, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_P"]) == pytest.approx( - 0.0022565, rel=1e-3 + m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] + ) == pytest.approx(0.00043230, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( + 0.01333278, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_O"]) == pytest.approx( - 0.000449, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( + 0.012626, rel=1e-3 ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NO"] - ) == pytest.approx(0.0155, rel=1e-2) + m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] + ) == pytest.approx(0.00947097, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NH"] - ) == pytest.approx(0.00091693, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] + ) == pytest.approx(1.3866e-06, abs=1e-6) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_ND"] - ) == pytest.approx(0.00064661, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_ND"] - ) == pytest.approx(1.4159e-5, rel=1e-3) - - @pytest.mark.component - def test_costing(self, system_frame): - m = system_frame - - BSM2.add_costing(m) - m.fs.costing.initialize() - results = BSM2.solve(m) - - assert_optimal_termination(results) - - # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.351097, rel=1e-3) - assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 17443323.82075141, rel=1e-3 - ) - assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 638749.398846816, rel=1e-3 + m.fs.Treated.properties[0].conc_mass_comp["X_PP"] + ) == pytest.approx(0.0035771, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( + 0.000210879, rel=1e-3 ) - @pytest.mark.component - def test_display(self, system_frame): - m = system_frame - BSM2.display_results(m) - BSM2.display_costing(m) - BSM2.display_performance_metrics(m) - @pytest.mark.requires_idaes_solver @pytest.mark.component - def test_optimization(self, system_frame): + def test_costing(self, system_frame): m = system_frame - BSM2.setup_optimization(system_frame, reactor_volume_equalities=False) - results = BSM2.solve(system_frame) - assert_optimal_termination(results) - assert degrees_of_freedom(system_frame) == 10 # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.349772203, rel=1e-5) + assert value(m.fs.costing.LCOW) == pytest.approx(0.46694196, rel=1e-3) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 17379540.339857, rel=1e-5 + 23878359.211, rel=1e-3 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 636129.6209807, rel=1e-5 + 825615.28, rel=1e-3 ) -class TestFullFlowsheet_with_equal_reactor_vols: +class TestFullFlowsheetBioPTrue: + @pytest.mark.requires_idaes_solver @pytest.fixture(scope="class") def system_frame(self): - m = BSM2.build() - BSM2.set_operating_conditions(m) - for mx in m.mixers: - mx.pressure_equality_constraints[0.0, 2].deactivate() - assert degrees_of_freedom(m) == 0 - assert_units_consistent(m) - BSM2.initialize_system(m) - for mx in m.mixers: - mx.pressure_equality_constraints[0.0, 2].deactivate() - assert degrees_of_freedom(m) == 0 - - m.results = BSM2.solve(m) - + m, res = main(bio_P=True) + m.results = res return m + @pytest.mark.requires_idaes_solver @pytest.mark.integration - def test_square_problem(self, system_frame): + def test_structure(self, system_frame): assert_units_consistent(system_frame) assert degrees_of_freedom(system_frame) == 0 assert_optimal_termination(system_frame.results) + @pytest.mark.requires_idaes_solver @pytest.mark.component - def test_square_solve(self, system_frame): + def test_solve(self, system_frame): m = system_frame assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( - 0.23889, rel=1e-3 + 0.2422, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].alkalinity) == pytest.approx( - 3.8096e-3, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( - 0.061909, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( + 6.6530e-07, abs=1e-6 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_S"]) == pytest.approx( - 0.00087127, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( + 0.00027824, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.0054462, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( + 0.057450, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.00020555, rel=1e-3 + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_N2"] + ) == pytest.approx(0.050224, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] + ) == pytest.approx(0.00019157, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] + ) == pytest.approx(0.0055542, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_O2"] + ) == pytest.approx(0.0076580, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] + ) == pytest.approx(0.0026406, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( + 0.36984, rel=1e-3 ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_BH"] - ) == pytest.approx(0.010903, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] + ) == pytest.approx(0.020860, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_IC"] + ) == pytest.approx(0.15191, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_BA"] - ) == pytest.approx(0.00078876, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_P"]) == pytest.approx( - 0.0022565, rel=1e-3 + m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] + ) == pytest.approx(0.00038907, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( + 0.013578, rel=1e-3 ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_O"]) == pytest.approx( - 0.000449, rel=1e-3 + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( + 0.012569, rel=1e-3 ) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NO"] - ) == pytest.approx(0.0155, rel=1e-2) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NH"] - ) == pytest.approx(0.00091693, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] + ) == pytest.approx(0.012282, rel=1e-3) assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_ND"] - ) == pytest.approx(0.00064661, rel=1e-3) + m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] + ) == pytest.approx(6.6978e-06, abs=1e-6) assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_ND"] - ) == pytest.approx(1.4159e-5, rel=1e-3) - - @pytest.mark.component - def test_costing(self, system_frame): - m = system_frame - - BSM2.add_costing(m) - m.fs.costing.initialize() - results = BSM2.solve(m) - - assert_optimal_termination(results) - - # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.351097, rel=1e-3) - assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 17443323.82075141, rel=1e-3 - ) - assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 638749.398846816, rel=1e-3 + m.fs.Treated.properties[0].conc_mass_comp["X_PP"] + ) == pytest.approx(0.0040285, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( + 0.00022424, rel=1e-3 ) - @pytest.mark.component - def test_display(self, system_frame): - m = system_frame - BSM2.display_results(m) - BSM2.display_costing(m) - @pytest.mark.requires_idaes_solver @pytest.mark.component - def test_optimization(self, system_frame): + def test_costing(self, system_frame): m = system_frame - BSM2.setup_optimization(system_frame, reactor_volume_equalities=True) - results = BSM2.solve(system_frame) - assert_optimal_termination(results) - assert degrees_of_freedom(system_frame) == 8 # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.349560273, rel=1e-5) + assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 17370674.42102, rel=1e-5 + 24019261.867, rel=1e-3 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 635577.7320509, rel=1e-5 + 830582.94, rel=1e-3 ) diff --git a/watertap/unit_models/anaerobic_digester.py b/watertap/unit_models/anaerobic_digester.py index 177f087f48..13e1a4c793 100644 --- a/watertap/unit_models/anaerobic_digester.py +++ b/watertap/unit_models/anaerobic_digester.py @@ -128,7 +128,6 @@ def variable_scaling_routine( ) # Scaling control volume variables - # TODO: Revisit this scaling factor & the addition of other scaling factors self.scale_variable_by_default( model.liquid_phase.volume[0], overwrite=overwrite ) From 93862e72a28163932e5ef0b51fa7511b0a28409e Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 16 Dec 2024 10:07:58 -0500 Subject: [PATCH 27/46] Update BSM2-P test --- .../test/test_BSM2_P_extension.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index e77125f1df..6d10be5671 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -34,22 +34,20 @@ solver = get_solver() +@pytest.mark.requires_idaes_solver class TestFullFlowsheetBioPFalse: - @pytest.mark.requires_idaes_solver @pytest.fixture(scope="class") def system_frame(self): m, res = main(bio_P=False) m.results = res return m - @pytest.mark.requires_idaes_solver @pytest.mark.integration def test_structure(self, system_frame): assert_units_consistent(system_frame) assert degrees_of_freedom(system_frame) == 0 assert_optimal_termination(system_frame.results) - @pytest.mark.requires_idaes_solver @pytest.mark.component def test_solve(self, system_frame): m = system_frame @@ -112,7 +110,6 @@ def test_solve(self, system_frame): 0.000210879, rel=1e-3 ) - @pytest.mark.requires_idaes_solver @pytest.mark.component def test_costing(self, system_frame): m = system_frame @@ -127,22 +124,20 @@ def test_costing(self, system_frame): ) +@pytest.mark.requires_idaes_solver class TestFullFlowsheetBioPTrue: - @pytest.mark.requires_idaes_solver @pytest.fixture(scope="class") def system_frame(self): m, res = main(bio_P=True) m.results = res return m - @pytest.mark.requires_idaes_solver @pytest.mark.integration def test_structure(self, system_frame): assert_units_consistent(system_frame) assert degrees_of_freedom(system_frame) == 0 assert_optimal_termination(system_frame.results) - @pytest.mark.requires_idaes_solver @pytest.mark.component def test_solve(self, system_frame): m = system_frame @@ -205,7 +200,6 @@ def test_solve(self, system_frame): 0.00022424, rel=1e-3 ) - @pytest.mark.requires_idaes_solver @pytest.mark.component def test_costing(self, system_frame): m = system_frame From 5f467b5d73552e87dfc87abfef1119c95b06120f Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 16 Dec 2024 10:56:06 -0500 Subject: [PATCH 28/46] Use inverseRSS scaling scheme --- .../BSM2_P_extension.py | 32 +++ .../test/test_BSM2_P_extension.py | 216 +++++++++--------- 2 files changed, 140 insertions(+), 108 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index dba2c6a505..63092fc7f7 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -93,6 +93,9 @@ ConstraintScalingScheme, ) +from idaes.core.util import DiagnosticsToolbox +from idaes.core.scaling import report_scaling_factors + # Set up logger _log = idaeslog.getLogger(__name__) @@ -148,6 +151,24 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) + dt = DiagnosticsToolbox(m) + print("---Numerical Issues---") + dt.report_numerical_issues() + # dt.display_variables_with_extreme_jacobians() + # dt.display_constraints_with_extreme_jacobians() + + badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + print("---------------- badly_scaled_var_list ----------------") + for x in badly_scaled_var_list: + print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + # + # print("--- Scaling Factors ---") + # report_scaling_factors(m, descend_into=True) + # + # print("---SVD---") + # svd = dt.prepare_svd_toolbox() + # svd.display_underdetermined_variables_and_constraints() + return m, results @@ -566,6 +587,15 @@ def scale_constraints(m): overwrite=True, ) + iscale.set_scaling_factor( + m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 + ) + scaler.scale_constraint_by_nominal_value( + m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], + scheme=ConstraintScalingScheme.inverseRSS, + overwrite=True, + ) + # Apply scaling scale_variables(m) scale_constraints(m) @@ -994,3 +1024,5 @@ def display_performance_metrics(m): time_point=0, ) print(stream_table_dataframe_to_string(stream_table)) + + m.fs.Treated.display() diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index 6d10be5671..14cdc37416 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -56,58 +56,58 @@ def test_solve(self, system_frame): 0.24219, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( - 6.4300e-07, abs=1e-6 + 4.45317e-06, abs=1e-6 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.000268855, rel=1e-3 + 0.000228743, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( 0.057450, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.051475018, rel=1e-3) + ) == pytest.approx(0.02489943, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.000182758, rel=1e-3) + ) == pytest.approx(0.03105108, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(0.0091486, rel=1e-3) + ) == pytest.approx(8.8672399e-9, abs=1e-6) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.00768617, rel=1e-3) + ) == pytest.approx(0.0077457066, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.634266, rel=1e-3) + ) == pytest.approx(0.76512024, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( - 0.36810, rel=1e-3 + 0.3666941, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.0187295, rel=1e-3) + ) == pytest.approx(0.0182633258, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.152977, rel=1e-3) + ) == pytest.approx(0.150769996, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(0.00043230, rel=1e-3) + ) == pytest.approx(2.9436694e-10, abs=1e-6) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.01333278, rel=1e-3 + 0.0125382, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.012626, rel=1e-3 + 0.01192398, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.00947097, rel=1e-3) + ) == pytest.approx(0.013242906, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] - ) == pytest.approx(1.3866e-06, abs=1e-6) + ) == pytest.approx(6.444066e-06, abs=1e-6) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.0035771, rel=1e-3) + ) == pytest.approx(0.0044143346, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.000210879, rel=1e-3 + 0.00021344778, rel=1e-3 ) @pytest.mark.component @@ -115,100 +115,100 @@ def test_costing(self, system_frame): m = system_frame # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.46694196, rel=1e-3) + assert value(m.fs.costing.LCOW) == pytest.approx(0.46998515597, rel=1e-3) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 23878359.211, rel=1e-3 + 24033233.86016, rel=1e-3 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 825615.28, rel=1e-3 + 831070.835, rel=1e-3 ) -@pytest.mark.requires_idaes_solver -class TestFullFlowsheetBioPTrue: - @pytest.fixture(scope="class") - def system_frame(self): - m, res = main(bio_P=True) - m.results = res - return m - - @pytest.mark.integration - def test_structure(self, system_frame): - assert_units_consistent(system_frame) - assert degrees_of_freedom(system_frame) == 0 - assert_optimal_termination(system_frame.results) - - @pytest.mark.component - def test_solve(self, system_frame): - m = system_frame - - assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( - 0.2422, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( - 6.6530e-07, abs=1e-6 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.00027824, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( - 0.057450, rel=1e-3 - ) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.050224, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.00019157, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(0.0055542, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.0076580, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.0026406, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( - 0.36984, rel=1e-3 - ) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.020860, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.15191, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(0.00038907, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.013578, rel=1e-3 - ) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.012569, rel=1e-3 - ) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.012282, rel=1e-3) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] - ) == pytest.approx(6.6978e-06, abs=1e-6) - assert value( - m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.0040285, rel=1e-3) - assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.00022424, rel=1e-3 - ) - - @pytest.mark.component - def test_costing(self, system_frame): - m = system_frame - - # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) - assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 24019261.867, rel=1e-3 - ) - assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 830582.94, rel=1e-3 - ) +# @pytest.mark.requires_idaes_solver +# class TestFullFlowsheetBioPTrue: +# @pytest.fixture(scope="class") +# def system_frame(self): +# m, res = main(bio_P=True) +# m.results = res +# return m +# +# @pytest.mark.integration +# def test_structure(self, system_frame): +# assert_units_consistent(system_frame) +# assert degrees_of_freedom(system_frame) == 0 +# assert_optimal_termination(system_frame.results) +# +# @pytest.mark.component +# def test_solve(self, system_frame): +# m = system_frame +# +# assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( +# 0.2422, rel=1e-3 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( +# 6.6530e-07, abs=1e-6 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( +# 0.00027824, rel=1e-3 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( +# 0.057450, rel=1e-3 +# ) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_N2"] +# ) == pytest.approx(0.050224, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] +# ) == pytest.approx(0.00019157, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] +# ) == pytest.approx(0.0055542, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_O2"] +# ) == pytest.approx(0.0076580, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] +# ) == pytest.approx(0.0026406, rel=1e-3) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( +# 0.36984, rel=1e-3 +# ) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] +# ) == pytest.approx(0.020860, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["S_IC"] +# ) == pytest.approx(0.15191, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] +# ) == pytest.approx(0.00038907, rel=1e-3) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( +# 0.013578, rel=1e-3 +# ) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( +# 0.012569, rel=1e-3 +# ) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] +# ) == pytest.approx(0.012282, rel=1e-3) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] +# ) == pytest.approx(6.6978e-06, abs=1e-6) +# assert value( +# m.fs.Treated.properties[0].conc_mass_comp["X_PP"] +# ) == pytest.approx(0.0040285, rel=1e-3) +# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( +# 0.00022424, rel=1e-3 +# ) +# +# @pytest.mark.component +# def test_costing(self, system_frame): +# m = system_frame +# +# # check costing +# assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) +# assert value(m.fs.costing.total_capital_cost) == pytest.approx( +# 24019261.867, rel=1e-3 +# ) +# assert value(m.fs.costing.total_operating_cost) == pytest.approx( +# 830582.94, rel=1e-3 +# ) From ac27d2b276e606859f8ecf73d31693936fa67786 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 16 Dec 2024 15:58:36 -0500 Subject: [PATCH 29/46] Add scaling for hydraulic retention time --- .../BSM2_P_extension.py | 44 ++-- .../test/test_BSM2_P_extension.py | 218 +++++++++--------- 2 files changed, 132 insertions(+), 130 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 63092fc7f7..13afa08f70 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -154,20 +154,20 @@ def main(bio_P=False): dt = DiagnosticsToolbox(m) print("---Numerical Issues---") dt.report_numerical_issues() - # dt.display_variables_with_extreme_jacobians() - # dt.display_constraints_with_extreme_jacobians() + dt.display_variables_with_extreme_jacobians() + dt.display_constraints_with_extreme_jacobians() - badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) - print("---------------- badly_scaled_var_list ----------------") - for x in badly_scaled_var_list: - print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + # badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + # print("---------------- badly_scaled_var_list ----------------") + # for x in badly_scaled_var_list: + # print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") # - # print("--- Scaling Factors ---") - # report_scaling_factors(m, descend_into=True) - # - # print("---SVD---") - # svd = dt.prepare_svd_toolbox() - # svd.display_underdetermined_variables_and_constraints() + print("--- Scaling Factors ---") + report_scaling_factors(m, descend_into=True) + + print("---SVD---") + svd = dt.prepare_svd_toolbox() + svd.display_underdetermined_variables_and_constraints() return m, results @@ -586,15 +586,17 @@ def scale_constraints(m): scheme=ConstraintScalingScheme.inverseMinimum, overwrite=True, ) - - iscale.set_scaling_factor( - m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 - ) - scaler.scale_constraint_by_nominal_value( - m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], - scheme=ConstraintScalingScheme.inverseRSS, - overwrite=True, - ) + iscale.set_scaling_factor(m.fs.AD.hydraulic_retention_time[0], 1e-6) + iscale.constraint_scaling_transform(m.fs.AD.AD_retention_time[0], 1e-4) + # + # iscale.set_scaling_factor( + # m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 + # ) + # scaler.scale_constraint_by_nominal_value( + # m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], + # scheme=ConstraintScalingScheme.inverseRSS, + # overwrite=True, + # ) # Apply scaling scale_variables(m) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index 14cdc37416..c281b924d7 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -56,58 +56,58 @@ def test_solve(self, system_frame): 0.24219, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( - 4.45317e-06, abs=1e-6 + 6.43000895e-07, abs=1e-6 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.000228743, rel=1e-3 + 0.0002761026, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( - 0.057450, rel=1e-3 + 0.057450006, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.02489943, rel=1e-3) + ) == pytest.approx(0.05206039, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.03105108, rel=1e-3) + ) == pytest.approx(0.00017686206, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(8.8672399e-9, abs=1e-6) + ) == pytest.approx(0.006048327, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.0077457066, rel=1e-3) + ) == pytest.approx(0.00767668, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.76512024, rel=1e-3) + ) == pytest.approx(0.6526703, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( - 0.3666941, rel=1e-3 + 0.36809786, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.0182633258, rel=1e-3) + ) == pytest.approx(0.0186535, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.150769996, rel=1e-3) + ) == pytest.approx(0.1514789, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(2.9436694e-10, abs=1e-6) + ) == pytest.approx(0.00041597, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.0125382, rel=1e-3 + 0.0133706, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.01192398, rel=1e-3 + 0.0123595, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.013242906, rel=1e-3) + ) == pytest.approx(0.0110773, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] - ) == pytest.approx(6.444066e-06, abs=1e-6) + ) == pytest.approx(5.0389213e-06, abs=1e-6) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.0044143346, rel=1e-3) + ) == pytest.approx(0.00369976, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.00021344778, rel=1e-3 + 0.000215704, rel=1e-3 ) @pytest.mark.component @@ -115,100 +115,100 @@ def test_costing(self, system_frame): m = system_frame # check costing - assert value(m.fs.costing.LCOW) == pytest.approx(0.46998515597, rel=1e-3) + assert value(m.fs.costing.LCOW) == pytest.approx(0.468069088, rel=1e-3) assert value(m.fs.costing.total_capital_cost) == pytest.approx( - 24033233.86016, rel=1e-3 + 23935727.743, rel=1e-3 ) assert value(m.fs.costing.total_operating_cost) == pytest.approx( - 831070.835, rel=1e-3 + 827635.247, rel=1e-3 ) -# @pytest.mark.requires_idaes_solver -# class TestFullFlowsheetBioPTrue: -# @pytest.fixture(scope="class") -# def system_frame(self): -# m, res = main(bio_P=True) -# m.results = res -# return m -# -# @pytest.mark.integration -# def test_structure(self, system_frame): -# assert_units_consistent(system_frame) -# assert degrees_of_freedom(system_frame) == 0 -# assert_optimal_termination(system_frame.results) -# -# @pytest.mark.component -# def test_solve(self, system_frame): -# m = system_frame -# -# assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( -# 0.2422, rel=1e-3 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( -# 6.6530e-07, abs=1e-6 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( -# 0.00027824, rel=1e-3 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( -# 0.057450, rel=1e-3 -# ) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_N2"] -# ) == pytest.approx(0.050224, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] -# ) == pytest.approx(0.00019157, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] -# ) == pytest.approx(0.0055542, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_O2"] -# ) == pytest.approx(0.0076580, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] -# ) == pytest.approx(0.0026406, rel=1e-3) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( -# 0.36984, rel=1e-3 -# ) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] -# ) == pytest.approx(0.020860, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["S_IC"] -# ) == pytest.approx(0.15191, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] -# ) == pytest.approx(0.00038907, rel=1e-3) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( -# 0.013578, rel=1e-3 -# ) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( -# 0.012569, rel=1e-3 -# ) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] -# ) == pytest.approx(0.012282, rel=1e-3) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] -# ) == pytest.approx(6.6978e-06, abs=1e-6) -# assert value( -# m.fs.Treated.properties[0].conc_mass_comp["X_PP"] -# ) == pytest.approx(0.0040285, rel=1e-3) -# assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( -# 0.00022424, rel=1e-3 -# ) -# -# @pytest.mark.component -# def test_costing(self, system_frame): -# m = system_frame -# -# # check costing -# assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) -# assert value(m.fs.costing.total_capital_cost) == pytest.approx( -# 24019261.867, rel=1e-3 -# ) -# assert value(m.fs.costing.total_operating_cost) == pytest.approx( -# 830582.94, rel=1e-3 -# ) +@pytest.mark.requires_idaes_solver +class TestFullFlowsheetBioPTrue: + @pytest.fixture(scope="class") + def system_frame(self): + m, res = main(bio_P=True) + m.results = res + return m + + @pytest.mark.integration + def test_structure(self, system_frame): + assert_units_consistent(system_frame) + assert degrees_of_freedom(system_frame) == 0 + assert_optimal_termination(system_frame.results) + + @pytest.mark.component + def test_solve(self, system_frame): + m = system_frame + + assert value(m.fs.Treated.properties[0].flow_vol) == pytest.approx( + 0.2422, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_A"]) == pytest.approx( + 6.6530e-07, abs=1e-6 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( + 0.00027824, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( + 0.057450, rel=1e-3 + ) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_N2"] + ) == pytest.approx(0.050224, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] + ) == pytest.approx(0.00019157, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] + ) == pytest.approx(0.0055542, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_O2"] + ) == pytest.approx(0.0076580, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] + ) == pytest.approx(0.0026406, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( + 0.36984, rel=1e-3 + ) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] + ) == pytest.approx(0.020860, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["S_IC"] + ) == pytest.approx(0.15191, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] + ) == pytest.approx(0.00038907, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( + 0.013578, rel=1e-3 + ) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( + 0.012569, rel=1e-3 + ) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] + ) == pytest.approx(0.012282, rel=1e-3) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] + ) == pytest.approx(6.6978e-06, abs=1e-6) + assert value( + m.fs.Treated.properties[0].conc_mass_comp["X_PP"] + ) == pytest.approx(0.0040285, rel=1e-3) + assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( + 0.00022424, rel=1e-3 + ) + + @pytest.mark.component + def test_costing(self, system_frame): + m = system_frame + + # check costing + assert value(m.fs.costing.LCOW) == pytest.approx(0.469711, rel=1e-3) + assert value(m.fs.costing.total_capital_cost) == pytest.approx( + 24019261.867, rel=1e-3 + ) + assert value(m.fs.costing.total_operating_cost) == pytest.approx( + 830582.94, rel=1e-3 + ) From b83aded4e565372dca6440ac5ef8ec3dc9d82019 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 16 Dec 2024 16:37:13 -0500 Subject: [PATCH 30/46] Change scaling factor for conc_mass_comp --- .../BSM2_P_extension.py | 5 +++- .../test/test_BSM2_P_extension.py | 30 +++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 13afa08f70..9a61a8972a 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -557,7 +557,10 @@ def scale_variables(m): if "pressure" in var.name: iscale.set_scaling_factor(var, 1e-5) if "conc_mass_comp" in var.name: - iscale.set_scaling_factor(var, 1e1) + if bio_P: + iscale.set_scaling_factor(var, 1e1) + else: + iscale.set_scaling_factor(var, 1e2) if "anions" in var.name: iscale.set_scaling_factor(var, 1e2) if "cations" in var.name: diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index c281b924d7..d5ed022084 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -59,55 +59,55 @@ def test_solve(self, system_frame): 6.43000895e-07, abs=1e-6 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_F"]) == pytest.approx( - 0.0002761026, rel=1e-3 + 0.0002760866, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_I"]) == pytest.approx( 0.057450006, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_N2"] - ) == pytest.approx(0.05206039, rel=1e-3) + ) == pytest.approx(0.0520459, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NH4"] - ) == pytest.approx(0.00017686206, rel=1e-3) + ) == pytest.approx(0.0001769519, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_NO3"] - ) == pytest.approx(0.006048327, rel=1e-3) + ) == pytest.approx(0.006044579, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_O2"] - ) == pytest.approx(0.00767668, rel=1e-3) + ) == pytest.approx(0.00767666, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_PO4"] - ) == pytest.approx(0.6526703, rel=1e-3) + ) == pytest.approx(0.6527876, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["S_K"]) == pytest.approx( - 0.36809786, rel=1e-3 + 0.36809549, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_Mg"] - ) == pytest.approx(0.0186535, rel=1e-3) + ) == pytest.approx(0.01865257, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["S_IC"] - ) == pytest.approx(0.1514789, rel=1e-3) + ) == pytest.approx(0.1507046, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_AUT"] - ) == pytest.approx(0.00041597, rel=1e-3) + ) == pytest.approx(0.000415746, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_H"]) == pytest.approx( - 0.0133706, rel=1e-3 + 0.0133707, rel=1e-3 ) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_I"]) == pytest.approx( - 0.0123595, rel=1e-3 + 0.01235979, rel=1e-3 ) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PAO"] - ) == pytest.approx(0.0110773, rel=1e-3) + ) == pytest.approx(0.0110803, rel=1e-3) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PHA"] ) == pytest.approx(5.0389213e-06, abs=1e-6) assert value( m.fs.Treated.properties[0].conc_mass_comp["X_PP"] - ) == pytest.approx(0.00369976, rel=1e-3) + ) == pytest.approx(0.00370076, rel=1e-3) assert value(m.fs.Treated.properties[0].conc_mass_comp["X_S"]) == pytest.approx( - 0.000215704, rel=1e-3 + 0.0002157186, rel=1e-3 ) @pytest.mark.component From e7758fc2b29d8297960363f721ea357f4de7dd71 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 16 Dec 2024 17:32:45 -0500 Subject: [PATCH 31/46] Clean up flowsheet --- .../BSM2_P_extension.py | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 9a61a8972a..3ebd213e1d 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -93,8 +93,6 @@ ConstraintScalingScheme, ) -from idaes.core.util import DiagnosticsToolbox -from idaes.core.scaling import report_scaling_factors # Set up logger _log = idaeslog.getLogger(__name__) @@ -151,24 +149,6 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) - dt = DiagnosticsToolbox(m) - print("---Numerical Issues---") - dt.report_numerical_issues() - dt.display_variables_with_extreme_jacobians() - dt.display_constraints_with_extreme_jacobians() - - # badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) - # print("---------------- badly_scaled_var_list ----------------") - # for x in badly_scaled_var_list: - # print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") - # - print("--- Scaling Factors ---") - report_scaling_factors(m, descend_into=True) - - print("---SVD---") - svd = dt.prepare_svd_toolbox() - svd.display_underdetermined_variables_and_constraints() - return m, results @@ -591,15 +571,6 @@ def scale_constraints(m): ) iscale.set_scaling_factor(m.fs.AD.hydraulic_retention_time[0], 1e-6) iscale.constraint_scaling_transform(m.fs.AD.AD_retention_time[0], 1e-4) - # - # iscale.set_scaling_factor( - # m.fs.AD.liquid_phase.reactions[0].reaction_rate["R24"], 1e6 - # ) - # scaler.scale_constraint_by_nominal_value( - # m.fs.AD.liquid_phase.reactions[0.0].rate_expression["R24"], - # scheme=ConstraintScalingScheme.inverseRSS, - # overwrite=True, - # ) # Apply scaling scale_variables(m) From 3a549307a68a4903fbcac78cc1e58a60b143c833 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Tue, 17 Dec 2024 09:33:46 -0500 Subject: [PATCH 32/46] Update AD testing --- .../BSM2_P_extension.py | 2 - .../tests/test_modified_adm1_reaction.py | 1 - .../tests/test_anaerobic_digester.py | 114 ++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index 3ebd213e1d..e7e7652884 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -1000,5 +1000,3 @@ def display_performance_metrics(m): time_point=0, ) print(stream_table_dataframe_to_string(stream_table)) - - m.fs.Treated.display() diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py index 5280e342b1..c903c455e9 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py @@ -940,7 +940,6 @@ def model(self): m.fs.unit.liquid_phase.properties_in[0].TSS m.fs.unit.liquid_phase.properties_out[0].TSS - # TODO: Add additional scaling factors? iscale.calculate_scaling_factors(m) iscale.set_scaling_factor(m.fs.unit.liquid_phase.heat, 1e-6) diff --git a/watertap/unit_models/tests/test_anaerobic_digester.py b/watertap/unit_models/tests/test_anaerobic_digester.py index 509afd9c59..946fad3bac 100644 --- a/watertap/unit_models/tests/test_anaerobic_digester.py +++ b/watertap/unit_models/tests/test_anaerobic_digester.py @@ -407,6 +407,63 @@ def test_variable_scaling_routine(self, model): assert sfx_rxn[ model.fs.unit.liquid_phase.reactions[0].reaction_rate["R19"] ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R1"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R2"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R3"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R4"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R5"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R6"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R7"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R8"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R9"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R10"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R11"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R12"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R13"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R14"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R15"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R16"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R17"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R18"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R19"] + ] == pytest.approx(10, rel=1e-8) # Check that unit model has scaling factors sfx_cv = model.fs.unit.liquid_phase.scaling_factor @@ -690,6 +747,63 @@ def test_scale_model(self, model): assert sfx_rxn[ model.fs.unit.liquid_phase.reactions[0].reaction_rate["R19"] ] == pytest.approx(1e2, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R1"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R2"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R3"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R4"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R5"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R6"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R7"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R8"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R9"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R10"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R11"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R12"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R13"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R14"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R15"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R16"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R17"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R18"] + ] == pytest.approx(10, rel=1e-8) + assert sfx_rxn[ + model.fs.unit.liquid_phase.reactions[0.0].I["R19"] + ] == pytest.approx(10, rel=1e-8) assert sfx_rxn[ model.fs.unit.liquid_phase.reactions[0.0].rate_expression["R1"] ] == pytest.approx(100, rel=1e-8) From aa789c040a1504ec4e0a7648e267d7193c1ed2fb Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Sun, 22 Dec 2024 22:43:45 -0500 Subject: [PATCH 33/46] Undo changes to modified ADM1 rxn test --- .../anaerobic_digestion/tests/test_modified_adm1_reaction.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py index c903c455e9..872fabfbc7 100644 --- a/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py +++ b/watertap/property_models/unit_specific/anaerobic_digestion/tests/test_modified_adm1_reaction.py @@ -940,9 +940,6 @@ def model(self): m.fs.unit.liquid_phase.properties_in[0].TSS m.fs.unit.liquid_phase.properties_out[0].TSS - iscale.calculate_scaling_factors(m) - iscale.set_scaling_factor(m.fs.unit.liquid_phase.heat, 1e-6) - return m @pytest.mark.unit From 10c9583c9fea133dc8bf0f7a9b33183fdcb6aa5c Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Sun, 22 Dec 2024 22:59:28 -0500 Subject: [PATCH 34/46] Add check for condition number in BSM2-P test --- .../test/test_BSM2_P_extension.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index d5ed022084..b5451b99e4 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -21,10 +21,14 @@ import pytest -from pyomo.environ import assert_optimal_termination, value +from pyomo.environ import assert_optimal_termination, TransformationFactory, value from pyomo.util.check_units import assert_units_consistent from idaes.core.util.model_statistics import degrees_of_freedom +from idaes.core.util.scaling import ( + get_jacobian, + jacobian_cond, +) from watertap.flowsheets.full_water_resource_recovery_facility.BSM2_P_extension import ( main, @@ -212,3 +216,14 @@ def test_costing(self, system_frame): assert value(m.fs.costing.total_operating_cost) == pytest.approx( 830582.94, rel=1e-3 ) + + @pytest.mark.component + def test_condition_number(self, system_frame): + m = system_frame + + # Check condition number to confirm scaling + sm = TransformationFactory("core.scale_model").create_using(m, rename=False) + jac, _ = get_jacobian(sm, scaled=False) + assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( + 4.329094e18, rel=1e-3 + ) From 7c8becb7f9bd2cdd9264d55a92db5ef4c7bdf84b Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Sun, 22 Dec 2024 23:25:30 -0500 Subject: [PATCH 35/46] Update condition number test --- .../test/test_BSM2_P_extension.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index b5451b99e4..027208c247 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -127,6 +127,16 @@ def test_costing(self, system_frame): 827635.247, rel=1e-3 ) + @pytest.mark.component + def test_condition_number(self, system_frame): + m = system_frame + + # Check condition number to confirm scaling + jac, _ = get_jacobian(m, scaled=False) + assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( + 2.36720268e18, rel=1e-3 + ) + @pytest.mark.requires_idaes_solver class TestFullFlowsheetBioPTrue: @@ -222,8 +232,7 @@ def test_condition_number(self, system_frame): m = system_frame # Check condition number to confirm scaling - sm = TransformationFactory("core.scale_model").create_using(m, rename=False) - jac, _ = get_jacobian(sm, scaled=False) + jac, _ = get_jacobian(m, scaled=False) assert (jacobian_cond(jac=jac, scaled=False)) == pytest.approx( - 4.329094e18, rel=1e-3 + 3.208168033e18, rel=1e-3 ) From 7681d4bd9caa400e2699239d5f8d2528627295a3 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 23 Dec 2024 09:14:25 -0500 Subject: [PATCH 36/46] Address pylint issue --- .../test/test_BSM2_P_extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py index 027208c247..59141dc431 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/test/test_BSM2_P_extension.py @@ -21,7 +21,7 @@ import pytest -from pyomo.environ import assert_optimal_termination, TransformationFactory, value +from pyomo.environ import assert_optimal_termination, value from pyomo.util.check_units import assert_units_consistent from idaes.core.util.model_statistics import degrees_of_freedom From 9f11bfc944b85f4e6b1e8164bec65aa56cfa1031 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 2 Jan 2025 09:57:40 -0500 Subject: [PATCH 37/46] Add CSTR scalers --- watertap/unit_models/cstr.py | 107 +++++++++++++++++++++ watertap/unit_models/cstr_injection.py | 123 ++++++++++++++++++++++++- 2 files changed, 228 insertions(+), 2 deletions(-) diff --git a/watertap/unit_models/cstr.py b/watertap/unit_models/cstr.py index d77ebb8b7d..e5da6b7a7a 100644 --- a/watertap/unit_models/cstr.py +++ b/watertap/unit_models/cstr.py @@ -21,6 +21,7 @@ from idaes.models.unit_models.cstr import CSTRData as CSTRIDAESData import idaes.logger as idaeslog +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from pyomo.environ import ( Constraint, @@ -38,12 +39,118 @@ _log = idaeslog.getLogger(__name__) +class CSTRScaler(CustomScalerBase): + """ + Default modular scaler for CSTR. + + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "volume": 1e-3, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.control_volume.properties_out, + source_state=model.control_volume.properties_in, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.reactions, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scaling control volume variables + self.scale_variable_by_default( + model.control_volume.volume[0], overwrite=overwrite + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.reactions, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + if hasattr(model, "CSTR_retention_time"): + self.scale_constraint_by_nominal_value( + model.CSTR_retention_time[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("CSTR") class CSTRData(CSTRIDAESData): """ CSTR unit block for BSM2 """ + default_scaler = CSTRScaler + CONFIG = CSTRIDAESData.CONFIG() def build(self): diff --git a/watertap/unit_models/cstr_injection.py b/watertap/unit_models/cstr_injection.py index 05e751e1a2..dea4fa6b35 100644 --- a/watertap/unit_models/cstr_injection.py +++ b/watertap/unit_models/cstr_injection.py @@ -36,6 +36,7 @@ UnitModelBlockData, useDefault, ) +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from idaes.core.util.config import ( is_physical_parameter_block, is_reaction_parameter_block, @@ -49,8 +50,6 @@ __author__ = "Andrew Lee, Adam Atia, Vibhav Dabadghao" -from enum import Enum, auto - class ElectricityConsumption(Enum): """ @@ -64,12 +63,132 @@ class ElectricityConsumption(Enum): calculated = auto() +class CSTR_InjectionScaler(CustomScalerBase): + """ + Default modular scaler for CSTR with injection. + + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "volume": 1e-3, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.control_volume.properties_out, + source_state=model.control_volume.properties_in, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.reactions, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scaling control volume variables + self.scale_variable_by_default( + model.control_volume.volume[0], overwrite=overwrite + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.reactions, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + if hasattr(model, "eq_hydraulic_retention_time"): + self.scale_constraint_by_nominal_value( + model.eq_hydraulic_retention_time[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + if hasattr(model, "eq_mass_transfer"): + self.scale_constraint_by_nominal_value( + model.eq_mass_transfer[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + if hasattr(model, "eq_electricity_consumption"): + self.scale_constraint_by_nominal_value( + model.eq_electricity_consumption[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("CSTR_Injection") class CSTR_InjectionData(InitializationMixin, UnitModelBlockData): """ CSTR Unit Model with Injection Class """ + default_scaler = CSTR_InjectionScaler + CONFIG = UnitModelBlockData.CONFIG() CONFIG.declare( From 534ccdd177c3ab6a4ad19412da8f9aab7fcfe675 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 2 Jan 2025 09:58:24 -0500 Subject: [PATCH 38/46] Add clarifier scaling --- watertap/unit_models/clarifier.py | 107 +++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/watertap/unit_models/clarifier.py b/watertap/unit_models/clarifier.py index a5f60261f3..1081aa93da 100644 --- a/watertap/unit_models/clarifier.py +++ b/watertap/unit_models/clarifier.py @@ -22,8 +22,10 @@ from idaes.core.util.tables import create_stream_table_dataframe import idaes.logger as idaeslog +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from pyomo.environ import ( + Constraint, Var, Param, units as pyunits, @@ -40,12 +42,115 @@ _log = idaeslog.getLogger(__name__) +class ClarifierScaler(CustomScalerBase): + """ + Default modular scaler for the clarifier unit model. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "surface_area": 1e-3, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.mixed_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.underflow_state, + source_state=model.mixed_state, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.effluent_state, + source_state=model.mixed_state, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.underflow_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.effluent_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level variables + self.scale_variable_by_default(model.surface_area, overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.mixed_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.underflow_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.effluent_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("Clarifier") class ClarifierData(SeparatorData): """ - Thickener unit model for BSM2 + Clarifier unit model for BSM2 """ + default_scaler = ClarifierScaler + CONFIG = SeparatorData.CONFIG() CONFIG.outlet_list = ["underflow", "overflow"] CONFIG.split_basis = SplittingType.componentFlow From beb4d7dac6deeba49b273fce62ee997c3387cf4f Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 2 Jan 2025 09:59:40 -0500 Subject: [PATCH 39/46] Add dewaterer/thickener scalers --- watertap/unit_models/dewatering.py | 107 ++++++++++++++++++++++++++++- watertap/unit_models/thickener.py | 105 ++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 1 deletion(-) diff --git a/watertap/unit_models/dewatering.py b/watertap/unit_models/dewatering.py index a5c14b00b2..bdae935210 100644 --- a/watertap/unit_models/dewatering.py +++ b/watertap/unit_models/dewatering.py @@ -34,16 +34,18 @@ import idaes.logger as idaeslog from pyomo.environ import ( + Constraint, Param, - units as pyunits, Var, NonNegativeReals, + units as pyunits, ) from pyomo.common.config import ConfigValue, In from idaes.core.util.exceptions import ( ConfigurationError, ) +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from watertap.costing.unit_models.dewatering import cost_dewatering __author__ = "Alejandro Garciadiego, Adam Atia" @@ -65,12 +67,115 @@ class ActivatedSludgeModelType(Enum): modified_ASM2D = auto() +class DewatererScaler(CustomScalerBase): + """ + Default modular scaler for the dewatering unit model. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "volume": 1e-3, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.mixed_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.underflow_state, + source_state=model.mixed_state, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.overflow_state, + source_state=model.mixed_state, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.underflow_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.overflow_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level variables + self.scale_variable_by_default(model.volume[0], overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.mixed_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.underflow_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.overflow_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("DewateringUnit") class DewateringData(SeparatorData): """ Dewatering unit block for BSM2 """ + default_scaler = DewatererScaler + CONFIG = SeparatorData.CONFIG() CONFIG.outlet_list = ["underflow", "overflow"] CONFIG.split_basis = SplittingType.componentFlow diff --git a/watertap/unit_models/thickener.py b/watertap/unit_models/thickener.py index 6d3f818f37..c244c52aa7 100644 --- a/watertap/unit_models/thickener.py +++ b/watertap/unit_models/thickener.py @@ -34,6 +34,7 @@ import idaes.logger as idaeslog from pyomo.environ import ( + Constraint, Param, Var, NonNegativeReals, @@ -44,6 +45,7 @@ from idaes.core.util.exceptions import ( ConfigurationError, ) +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from watertap.costing.unit_models.thickener import cost_thickener __author__ = "Alejandro Garciadiego, Adam Atia" @@ -65,12 +67,115 @@ class ActivatedSludgeModelType(Enum): modified_ASM2D = auto() +class ThickenerScaler(CustomScalerBase): + """ + Default modular scaler for the thickener unit model. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "volume": 1e-3, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.mixed_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.underflow_state, + source_state=model.mixed_state, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.overflow_state, + source_state=model.mixed_state, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.underflow_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.overflow_state, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level variables + self.scale_variable_by_default(model.volume[0], overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.mixed_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.underflow_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.overflow_state, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("Thickener") class ThickenerData(SeparatorData): """ Thickener unit model for BSM2 """ + default_scaler = ThickenerScaler + CONFIG = SeparatorData.CONFIG() CONFIG.outlet_list = ["underflow", "overflow"] CONFIG.split_basis = SplittingType.componentFlow From f791e586feb1aa5c4459be640c7eb6bb805e56b3 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 2 Jan 2025 10:02:14 -0500 Subject: [PATCH 40/46] Add translator scalers --- .../translators/translator_adm1_asm1.py | 77 ++++++++++++++++++ .../translators/translator_adm1_asm2d.py | 78 +++++++++++++++++++ .../translators/translator_asm1_adm1.py | 77 ++++++++++++++++++ .../translators/translator_asm2d_adm1.py | 76 ++++++++++++++++++ 4 files changed, 308 insertions(+) diff --git a/watertap/unit_models/translators/translator_adm1_asm1.py b/watertap/unit_models/translators/translator_adm1_asm1.py index 712b22bd1f..709794af06 100644 --- a/watertap/unit_models/translators/translator_adm1_asm1.py +++ b/watertap/unit_models/translators/translator_adm1_asm1.py @@ -35,10 +35,12 @@ from watertap.core.solvers import get_solver import idaes.logger as idaeslog import idaes.core.util.scaling as iscale +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from idaes.core.util.exceptions import InitializationError from pyomo.environ import ( + Constraint, Param, units as pyunits, check_optimal_termination, @@ -52,12 +54,87 @@ _log = idaeslog.getLogger(__name__) +class ADM1ASM1Scaler(CustomScalerBase): + """ + Default modular scaler for the ADM1-ASM1 translator block. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("Translator_ADM1_ASM1") class TranslatorDataADM1ASM1(TranslatorData): """ Translator block representing the ADM1/ASM1 interface """ + default_scaler = ADM1ASM1Scaler + CONFIG = TranslatorData.CONFIG() CONFIG.declare( "reaction_package", diff --git a/watertap/unit_models/translators/translator_adm1_asm2d.py b/watertap/unit_models/translators/translator_adm1_asm2d.py index 410921792a..54815872f3 100644 --- a/watertap/unit_models/translators/translator_adm1_asm2d.py +++ b/watertap/unit_models/translators/translator_adm1_asm2d.py @@ -37,9 +37,13 @@ import idaes.logger as idaeslog import idaes.core.util.scaling as iscale +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme + + from idaes.core.util.exceptions import InitializationError from pyomo.environ import ( + Constraint, units as pyunits, check_optimal_termination, Set, @@ -53,12 +57,86 @@ _log = idaeslog.getLogger(__name__) +class ADM1ASM2dScaler(CustomScalerBase): + """ + Default modular scaler for ADM1-ASM2d translator block. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("Translator_ADM1_ASM2D") class TranslatorDataADM1ASM2D(TranslatorData): """ Translator block representing the ADM1/ASM2D interface """ + default_scaler = ADM1ASM2dScaler + CONFIG = TranslatorData.CONFIG() CONFIG.declare( diff --git a/watertap/unit_models/translators/translator_asm1_adm1.py b/watertap/unit_models/translators/translator_asm1_adm1.py index a3e0f084ba..6f620dd269 100644 --- a/watertap/unit_models/translators/translator_asm1_adm1.py +++ b/watertap/unit_models/translators/translator_asm1_adm1.py @@ -36,8 +36,10 @@ from watertap.core.solvers import get_solver import idaes.logger as idaeslog import idaes.core.util.scaling as iscale +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from pyomo.environ import ( + Constraint, Param, NonNegativeReals, Var, @@ -56,12 +58,87 @@ _log = idaeslog.getLogger(__name__) +class ASM1ADM1Scaler(CustomScalerBase): + """ + Default modular scaler for the ASM1-ADM1 translator block. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("Translator_ASM1_ADM1") class TranslatorDataASM1ADM1(TranslatorData): """ Translator block representing the ASM1/ADM1 interface """ + default_scaler = ASM1ADM1Scaler + CONFIG = TranslatorData.CONFIG() CONFIG.declare( "reaction_package", diff --git a/watertap/unit_models/translators/translator_asm2d_adm1.py b/watertap/unit_models/translators/translator_asm2d_adm1.py index 7a114ae72f..921cf3b805 100644 --- a/watertap/unit_models/translators/translator_asm2d_adm1.py +++ b/watertap/unit_models/translators/translator_asm2d_adm1.py @@ -35,8 +35,10 @@ from idaes.core.util.model_statistics import degrees_of_freedom from watertap.core.solvers import get_solver import idaes.logger as idaeslog +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from pyomo.environ import ( + Constraint, Param, units as pyunits, check_optimal_termination, @@ -54,12 +56,86 @@ _log = idaeslog.getLogger(__name__) +class ASM2dADM1Scaler(CustomScalerBase): + """ + Default modular scaler for ASM2d-ADM1 translator block. + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + for c in model.component_data_objects(Constraint, descend_into=False): + self.scale_constraint_by_nominal_value( + c, + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("Translator_ASM2d_ADM1") class TranslatorDataASM2dADM1(TranslatorData): """ Translator block representing the ASM2d/ADM1 interface """ + default_scaler = ASM2dADM1Scaler + CONFIG = TranslatorData.CONFIG() # TODO: Change the default to False From e98c7bbb3285df2e73178e0e5b3404e31c1c668d Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 2 Jan 2025 10:58:10 -0500 Subject: [PATCH 41/46] Add config option for scaling routine to BSM2 --- .../BSM2.py | 96 +++++++++++++------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py index 9d5fb13457..d587c300c3 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py @@ -23,22 +23,32 @@ from pyomo.network import Arc, SequentialDecomposition from watertap.unit_models.anaerobic_digester import AD -from watertap.unit_models.thickener import Thickener -from watertap.unit_models.dewatering import DewateringUnit -from watertap.unit_models.cstr import CSTR -from watertap.unit_models.clarifier import Clarifier - -from watertap.unit_models.translators.translator_asm1_adm1 import Translator_ASM1_ADM1 -from watertap.unit_models.translators.translator_adm1_asm1 import Translator_ADM1_ASM1 +from watertap.unit_models.thickener import Thickener, ThickenerScaler +from watertap.unit_models.dewatering import DewateringUnit, DewatererScaler +from watertap.unit_models.cstr import CSTR, CSTRScaler +from watertap.unit_models.cstr_injection import CSTR_InjectionScaler +from watertap.unit_models.clarifier import Clarifier, ClarifierScaler + +from watertap.unit_models.translators.translator_asm1_adm1 import ( + Translator_ASM1_ADM1, + ASM1ADM1Scaler, +) +from watertap.unit_models.translators.translator_adm1_asm1 import ( + Translator_ADM1_ASM1, + ADM1ASM1Scaler, +) import idaes.logger as idaeslog from watertap.core.solvers import get_solver import idaes.core.util.scaling as iscale +from idaes.core.util import DiagnosticsToolbox from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties import ( ADM1ParameterBlock, + ADM1PropertiesScaler, ) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_reactions import ( ADM1ReactionParameterBlock, + ADM1ReactionScaler, ) from idaes.models.unit_models.mixer import MomentumMixingType from idaes.models.unit_models.separator import SplittingType @@ -71,19 +81,23 @@ from pyomo.util.check_units import assert_units_consistent -def main(reactor_volume_equalities=False): +def main(reactor_volume_equalities=False, scalers=True): m = build() set_operating_conditions(m) + dt = DiagnosticsToolbox(m) + print("---Structural Issues---") + dt.report_structural_issues() assert_degrees_of_freedom(m, 0) assert_units_consistent(m) + scale_system(m, scalers=scalers) initialize_system(m) assert_degrees_of_freedom(m, 0) results = solve(m) - add_costing(m) + add_costing(m, scalers=scalers) m.fs.costing.initialize() assert_degrees_of_freedom(m, 0) @@ -101,6 +115,8 @@ def main(reactor_volume_equalities=False): # display_results(m) display_costing(m) display_performance_metrics(m) + print("---Numerical Issues---") + dt.report_numerical_issues() return m, results @@ -385,17 +401,34 @@ def set_operating_conditions(m): for mx in m.mixers: mx.pressure_equality_constraints[0.0, 2].deactivate() - for var in m.fs.component_data_objects(pyo.Var, descend_into=True): - if "flow_vol" in var.name: - iscale.set_scaling_factor(var, 1e1) - if "temperature" in var.name: - iscale.set_scaling_factor(var, 1e-1) - if "pressure" in var.name: - iscale.set_scaling_factor(var, 1e-6) - if "conc_mass_comp" in var.name: - iscale.set_scaling_factor(var, 1e1) - iscale.calculate_scaling_factors(m) +def scale_system(m, scalers=True): + if scalers: + pass + # unit_scaler = UnitScaler() + # unit_scaler.scale_model( + # m.fs.unit, + # submodel_scalers={ + # m.fs.unit.control_volume.properties_in: ADM1PropertiesScaler, + # m.fs.unit.control_volume.properties_out: ASM1PropertiesScaler, + # m.fs.unit.properties_in: ADM1PropertiesScaler, + # m.fs.unit.properties_out: ASM1PropertiesScaler, + # m.fs.unit.mixed_state: ADM1PropertiesScaler, + # m.fs.unit.underflow_state: ADM1PropertiesScaler, + # }, + # ) + else: + for var in m.fs.component_data_objects(pyo.Var, descend_into=True): + if "flow_vol" in var.name: + iscale.set_scaling_factor(var, 1e1) + if "temperature" in var.name: + iscale.set_scaling_factor(var, 1e-1) + if "pressure" in var.name: + iscale.set_scaling_factor(var, 1e-6) + if "conc_mass_comp" in var.name: + iscale.set_scaling_factor(var, 1e1) + + iscale.calculate_scaling_factors(m) def initialize_system(m): @@ -470,7 +503,7 @@ def function(unit): mx.pressure_equality_constraints[0.0, 2].deactivate() -def add_costing(m): +def add_costing(m, scalers=True): m.fs.costing = WaterTAPCosting() m.fs.costing.base_currency = pyo.units.USD_2020 @@ -503,15 +536,24 @@ def add_costing(m): m.fs.costing.add_specific_energy_consumption(m.fs.FeedWater.properties[0].flow_vol) m.fs.objective = pyo.Objective(expr=m.fs.costing.LCOW) - iscale.set_scaling_factor(m.fs.costing.LCOW, 1e3) - iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) - for block in m.fs.component_objects(pyo.Block, descend_into=True): - if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): - iscale.set_scaling_factor(block.costing.capital_cost, 1e-6) + if scalers: + pass + else: + + iscale.set_scaling_factor(m.fs.costing.LCOW, 1e3) + iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) + + for block in m.fs.component_objects(pyo.Block, descend_into=True): + if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): + iscale.set_scaling_factor(block.costing.capital_cost, 1e-6) - iscale.constraint_scaling_transform(m.fs.DU.costing.capital_cost_constraint, 1e-6) - iscale.constraint_scaling_transform(m.fs.RADM.costing.capital_cost_constraint, 1e-6) + iscale.constraint_scaling_transform( + m.fs.DU.costing.capital_cost_constraint, 1e-6 + ) + iscale.constraint_scaling_transform( + m.fs.RADM.costing.capital_cost_constraint, 1e-6 + ) def setup_optimization(m, reactor_volume_equalities=False): From 6b0957e71df50dda6e75b744588831b81a0936f1 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 2 Jan 2025 13:05:45 -0500 Subject: [PATCH 42/46] Add aeration tank scaler --- watertap/unit_models/aeration_tank.py | 121 ++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/watertap/unit_models/aeration_tank.py b/watertap/unit_models/aeration_tank.py index f806a98b8d..5531eaba9b 100644 --- a/watertap/unit_models/aeration_tank.py +++ b/watertap/unit_models/aeration_tank.py @@ -20,6 +20,7 @@ from idaes.core import ( declare_process_block_class, ) +from idaes.core.scaling import CustomScalerBase, ConstraintScalingScheme from watertap.unit_models.cstr_injection import ( @@ -30,12 +31,132 @@ __author__ = "Adam Atia" +class AerationTankScaler(CustomScalerBase): + """ + Default modular scaler for the aeration tank unit model. + + This Scaler relies on the associated property and reaction packages, + either through user provided options (submodel_scalers argument) or by default + Scalers assigned to the packages. + """ + + DEFAULT_SCALING_FACTORS = { + "volume": 1e-3, + } + + def variable_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to variables in model. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_in, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.propagate_state_scaling( + target_state=model.control_volume.properties_out, + source_state=model.control_volume.properties_in, + overwrite=overwrite, + ) + + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_out, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.reactions, + method="variable_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scaling control volume variables + self.scale_variable_by_default( + model.control_volume.volume[0], overwrite=overwrite + ) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + """ + Routine to apply scaling factors to constraints in model. + + Submodel Scalers are called for the property and reaction blocks. All other constraints + are scaled using the inverse maximum scheme. + + Args: + model: model to be scaled + overwrite: whether to overwrite existing scaling factors + submodel_scalers: dict of Scalers to use for sub-models, keyed by submodel local name + + Returns: + None + """ + # Call scaling methods for sub-models + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_in, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.properties_out, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + self.call_submodel_scaler_method( + submodel=model.control_volume.reactions, + method="constraint_scaling_routine", + submodel_scalers=submodel_scalers, + overwrite=overwrite, + ) + + # Scale unit level constraints + if hasattr(model, "eq_hydraulic_retention_time"): + self.scale_constraint_by_nominal_value( + model.eq_hydraulic_retention_time[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + if hasattr(model, "eq_mass_transfer"): + self.scale_constraint_by_nominal_value( + model.eq_mass_transfer[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + if hasattr(model, "eq_electricity_consumption"): + self.scale_constraint_by_nominal_value( + model.eq_electricity_consumption[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=overwrite, + ) + + @declare_process_block_class("AerationTank") class AerationTankData(CSTR_InjectionData): """ CSTR Unit Model with Injection Class """ + default_scaler = AerationTankScaler + CONFIG = CSTR_InjectionData.CONFIG() CONFIG.electricity_consumption = ElectricityConsumption.calculated CONFIG.get("has_aeration")._domain = In([True]) From 93c5410048df22e7c33e47a8241c924ef97847a1 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 6 Jan 2025 10:59:49 -0500 Subject: [PATCH 43/46] Preliminary version of BSM2 with scalers --- .../BSM2.py | 190 ++++++++++++++++-- 1 file changed, 172 insertions(+), 18 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py index d587c300c3..8bc736c326 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py @@ -22,11 +22,10 @@ import pyomo.environ as pyo from pyomo.network import Arc, SequentialDecomposition -from watertap.unit_models.anaerobic_digester import AD +from watertap.unit_models.anaerobic_digester import AD, ADScaler from watertap.unit_models.thickener import Thickener, ThickenerScaler from watertap.unit_models.dewatering import DewateringUnit, DewatererScaler from watertap.unit_models.cstr import CSTR, CSTRScaler -from watertap.unit_models.cstr_injection import CSTR_InjectionScaler from watertap.unit_models.clarifier import Clarifier, ClarifierScaler from watertap.unit_models.translators.translator_asm1_adm1 import ( @@ -42,6 +41,11 @@ from watertap.core.solvers import get_solver import idaes.core.util.scaling as iscale from idaes.core.util import DiagnosticsToolbox +from idaes.core.scaling.scaling_base import ScalerBase +from idaes.core.scaling.custom_scaler_base import ( + CustomScalerBase, + ConstraintScalingScheme, +) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties import ( ADM1ParameterBlock, ADM1PropertiesScaler, @@ -65,12 +69,18 @@ Product, ) -from watertap.unit_models.aeration_tank import AerationTank, ElectricityConsumption +from watertap.unit_models.aeration_tank import ( + AerationTank, + AerationTankScaler, + ElectricityConsumption, +) from watertap.property_models.unit_specific.activated_sludge.asm1_properties import ( ASM1ParameterBlock, + ASM1PropertiesScaler, ) from watertap.property_models.unit_specific.activated_sludge.asm1_reactions import ( ASM1ReactionParameterBlock, + ASM1ReactionScaler, ) from watertap.core.util.initialization import assert_degrees_of_freedom from watertap.costing import WaterTAPCosting @@ -93,6 +103,11 @@ def main(reactor_volume_equalities=False, scalers=True): scale_system(m, scalers=scalers) initialize_system(m) + # badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + # print("---------------- badly_scaled_var_list ----------------") + # for x in badly_scaled_var_list: + # print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + assert_degrees_of_freedom(m, 0) results = solve(m) @@ -115,8 +130,17 @@ def main(reactor_volume_equalities=False, scalers=True): # display_results(m) display_costing(m) display_performance_metrics(m) + + badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + print("---------------- badly_scaled_var_list ----------------") + for x in badly_scaled_var_list: + print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + print("---Numerical Issues---") dt.report_numerical_issues() + dt.display_variables_at_or_outside_bounds() + dt.display_variables_with_extreme_jacobians() + dt.display_constraints_with_extreme_jacobians() return m, results @@ -404,19 +428,140 @@ def set_operating_conditions(m): def scale_system(m, scalers=True): if scalers: - pass - # unit_scaler = UnitScaler() - # unit_scaler.scale_model( - # m.fs.unit, - # submodel_scalers={ - # m.fs.unit.control_volume.properties_in: ADM1PropertiesScaler, - # m.fs.unit.control_volume.properties_out: ASM1PropertiesScaler, - # m.fs.unit.properties_in: ADM1PropertiesScaler, - # m.fs.unit.properties_out: ASM1PropertiesScaler, - # m.fs.unit.mixed_state: ADM1PropertiesScaler, - # m.fs.unit.underflow_state: ADM1PropertiesScaler, - # }, + sb = ScalerBase() + csb = CustomScalerBase() + + ad_scaler = ADScaler() + ad_scaler.scale_model( + m.fs.RADM, + submodel_scalers={ + m.fs.RADM.liquid_phase.properties_in: ADM1PropertiesScaler, + m.fs.RADM.liquid_phase.properties_out: ADM1PropertiesScaler, + m.fs.RADM.liquid_phase.reactions: ADM1ReactionScaler, + }, + ) + sb.set_variable_scaling_factor(m.fs.RADM.hydraulic_retention_time[0], 1e-6) + # sb.set_variable_scaling_factor(m.fs.RADM.liquid_phase.properties_in[0].flow_vol, 1e3, overwrite=True) + # sb.set_variable_scaling_factor(m.fs.RADM.liquid_phase.properties_out[0].flow_vol, 1e3, overwrite=True) + # sb.set_variable_scaling_factor(m.fs.RADM.KH_h2[0], 1e4) + # sb.set_variable_scaling_factor(m.fs.RADM.KH_h2[0], 1e3) + # sb.set_variable_scaling_factor(m.fs.RADM.KH_h2[0], 1e2) + # sb.set_variable_scaling_factor(m.fs.RADM.KH_ch4[0], 1e3) + # sb.set_variable_scaling_factor(m.fs.RADM.KH_ch4[0], 1e2) + # sb.set_variable_scaling_factor(m.fs.RADM.volume_AD[0], 1e3) + # sb.set_variable_scaling_factor(m.fs.RADM.vapor_phase[0].pressure, 1e-5, overwrite=True) + # sb.set_variable_scaling_factor(m.fs.RADM.vapor_phase[0].pressure, 1e-6, overwrite=True) + # sb.set_variable_scaling_factor(m.fs.RADM.liquid_phase.heat[0], 1e3) + # sb.set_variable_scaling_factor(m.fs.RADM.liquid_phase.heat[0], 1e2) + + cstr_list = [m.fs.R1, m.fs.R2] + cstr_scaler = CSTRScaler() + for unit in cstr_list: + cstr_scaler.scale_model( + unit, + submodel_scalers={ + unit.control_volume.properties_in: ASM1PropertiesScaler, + unit.control_volume.properties_out: ASM1PropertiesScaler, + unit.control_volume.reactions: ASM1ReactionScaler, + }, + ) + sb.set_variable_scaling_factor(unit.hydraulic_retention_time[0], 1e-2) + + aeration_list = [m.fs.R3, m.fs.R4, m.fs.R5] + aeration_scaler = AerationTankScaler() + for unit in aeration_list: + aeration_scaler.scale_model( + unit, + submodel_scalers={ + unit.control_volume.properties_in: ASM1PropertiesScaler, + unit.control_volume.properties_out: ASM1PropertiesScaler, + unit.control_volume.reactions: ASM1ReactionScaler, + }, + ) + + clarifier_list = [m.fs.CL, m.fs.CL1] + clarifier_scaler = ClarifierScaler() + for unit in clarifier_list: + clarifier_scaler.scale_model( + unit, + submodel_scalers={ + unit.mixed_state: ASM1PropertiesScaler, + unit.underflow_state: ASM1PropertiesScaler, + unit.effluent_state: ASM1PropertiesScaler, + }, + ) + + thickener_scaler = ThickenerScaler() + thickener_scaler.scale_model( + m.fs.TU, + submodel_scalers={ + m.fs.TU.mixed_state: ASM1PropertiesScaler, + m.fs.TU.underflow_state: ASM1PropertiesScaler, + m.fs.TU.overflow_state: ASM1ReactionScaler, + }, + ) + + dewaterer_scaler = DewatererScaler() + dewaterer_scaler.scale_model( + m.fs.DU, + submodel_scalers={ + m.fs.DU.mixed_state: ASM1PropertiesScaler, + m.fs.DU.underflow_state: ASM1PropertiesScaler, + m.fs.DU.overflow_state: ASM1ReactionScaler, + }, + ) + sb.set_variable_scaling_factor(m.fs.DU.volume[0], 1, overwrite=True) + + as_ad_scaler = ASM1ADM1Scaler() + as_ad_scaler.scale_model( + m.fs.asm_adm, + submodel_scalers={ + m.fs.asm_adm.properties_in: ASM1PropertiesScaler, + m.fs.asm_adm.properties_out: ADM1PropertiesScaler, + }, + ) + + ad_as_scaler = ADM1ASM1Scaler() + ad_as_scaler.scale_model( + m.fs.adm_asm, + submodel_scalers={ + m.fs.adm_asm.properties_in: ADM1PropertiesScaler, + m.fs.adm_asm.properties_out: ASM1PropertiesScaler, + }, + ) + + for var in m.fs.component_data_objects(pyo.Var, descend_into=True): + # if "flow_vol" in var.name: + # sb.set_variable_scaling_factor(var, 1e2, overwrite=True) + if "flow_vol" in var.name: + sb.set_variable_scaling_factor(var, 1e2) + if "temperature" in var.name: + sb.set_variable_scaling_factor(var, 1e-2) + if "pressure" in var.name: + sb.set_variable_scaling_factor(var, 1e-5) + if "conc_mass_comp" in var.name: + sb.set_variable_scaling_factor(var, 1e3) + # if "cation" in var.name: + # sb.set_variable_scaling_factor(var, 1e3) + if "conc_mol" in var.name: + sb.set_variable_scaling_factor(var, 1e2) + if "alkalinity" in var.name: + sb.set_variable_scaling_factor(var, 1e3) + + # for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): + # if "flow_vol_equality" in c.name: + # csb.scale_constraint_by_nominal_value( + # c, + # scheme=ConstraintScalingScheme.inverseMaximum, + # overwrite=True, + # ) + + # csb.scale_constraint_by_nominal_value( + # m.fs.RADM.AD_retention_time[0], + # scheme=ConstraintScalingScheme.inverseMaximum, + # overwrite=True, # ) + else: for var in m.fs.component_data_objects(pyo.Var, descend_into=True): if "flow_vol" in var.name: @@ -538,9 +683,18 @@ def add_costing(m, scalers=True): m.fs.objective = pyo.Objective(expr=m.fs.costing.LCOW) if scalers: - pass - else: + sb = ScalerBase() + sb.set_variable_scaling_factor(m.fs.costing.total_capital_cost, 1e-7) + + for block in m.fs.component_objects(pyo.Block, descend_into=True): + if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): + sb.set_variable_scaling_factor(block.costing.capital_cost, 1e-6) + sb.set_constraint_scaling_factor(m.fs.DU.costing.capital_cost_constraint, 1e-6) + sb.set_constraint_scaling_factor( + m.fs.RADM.costing.capital_cost_constraint, 1e-6 + ) + else: iscale.set_scaling_factor(m.fs.costing.LCOW, 1e3) iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) @@ -796,4 +950,4 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main() + m, results = main(scalers=True) From 0f256080f18a977f20c493e65e3b8c8ae7fcc00e Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Wed, 8 Jan 2025 15:01:00 -0500 Subject: [PATCH 44/46] BSM2-P solving to acceptable level --- .../BSM2.py | 31 +- .../BSM2_P_extension.py | 279 +++++++++++++++--- 2 files changed, 250 insertions(+), 60 deletions(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py index 8bc736c326..e42140b007 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py @@ -91,7 +91,7 @@ from pyomo.util.check_units import assert_units_consistent -def main(reactor_volume_equalities=False, scalers=True): +def main(reactor_volume_equalities=False, has_scalers=True): m = build() set_operating_conditions(m) dt = DiagnosticsToolbox(m) @@ -100,7 +100,7 @@ def main(reactor_volume_equalities=False, scalers=True): assert_degrees_of_freedom(m, 0) assert_units_consistent(m) - scale_system(m, scalers=scalers) + scale_system(m, has_scalers=has_scalers) initialize_system(m) # badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) @@ -112,7 +112,7 @@ def main(reactor_volume_equalities=False, scalers=True): results = solve(m) - add_costing(m, scalers=scalers) + add_costing(m, has_scalers=has_scalers) m.fs.costing.initialize() assert_degrees_of_freedom(m, 0) @@ -426,8 +426,8 @@ def set_operating_conditions(m): mx.pressure_equality_constraints[0.0, 2].deactivate() -def scale_system(m, scalers=True): - if scalers: +def scale_system(m, has_scalers=True): + if has_scalers: sb = ScalerBase() csb = CustomScalerBase() @@ -497,7 +497,7 @@ def scale_system(m, scalers=True): submodel_scalers={ m.fs.TU.mixed_state: ASM1PropertiesScaler, m.fs.TU.underflow_state: ASM1PropertiesScaler, - m.fs.TU.overflow_state: ASM1ReactionScaler, + m.fs.TU.overflow_state: ASM1PropertiesScaler, }, ) @@ -507,7 +507,7 @@ def scale_system(m, scalers=True): submodel_scalers={ m.fs.DU.mixed_state: ASM1PropertiesScaler, m.fs.DU.underflow_state: ASM1PropertiesScaler, - m.fs.DU.overflow_state: ASM1ReactionScaler, + m.fs.DU.overflow_state: ASM1PropertiesScaler, }, ) sb.set_variable_scaling_factor(m.fs.DU.volume[0], 1, overwrite=True) @@ -549,12 +549,11 @@ def scale_system(m, scalers=True): sb.set_variable_scaling_factor(var, 1e3) # for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): - # if "flow_vol_equality" in c.name: - # csb.scale_constraint_by_nominal_value( - # c, - # scheme=ConstraintScalingScheme.inverseMaximum, - # overwrite=True, - # ) + # csb.scale_constraint_by_nominal_value( + # c, + # scheme=ConstraintScalingScheme.inverseMaximum, + # overwrite=True, + # ) # csb.scale_constraint_by_nominal_value( # m.fs.RADM.AD_retention_time[0], @@ -648,7 +647,7 @@ def function(unit): mx.pressure_equality_constraints[0.0, 2].deactivate() -def add_costing(m, scalers=True): +def add_costing(m, has_scalers=True): m.fs.costing = WaterTAPCosting() m.fs.costing.base_currency = pyo.units.USD_2020 @@ -682,7 +681,7 @@ def add_costing(m, scalers=True): m.fs.objective = pyo.Objective(expr=m.fs.costing.LCOW) - if scalers: + if has_scalers: sb = ScalerBase() sb.set_variable_scaling_factor(m.fs.costing.total_capital_cost, 1e-7) @@ -950,4 +949,4 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main(scalers=True) + m, results = main(has_scalers=True) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index e7e7652884..e0c39db927 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -44,37 +44,53 @@ create_stream_table_dataframe, stream_table_dataframe_to_string, ) -from watertap.unit_models.cstr_injection import CSTR_Injection -from watertap.unit_models.clarifier import Clarifier +from idaes.core.scaling.custom_scaler_base import ( + CustomScalerBase, + ConstraintScalingScheme, +) +from idaes.core.scaling import AutoScaler +from idaes.core.scaling.scaling_base import ScalerBase +from watertap.unit_models.cstr_injection import CSTR_Injection, CSTR_InjectionScaler +from watertap.unit_models.clarifier import Clarifier, ClarifierScaler from watertap.property_models.unit_specific.anaerobic_digestion.modified_adm1_properties import ( ModifiedADM1ParameterBlock, + ModifiedADM1PropertiesScaler, ) from watertap.property_models.unit_specific.anaerobic_digestion.adm1_properties_vapor import ( ADM1_vaporParameterBlock, ) from watertap.property_models.unit_specific.anaerobic_digestion.modified_adm1_reactions import ( ModifiedADM1ReactionParameterBlock, + ModifiedADM1ReactionScaler, ) from watertap.property_models.unit_specific.activated_sludge.modified_asm2d_properties import ( ModifiedASM2dParameterBlock, + ModifiedASM2dPropertiesScaler, ) from watertap.property_models.unit_specific.activated_sludge.modified_asm2d_reactions import ( ModifiedASM2dReactionParameterBlock, + ModifiedASM2dReactionScaler, ) from watertap.unit_models.translators.translator_adm1_asm2d import ( Translator_ADM1_ASM2D, + ADM1ASM2dScaler, ) from idaes.models.unit_models.mixer import MomentumMixingType -from watertap.unit_models.translators.translator_asm2d_adm1 import Translator_ASM2d_ADM1 -from watertap.unit_models.anaerobic_digester import AD -from watertap.unit_models.cstr import CSTR +from watertap.unit_models.translators.translator_asm2d_adm1 import ( + Translator_ASM2d_ADM1, + ASM2dADM1Scaler, +) +from watertap.unit_models.anaerobic_digester import AD, ADScaler +from watertap.unit_models.cstr import CSTR, CSTRScaler from watertap.unit_models.dewatering import ( DewateringUnit, ActivatedSludgeModelType as dewater_type, + DewatererScaler, ) from watertap.unit_models.thickener import ( Thickener, ActivatedSludgeModelType as thickener_type, + ThickenerScaler, ) from watertap.core.util.initialization import ( @@ -88,19 +104,18 @@ cost_circular_clarifier, cost_primary_clarifier, ) -from idaes.core.scaling.custom_scaler_base import ( - CustomScalerBase, - ConstraintScalingScheme, -) + +from idaes.core.util import DiagnosticsToolbox # Set up logger _log = idaeslog.getLogger(__name__) -def main(bio_P=False): +def main(bio_P=False, has_scalers=True): m = build(bio_P=bio_P) set_operating_conditions(m, bio_P=bio_P) + scale_system(m, bio_P=bio_P, has_scalers=has_scalers) for mx in m.fs.mixers: mx.pressure_equality_constraints[0.0, 2].deactivate() @@ -136,7 +151,7 @@ def main(bio_P=False): fail_flag=True, ) - add_costing(m) + add_costing(m, has_scalers=has_scalers) m.fs.costing.initialize() interval_initializer(m.fs.costing) @@ -149,6 +164,18 @@ def main(bio_P=False): display_costing(m) display_performance_metrics(m) + badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) + print("---------------- badly_scaled_var_list ----------------") + for x in badly_scaled_var_list: + print(f"{x[0].name}\t{x[0].value}\tsf: {iscale.get_scaling_factor(x[0])}") + + print("---Numerical Issues---") + dt = DiagnosticsToolbox(m) + dt.report_numerical_issues() + dt.display_variables_at_or_outside_bounds() + dt.display_variables_with_extreme_jacobians() + dt.display_constraints_with_extreme_jacobians() + return m, results @@ -526,9 +553,162 @@ def set_operating_conditions(m, bio_P=False): m.fs.thickener.hydraulic_retention_time.fix(86400 * pyo.units.s) m.fs.thickener.diameter.fix(10 * pyo.units.m) - scaler = CustomScalerBase() - def scale_variables(m): +def scale_system(m, bio_P=False, has_scalers=True): + sb = ScalerBase() + csb = CustomScalerBase() + auto = AutoScaler() + + if has_scalers: + ad_scaler = ADScaler() + ad_scaler.scale_model( + m.fs.AD, + submodel_scalers={ + m.fs.AD.liquid_phase.properties_in: ModifiedADM1PropertiesScaler, + m.fs.AD.liquid_phase.properties_out: ModifiedADM1PropertiesScaler, + m.fs.AD.liquid_phase.reactions: ModifiedADM1ReactionScaler, + }, + ) + + if bio_P: + sb.set_variable_scaling_factor(m.fs.AD.liquid_phase.heat[0], 1e3) + else: + sb.set_variable_scaling_factor(m.fs.AD.liquid_phase.heat[0], 1e4) + csb.scale_constraint_by_nominal_value( + m.fs.AD.liquid_phase.enthalpy_balances[0], + scheme=ConstraintScalingScheme.inverseMinimum, + overwrite=True, + ) + sb.set_variable_scaling_factor(m.fs.AD.hydraulic_retention_time[0], 1e-6) + csb.scale_constraint_by_nominal_value( + m.fs.AD.AD_retention_time[0], + scheme=ConstraintScalingScheme.inverseMaximum, + overwrite=True, + ) + # sb.set_constraint_scaling_factor(m.fs.AD.AD_retention_time[0], 1e-6) + # sb.set_variable_scaling_factor(m.fs.AD.vapor_phase[0].pressure, 1e-5, overwrite=True) + # sb.set_variable_scaling_factor(m.fs.AD.volume_AD[0], 1e-3) + # sb.set_variable_scaling_factor(m.fs.AD.KH_ch4[0], 1e3) + sb.set_variable_scaling_factor( + m.fs.AD.KH_h2[0], 1e4 + ) # This should be uncommented for now - start adding additional scaling + + cstr_list = [m.fs.R1, m.fs.R2, m.fs.R3, m.fs.R4] + cstr_scaler = CSTRScaler() + for unit in cstr_list: + cstr_scaler.scale_model( + unit, + submodel_scalers={ + unit.control_volume.properties_in: ModifiedASM2dPropertiesScaler, + unit.control_volume.properties_out: ModifiedASM2dPropertiesScaler, + unit.control_volume.reactions: ModifiedASM2dReactionScaler, + }, + ) + sb.set_variable_scaling_factor(unit.hydraulic_retention_time[0], 1e-2) + + cstr_injection_list = [m.fs.R5, m.fs.R6, m.fs.R7] + cstr_injection_scaler = CSTR_InjectionScaler() + for unit in cstr_injection_list: + cstr_injection_scaler.scale_model( + unit, + submodel_scalers={ + unit.control_volume.properties_in: ModifiedASM2dPropertiesScaler, + unit.control_volume.properties_out: ModifiedASM2dPropertiesScaler, + unit.control_volume.reactions: ModifiedASM2dReactionScaler, + }, + ) + + sb.set_variable_scaling_factor(unit.KLa, 1e-1) + sb.set_variable_scaling_factor(unit.hydraulic_retention_time[0], 1e-3) + + clarifier_list = [m.fs.CL, m.fs.CL2] + clarifier_scaler = ClarifierScaler() + for unit in clarifier_list: + clarifier_scaler.scale_model( + unit, + submodel_scalers={ + unit.mixed_state: ModifiedASM2dPropertiesScaler, + unit.underflow_state: ModifiedASM2dPropertiesScaler, + unit.effluent_state: ModifiedASM2dPropertiesScaler, + }, + ) + + thickener_scaler = ThickenerScaler() + thickener_scaler.scale_model( + m.fs.thickener, + submodel_scalers={ + m.fs.thickener.mixed_state: ModifiedASM2dPropertiesScaler, + m.fs.thickener.underflow_state: ModifiedASM2dPropertiesScaler, + m.fs.thickener.overflow_state: ModifiedASM2dPropertiesScaler, + }, + ) + + dewaterer_scaler = DewatererScaler() + dewaterer_scaler.scale_model( + m.fs.dewater, + submodel_scalers={ + m.fs.dewater.mixed_state: ModifiedASM2dPropertiesScaler, + m.fs.dewater.underflow_state: ModifiedASM2dPropertiesScaler, + m.fs.dewater.overflow_state: ModifiedASM2dPropertiesScaler, + }, + ) + sb.set_variable_scaling_factor(m.fs.dewater.volume[0], 1, overwrite=True) + + as_ad_scaler = ASM2dADM1Scaler() + as_ad_scaler.scale_model( + m.fs.translator_asm2d_adm1, + submodel_scalers={ + m.fs.translator_asm2d_adm1.properties_in: ModifiedASM2dPropertiesScaler, + m.fs.translator_asm2d_adm1.properties_out: ModifiedADM1PropertiesScaler, + }, + ) + + ad_as_scaler = ADM1ASM2dScaler() + ad_as_scaler.scale_model( + m.fs.translator_adm1_asm2d, + submodel_scalers={ + m.fs.translator_adm1_asm2d.properties_in: ModifiedADM1PropertiesScaler, + m.fs.translator_adm1_asm2d.properties_out: ModifiedASM2dPropertiesScaler, + }, + ) + + for var in m.fs.component_data_objects(pyo.Var, descend_into=True): + if "flow_vol" in var.name: + sb.set_variable_scaling_factor(var, 1e2, overwrite=True) + # if "flow_vol" in var.name: + # auto.scale_variables_by_magnitude(var) + # if "flow_vol" in var.name: + # sb.set_variable_scaling_factor(var, 1e3) + if "temperature" in var.name: + sb.set_variable_scaling_factor(var, 1e-2) + if "pressure" in var.name: + sb.set_variable_scaling_factor(var, 1e-5) + if "conc_mass_comp" in var.name: + if bio_P: + sb.set_variable_scaling_factor(var, 1e1) + else: + sb.set_variable_scaling_factor(var, 1e2) + if "anions" in var.name: + sb.set_variable_scaling_factor(var, 1e2) + if "cations" in var.name: + sb.set_variable_scaling_factor(var, 1e2) + + # for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): + # if "flow_vol_equality" in c.name: + # csb.scale_constraint_by_nominal_value( + # c, + # scheme=ConstraintScalingScheme.inverseMinimum, + # overwrite=True, + # ) + + # for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): + # csb.scale_constraint_by_nominal_value( + # c, + # scheme=ConstraintScalingScheme.harmonicMean, + # overwrite=True, + # ) + + else: for var in m.fs.component_data_objects(pyo.Var, descend_into=True): if "flow_vol" in var.name: iscale.set_scaling_factor(var, 1e3) @@ -546,36 +726,32 @@ def scale_variables(m): if "cations" in var.name: iscale.set_scaling_factor(var, 1e2) - def scale_constraints(m): for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): if "flow_vol_equality" in c.name: - scaler.scale_constraint_by_nominal_value( + csb.scale_constraint_by_nominal_value( c, scheme=ConstraintScalingScheme.inverseMaximum, overwrite=True, ) - m.fs.aerobic_reactors = (m.fs.R5, m.fs.R6, m.fs.R7) - for R in m.fs.aerobic_reactors: - iscale.set_scaling_factor(R.KLa, 1e-2) - iscale.set_scaling_factor(R.hydraulic_retention_time[0], 1e-3) + m.fs.aerobic_reactors = (m.fs.R5, m.fs.R6, m.fs.R7) + for R in m.fs.aerobic_reactors: + iscale.set_scaling_factor(R.KLa, 1e-2) + iscale.set_scaling_factor(R.hydraulic_retention_time[0], 1e-3) - if bio_P: - iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) - else: - iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) - scaler.scale_constraint_by_nominal_value( - m.fs.AD.liquid_phase.enthalpy_balances[0], - scheme=ConstraintScalingScheme.inverseMinimum, - overwrite=True, - ) - iscale.set_scaling_factor(m.fs.AD.hydraulic_retention_time[0], 1e-6) - iscale.constraint_scaling_transform(m.fs.AD.AD_retention_time[0], 1e-4) + if bio_P: + iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e3) + else: + iscale.set_scaling_factor(m.fs.AD.liquid_phase.heat, 1e4) + csb.scale_constraint_by_nominal_value( + m.fs.AD.liquid_phase.enthalpy_balances[0], + scheme=ConstraintScalingScheme.inverseMinimum, + overwrite=True, + ) + iscale.set_scaling_factor(m.fs.AD.hydraulic_retention_time[0], 1e-6) + iscale.constraint_scaling_transform(m.fs.AD.AD_retention_time[0], 1e-4) - # Apply scaling - scale_variables(m) - scale_constraints(m) - iscale.calculate_scaling_factors(m) + iscale.calculate_scaling_factors(m) def initialize_system(m, bio_P=False, solver=None): @@ -718,7 +894,7 @@ def solve(m, solver=None): return results -def add_costing(m): +def add_costing(m, has_scalers=True): m.fs.costing = WaterTAPCosting() m.fs.costing.base_currency = pyo.units.USD_2020 @@ -753,16 +929,31 @@ def add_costing(m): m.fs.costing.add_specific_energy_consumption(m.fs.FeedWater.properties[0].flow_vol) m.fs.objective = pyo.Objective(expr=m.fs.costing.LCOW) - iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) + if has_scalers: + sb = ScalerBase() + sb.set_variable_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) - for block in m.fs.component_objects(pyo.Block, descend_into=True): - if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): - iscale.set_scaling_factor(block.costing.capital_cost, 1e-5) + for block in m.fs.component_objects(pyo.Block, descend_into=True): + if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): + sb.set_variable_scaling_factor(block.costing.capital_cost, 1e-5) - iscale.constraint_scaling_transform(m.fs.AD.costing.capital_cost_constraint, 1e-6) - iscale.constraint_scaling_transform( - m.fs.dewater.costing.capital_cost_constraint, 1e-6 - ) + sb.set_constraint_scaling_factor(m.fs.AD.costing.capital_cost_constraint, 1e-6) + sb.set_constraint_scaling_factor( + m.fs.dewater.costing.capital_cost_constraint, 1e-6 + ) + else: + iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) + + for block in m.fs.component_objects(pyo.Block, descend_into=True): + if isinstance(block, UnitModelBlockData) and hasattr(block, "costing"): + iscale.set_scaling_factor(block.costing.capital_cost, 1e-5) + + iscale.constraint_scaling_transform( + m.fs.AD.costing.capital_cost_constraint, 1e-6 + ) + iscale.constraint_scaling_transform( + m.fs.dewater.costing.capital_cost_constraint, 1e-6 + ) def display_costing(m): @@ -978,7 +1169,7 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main(bio_P=False) + m, results = main(bio_P=False, has_scalers=True) stream_table = create_stream_table_dataframe( { From 0c4493a2e562ef4275832ff06489a98313feb76a Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Fri, 10 Jan 2025 15:59:33 -0500 Subject: [PATCH 45/46] Add To-Do note on TransformationFactory scaling --- .../flowsheets/full_water_resource_recovery_facility/BSM2.py | 3 ++- .../full_water_resource_recovery_facility/BSM2_P_extension.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py index e42140b007..33275122ed 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2.py @@ -101,6 +101,7 @@ def main(reactor_volume_equalities=False, has_scalers=True): assert_degrees_of_freedom(m, 0) assert_units_consistent(m) scale_system(m, has_scalers=has_scalers) + # TODO: Should follow this with TransformationFactory("core.scale_model"), but would need to refactor tear guesses initialize_system(m) # badly_scaled_var_list = iscale.badly_scaled_var_generator(m, large=1e2, small=1e-2) @@ -949,4 +950,4 @@ def display_performance_metrics(m): if __name__ == "__main__": - m, results = main(has_scalers=True) + m, results = main(has_scalers=False) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index e0c39db927..d53b5cbb7b 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -116,6 +116,7 @@ def main(bio_P=False, has_scalers=True): m = build(bio_P=bio_P) set_operating_conditions(m, bio_P=bio_P) scale_system(m, bio_P=bio_P, has_scalers=has_scalers) + # TODO: Should follow this with TransformationFactory("core.scale_model"), but would need to refactor tear guesses for mx in m.fs.mixers: mx.pressure_equality_constraints[0.0, 2].deactivate() From fde1c01cf566c5aa9408754b0b21accae91b04b9 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Mon, 13 Jan 2025 22:06:58 -0500 Subject: [PATCH 46/46] Try refining scaling more --- .../BSM2_P_extension.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py index d53b5cbb7b..9db3423124 100644 --- a/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py +++ b/watertap/flowsheets/full_water_resource_recovery_facility/BSM2_P_extension.py @@ -176,6 +176,9 @@ def main(bio_P=False, has_scalers=True): dt.display_variables_at_or_outside_bounds() dt.display_variables_with_extreme_jacobians() dt.display_constraints_with_extreme_jacobians() + print("---SVD---") + svd = dt.prepare_svd_toolbox() + svd.display_underdetermined_variables_and_constraints() return m, results @@ -590,6 +593,9 @@ def scale_system(m, bio_P=False, has_scalers=True): # sb.set_variable_scaling_factor(m.fs.AD.vapor_phase[0].pressure, 1e-5, overwrite=True) # sb.set_variable_scaling_factor(m.fs.AD.volume_AD[0], 1e-3) # sb.set_variable_scaling_factor(m.fs.AD.KH_ch4[0], 1e3) + sb.set_variable_scaling_factor( + m.fs.AD.liquid_phase.properties_in[0.0].flow_vol, 1e3, overwrite=True + ) sb.set_variable_scaling_factor( m.fs.AD.KH_h2[0], 1e4 ) # This should be uncommented for now - start adding additional scaling @@ -702,10 +708,18 @@ def scale_system(m, bio_P=False, has_scalers=True): # overwrite=True, # ) + # for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): + # if "eq_flow_vol_rule" in c.name: + # csb.scale_constraint_by_nominal_value( + # c, + # scheme=ConstraintScalingScheme.inverseRSS, + # overwrite=True, + # ) + # for c in m.fs.component_data_objects(pyo.Constraint, descend_into=True): # csb.scale_constraint_by_nominal_value( # c, - # scheme=ConstraintScalingScheme.harmonicMean, + # scheme=ConstraintScalingScheme.inverseMaximum, # overwrite=True, # )