From 989e08e77ea764b28dcdde704dc37f3eefa88a45 Mon Sep 17 00:00:00 2001 From: MarcusHolly <96305519+MarcusHolly@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:39:01 -0400 Subject: [PATCH] Pressure Changer and Coag & Floc Test Harness (#1363) * Update testing for coag and floc model * Update test for pressure changer * Add tests for configuration errors --------- Co-authored-by: Adam Atia Co-authored-by: Ludovico Bianchi --- .../unit_models/tests/test_coag_floc_model.py | 612 +++++---------- .../tests/test_pressure_changer.py | 703 ++++++------------ 2 files changed, 392 insertions(+), 923 deletions(-) diff --git a/watertap/unit_models/tests/test_coag_floc_model.py b/watertap/unit_models/tests/test_coag_floc_model.py index a3b511aa31..ee3d101120 100644 --- a/watertap/unit_models/tests/test_coag_floc_model.py +++ b/watertap/unit_models/tests/test_coag_floc_model.py @@ -16,20 +16,14 @@ from watertap.unit_models.coag_floc_model import CoagulationFlocculation from pyomo.environ import ( ConcreteModel, - assert_optimal_termination, value, - Param, - Var, units as pyunits, - Constraint, ) from idaes.core import FlowsheetBlock from idaes.core.util.exceptions import ConfigurationError -from idaes.core.util.model_statistics import degrees_of_freedom -from pyomo.util.check_units import assert_units_consistent import idaes.core.util.scaling as iscale -from idaes.core.util.testing import initialization_tester from watertap.core.solvers import get_solver +from watertap.unit_models.tests.unit_test_harness import UnitTestHarness import re __author__ = "Austin Ladshaw" @@ -38,422 +32,161 @@ # ----------------------------------------------------------------------------- -# Start test class -class TestCoagulation_withChemicals: - @pytest.fixture(scope="class") - def coag_obj_w_chems(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.properties = CoagulationParameterBlock() - ## NOTE: These values provided are just DUMMY values for the purposes - # of testing. They are not meant to be representative of any - # particular chemicals or real-world additives. - chem_dict = { - "Alum": { - "parameter_data": { - "mw_additive": (200, pyunits.g / pyunits.mol), - "moles_salt_per_mole_additive": 3, - "mw_salt": (100, pyunits.g / pyunits.mol), - } - }, - "Poly": { - "parameter_data": { - "mw_additive": (25, pyunits.g / pyunits.mol), - "moles_salt_per_mole_additive": 0, - "mw_salt": (23, pyunits.g / pyunits.mol), - } - }, - } - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=chem_dict - ) - - return model - - @pytest.mark.unit - def test_build_model(self, coag_obj_w_chems): - model = coag_obj_w_chems - - assert len(model.fs.unit.config.chemical_additives) == 2 - assert isinstance(model.fs.unit.slope, Var) - assert isinstance(model.fs.unit.intercept, Var) - assert isinstance(model.fs.unit.initial_turbidity_ntu, Var) - assert isinstance(model.fs.unit.final_turbidity_ntu, Var) - assert isinstance(model.fs.unit.chemical_doses, Var) - assert len(model.fs.unit.chemical_doses) == 2 - assert isinstance(model.fs.unit.chemical_mw, Param) - assert len(model.fs.unit.chemical_mw) == 2 - assert isinstance(model.fs.unit.salt_mw, Param) - assert len(model.fs.unit.salt_mw) == 2 - assert isinstance(model.fs.unit.salt_from_additive_mole_ratio, Param) - assert len(model.fs.unit.salt_from_additive_mole_ratio) == 2 - assert isinstance(model.fs.unit.tss_loss_rate, Var) - assert isinstance(model.fs.unit.eq_tss_loss_rate, Constraint) - - assert isinstance(model.fs.unit.rapid_mixing_retention_time, Var) - assert isinstance(model.fs.unit.num_rapid_mixing_basins, Var) - assert isinstance(model.fs.unit.rapid_mixing_vel_grad, Var) - - assert isinstance(model.fs.unit.floc_retention_time, Var) - assert isinstance(model.fs.unit.single_paddle_length, Var) - assert isinstance(model.fs.unit.single_paddle_width, Var) - assert isinstance(model.fs.unit.paddle_rotational_speed, Var) - assert isinstance(model.fs.unit.paddle_drag_coef, Var) - assert isinstance(model.fs.unit.vel_fraction, Var) - assert isinstance(model.fs.unit.num_paddle_wheels, Var) - assert isinstance(model.fs.unit.num_paddles_per_wheel, Var) - - assert isinstance(model.fs.unit.tds_gain_rate, Var) - assert isinstance(model.fs.unit.eq_tds_gain_rate, Constraint) - - assert isinstance(model.fs.unit.eq_mass_transfer_term, Constraint) - - assert isinstance(model.fs.unit.eq_rapid_mixing_basin_vol, Constraint) - assert isinstance(model.fs.unit.rapid_mixing_basin_vol, Var) - - assert isinstance(model.fs.unit.eq_rapid_mixing_power, Constraint) - assert isinstance(model.fs.unit.rapid_mixing_power, Var) - - assert isinstance(model.fs.unit.eq_floc_basin_vol, Constraint) - assert isinstance(model.fs.unit.floc_basin_vol, Var) - - assert isinstance(model.fs.unit.eq_floc_wheel_speed, Constraint) - assert isinstance(model.fs.unit.floc_wheel_speed, Var) - - assert isinstance(model.fs.unit.eq_flocculation_power, Constraint) - assert isinstance(model.fs.unit.flocculation_power, Var) - - assert isinstance(model.fs.unit.eq_total_power, Constraint) - assert isinstance(model.fs.unit.total_power, Var) - - @pytest.mark.unit - def test_stats(self, coag_obj_w_chems): - model = coag_obj_w_chems - - # Check to make sure we have the correct DOF and - # check to make sure the units are correct - assert_units_consistent(model) - assert degrees_of_freedom(model) == 22 - - # set the operational parameters - model.fs.unit.fix_tss_turbidity_relation_defaults() - model.fs.unit.initial_turbidity_ntu.fix() - model.fs.unit.final_turbidity_ntu.fix(5) - model.fs.unit.chemical_doses[0, "Alum"].fix(10) - model.fs.unit.chemical_doses[0, "Poly"].fix(5) - - # set the inlet streams - assert degrees_of_freedom(model) == 17 - model.fs.unit.inlet.pressure.fix(101325) - model.fs.unit.inlet.temperature.fix(298.15) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(1) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix(0.01) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TSS"].fix(0.01) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "Sludge"].fix(0.0) - - # set performance vars - model.fs.unit.rapid_mixing_retention_time[0].fix(60) - model.fs.unit.num_rapid_mixing_basins.fix(4) - model.fs.unit.rapid_mixing_vel_grad[0].fix(750) - - model.fs.unit.floc_retention_time[0].fix(1800) - model.fs.unit.single_paddle_length.fix(4) - model.fs.unit.single_paddle_width.fix(0.5) - model.fs.unit.paddle_rotational_speed[0].fix(0.03) - - model.fs.unit.paddle_drag_coef[0].fix(1.5) - model.fs.unit.vel_fraction.fix(0.7) - model.fs.unit.num_paddle_wheels.fix(4) - model.fs.unit.num_paddles_per_wheel.fix(4) - - assert degrees_of_freedom(model) == 0 - - @pytest.mark.unit - def test_scaling(self, coag_obj_w_chems): - model = coag_obj_w_chems - - # Set some scaling factors and look for 'bad' scaling - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1, index=("Liq", "H2O") - ) - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "TSS") - ) - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "TDS") +def build(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties = CoagulationParameterBlock() + ## NOTE: These values provided are just DUMMY values for the purposes + # of testing. They are not meant to be representative of any + # particular chemicals or real-world additives. + chem_dict = { + "Alum": { + "parameter_data": { + "mw_additive": (200, pyunits.g / pyunits.mol), + "moles_salt_per_mole_additive": 3, + "mw_salt": (100, pyunits.g / pyunits.mol), + } + }, + "Poly": { + "parameter_data": { + "mw_additive": (25, pyunits.g / pyunits.mol), + "moles_salt_per_mole_additive": 0, + "mw_salt": (23, pyunits.g / pyunits.mol), + } + }, + } + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=chem_dict + ) + + m.fs.unit.fix_tss_turbidity_relation_defaults() + m.fs.unit.initial_turbidity_ntu.fix() + m.fs.unit.final_turbidity_ntu.fix(5) + m.fs.unit.chemical_doses[0, "Alum"].fix(10) + m.fs.unit.chemical_doses[0, "Poly"].fix(5) + + # set the inlet streams + m.fs.unit.inlet.pressure.fix(101325) + m.fs.unit.inlet.temperature.fix(298.15) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(1) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix(0.01) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TSS"].fix(0.01) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "Sludge"].fix(0.0) + + # set performance vars + m.fs.unit.rapid_mixing_retention_time[0].fix(60) + m.fs.unit.num_rapid_mixing_basins.fix(4) + m.fs.unit.rapid_mixing_vel_grad[0].fix(750) + + m.fs.unit.floc_retention_time[0].fix(1800) + m.fs.unit.single_paddle_length.fix(4) + m.fs.unit.single_paddle_width.fix(0.5) + m.fs.unit.paddle_rotational_speed[0].fix(0.03) + + m.fs.unit.paddle_drag_coef[0].fix(1.5) + m.fs.unit.vel_fraction.fix(0.7) + m.fs.unit.num_paddle_wheels.fix(4) + m.fs.unit.num_paddles_per_wheel.fix(4) + + iscale.set_scaling_factor( + m.fs.unit.control_volume.properties_in[0.0].mass_frac_phase_comp[ + "Liq", "Sludge" + ], + 1e12, + ) + iscale.calculate_scaling_factors(m.fs.unit) + + return m + + +def build_no_chemicals(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties = CoagulationParameterBlock() + m.fs.unit = CoagulationFlocculation(property_package=m.fs.properties) + + m.fs.unit.fix_tss_turbidity_relation_defaults() + m.fs.unit.initial_turbidity_ntu.fix(5000) + m.fs.unit.final_turbidity_ntu.fix(100) + + tss_in = value(m.fs.unit.compute_inlet_tss_mass_flow(0)) + + m.fs.unit.inlet.pressure.fix(101325) + m.fs.unit.inlet.temperature.fix(298.15) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(1) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix(0.01) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TSS"].fix(tss_in) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "Sludge"].fix(0.0) + + # set performance vars + m.fs.unit.rapid_mixing_retention_time[0].fix(60) + m.fs.unit.num_rapid_mixing_basins.fix(4) + m.fs.unit.rapid_mixing_vel_grad[0].fix(750) + + m.fs.unit.floc_retention_time[0].fix(1800) + m.fs.unit.single_paddle_length.fix(4) + m.fs.unit.single_paddle_width.fix(0.5) + m.fs.unit.paddle_rotational_speed[0].fix(0.03) + + m.fs.unit.paddle_drag_coef[0].fix(1.5) + m.fs.unit.vel_fraction.fix(0.7) + m.fs.unit.num_paddle_wheels.fix(4) + m.fs.unit.num_paddles_per_wheel.fix(4) + + iscale.set_scaling_factor( + m.fs.unit.control_volume.properties_in[0.0].mass_frac_phase_comp[ + "Liq", "Sludge" + ], + 1e12, + ) + iscale.calculate_scaling_factors(m.fs.unit) + + return m + + +class TestCoagFloc(UnitTestHarness): + def configure(self): + m = build() + + self.unit_solutions[m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "H2O"]] = 1 + self.unit_solutions[ + m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "Sludge"] + ] = 0.009990636 + self.unit_solutions[m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TDS"]] = ( + 0.01001510 ) - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "Sludge") + self.unit_solutions[m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TSS"]] = ( + 9.36352627e-6 ) - # set scaling factors for performance - iscale.set_scaling_factor(model.fs.unit.rapid_mixing_retention_time, 1e-1) - iscale.set_scaling_factor(model.fs.unit.num_rapid_mixing_basins, 1) - iscale.set_scaling_factor(model.fs.unit.rapid_mixing_vel_grad, 1e-2) + return m - iscale.set_scaling_factor(model.fs.unit.floc_retention_time, 1e-3) - iscale.set_scaling_factor(model.fs.unit.single_paddle_length, 1) - iscale.set_scaling_factor(model.fs.unit.single_paddle_width, 1) - iscale.set_scaling_factor(model.fs.unit.paddle_rotational_speed, 10) - iscale.set_scaling_factor(model.fs.unit.paddle_drag_coef, 1) - iscale.set_scaling_factor(model.fs.unit.vel_fraction, 1) - iscale.set_scaling_factor(model.fs.unit.num_paddle_wheels, 1) - iscale.set_scaling_factor(model.fs.unit.num_paddles_per_wheel, 1) +class TestCoagFlocNoChemicals(UnitTestHarness): + def configure(self): + m = build_no_chemicals() - iscale.calculate_scaling_factors(model.fs) - - # check that all variables have scaling factors - unscaled_var_list = list(iscale.unscaled_variables_generator(model)) - assert len(unscaled_var_list) == 0 - - # check if any variables are badly scaled - badly_scaled_var_values = { - var.name: val - for (var, val) in iscale.badly_scaled_var_generator( - model, large=1e2, small=1e-2 - ) - } - assert not badly_scaled_var_values - - @pytest.mark.component - def test_initialization(self, coag_obj_w_chems): - model = coag_obj_w_chems - initialization_tester(model) - - # check to make sure DOF does not change - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve(self, coag_obj_w_chems): - model = coag_obj_w_chems - - # first, check to make sure that after initialized, the scaling is still good - badly_scaled_var_values = { - var.name: val - for (var, val) in iscale.badly_scaled_var_generator( - model, large=1e2, small=1e-2 - ) - } - assert not badly_scaled_var_values - - # run solver and check for optimal solution - results = solver.solve(model) - assert_optimal_termination(results) - - @pytest.mark.component - def test_solution(self, coag_obj_w_chems): - model = coag_obj_w_chems - - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "H2O"] - ) == pytest.approx(1, rel=1e-4) - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "Sludge"] - ) == pytest.approx(0.00999, rel=1e-4) - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TDS"] - ) == pytest.approx(0.010015, rel=1e-4) - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TSS"] - ) == pytest.approx(9.36352e-06, rel=1e-4) - - @pytest.mark.component - def test_performance_contents(self, coag_obj_w_chems): - model = coag_obj_w_chems - - dict = model.fs.unit._get_performance_contents() - - assert "vars" in dict - assert "Total Power Usage (kW)" in dict["vars"] - assert "Rapid Mixing Power (kW)" in dict["vars"] - assert "Flocc Mixing Power (kW)" in dict["vars"] - - assert value(dict["vars"]["Total Power Usage (kW)"]) == pytest.approx( - 0.56798, rel=1e-4 + self.unit_solutions[m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "H2O"]] = 1 + self.unit_solutions[ + m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "Sludge"] + ] = 0.009112749 + self.unit_solutions[m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TDS"]] = ( + 0.01 ) - assert value(dict["vars"]["Rapid Mixing Power (kW)"]) == pytest.approx( - 0.12115, rel=1e-4 - ) - assert value(dict["vars"]["Flocc Mixing Power (kW)"]) == pytest.approx( - 0.44684, rel=1e-4 + self.unit_solutions[m.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TSS"]] = ( + 0.0001872506 ) + return m -# ----------------------------------------------------------------------------- -# Start test class without chemicals added -class TestCoagulation_withNoChemicals: - @pytest.fixture(scope="class") - def coag_obj_wo_chems(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.properties = CoagulationParameterBlock() - model.fs.unit = CoagulationFlocculation(property_package=model.fs.properties) - - return model - - @pytest.mark.unit - def test_build_model(self, coag_obj_wo_chems): - model = coag_obj_wo_chems - - assert len(model.fs.unit.config.chemical_additives) == 0 - assert isinstance(model.fs.unit.slope, Var) - assert isinstance(model.fs.unit.intercept, Var) - assert isinstance(model.fs.unit.initial_turbidity_ntu, Var) - assert isinstance(model.fs.unit.final_turbidity_ntu, Var) - assert isinstance(model.fs.unit.chemical_doses, Var) - assert len(model.fs.unit.chemical_doses) == 0 - assert isinstance(model.fs.unit.chemical_mw, Param) - assert len(model.fs.unit.chemical_mw) == 0 - assert isinstance(model.fs.unit.salt_mw, Param) - assert len(model.fs.unit.salt_mw) == 0 - assert isinstance(model.fs.unit.salt_from_additive_mole_ratio, Param) - assert len(model.fs.unit.salt_from_additive_mole_ratio) == 0 - assert isinstance(model.fs.unit.tss_loss_rate, Var) - assert isinstance(model.fs.unit.eq_tss_loss_rate, Constraint) - - assert not hasattr(model.fs.unit, "tds_gain_rate") - assert not hasattr(model.fs.unit, "eq_tds_gain_rate") - - assert isinstance(model.fs.unit.eq_mass_transfer_term, Constraint) - - @pytest.mark.unit - def test_stats(self, coag_obj_wo_chems): - model = coag_obj_wo_chems - - # Check to make sure we have the correct DOF and - # check to make sure the units are correct - assert_units_consistent(model) - assert degrees_of_freedom(model) == 20 - - # set the operational parameters - model.fs.unit.fix_tss_turbidity_relation_defaults() - model.fs.unit.initial_turbidity_ntu.fix(5000) - model.fs.unit.final_turbidity_ntu.fix(100) - - # set the inlet streams - assert degrees_of_freedom(model) == 17 - - tss_in = value(model.fs.unit.compute_inlet_tss_mass_flow(0)) - assert tss_in == pytest.approx(0.0093, rel=1e-4) - model.fs.unit.inlet.pressure.fix(101325) - model.fs.unit.inlet.temperature.fix(298.15) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(1) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix(0.01) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TSS"].fix(tss_in) - model.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "Sludge"].fix(0.0) - - # set performance vars - model.fs.unit.rapid_mixing_retention_time[0].fix(60) - model.fs.unit.num_rapid_mixing_basins.fix(4) - model.fs.unit.rapid_mixing_vel_grad[0].fix(750) - - model.fs.unit.floc_retention_time[0].fix(1800) - model.fs.unit.single_paddle_length.fix(4) - model.fs.unit.single_paddle_width.fix(0.5) - model.fs.unit.paddle_rotational_speed[0].fix(0.03) - - model.fs.unit.paddle_drag_coef[0].fix(1.5) - model.fs.unit.vel_fraction.fix(0.7) - model.fs.unit.num_paddle_wheels.fix(4) - model.fs.unit.num_paddles_per_wheel.fix(4) - - assert degrees_of_freedom(model) == 0 +class TestCoagFlocErrorLog: @pytest.mark.unit - def test_scaling(self, coag_obj_wo_chems): - model = coag_obj_wo_chems + def test_dictionary_error(self): - # Set some scaling factors and look for 'bad' scaling - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1, index=("Liq", "H2O") - ) - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "TSS") - ) - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "TDS") - ) - model.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "Sludge") - ) - - # Here we are skipping setting scaling factors for performance to test the - # effectiveness of the defaults AND get better test coverage - - iscale.calculate_scaling_factors(model.fs) - - # check that all variables have scaling factors - unscaled_var_list = list(iscale.unscaled_variables_generator(model)) - assert len(unscaled_var_list) == 0 - - # check if any variables are badly scaled - badly_scaled_var_values = { - var.name: val - for (var, val) in iscale.badly_scaled_var_generator( - model, large=1e2, small=1e-2 - ) - } - print(iscale.get_scaling_factor(model.fs.unit.tss_loss_rate)) - assert not badly_scaled_var_values - - @pytest.mark.component - def test_initialization(self, coag_obj_wo_chems): - model = coag_obj_wo_chems - initialization_tester(model) - - # check to make sure DOF does not change - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve(self, coag_obj_wo_chems): - model = coag_obj_wo_chems - - # first, check to make sure that after initialized, the scaling is still good - badly_scaled_var_values = { - var.name: val - for (var, val) in iscale.badly_scaled_var_generator( - model, large=1e2, small=1e-2 - ) - } - assert not badly_scaled_var_values - - # run solver and check for optimal solution - results = solver.solve(model) - assert_optimal_termination(results) - - @pytest.mark.component - def test_solution(self, coag_obj_wo_chems): - model = coag_obj_wo_chems - - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "H2O"] - ) == pytest.approx(1, rel=1e-4) - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "Sludge"] - ) == pytest.approx(0.0091127, rel=1e-4) - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TDS"] - ) == pytest.approx(0.01, rel=1e-4) - assert value( - model.fs.unit.outlet.flow_mass_phase_comp[0, "Liq", "TSS"] - ) == pytest.approx(0.00018725, rel=1e-4) - - -# ----------------------------------------------------------------------------- -# Start test class with bad config -class TestCoagulation_withBadConfig: - @pytest.fixture(scope="class") - def coag_obj_bad_config(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.properties = CoagulationParameterBlock() - - return model - - @pytest.mark.unit - def test_build_model_catch_errors(self, coag_obj_bad_config): - model = coag_obj_bad_config + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.properties = CoagulationParameterBlock() bad_dict1 = { "Alum": { @@ -464,11 +197,13 @@ def test_build_model_catch_errors(self, coag_obj_bad_config): } } } + with pytest.raises( - ConfigurationError, match="Did not provide a 'parameter_data' for chemical" + ConfigurationError, + match="Did not provide a 'parameter_data' for chemical", ): - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=bad_dict1 + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=bad_dict1 ) bad_dict2 = { @@ -483,8 +218,8 @@ def test_build_model_catch_errors(self, coag_obj_bad_config): with pytest.raises( ConfigurationError, match="Did not provide a 'mw_additive' for chemical" ): - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=bad_dict2 + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=bad_dict2 ) bad_dict3 = { @@ -500,8 +235,8 @@ def test_build_model_catch_errors(self, coag_obj_bad_config): ConfigurationError, match="Did not provide a number for 'moles_salt_per_mole_additive'", ): - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=bad_dict3 + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=bad_dict3 ) bad_dict4 = { @@ -517,8 +252,8 @@ def test_build_model_catch_errors(self, coag_obj_bad_config): ConfigurationError, match="Did not provide a 'moles_salt_per_mole_additive' for chemical", ): - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=bad_dict4 + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=bad_dict4 ) bad_dict5 = { @@ -533,8 +268,8 @@ def test_build_model_catch_errors(self, coag_obj_bad_config): with pytest.raises( ConfigurationError, match="Did not provide a 'mw_salt' for chemical" ): - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=bad_dict5 + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=bad_dict5 ) bad_dict6 = { @@ -549,24 +284,15 @@ def test_build_model_catch_errors(self, coag_obj_bad_config): with pytest.raises( ConfigurationError, match="Did not provide a tuple for 'mw_additive'" ): - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties, chemical_additives=bad_dict6 + m.fs.unit = CoagulationFlocculation( + property_package=m.fs.properties, chemical_additives=bad_dict6 ) - -# ----------------------------------------------------------------------------- -# Start test class with bad config -class TestCoagulation_withBadProperties: - @pytest.fixture(scope="class") - def coag_obj_bad_properties(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - - return model - @pytest.mark.unit - def test_build_model_catch_prop_errors(self, coag_obj_bad_properties): - model = coag_obj_bad_properties + def test_property_error(self): + + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) error_msg = ( "Coagulation-Flocculation model MUST contain ('Liq','TDS') " @@ -574,10 +300,8 @@ def test_build_model_catch_prop_errors(self, coag_obj_bad_properties): "the following components [('Liq', 'H2O'), ('Liq', 'NaCl')]" ) with pytest.raises(ConfigurationError, match=re.escape(error_msg)): - model.fs.properties = NaClParameterBlock() - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties - ) + m.fs.properties = NaClParameterBlock() + m.fs.unit = CoagulationFlocculation(property_package=m.fs.properties) error_msg = ( "Coagulation-Flocculation model MUST contain ('Liq','Sludge') " @@ -585,9 +309,5 @@ def test_build_model_catch_prop_errors(self, coag_obj_bad_properties): "the following components [('Liq', 'H2O'), ('Liq', 'TDS')]" ) with pytest.raises(ConfigurationError, match=re.escape(error_msg)): - model.fs.properties = SeawaterParameterBlock() - model.fs.unit = CoagulationFlocculation( - property_package=model.fs.properties - ) - - # NOTE: package must also contain ('Liq','TSS') as a component + m.fs.properties = SeawaterParameterBlock() + m.fs.unit = CoagulationFlocculation(property_package=m.fs.properties) diff --git a/watertap/unit_models/tests/test_pressure_changer.py b/watertap/unit_models/tests/test_pressure_changer.py index ae1f017e09..1b7fb2c985 100644 --- a/watertap/unit_models/tests/test_pressure_changer.py +++ b/watertap/unit_models/tests/test_pressure_changer.py @@ -9,29 +9,12 @@ # information, respectively. These files are also available online at the URL # "https://github.com/watertap-org/watertap/" ################################################################################# - -import pytest from pyomo.environ import ( ConcreteModel, - TerminationCondition, - SolverStatus, - value, - Constraint, - Var, ) -from pyomo.util.check_units import assert_units_consistent from idaes.core import FlowsheetBlock from watertap.core.solvers import get_solver -from idaes.core.util.model_statistics import degrees_of_freedom -from idaes.core.util.testing import initialization_tester -from idaes.core.util.scaling import ( - calculate_scaling_factors, - unscaled_variables_generator, - unscaled_constraints_generator, - get_scaling_factor, - set_scaling_factor, -) import watertap.property_models.seawater_prop_pack as props import watertap.property_models.multicomp_aq_sol_prop_pack as props2 @@ -41,6 +24,9 @@ VariableEfficiency, ) +import idaes.core.util.scaling as iscale +from watertap.unit_models.tests.unit_test_harness import UnitTestHarness + # ----------------------------------------------------------------------------- # Get default solver for testing solver = get_solver() @@ -49,496 +35,259 @@ # ----------------------------------------------------------------------------- -class TestPumpIsothermal: - @pytest.fixture(scope="class") - def Pump_frame(self): - m = ConcreteModel() - m.fs = FlowsheetBlock(dynamic=False) - - m.fs.properties = props.SeawaterParameterBlock() - - m.fs.unit = Pump(property_package=m.fs.properties) - - # fully specify system - feed_flow_mass = 1 - feed_mass_frac_TDS = 0.035 - feed_pressure_in = 1e5 - feed_pressure_out = 5e5 - feed_temperature = 273.15 + 25 - efi_pump = 0.75 - - feed_mass_frac_H2O = 1 - feed_mass_frac_TDS - m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( - feed_flow_mass * feed_mass_frac_TDS +def build_pump(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = props.SeawaterParameterBlock() + + m.fs.unit = Pump(property_package=m.fs.properties) + + # fully specify system + feed_flow_mass = 1 + feed_mass_frac_TDS = 0.035 + feed_pressure_in = 1e5 + feed_pressure_out = 5e5 + feed_temperature = 273.15 + 25 + efi_pump = 0.75 + + feed_mass_frac_H2O = 1 - feed_mass_frac_TDS + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + feed_flow_mass * feed_mass_frac_TDS + ) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + feed_flow_mass * feed_mass_frac_H2O + ) + m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) + m.fs.unit.inlet.temperature[0].fix(feed_temperature) + m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) + m.fs.unit.efficiency_pump.fix(efi_pump) + + iscale.set_scaling_factor(m.fs.unit.control_volume.work[0], 1e-2) + iscale.set_scaling_factor(m.fs.unit.work_fluid[0], 1e-2) + iscale.calculate_scaling_factors(m.fs.unit) + + return m + + +def build_energy_recovery_device(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = props.SeawaterParameterBlock() + + m.fs.unit = EnergyRecoveryDevice(property_package=m.fs.properties) + + # fully specify system + feed_flow_mass = 1 + feed_mass_frac_TDS = 0.035 + feed_pressure_in = 5e5 + feed_pressure_out = 1e5 + feed_temperature = 273.15 + 25 + efi_pump = 0.75 + + feed_mass_frac_H2O = 1 - feed_mass_frac_TDS + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + feed_flow_mass * feed_mass_frac_TDS + ) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + feed_flow_mass * feed_mass_frac_H2O + ) + m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) + m.fs.unit.inlet.temperature[0].fix(feed_temperature) + m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) + m.fs.unit.efficiency_pump.fix(efi_pump) + + iscale.set_scaling_factor(m.fs.unit.control_volume.work[0], 1e-2) + iscale.set_scaling_factor(m.fs.unit.work_fluid[0], 1e-2) + iscale.calculate_scaling_factors(m.fs.unit) + + return m + + +def build_pump_variable_flow(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = props.SeawaterParameterBlock() + + m.fs.unit = Pump( + property_package=m.fs.properties, + variable_efficiency=VariableEfficiency.flow, + ) + # fully specify system + feed_flow_mass = 1 + feed_mass_frac_TDS = 0.035 + feed_pressure_in = 1e5 + feed_pressure_out = 5e5 + feed_temperature = 273.15 + 25 + efi_pump = 0.75 + + feed_mass_frac_H2O = 1 - feed_mass_frac_TDS + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( + feed_flow_mass * feed_mass_frac_TDS + ) + m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( + feed_flow_mass * feed_mass_frac_H2O + ) + m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) + m.fs.unit.inlet.temperature[0].fix(feed_temperature) + m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) + + m.fs.unit.bep_eta.fix(efi_pump) + m.fs.unit.flow_ratio.fix(1) + + iscale.set_scaling_factor(m.fs.unit.control_volume.work[0], 1e-2) + iscale.set_scaling_factor(m.fs.unit.work_fluid[0], 1e-2) + iscale.set_scaling_factor(m.fs.unit.bep_flow, 1e4) + iscale.calculate_scaling_factors(m.fs.unit) + + return m + + +def build_pump_isothermal_energybalancetype_none(): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + + m.fs.properties = props2.MCASParameterBlock( + solute_list=["TDS"], + mw_data={"TDS": 58e-3}, + ignore_neutral_charge=True, + ) + + m.fs.unit = Pump(property_package=m.fs.properties) + + # fully specify system + feed_flow_mol = 1 + feed_mol_frac_TDS = 0.035 + feed_pressure_in = 1e5 + feed_pressure_out = 5e5 + feed_temperature = 273.15 + 25 + efi_pump = 0.75 + + feed_mass_frac_H2O = 1 - feed_mol_frac_TDS + m.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "TDS"].fix( + feed_flow_mol * feed_mol_frac_TDS + ) + m.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( + feed_flow_mol * feed_mass_frac_H2O + ) + m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) + m.fs.unit.inlet.temperature[0].fix(feed_temperature) + m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) + m.fs.unit.efficiency_pump.fix(efi_pump) + + iscale.set_scaling_factor(m.fs.unit.control_volume.work[0], 1e-2) + iscale.set_scaling_factor(m.fs.unit.work_fluid[0], 1e-2) + iscale.calculate_scaling_factors(m.fs.unit) + + return m + + +class TestPump(UnitTestHarness): + def configure(self): + m = build_pump() + + self.unit_solutions[m.fs.unit.work_mechanical[0]] = 521.05643 + self.unit_solutions[m.fs.unit.deltaP[0]] = 4e5 + self.unit_solutions[m.fs.unit.ratioP[0]] = 5 + self.unit_solutions[m.fs.unit.work_fluid[0]] = 390.7923225 + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].temperature] = ( + 298.15 ) - m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( - feed_flow_mass * feed_mass_frac_H2O + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].enth_flow] = ( + 9.931834173e4 ) - m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) - m.fs.unit.inlet.temperature[0].fix(feed_temperature) - m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) - m.fs.unit.efficiency_pump.fix(efi_pump) - return m - - @pytest.mark.unit - def test_build(self, Pump_frame): - m = Pump_frame - - # check that IDAES pump model has not changed variable and constraint names - var_list = [ - "work_mechanical", - "deltaP", - "ratioP", - "work_fluid", - "efficiency_pump", - ] - for v in var_list: - assert hasattr(m.fs.unit, v) - var = getattr(m.fs.unit, v) - assert isinstance(var, Var) - - con_list = ["ratioP_calculation", "fluid_work_calculation", "actual_work"] - for c in con_list: - assert hasattr(m.fs.unit, c) - con = getattr(m.fs.unit, c) - assert isinstance(con, Constraint) - - # check that IDAES control volume has not changed variable and constraint names - assert hasattr(m.fs.unit.control_volume, "work") - assert hasattr(m.fs.unit.control_volume, "deltaP") - - assert hasattr(m.fs.unit.control_volume, "material_balances") - assert hasattr(m.fs.unit.control_volume, "pressure_balance") - - # check that energy balance was removed - assert not hasattr(m.fs.unit.control_volume, "enthalpy_balances") - - # check that isothermal constraint was added - assert hasattr(m.fs.unit.control_volume, "isothermal_balance") - assert isinstance(m.fs.unit.control_volume.isothermal_balance, Constraint) - - @pytest.mark.unit - def test_dof(self, Pump_frame): - m = Pump_frame - assert degrees_of_freedom(m) == 0 - - @pytest.mark.unit - def test_calculate_scaling(self, Pump_frame): - m = Pump_frame - - m.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1, index=("Liq", "H2O") - ) - m.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "TDS") - ) - calculate_scaling_factors(m) - - if ( - get_scaling_factor(m.fs.unit.ratioP) is None - ): # if IDAES hasn't specified a scaling factor - set_scaling_factor(m.fs.unit.ratioP, 1) - - # check that all variables have scaling factors - unscaled_var_list = list(unscaled_variables_generator(m)) - assert len(unscaled_var_list) == 0 - # check that all constraints have been scaled - unscaled_constraint_list = list(unscaled_constraints_generator(m)) - assert len(unscaled_constraint_list) == 0 - - @pytest.mark.component - def test_initialize(self, Pump_frame): - initialization_tester(Pump_frame) - - @pytest.mark.component - def test_solve(self, Pump_frame): - m = Pump_frame - results = solver.solve(m) - - # Check for optimal solution - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_conservation(self, Pump_frame): - m = Pump_frame - b = m.fs.unit.control_volume - comp_lst = ["TDS", "H2O"] - - for t in m.fs.config.time: - # mass balance - for j in comp_lst: - assert ( - abs( - value( - b.properties_in[t].flow_mass_phase_comp["Liq", j] - - b.properties_out[t].flow_mass_phase_comp["Liq", j] - ) - ) - <= 1e-6 - ) - - @pytest.mark.component - def test_solution(self, Pump_frame): - m = Pump_frame - - # pump variables - assert pytest.approx(521.06, rel=1e-3) == value(m.fs.unit.work_mechanical[0]) - assert pytest.approx(4e5, rel=1e-3) == value(m.fs.unit.deltaP[0]) - assert pytest.approx(5, rel=1e-3) == value(m.fs.unit.ratioP[0]) - assert pytest.approx(390.79, rel=1e-3) == value(m.fs.unit.work_fluid[0]) - - # outlet state variables - assert pytest.approx(0.965, rel=1e-3) == value( + self.unit_solutions[ m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ "Liq", "H2O" ] - ) - assert pytest.approx(0.035, rel=1e-3) == value( + ] = 0.965 + self.unit_solutions[ m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ "Liq", "TDS" ] - ) - assert pytest.approx(298.15, rel=1e-5) == value( - m.fs.unit.control_volume.properties_out[0].temperature - ) - assert pytest.approx(9.9318e4, rel=1e-5) == value( - m.fs.unit.control_volume.properties_out[0].enth_flow - ) - + ] = 0.035 -class TestEnergyRecoveryDevice(TestPumpIsothermal): - @pytest.fixture(scope="class") - def Pump_frame(self): - m = ConcreteModel() - m.fs = FlowsheetBlock(dynamic=False) - - m.fs.properties = props.SeawaterParameterBlock() + return m - m.fs.unit = EnergyRecoveryDevice(property_package=m.fs.properties) - # fully specify system - feed_flow_mass = 1 - feed_mass_frac_TDS = 0.035 - feed_pressure_in = 5e5 - feed_pressure_out = 1e5 - feed_temperature = 273.15 + 25 - efi_pump = 0.75 +class TestEnergyRecoveryDevice(UnitTestHarness): + def configure(self): + m = build_energy_recovery_device() - feed_mass_frac_H2O = 1 - feed_mass_frac_TDS - m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( - feed_flow_mass * feed_mass_frac_TDS + self.unit_solutions[m.fs.unit.work_mechanical[0]] = -293.094242 + self.unit_solutions[m.fs.unit.deltaP[0]] = -4e5 + self.unit_solutions[m.fs.unit.ratioP[0]] = 0.2 + self.unit_solutions[m.fs.unit.work_fluid[0]] = -390.7923225 + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].temperature] = ( + 298.15 ) - m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( - feed_flow_mass * feed_mass_frac_H2O + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].enth_flow] = ( + 9.89617297e4 ) - m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) - m.fs.unit.inlet.temperature[0].fix(feed_temperature) - m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) - m.fs.unit.efficiency_pump.fix(efi_pump) - return m - - @pytest.mark.component - def test_solution(self, Pump_frame): - m = Pump_frame - - # pump variables - assert pytest.approx(-293.09, rel=1e-3) == value(m.fs.unit.work_mechanical[0]) - assert pytest.approx(-4e5, rel=1e-3) == value(m.fs.unit.deltaP[0]) - assert pytest.approx(1.0 / 5, rel=1e-3) == value(m.fs.unit.ratioP[0]) - assert pytest.approx(-390.79, rel=1e-3) == value(m.fs.unit.work_fluid[0]) - - # outlet state variables - assert pytest.approx(0.965, rel=1e-3) == value( + self.unit_solutions[ m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ "Liq", "H2O" ] - ) - assert pytest.approx(0.035, rel=1e-3) == value( + ] = 0.965 + self.unit_solutions[ m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ "Liq", "TDS" ] - ) - assert pytest.approx(298.15, rel=1e-5) == value( - m.fs.unit.control_volume.properties_out[0].temperature - ) + ] = 0.035 + return m -class TestPumpVariable_Flow: - @pytest.fixture(scope="class") - def Pump_frame(self): - m = ConcreteModel() - m.fs = FlowsheetBlock(dynamic=False) - m.fs.properties = props.SeawaterParameterBlock() +class TestPumpVariableFlow(UnitTestHarness): + def configure(self): + m = build_pump_variable_flow() - m.fs.unit = Pump( - property_package=m.fs.properties, - variable_efficiency=VariableEfficiency.flow, - ) - # fully specify system - feed_flow_mass = 1 - feed_mass_frac_TDS = 0.035 - feed_pressure_in = 1e5 - feed_pressure_out = 5e5 - feed_temperature = 273.15 + 25 - efi_pump = 0.75 - - feed_mass_frac_H2O = 1 - feed_mass_frac_TDS - m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix( - feed_flow_mass * feed_mass_frac_TDS + self.unit_solutions[m.fs.unit.work_mechanical[0]] = 521.05643 + self.unit_solutions[m.fs.unit.deltaP[0]] = 4e5 + self.unit_solutions[m.fs.unit.ratioP[0]] = 5 + self.unit_solutions[m.fs.unit.work_fluid[0]] = 390.7923225 + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].temperature] = ( + 298.15 ) - m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix( - feed_flow_mass * feed_mass_frac_H2O + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].enth_flow] = ( + 9.93183417e4 ) - m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) - m.fs.unit.inlet.temperature[0].fix(feed_temperature) - m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) + self.unit_solutions[ + m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ + "Liq", "H2O" + ] + ] = 0.965 + self.unit_solutions[ + m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ + "Liq", "TDS" + ] + ] = 0.035 - m.fs.unit.bep_eta.fix(efi_pump) - m.fs.unit.flow_ratio.fix(1) return m - @pytest.mark.unit - def test_build(self, Pump_frame): - m = Pump_frame - - # check that IDAES pump model has not changed variable and constraint names - var_list = [ - "bep_flow", - "bep_eta", - "flow_ratio", - ] - for v in var_list: - assert hasattr(m.fs.unit, v) - var = getattr(m.fs.unit, v) - assert isinstance(var, Var) - - assert isinstance(m.fs.unit.flow_ratio_constraint, Constraint) - - @pytest.mark.unit - def test_calculate_scaling(self, Pump_frame): - m = Pump_frame - m.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1, index=("Liq", "H2O") - ) - m.fs.properties.set_default_scaling( - "flow_mass_phase_comp", 1e2, index=("Liq", "TDS") - ) - - calculate_scaling_factors(m) - - # if IDAES hasn't specified a scaling factor - if get_scaling_factor(m.fs.unit.ratioP) is None: - set_scaling_factor(m.fs.unit.ratioP, 1) - - if get_scaling_factor(m.fs.unit.control_volume.work) is None: - set_scaling_factor(m.fs.unit.control_volume.work, 1) - - # check that all variables and constraints have scaling factors - unscaled_var_list = list(unscaled_variables_generator(m)) - assert len(unscaled_var_list) == 0 - - unscaled_constraint_list = list(unscaled_constraints_generator(m)) - assert len(unscaled_constraint_list) == 0 - - @pytest.mark.unit - def test_dof(self, Pump_frame): - m = Pump_frame - assert degrees_of_freedom(m) == 0 - - @pytest.mark.component - def test_initialize(self, Pump_frame): - initialization_tester(Pump_frame) - - @pytest.mark.component - def test_solve(self, Pump_frame): - results = solver.solve(Pump_frame) - - # Check for optimal solution - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.unit - def test_conservation(self, Pump_frame): - m = Pump_frame - b = m.fs.unit.control_volume - comp_lst = ["TDS", "H2O"] - - for t in m.fs.config.time: - # mass balance - for j in comp_lst: - assert ( - abs( - value( - b.properties_in[t].flow_mass_phase_comp["Liq", j] - - b.properties_out[t].flow_mass_phase_comp["Liq", j] - ) - ) - <= 1e-6 - ) - - @pytest.mark.unit - def test_units(self, Pump_frame): - assert_units_consistent(Pump_frame) - - @pytest.mark.component - def test_solution(self, Pump_frame): - m = Pump_frame - - default_flow_vol = m.fs.unit.bep_flow() - assert pytest.approx(9.9318e4, rel=1e-5) == value( - m.fs.unit.control_volume.properties_out[0].enth_flow - ) - assert pytest.approx(1, rel=1e-3) == value(m.fs.unit.eta_ratio[0]) - - # Test low bep flow case - m.fs.unit.flow_ratio.unfix() - m.fs.unit.bep_flow.fix(default_flow_vol / 2) - results = solver.solve(m) - assert pytest.approx(0.4, rel=1e-5) == value(m.fs.unit.eta_ratio[0]) - - # Test high bep flow case - m.fs.unit.bep_flow.fix(default_flow_vol * 2) - results = solver.solve(m) - assert pytest.approx(0.4, rel=1e-5) == value(m.fs.unit.eta_ratio[0]) - - # Test sub-optimal bep flow case - m.fs.unit.bep_flow.fix(default_flow_vol * 0.8) - results = solver.solve(m) - assert pytest.approx(0.9345625, rel=1e-5) == value(m.fs.unit.eta_ratio[0]) - - -class TestPumpIsothermal_with_energybalancetype_none: - @pytest.fixture(scope="class") - def Pump_frame(self): - m = ConcreteModel() - m.fs = FlowsheetBlock(dynamic=False) - - m.fs.properties = props2.MCASParameterBlock( - solute_list=["TDS"], - mw_data={"TDS": 58e-3}, - ignore_neutral_charge=True, - ) - m.fs.unit = Pump(property_package=m.fs.properties) +class TestPumpIsothermal(UnitTestHarness): + def configure(self): + m = build_pump_isothermal_energybalancetype_none() - # fully specify system - feed_flow_mol = 1 - feed_mol_frac_TDS = 0.035 - feed_pressure_in = 1e5 - feed_pressure_out = 5e5 - feed_temperature = 273.15 + 25 - efi_pump = 0.75 - - feed_mass_frac_H2O = 1 - feed_mol_frac_TDS - m.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "TDS"].fix( - feed_flow_mol * feed_mol_frac_TDS - ) - m.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - feed_flow_mol * feed_mass_frac_H2O + self.unit_solutions[m.fs.unit.work_mechanical[0]] = 10.3466667 + self.unit_solutions[m.fs.unit.deltaP[0]] = 4e5 + self.unit_solutions[m.fs.unit.ratioP[0]] = 5 + self.unit_solutions[m.fs.unit.work_fluid[0]] = 7.76 + self.unit_solutions[m.fs.unit.control_volume.properties_out[0].temperature] = ( + 298.15 ) - m.fs.unit.inlet.pressure[0].fix(feed_pressure_in) - m.fs.unit.inlet.temperature[0].fix(feed_temperature) - m.fs.unit.outlet.pressure[0].fix(feed_pressure_out) - m.fs.unit.efficiency_pump.fix(efi_pump) - return m + self.unit_solutions[ + m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ + "Liq", "H2O" + ] + ] = 0.01737 + self.unit_solutions[ + m.fs.unit.control_volume.properties_out[0].flow_mass_phase_comp[ + "Liq", "TDS" + ] + ] = 0.00203 - @pytest.mark.unit - def test_build(self, Pump_frame): - m = Pump_frame - - # check that IDAES pump model has not changed variable and constraint names - var_list = [ - "work_mechanical", - "deltaP", - "ratioP", - "work_fluid", - "efficiency_pump", - ] - for v in var_list: - assert hasattr(m.fs.unit, v) - var = getattr(m.fs.unit, v) - assert isinstance(var, Var) - - con_list = ["ratioP_calculation", "fluid_work_calculation", "actual_work"] - for c in con_list: - assert hasattr(m.fs.unit, c) - con = getattr(m.fs.unit, c) - assert isinstance(con, Constraint) - - # check that IDAES control volume has not changed variable and constraint names - assert hasattr(m.fs.unit.control_volume, "work") - assert hasattr(m.fs.unit.control_volume, "deltaP") - - assert hasattr(m.fs.unit.control_volume, "material_balances") - assert hasattr(m.fs.unit.control_volume, "pressure_balance") - assert not hasattr(m.fs.unit.control_volume, "energy_balance") - - # check that energy balance was removed - assert not hasattr(m.fs.unit.control_volume, "enthalpy_balances") - - # check that isothermal constraint was added - assert hasattr(m.fs.unit.control_volume, "isothermal_balance") - assert isinstance(m.fs.unit.control_volume.isothermal_balance, Constraint) - - @pytest.mark.unit - def test_dof(self, Pump_frame): - m = Pump_frame - assert degrees_of_freedom(m) == 0 - - @pytest.mark.unit - def test_calculate_scaling(self, Pump_frame): - m = Pump_frame - - m.fs.properties.set_default_scaling( - "flow_mol_phase_comp", 1, index=("Liq", "H2O") - ) - m.fs.properties.set_default_scaling( - "flow_mol_phase_comp", 1e2, index=("Liq", "TDS") - ) - calculate_scaling_factors(m) - - if ( - get_scaling_factor(m.fs.unit.ratioP) is None - ): # if IDAES hasn't specified a scaling factor - set_scaling_factor(m.fs.unit.ratioP, 1) - - # check that all variables have scaling factors - unscaled_var_list = list(unscaled_variables_generator(m)) - assert len(unscaled_var_list) == 0 - # check that all constraints have been scaled - unscaled_constraint_list = list(unscaled_constraints_generator(m)) - assert len(unscaled_constraint_list) == 0 - - @pytest.mark.component - def test_initialize(self, Pump_frame): - initialization_tester(Pump_frame) - - @pytest.mark.component - def test_solve(self, Pump_frame): - m = Pump_frame - results = solver.solve(m) - - # Check for optimal solution - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_conservation(self, Pump_frame): - m = Pump_frame - b = m.fs.unit.control_volume - comp_lst = ["TDS", "H2O"] - - for t in m.fs.config.time: - # mole balance - for j in comp_lst: - assert ( - abs( - value( - b.properties_in[t].flow_mol_phase_comp["Liq", j] - - b.properties_out[t].flow_mol_phase_comp["Liq", j] - ) - ) - <= 1e-6 - ) + return m