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..15f8a79422 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-6), + } + + 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) + + # 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 @@ -390,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] @@ -443,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/adm1_properties_vapor.py b/watertap/property_models/unit_specific/anaerobic_digestion/adm1_properties_vapor.py index 50cd540d72..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 @@ -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 @@ -364,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] @@ -413,8 +446,14 @@ 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 + 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 5f6e7de77f..4b8eaf66ab 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" @@ -1159,12 +1160,137 @@ def define_metadata(cls, obj): ) +class ADM1ReactionScaler(CustomScalerBase): + """ + Scaler for the Anaerobic Digestion 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, + "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(): + self.scale_variable_by_default(j, overwrite=overwrite) + + def constraint_scaling_routine( + self, model, overwrite: bool = False, submodel_scalers: dict = None + ): + # 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( + j, + 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): """ This Class contains methods which should be applied to Reaction Blocks as a whole, rather than individual elements of indexed Reaction Blocks. """ + default_scaler = ADM1ReactionScaler + def initialize(self, outlvl=idaeslog.NOTSET, **kwargs): """ Initialization routine for reaction package. @@ -1769,8 +1895,11 @@ 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) + 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) @@ -1787,11 +1916,8 @@ 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) for i, c in self.rate_expression.items(): iscale.constraint_scaling_transform( 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..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 @@ -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,235 @@ 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 + model.rxns[1].I + + 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) == 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) + 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) == 51 + assert sfx[model.rxns[1].rate_expression["R1"]] == pytest.approx( + 5.574193548e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R2"]] == pytest.approx( + 3.0857142857e5, 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.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.024242424e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R8"]] == pytest.approx( + 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.44175824176e5, rel=1e-8 + ) + assert sfx[model.rxns[1].rate_expression["R11"]] == pytest.approx( + 2.4868421053e4, 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.66101694915e6, 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.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.35e7, 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, 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) + 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) == 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) + 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..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 @@ -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) == 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) + + @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) == 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) 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..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 @@ -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) == 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)