diff --git a/watertap/examples/chemistry/__init__.py b/watertap/examples/chemistry/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/watertap/examples/chemistry/chem_scaling_utils.py b/watertap/examples/chemistry/chem_scaling_utils.py deleted file mode 100644 index 2537f99e15..0000000000 --- a/watertap/examples/chemistry/chem_scaling_utils.py +++ /dev/null @@ -1,207 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This is a set of scaling utility helper functions used by the chemistry tests. -""" -from idaes.core.util import scaling as iscale -from pyomo.environ import value - -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - power_law_equil, - solubility_product, -) - -__author__ = "Austin Ladshaw, Xinhong Liu" - -## Helper function for setting eps values associated with solubility_product functions -# NOTE: Function does nothing if no solubility reactions are present -def _set_eps_vals(rxn_params, rxn_config, factor=1e-2, max_k_eq_ref=1e-12): - # For solubility reactions, have to set the eps value - if hasattr(rxn_params, "equilibrium_reaction_idx"): - for rid in rxn_params.equilibrium_reaction_idx: - # Grab the 'k_eq_ref' value from the reaction config - scale = rxn_config["equilibrium_reactions"][rid]["parameter_data"][ - "k_eq_ref" - ][0] - - # NOTE: ONLY certain functions have an eps value that we need to set - if hasattr(rxn_params.component("reaction_" + rid), "eps"): - # highest allowable value for setting eps based on k_eq_ref - rxn_params.component("reaction_" + rid).eps.value = ( - min(max_k_eq_ref, scale) * factor - ) - - -## Helper function for setting scaling factors for equilibrium reactions -def _set_equ_rxn_scaling(unit, rxn_params, rxn_config, min_k_eq_ref=1e-3): - # Add scaling factors for reactions (changes depending on if it is a log form or not) - for i in unit.control_volume.equilibrium_reaction_extent_index: - # i[0] = time, i[1] = reaction - # Grab the 'k_eq_ref' value from the reaction config - scale = max( - min_k_eq_ref, - rxn_config["equilibrium_reactions"][i[1]]["parameter_data"]["k_eq_ref"][0], - ) - iscale.set_scaling_factor( - unit.control_volume.equilibrium_reaction_extent[0.0, i[1]], 10 / scale - ) - - # Add scale for constraints in log form - log_scale = min(min_k_eq_ref, 1e-3) - # Scale keq calculation from keq_ref - iscale.constraint_scaling_transform( - unit.control_volume.reactions[0.0].log_k_eq_constraint[i[1]], - 1 * scale / log_scale, - ) - - if ( - rxn_config["equilibrium_reactions"][i[1]]["equilibrium_form"] - is solubility_product - or rxn_config["equilibrium_reactions"][i[1]]["equilibrium_form"] - is power_law_equil - ): - if hasattr(rxn_params.component("reaction_" + i[1]), "eps"): - iscale.constraint_scaling_transform( - unit.control_volume.reactions[0.0].equilibrium_constraint[i[1]], - 10 / scale, - ) - else: - iscale.constraint_scaling_transform( - unit.control_volume.reactions[0.0].equilibrium_constraint[i[1]], - 1 / scale, - ) - - else: - # Solubility_product with eps has different order of magnitude compare to other equilibrium constraints - if hasattr(rxn_params.component("reaction_" + i[1]), "eps"): - iscale.constraint_scaling_transform( - unit.control_volume.reactions[0.0].equilibrium_constraint[i[1]], - 1 * scale / log_scale, - ) - else: - iscale.constraint_scaling_transform( - unit.control_volume.reactions[0.0].equilibrium_constraint[i[1]], - 0.1 * scale / log_scale, - ) - - -## Helper function for setting scaling factors for inherent reactions -def _set_inherent_rxn_scaling(unit, thermo_config, min_k_eq_ref=1e-3): - # Add scaling factors for reactions (changes depending on if it is a log form or not) - for i in unit.control_volume.inherent_reaction_extent_index: - # i[0] = time, i[1] = reaction - # Grab the 'k_eq_ref' value from the thermo config - scale = max( - min_k_eq_ref, - thermo_config["inherent_reactions"][i[1]]["parameter_data"]["k_eq_ref"][0], - ) - iscale.set_scaling_factor( - unit.control_volume.inherent_reaction_extent[0.0, i[1]], 10 / scale - ) - iscale.constraint_scaling_transform( - unit.control_volume.properties_out[0.0].inherent_equilibrium_constraint[ - i[1] - ], - 0.1, - ) - - -## Helper function for setting scaling factors for rate reactions -## TODO: Need to work out better scaling for rate reactions using min_scale -def _set_rate_rxn_scaling(rxn_params, unit, min_scale=1e-3): - for i in rxn_params.rate_reaction_idx: - scale = value(unit.control_volume.reactions[0.0].reaction_rate[i].expr) - iscale.set_scaling_factor( - unit.control_volume.rate_reaction_extent[0.0, i], 1000 / scale - ) - - -## Helper function for setting scaling factors for the mass balance for FpcTP state vars -def _set_mat_bal_scaling_FpcTP(unit, min_flow_mol_phase_comp=1e-3): - # For species - for i in unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - scale = max( - min_flow_mol_phase_comp, unit.inlet.flow_mol_phase_comp[0, i[0], i[1]].value - ) - - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], 10 / scale - ) - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0].mole_frac_phase_comp[i], 100 / scale - ) - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], 10 / scale - ) - iscale.constraint_scaling_transform( - unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - if hasattr(unit.control_volume, "volume"): - iscale.set_scaling_factor( - unit.control_volume.volume, 10 / unit.volume[0.0].value - ) - - -## Helper function for setting scaling factors for the mass balance for FTPx state vars -def _set_mat_bal_scaling_FTPx(unit, min_mole_frac_comp=1e-3): - # For species - for i in unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - scale = max(min_mole_frac_comp, unit.inlet.mole_frac_comp[0, i[1]].value) - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], 10 / scale - ) - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0].mole_frac_phase_comp[i], 100 / scale - ) - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], 10 / scale - ) - iscale.constraint_scaling_transform( - unit.control_volume.properties_out[0.0].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - if hasattr(unit.control_volume, "volume"): - iscale.set_scaling_factor( - unit.control_volume.volume, 10 / unit.volume[0.0].value - ) - - -## Helper function for setting energy balance scaling factors -def _set_ene_bal_scaling(unit): - max_enth_mol_phase = 1 - min_scale = 1 - for phase in unit.control_volume.properties_in[0.0].enth_mol_phase: - val = max( - min_scale, - abs( - value(unit.control_volume.properties_in[0.0].enth_mol_phase[phase].expr) - ), - ) - max_enth_mol_phase = max(val, max_enth_mol_phase) - iscale.set_scaling_factor( - unit.control_volume.properties_in[0.0]._enthalpy_flow_term[phase], 1 / val - ) - iscale.set_scaling_factor( - unit.control_volume.properties_out[0.0]._enthalpy_flow_term[phase], 1 / val - ) - - iscale.constraint_scaling_transform( - unit.control_volume.enthalpy_balances[0.0], 1 / max_enth_mol_phase - ) diff --git a/watertap/examples/chemistry/tests/__init__.py b/watertap/examples/chemistry/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/watertap/examples/chemistry/tests/conftest.py b/watertap/examples/chemistry/tests/conftest.py deleted file mode 100644 index afa45f33a7..0000000000 --- a/watertap/examples/chemistry/tests/conftest.py +++ /dev/null @@ -1,55 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# -import pytest -from _pytest.config import Config -from pyomo.common.dependencies import attempt_import - -from watertap.edb import ElectrolyteDB -from watertap.edb.commands import _load_bootstrap - -mongomock, mongomock_available = attempt_import("mongomock") - - -class MockDB(ElectrolyteDB): - def __init__(self, db="foo", **kwargs): - if not mongomock_available: - pytest.skip(reason="mongomock (EDB optional dependency) not available") - - self._client = mongomock.MongoClient() - self._db = getattr(self._client, db) - # note: don't call superclass! - self._database_name = db - self._server_url = "mock" - - -def _reset(edb: ElectrolyteDB): - edb._client.drop_database(edb.database) - - -@pytest.fixture(scope="module") -def edb(pytestconfig: Config) -> ElectrolyteDB: - """ - Create and populate an EDB instance - """ - mock_allowed = not pytestconfig.option.edb_no_mock - if ElectrolyteDB.can_connect(): - _edb = ElectrolyteDB() - else: - if mock_allowed: - _edb = MockDB() - else: - pytest.fail( - "EDB could not connect to a database instance, but mocking is not allowed" - ) - _load_bootstrap(_edb) - yield _edb - _reset(_edb) diff --git a/watertap/examples/chemistry/tests/test__fixtures.py b/watertap/examples/chemistry/tests/test__fixtures.py deleted file mode 100644 index 452c5411e7..0000000000 --- a/watertap/examples/chemistry/tests/test__fixtures.py +++ /dev/null @@ -1,33 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# -import pytest - -from watertap.edb import ElectrolyteDB - - -@pytest.fixture -def edb_client_server_info(edb: ElectrolyteDB) -> dict: - """ - Perform an operation that ensures that the `edb` fixture has a client that: - - Is able to connect to the server (non-mock), or - - Is a "convincing fake" (mock), so that test functions using `edb` can expect a realistic behavior - - Additionally, if this fixture is dispatched before "real" users (here this is done by using a `test__` prefix with two underscores), - it avoids any first-use inconsistencies, such as e.g. the time spent to wait for a connection - being counted as part of the duration of the first test for which the `edb` fixture is instantiated. - """ - return edb._client.server_info() - - -def test_edb_client_server_connection(edb_client_server_info: dict): - "This should fail unless the EDB instance's client is either successfully connected or a realistic mock (i.e. not None, a dict, ...)." - assert edb_client_server_info["ok"] diff --git a/watertap/examples/chemistry/tests/test_chlorination.py b/watertap/examples/chemistry/tests/test_chlorination.py deleted file mode 100644 index db51b8ebea..0000000000 --- a/watertap/examples/chemistry/tests/test_chlorination.py +++ /dev/null @@ -1,932 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is to establish that the core chemistry packages in IDAES - can solve a complex chlorination problem wherein the hypochlorite - species get scavenged by NH3 in solution. NH3 binds with the chlorine - in solution to dilute the effectiveness of the chlorination process, - thus, it is important to have a model that can account for these - impacts. - - Additionally, this test will establish that we can mix-and-match - both inherent and equilibrium expressions in the system. This may - be an important factor from a modeling perspective as not all - unit processes will have all inherent reactions, thus, we may desire - to add/remove reactions as equilibrium statements at will. - - Inherent Reactions: - H2O <---> H + OH - NH4 <---> H + NH3 - HOCl <---> H + OCl - - True NH3/HOCl Reactions (not included): - NH3 + HOCl <---> NH2Cl + H2O - NH2Cl + HOCl <---> NHCl2 + H2O - NHCl2 + HOCl <---> NCl3 + H2O - - NOTE: Although there are several cascading reactions for NH3 to scavenge - HOCl, we only care about the fact that every 1 NH3 can take upto - 3 HOCl, so here I am lumping all ammonia chloride formation reactions - into a single reaction. This vastly improves the convergence behavior - of the full system of equations. - - Effective NH3/HOCl Reaction (included): - NH3 + 3 HOCl <---> NCl3 + 3 H2O -""" -# Importing testing libraries -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -# Imports from idaes core -from idaes.core import AqueousPhase -from idaes.core.base.components import Solvent, Solute, Cation, Anion -from idaes.core.base.phases import PhaseType as PT - -# Imports from idaes generic models -import idaes.models.properties.modular_properties.pure.Perrys as Perrys -from idaes.models.properties.modular_properties.state_definitions import FTPx -from idaes.models.properties.modular_properties.eos.ideal import Ideal - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -# Import built-in van't Hoff function -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -from idaes.core.util import scaling as iscale - -# Import pyomo methods to check the system units -from pyomo.util.check_units import assert_units_consistent - -# Import idaes methods to check the model during construction -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import ( - degrees_of_freedom, - fixed_variables_set, - activated_constraints_set, - number_variables, - number_total_constraints, - number_unused_variables, -) - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor - -# Import the core idaes objects for Flowsheets and types of balances -from idaes.core import FlowsheetBlock - -# Import log10 function from pyomo -from pyomo.environ import log10 - -import idaes.logger as idaeslog - -# Import scaling helper functions -from watertap.examples.chemistry.chem_scaling_utils import ( - _set_equ_rxn_scaling, - _set_inherent_rxn_scaling, - _set_mat_bal_scaling_FTPx, - _set_ene_bal_scaling, -) - -__author__ = "Austin Ladshaw" - -# Configuration dictionary -thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": (4.6543, None), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "NH3": { - "type": Solute, - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "parameter_data": { - "mw": (17.031, pyunits.g / pyunits.mol), - "pressure_crit": (113e5, pyunits.Pa), - "temperature_crit": (405.4, pyunits.K), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (3.5383, pyunits.kmol * pyunits.m**-3), - "2": (0.25443, pyunits.dimensionless), - "3": (405.65, pyunits.K), - "4": (0.2888, pyunits.dimensionless), - }, - "cp_mol_ig_comp_coeff": { - "A": (19.99563, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 49.77119, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - -15.37599, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - 1.921168, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.189174, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-53.30667, pyunits.kJ / pyunits.mol), - "G": (203.8591, pyunits.J / pyunits.mol / pyunits.K), - "H": (-45.89806, pyunits.kJ / pyunits.mol), - }, - "cp_mol_liq_comp_coeff": { - "1": (71128, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "enth_mol_form_liq_comp_ref": (-80.29, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (-45.9, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 111, - pyunits.J / pyunits.K / pyunits.mol, - ), - "entr_mol_form_vap_comp_ref": (192.77, pyunits.J / pyunits.mol), - "pressure_sat_comp_coeff": { - "A": (4.86886, None), - "B": (1113.928, pyunits.K), - "C": (-10.409, pyunits.K), - }, - } - # End parameter_data - }, - "NH4_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.039, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (3.5383, pyunits.kmol * pyunits.m**-3), - "2": (0.25443, pyunits.dimensionless), - "3": (405.65, pyunits.K), - "4": (0.2888, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-132.5, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (71128, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 113.4, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HOCl": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (52.46, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (4.985, pyunits.kmol * pyunits.m**-3), - "2": (0.36, pyunits.dimensionless), - "3": (1464.06, pyunits.K), - "4": (0.739, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-120.9, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (83993.8, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 142, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OCl_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (51.46, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (4.985, pyunits.kmol * pyunits.m**-3), - "2": (0.36, pyunits.dimensionless), - "3": (1464.06, pyunits.K), - "4": (0.739, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-107.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (83993.8, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (42, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "NH2Cl": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (51.48, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (4.519, pyunits.kmol * pyunits.m**-3), - "2": (0.444, pyunits.dimensionless), - "3": (988.9, pyunits.K), - "4": (0.37, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (66.9, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (71128, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "NHCl2": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (85.92, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (4.519, pyunits.kmol * pyunits.m**-3), - "2": (0.444, pyunits.dimensionless), - "3": (988.9, pyunits.K), - "4": (0.37, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (176.6, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (71128, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "NCl3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (120.365, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (4.519, pyunits.kmol * pyunits.m**-3), - "2": (0.444, pyunits.dimensionless), - "3": (988.9, pyunits.K), - "4": (0.37, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (299.2, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (71128, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 650), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Inherent reactions - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "NH4_Ka": { - "stoichiometry": { - ("Liq", "NH4_+"): -1, - ("Liq", "H_+"): 1, - ("Liq", "NH3"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (52.21, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-9.2767 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "NH4_+"): -1, - ("Liq", "H_+"): 1, - ("Liq", "NH3"): 1, - }, - } - # End parameter_data - }, - # End R2 - "HOCl_Ka": { - "stoichiometry": { - ("Liq", "HOCl"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OCl_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (13.8, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-7.6422 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HOCl"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OCl_-"): 1, - }, - } - # End parameter_data - } - # End R4 - } - # End equilibrium_reactions -} -# End thermo_config definition - -# NOTE: These reactions are (usually) complete reactions, thus, it may be -# better to model them as "stoichiometric" reactions for better -# convergence behavior of the non-linear system -# Define the reaction_config for NH3/HOCl reaction -reaction_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "NCl3_K": { - "stoichiometry": { - ("Liq", "NH3"): 1, - ("Liq", "HOCl"): 3, - ("Liq", "NCl3"): -1, - ("Liq", "H2O"): -3, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-11.4 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "NH3"): 1, - ("Liq", "HOCl"): 1, - ("Liq", "NCl3"): -1, - ("Liq", "H2O"): 0, - }, - } - # End parameter_data - }, - # End R1 - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Get default solver for testing -solver = get_solver() - -# Start test class -class TestChlorination: - @pytest.fixture(scope="class") - def chlorination_obj(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=True, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "OCl_-"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "NH4_+"].fix(0.0) - - model.fs.unit.inlet.mole_frac_comp[0, "NH2Cl"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "NHCl2"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "NCl3"].fix(0.0) - - waste_stream_ammonia = 1 # mg/L - total_molar_density = 54.8 # mol/L - total_ammonia_inlet = waste_stream_ammonia / 17000 # mol/L - total_molar_density += total_ammonia_inlet - - # Free Chlorine (mg-Cl2/L) = total_chlorine_inlet (mol/L) * 70,900 - free_chlorine_added = 15 # mg/L as Cl2 - total_chlorine_inlet = free_chlorine_added / 70900 # mol/L - total_molar_density += total_chlorine_inlet - - model.fs.unit.inlet.mole_frac_comp[0, "NH3"].fix( - total_ammonia_inlet / total_molar_density - ) - model.fs.unit.inlet.mole_frac_comp[0, "HOCl"].fix( - total_chlorine_inlet / total_molar_density - ) - - # Perform a summation of all non-H2O molefractions to find the H2O molefraction - sum = 0 - for i in model.fs.unit.inlet.mole_frac_comp: - # NOTE: i will be a tuple with format (time, component) - if i[1] != "H2O": - sum += value(model.fs.unit.inlet.mole_frac_comp[i[0], i[1]]) - - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1 - sum) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(300.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.mark.unit - def test_build_model(self, chlorination_obj): - model = chlorination_obj - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 10 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - assert "OCl_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OCl_-"), Anion) - - assert "NH4_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("NH4_+"), Cation) - - assert "NH2Cl" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("NH2Cl"), Solute) - - assert "NHCl2" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("NHCl2"), Solute) - - assert "NCl3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.NCl3, Solute) - - assert "NH3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.NH3, Solute) - - assert "HOCl" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.HOCl, Solute) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 1 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.component - def test_units(self, chlorination_obj): - model = chlorination_obj - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof(self, chlorination_obj): - model = chlorination_obj - assert degrees_of_freedom(model) == 0 - - @pytest.mark.unit - def test_stats(self, chlorination_obj): - model = chlorination_obj - assert number_variables(model) == 278 - assert number_total_constraints(model) == 99 - assert number_unused_variables(model) == 43 - - @pytest.mark.component - def test_scaling(self, chlorination_obj): - model = chlorination_obj - - _set_inherent_rxn_scaling(model.fs.unit, thermo_config) - _set_equ_rxn_scaling(model.fs.unit, model.fs.rxn_params, reaction_config) - _set_mat_bal_scaling_FTPx(model.fs.unit) - _set_ene_bal_scaling(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - assert isinstance( - model.fs.unit.control_volume.reactions[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize(self, chlorination_obj): - model = chlorination_obj - - orig_fixed_vars = fixed_variables_set(model) - orig_act_consts = activated_constraints_set(model) - - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - - fin_fixed_vars = fixed_variables_set(model) - fin_act_consts = activated_constraints_set(model) - - assert degrees_of_freedom(model) == 0 - - assert len(fin_act_consts) == len(orig_act_consts) - assert len(fin_fixed_vars) == len(orig_fixed_vars) - - @pytest.mark.component - def test_solve(self, chlorination_obj): - model = chlorination_obj - solver.options["max_iter"] = 200 - results = solver.solve(model, tee=True) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution(self, chlorination_obj): - model = chlorination_obj - - assert pytest.approx(300.002, rel=1e-5) == value( - model.fs.unit.outlet.temperature[0] - ) - assert pytest.approx(10.0000, rel=1e-5) == value( - model.fs.unit.outlet.flow_mol[0] - ) - assert pytest.approx(101325.0, rel=1e-5) == value( - model.fs.unit.outlet.pressure[0] - ) - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - assert pytest.approx(55.2044, rel=1e-5) == total_molar_density - - pH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "H_+"] * total_molar_density) - ) - pOH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"] * total_molar_density) - ) - assert pytest.approx(6.0522001, rel=1e-4) == pH - assert pytest.approx(7.9476201, rel=1e-4) == pOH - - hypo_remaining = ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "HOCl" - ] - ) - / 1000 - ) - hypo_remaining += ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "OCl_-" - ] - ) - / 1000 - ) - combined_chlorine = 0 - combined_chlorine += ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "NH2Cl" - ] - ) - / 1000 - ) - combined_chlorine += ( - 2 - * value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "NHCl2" - ] - ) - / 1000 - ) - combined_chlorine += ( - 3 - * value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "NCl3" - ] - ) - / 1000 - ) - - hypo_remaining = hypo_remaining * 70900 - combined_chlorine = combined_chlorine * 70900 - assert pytest.approx(2.50902829, rel=1e-3) == hypo_remaining - assert pytest.approx(12.607332, rel=1e-3) == combined_chlorine diff --git a/watertap/examples/chemistry/tests/test_docs.py b/watertap/examples/chemistry/tests/test_docs.py deleted file mode 100644 index dc5b4253a6..0000000000 --- a/watertap/examples/chemistry/tests/test_docs.py +++ /dev/null @@ -1,93 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# -""" -Tests for documentation -""" -# stdlib -from pathlib import Path -import re - -# third-party -import pytest - -__authors__ = ("Dan Gunter",) - - -# root of documentation -docs_dir = Path(__file__).parent.parent.parent / "docs" -# generated by sphinx-apidoc command -apidoc = docs_dir / "apidoc" - - -def has_docs(): - # assume this file is under //tests/ , docs in /docs - # check dir docs/apidoc dir - if not apidoc.exists() or not apidoc.is_dir(): - return False - # XXX: add test for Sphinx too? - return True - - -def get_autodoc(pth): - """Get the name, location, and options for all autodoc directives - in the file at 'pth'. - - Yields: - tuple of length 4: type of thing, name of it, filename, line number (from 1) - """ - mode = "text" # state machine with 2 states; other one is "autodoc" - auto_what, auto_name, auto_options, auto_where = None, None, None, None - with open(pth, encoding="utf-8") as f: - for i, line in enumerate(f): - if mode == "text": - match = re.match(r"..\s+auto(\w+)::\s*([a-zA-Z][a-zaA-Z_.0-9]*)", line) - if match: - auto_what = match.group(1) - auto_name = match.group(2) - auto_where = f"{f.name}:{i + 1}" - auto_options = [] - mode = "autodoc" - elif mode == "autodoc": - # blank or unindented line, back to text mode - if not re.match(r"^\s\s", line): - yield auto_what, auto_name, auto_where, auto_options - auto_what = None - mode = "text" - else: - # try to extract an option of the form ' ::' - match = re.match(r"\s+:(\w+):", line) - if match: - auto_options.append(match.group(1)) - else: - raise RuntimeError(f"unknown mode: {mode}") - if auto_what: - yield auto_what, auto_name, auto_where, auto_options - - -@pytest.mark.skipif(not has_docs(), reason="Docs not present") -def test_autodoc_has_noindex(): - bad = [] # is anyone truly bad or good? when it comes to autodocs: yes - for p in docs_dir.glob("**/*.rst"): - try: - # If it is NOT a subdir of apidoc, raises ValueError - p.relative_to(apidoc) - # ignore anything under apidoc - except ValueError: - # not in apidoc, so look for autodoc sections - for what, name, where, options in get_autodoc(p): - if "noindex" not in options: - n = len(bad) + 1 - bad.append(f"{n}) {what} '{name}' in {where}") - if len(bad) > 0: - bad_ones = "\n".join(bad) - err = f"{len(bad)} 'autodoc' directive(s) missing 'noindex':\n{bad_ones}" - assert False, err diff --git a/watertap/examples/chemistry/tests/test_enrtl_water_pH.py b/watertap/examples/chemistry/tests/test_enrtl_water_pH.py deleted file mode 100644 index 1167164f34..0000000000 --- a/watertap/examples/chemistry/tests/test_enrtl_water_pH.py +++ /dev/null @@ -1,1123 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is to establish that the core chemistry packages in IDAES solve - a simple water dissociation problem and return the correct pH value, as well - as excerising the implementation of the ENRTL model within that same context. - - This test also includes checking the pH value of a typical acid and checking - the calculated activity coefficients for a relatively dilute system of ions. -""" -# Importing testing libraries -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -# Imports from idaes core -from idaes.core import AqueousPhase -from idaes.core.base.components import Solvent, Solute, Cation, Anion -from idaes.core.base.phases import PhaseType as PT - -# Imports from idaes generic models -import idaes.models.properties.modular_properties.pure.Perrys as Perrys -from idaes.models.properties.modular_properties.pure.electrolyte import ( - relative_permittivity_constant, -) -from idaes.models.properties.modular_properties.state_definitions import FTPx -from idaes.models.properties.modular_properties.eos.enrtl import ENRTL -from idaes.models.properties.modular_properties.eos.enrtl_reference_states import ( - Unsymmetric, -) - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -# Import built-in van't Hoff function -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -# Import the scaling methods -from idaes.core.util import scaling as iscale - -# Import pyomo methods to check the system units -from pyomo.util.check_units import assert_units_consistent - -# Import idaes methods to check the model during construction -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import degrees_of_freedom - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor - -# Import the core idaes objects for Flowsheets and types of balances -from idaes.core import FlowsheetBlock - -# Import log10 function from pyomo -from pyomo.environ import log10 - -import idaes.logger as idaeslog - -__author__ = "Austin Ladshaw" - -# Configuration dictionary -water_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "relative_permittivity_liq_comp": relative_permittivity_constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "relative_permittivity_liq_comp": 78.54, - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": (4.6543, None), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": { - "type": AqueousPhase, - "equation_of_state": ENRTL, - "equation_of_state_options": {"reference_state": Unsymmetric}, - }, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 650), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Inherent reactions - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.activity, - "parameter_data": { - # NOTE: The k value on the activity basis is UNITLESS - # based on a standard molar concentration of 1 mol/L - # HOWEVER, the typical Kw dissociation constant of - # 1e-14 is defined on a molar basis. Thus, we must - # divide by the total (~55.2 M) concentration raised to the - # net reaction order (i.e., 2 in this case). - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2**2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - } - # End R1 - } - # End equilibrium_reactions -} -# End thermo_config definition - -# Define the reaction_config for water dissociation -# This config is REQUIRED to use EquilibriumReactor even if we have no equilibrium reactions -reaction_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "dummy": { - "stoichiometry": {}, - "equilibrium_form": log_power_law_equil, - } - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Get default solver for testing -solver = get_solver() - -# Start test class -class TestENRTLwater: - @pytest.fixture(scope="class") - def water_model(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**water_thermo_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=False, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - # NOTE: ENRTL model cannot initialize if the inlet values are 0 - zero = 1e-20 - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1.0 - 2 * zero) - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.mark.unit - def test_build_water(self, water_model): - model = water_model - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 3 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - @pytest.mark.unit - def test_units_water(self, water_model): - model = water_model - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof_water(self, water_model): - model = water_model - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_scaling_water(self, water_model): - model = water_model - - for i in model.fs.unit.control_volume.inherent_reaction_extent_index: - scale = value( - model.fs.unit.control_volume.properties_out[0.0].k_eq[i[1]].expr - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.inherent_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].inherent_equilibrium_constraint[i[1]], - 0.1, - ) - - # Next, try adding scaling for species - min = 1e-3 - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if model.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min: - scale = model.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - i - ], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert hasattr(model.fs.unit.control_volume, "scaling_factor") - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert hasattr( - model.fs.unit.control_volume.properties_out[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert hasattr( - model.fs.unit.control_volume.properties_in[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize_solver_water(self, water_model): - model = water_model - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve_water(self, water_model): - model = water_model - solver.options["max_iter"] = 200 - solver.options["halt_on_ampl_error"] = "yes" - results = solver.solve(model, tee=True, symbolic_solver_labels=True) - print(results.solver.termination_condition) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution_water(self, water_model): - model = water_model - - pH = -log10( - value( - model.fs.unit.control_volume.properties_out[0.0].act_phase_comp[ - "Liq", "H_+" - ] - ) - * 55.2 - ) - pOH = -log10( - value( - model.fs.unit.control_volume.properties_out[0.0].act_phase_comp[ - "Liq", "OH_-" - ] - ) - * 55.2 - ) - - assert pytest.approx(7.0001611, rel=1e-4) == pH - assert pytest.approx(7.0001611, rel=1e-4) == pOH - - -# Configuration dictionary -carbonic_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "relative_permittivity_liq_comp": relative_permittivity_constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "relative_permittivity_liq_comp": 78.54, - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": (4.6543, None), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (61.0168, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (60.01, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Na_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (22.989769, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.252, pyunits.kmol * pyunits.m**-3), - "2": (0.347, pyunits.dimensionless), - "3": (1595.8, pyunits.K), - "4": (0.6598, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-240.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (59, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": { - "type": AqueousPhase, - "equation_of_state": ENRTL, - "equation_of_state_options": {"reference_state": Unsymmetric}, - }, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 650), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Inherent reactions - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.activity, - "parameter_data": { - # NOTE: The k value on the activity basis is UNITLESS - # based on a standard molar concentration of 1 mol/L - # HOWEVER, the typical Kw dissociation constant of - # 1e-14 is defined on a molar basis. Thus, we must - # divide by the total (~55.2 M) concentration raised to the - # net reaction order (i.e., 2 in this case). - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2**2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.activity, - "parameter_data": { - # NOTE: The k value on the activity basis is UNITLESS - # based on a standard molar concentration of 1 mol/L - # HOWEVER, the typical Ka1 dissociation constant of - # 10**-6.33 is defined on a molar basis. Thus, we must - # divide by the total (~55.2 M) concentration raised to the - # net reaction order (i.e., 1 in this case). - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (300, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - # End R2 - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.activity, - "parameter_data": { - # NOTE: The k value on the activity basis is UNITLESS - # based on a standard molar concentration of 1 mol/L - # HOWEVER, the typical Ka1 dissociation constant of - # 10**-10.35 is defined on a molar basis. Thus, we must - # divide by the total (~55.2 M) concentration raised to the - # net reaction order (i.e., 1 in this case). - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (300, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - } - # End R3 - } - # End equilibrium_reactions -} -# End thermo_config definition - -# Start test class -class TestENRTLcarbonicAcid: - @pytest.fixture(scope="class") - def carbonic_acid_model(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**carbonic_thermo_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=False, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - # NOTE: ENRTL model cannot initialize if the inlet values are 0 - zero = 1e-20 - acid = 0.00206 / (55.2 + 0.00206) - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(zero) - - # Added as conjugate base form - model.fs.unit.inlet.mole_frac_comp[0, "CO3_2-"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "HCO3_-"].fix(acid) - model.fs.unit.inlet.mole_frac_comp[0, "H2CO3"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "Na_+"].fix(acid) - - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix( - 1.0 - 4 * zero - acid - value(model.fs.unit.inlet.mole_frac_comp[0, "Na_+"]) - ) - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.mark.unit - def test_build_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 7 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - assert "Na_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Na_+"), Cation) - assert "HCO3_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("HCO3_-"), Anion) - assert "CO3_2-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO3_2-"), Anion) - assert "H2CO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2CO3, Solute) - - # NOTE: This test takes ~8s and I have no idea why... - @pytest.mark.unit - def test_units_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_scaling_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - - for i in model.fs.unit.control_volume.inherent_reaction_extent_index: - scale = value( - model.fs.unit.control_volume.properties_out[0.0].k_eq[i[1]].expr - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.inherent_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].inherent_equilibrium_constraint[i[1]], - 0.1, - ) - - # Next, try adding scaling for species - min = 1e-3 - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if model.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min: - scale = model.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - i - ], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert hasattr(model.fs.unit.control_volume, "scaling_factor") - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert hasattr( - model.fs.unit.control_volume.properties_out[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert hasattr( - model.fs.unit.control_volume.properties_in[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize_solver_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - solver.options["max_iter"] = 100 - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - results = solver.solve(model, tee=True) - print(results.solver.termination_condition) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution_carbonic_acid(self, carbonic_acid_model): - model = carbonic_acid_model - - pH = -log10( - value( - model.fs.unit.control_volume.properties_out[0.0].act_phase_comp[ - "Liq", "H_+" - ] - ) - * 55.2 - ) - pOH = -log10( - value( - model.fs.unit.control_volume.properties_out[0.0].act_phase_comp[ - "Liq", "OH_-" - ] - ) - * 55.2 - ) - - assert pytest.approx(8.28, rel=1e-2) == pH - assert pytest.approx(5.72, rel=1e-2) == pOH - assert pytest.approx(14.00, rel=1e-2) == pH + pOH - - gamma = {} - for index in model.fs.unit.control_volume.properties_out[ - 0.0 - ].conc_mol_phase_comp: - gamma[index] = value( - model.fs.unit.control_volume.properties_out[0.0].act_phase_comp[index] - ) / value(model.fs.unit.outlet.mole_frac_comp[0, index[1]]) - - assert pytest.approx(0.95075, rel=1e-5) == gamma[("Liq", "OH_-")] - assert pytest.approx(0.95075, rel=1e-5) == gamma[("Liq", "H_+")] - assert pytest.approx(0.95075, rel=1e-5) == gamma[("Liq", "Na_+")] - assert pytest.approx(0.95075, rel=1e-5) == gamma[("Liq", "HCO3_-")] - assert pytest.approx(0.81710, rel=1e-5) == gamma[("Liq", "CO3_2-")] - assert pytest.approx(1.00000, rel=1e-5) == gamma[("Liq", "H2O")] - assert pytest.approx(1.00000, rel=1e-5) == gamma[("Liq", "H2CO3")] - - total_acid = ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "H2CO3" - ] - ) - / 1000 - ) - total_acid += ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "HCO3_-" - ] - ) - / 1000 - ) - total_acid += ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - "Liq", "CO3_2-" - ] - ) - / 1000 - ) - - assert pytest.approx(0.002061178349769601, rel=1e-5) == total_acid diff --git a/watertap/examples/chemistry/tests/test_pH_dependent_solubility.py b/watertap/examples/chemistry/tests/test_pH_dependent_solubility.py deleted file mode 100644 index 3122316e3a..0000000000 --- a/watertap/examples/chemistry/tests/test_pH_dependent_solubility.py +++ /dev/null @@ -1,2677 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is of the core IDAES components that allow for the declaration - and usage of solids phases in conjunction with aqueous phases. - - In these tests, we assess the convergence behavior of the solubility problem - under various pH conditions for a solubility reaction that is inherently pH - dependent. Several cases will be tested under different pH conditions and - with different levels of complexity. - - Case 1 (remineralization, post-RO): - Aqueous Rxns: H2O <--> H + OH logK = -14 - Solubility: Ca(OH)2 <--> Ca + 2 OH logK = -5.26 - - - Case 2 (softening, without explicit lime): - Aqueous Rxns: H2O <--> H + OH logK = -14 - H2CO3 <--> H + HCO3 logK = -6.3 - HCO3 <--> H + CO3 logK = -10.2 - Solubility: CaCO3 <--> Ca + CO3 logK = -12 - - - [NOTE: IPOPT/IDAES does not handle simultaneous dissolution and - precipitation very well. To implement this in a flowsheet, - there should be separate units for dissolution and precipitation. - For instance, have 1 unit for addition and dissolution of lime, - then a separate downstream unit for precipitation of CaCO3.] - Case 3: (softening, with lime: precipitation + dissolution) - Aqueous Rxns: H2O <--> H + OH logK = -14 - H2CO3 <--> H + HCO3 logK = -6.3 - HCO3 <--> H + CO3 logK = -10.2 - Solubility: CaCO3 <--> Ca + CO3 logK = -12 - Ca(OH)2 <--> Ca + 2 OH logK = -5.26 - - - Case 4: (phosphorus removal, most realistic test) - Aqueous Rxns: - H2O <---> H + OH - H2CO3 <---> H + HCO3 - HCO3 <---> H + CO3 - H2PO4 <---> H + HPO4 - HPO4 <---> H + PO4 - FeOH <---> Fe + OH - Fe(OH)2 <---> FeOH + OH - Fe(OH)3 <---> Fe(OH)2 + OH - Fe(OH)4 <---> Fe(OH)3 + OH - Solubility: - FePO4 <---> Fe + PO4 -""" - -# Importing testing libraries -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -# Imports from idaes core -from idaes.core import AqueousPhase, SolidPhase, FlowsheetBlock, EnergyBalanceType -from idaes.core.base.components import Solvent, Solute, Cation, Anion, Component -from idaes.core.base.phases import PhaseType as PT - -# Imports from idaes generic models -from idaes.models.properties.modular_properties.pure.ConstantProperties import Constant -from idaes.models.properties.modular_properties.state_definitions import FpcTP -from idaes.models.properties.modular_properties.eos.ideal import Ideal - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -# Import built-in van't Hoff function -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_solubility_product, -) - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -from idaes.core.util import scaling as iscale - -import idaes.logger as idaeslog - -# Import idaes methods to check the model during construction -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import degrees_of_freedom - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor - -# Import log10 function from pyomo -from pyomo.environ import log10 - -# Import scaling helper functions -from watertap.examples.chemistry.chem_scaling_utils import ( - _set_eps_vals, - _set_equ_rxn_scaling, - _set_mat_bal_scaling_FpcTP, - _set_ene_bal_scaling, -) - -__author__ = "Austin Ladshaw" - -# Get default solver for testing -solver = get_solver() - -# Case 1 Config -case1_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-285, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (69, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-230, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -10, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca_2+": { - "type": Cation, - "charge": 2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (40.078, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-542.83, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -53, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca(OH)2": { - "type": Component, - "valid_phase_types": PT.solidPhase, - "dens_mol_sol_comp": Constant, - "enth_mol_sol_comp": Constant, - "cp_mol_sol_comp": Constant, - "entr_mol_sol_comp": Constant, - "parameter_data": { - "mw": (74.093, pyunits.g / pyunits.mol), - "dens_mol_sol_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_sol_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_sol_comp_ref": (-986, pyunits.kJ / pyunits.mol), - "entr_mol_form_sol_comp_ref": (83, pyunits.J / pyunits.K / pyunits.mol), - }, - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - "Sol": {"type": SolidPhase, "equation_of_state": Ideal}, - }, - "state_definition": FpcTP, - "state_bounds": {"temperature": (273.15, 300, 650), "pressure": (5e4, 1e5, 1e6)}, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, -} -# End thermo_config definition - -# Case 1 rxn config (with log_solubility_product) -case1_log_rxn_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "CaOH2_Ksp": { - "stoichiometry": { - ("Sol", "Ca(OH)2"): -1, - ("Liq", "Ca_2+"): 1, - ("Liq", "OH_-"): 2, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-5.26 / 55.2 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298.0, pyunits.K), - "reaction_order": { - ("Sol", "Ca(OH)2"): 0, - ("Liq", "Ca_2+"): 1, - ("Liq", "OH_-"): 2, - }, - } - # End parameter_data - }, - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - } - # End Reactions - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Defaults to pH of 7 with no lime added -def run_case1( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaOH2=1e-20, - xCa=1e-20, - thermo_config=None, - rxn_config=None, - has_energy_balance=True, -): - print("==========================================================================") - print("Case 1: Remineralization via lime dissolution") - print("xOH = " + str(xOH)) - print("xH = " + str(xH)) - print("xCaOH2 = " + str(xCaOH2)) - print("Initial pH = " + str(-log10(xH * 55.2))) - print() - - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **rxn_config - ) - - args = { - "property_package": model.fs.thermo_params, - "reaction_package": model.fs.rxn_params, - "has_rate_reactions": False, - "has_equilibrium_reactions": True, - "has_heat_transfer": False, - "has_heat_of_reaction": False, - "has_pressure_change": False, - } - if has_energy_balance == False: - args["energy_balance_type"] = EnergyBalanceType.none - - model.fs.unit = EquilibriumReactor(**args) - - total_flow_mol = 10 - - # Set flow_mol_phase_comp - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Ca_2+"].fix(xCa * total_flow_mol) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H_+"].fix(xH * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "OH_-"].fix(xOH * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "Ca(OH)2"].fix( - xCaOH2 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - (1 - xH - xOH - xCaOH2 - xCa) * total_flow_mol - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - if has_energy_balance == False: - model.fs.unit.outlet.temperature.fix(298.0) - - assert degrees_of_freedom(model) == 0 - - ## ==================== Start Scaling for this problem =========================== - _set_eps_vals(model.fs.rxn_params, rxn_config) - _set_equ_rxn_scaling(model.fs.unit, model.fs.rxn_params, rxn_config) - _set_mat_bal_scaling_FpcTP(model.fs.unit) - if has_energy_balance == True: - _set_ene_bal_scaling(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - ## ==================== END Scaling for this problem =========================== - - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - results = solver.solve(model, tee=True) - - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - print("comp\toutlet.tot_molfrac") - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i] - ) - ) - ) - print() - - # NOTE: Changed all to mole fraction - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp[i] - ) - ) - ) - print() - - Ca = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "Ca_2+" - ] - ) - OH = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "OH_-" - ] - ) - H = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "H_+" - ] - ) - Ksp = value(model.fs.unit.control_volume.reactions[0.0].k_eq["CaOH2_Ksp"].expr) - - print("Final pH = " + str(-log10(H * 55.2))) - print("Expected max/min pH = " + str(14 + log10(xCaOH2 * 55.2 * 2))) - - print() - if Ksp * 1.01 >= Ca * OH * OH: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp / Ca / OH / OH) + ">=1") - assert False - print("Ksp =\t" + str(Ksp)) - print("Ca*OH**2 =\t" + str(Ca * OH * OH)) - - print("==========================================================================") - - return model - - -## ================================= Case 1 Tests =============================== -@pytest.mark.component -def test_case_1_no_dissolution(): - model = run_case1( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaOH2=1e-20, - thermo_config=case1_thermo_config, - rxn_config=case1_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case_1_high_dissolution(): - model = run_case1( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaOH2=1e-5, - thermo_config=case1_thermo_config, - rxn_config=case1_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case_1_mid_dissolution(): - model = run_case1( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaOH2=1e-7, - thermo_config=case1_thermo_config, - rxn_config=case1_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case_1_low_dissolution(): - model = run_case1( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaOH2=1e-9, - thermo_config=case1_thermo_config, - rxn_config=case1_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_1_high_precipitation(): - model = run_case1( - xOH=1e-1 / 55.2, - xH=1e-13 / 55.2, - xCaOH2=1e-20, - xCa=1e-1, - thermo_config=case1_thermo_config, - rxn_config=case1_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_1_low_precipitation(): - model = run_case1( - xOH=1e-3 / 55.2, - xH=1e-11 / 55.2, - xCaOH2=1e-20, - xCa=1e-1, - thermo_config=case1_thermo_config, - rxn_config=case1_log_rxn_config, - has_energy_balance=True, - ) - - -# Case 2 Config -""" - Case 2 (softening, without explicit lime): - Aqueous Rxns: H2O <--> H + OH logK = -14 - H2CO3 <--> H + HCO3 logK = -6.3 - HCO3 <--> H + CO3 logK = -10.2 - Solubility: CaCO3 <--> Ca + CO3 logK = -12 -""" -case2_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-285, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (69, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-230, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -10, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca_2+": { - "type": Cation, - "charge": 2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (40.078, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-542.83, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -53, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CaCO3": { - "type": Component, - "valid_phase_types": PT.solidPhase, - "dens_mol_sol_comp": Constant, - "enth_mol_sol_comp": Constant, - "cp_mol_sol_comp": Constant, - "entr_mol_sol_comp": Constant, - "parameter_data": { - "mw": (100.0869, pyunits.g / pyunits.mol), - "dens_mol_sol_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_sol_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_sol_comp_ref": (-1207, pyunits.kJ / pyunits.mol), - "entr_mol_form_sol_comp_ref": ( - 91.7, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - "Sol": {"type": SolidPhase, "equation_of_state": Ideal}, - }, - "state_definition": FpcTP, - "state_bounds": {"temperature": (273.15, 300, 650), "pressure": (5e4, 1e5, 1e6)}, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, -} -# End thermo_config definition - -# Case 2 rxn config (with log_solubility_product) -case2_log_rxn_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "CaCO3_Ksp": { - "stoichiometry": { - ("Sol", "CaCO3"): -1, - ("Liq", "Ca_2+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-12 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298.0, pyunits.K), - "reaction_order": { - ("Sol", "CaCO3"): 0, - ("Liq", "Ca_2+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - } - # End last reaction - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Defaults to pH of 7 with no lime added -def run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=1e-20, - xH2CO3=1e-20, - xHCO3=1e-20, - xCO3=1e-20, - thermo_config=None, - rxn_config=None, - has_energy_balance=True, - scaling_ref=1e-3, - init_tol=1e-6, -): - print("==========================================================================") - print("Case 2: Water softening via pH changes") - print("xOH = " + str(xOH)) - print("xH = " + str(xH)) - print("xCaCO3 = " + str(xCaCO3)) - print("xCa = " + str(xCa)) - print("xH2CO3 = " + str(xH2CO3)) - print("xHCO3 = " + str(xHCO3)) - print("xCO3 = " + str(xCO3)) - print() - - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **rxn_config - ) - - args = { - "property_package": model.fs.thermo_params, - "reaction_package": model.fs.rxn_params, - "has_rate_reactions": False, - "has_equilibrium_reactions": True, - "has_heat_transfer": False, - "has_heat_of_reaction": False, - "has_pressure_change": False, - } - if has_energy_balance == False: - args["energy_balance_type"] = EnergyBalanceType.none - - model.fs.unit = EquilibriumReactor(**args) - - total_flow_mol = 10 - - # Set flow_mol_phase_comp - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Ca_2+"].fix(xCa * total_flow_mol) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H_+"].fix(xH * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "OH_-"].fix(xOH * total_flow_mol) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2CO3"].fix( - xH2CO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "HCO3_-"].fix( - xHCO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "CO3_2-"].fix( - xCO3 * total_flow_mol - ) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "CaCO3"].fix( - xCaCO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - (1 - xH - xOH - xCaCO3 - xCa - xH2CO3 - xHCO3 - xCO3) * total_flow_mol - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - if has_energy_balance == False: - model.fs.unit.outlet.temperature.fix(298.0) - - assert degrees_of_freedom(model) == 0 - - ## ==================== Start Scaling for this problem =========================== - _set_eps_vals(model.fs.rxn_params, rxn_config, max_k_eq_ref=1e-12) - _set_equ_rxn_scaling( - model.fs.unit, model.fs.rxn_params, rxn_config, min_k_eq_ref=scaling_ref - ) - _set_mat_bal_scaling_FpcTP(model.fs.unit, min_flow_mol_phase_comp=scaling_ref * 10) - if has_energy_balance == True: - _set_ene_bal_scaling(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - ## ==================== END Scaling for this problem =========================== - - # for macOS - init_options = {**solver.options} - init_options["tol"] = init_tol - init_options["constr_viol_tol"] = init_tol - init_options["ma27_pivtol"] = 5e-1 - model.fs.unit.initialize(optarg=init_options, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - iscale.calculate_scaling_factors(model.fs.unit) - - solver.options["ma27_pivtol"] = 5e-1 - results = solver.solve(model, tee=True) - del solver.options["ma27_pivtol"] - - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - ## ==================== Check the results ================================ - print("comp\toutlet.tot_molfrac") - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i] - ) - ) - ) - print() - - # NOTE: Changed all to mole fraction - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp[i] - ) - ) - ) - print() - - Ca = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "Ca_2+" - ] - ) - OH = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "OH_-" - ] - ) - H = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "H_+" - ] - ) - CO3 = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "CO3_2-" - ] - ) - Ksp = value(model.fs.unit.control_volume.reactions[0.0].k_eq["CaCO3_Ksp"].expr) - - print("Final pH = " + str(-log10(H * 55.2))) - - print() - print("Ksp =\t" + str(Ksp)) - print("Ca*CO3 =\t" + str(Ca * CO3)) - print() - if Ksp * 1.01 >= Ca * CO3: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp / Ca / CO3) + ">=1") - assert False - - print("==========================================================================") - - return model - - -## ================================= Case 1 Tests =============================== -@pytest.mark.component -@pytest.mark.xfail -def test_case_2_do_nothing(): - model = run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=1e-20, - xH2CO3=1e-20, - xHCO3=1e-20, - xCO3=1e-20, - thermo_config=case2_thermo_config, - rxn_config=case2_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_2_seawater_no_ca(): - model = run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=1e-20, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - thermo_config=case2_thermo_config, - rxn_config=case2_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_2_seawater_with_ca(): - model = run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=200 / 50000 / 2 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - thermo_config=case2_thermo_config, - rxn_config=case2_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_2_seawater_added_carbonates(): - model = run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=200 / 50000 / 2 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=0.1 / 55.2, - thermo_config=case2_thermo_config, - rxn_config=case2_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case_2_low_pH_no_precip(): - model = run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=200 / 50000 / 2 / 55.2, - xH2CO3=0.00206 / 55.2, - xHCO3=1e-20, - xCO3=1e-20, - thermo_config=case2_thermo_config, - rxn_config=case2_log_rxn_config, - has_energy_balance=True, - scaling_ref=1e-5, - init_tol=1e-12, - ) - - -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case_2_ultra_high_ca(): - model = run_case2( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCa=6000 / 50000 / 2 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=0.1 / 55.2, - thermo_config=case2_thermo_config, - rxn_config=case2_log_rxn_config, - has_energy_balance=True, - ) - - -# Case 3 Config -""" - Case 3: (softening, with lime: precipitation + dissolution) - Aqueous Rxns: H2O <--> H + OH logK = -14 - H2CO3 <--> H + HCO3 logK = -6.3 - HCO3 <--> H + CO3 logK = -10.2 - Solubility: CaCO3 <--> Ca + CO3 logK = -12 - Ca(OH)2 <--> Ca + 2 OH logK = -5.26 -""" -case3_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-285, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (69, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-230, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -10, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca_2+": { - "type": Cation, - "charge": 2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (40.078, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-542.83, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -53, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CaCO3": { - "type": Component, - "valid_phase_types": PT.solidPhase, - "dens_mol_sol_comp": Constant, - "enth_mol_sol_comp": Constant, - "cp_mol_sol_comp": Constant, - "entr_mol_sol_comp": Constant, - "parameter_data": { - "mw": (100.0869, pyunits.g / pyunits.mol), - "dens_mol_sol_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_sol_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_sol_comp_ref": (-1207, pyunits.kJ / pyunits.mol), - "entr_mol_form_sol_comp_ref": ( - 91.7, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - "Ca(OH)2": { - "type": Component, - "valid_phase_types": PT.solidPhase, - "dens_mol_sol_comp": Constant, - "enth_mol_sol_comp": Constant, - "cp_mol_sol_comp": Constant, - "entr_mol_sol_comp": Constant, - "parameter_data": { - "mw": (74.093, pyunits.g / pyunits.mol), - "dens_mol_sol_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "cp_mol_sol_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_sol_comp_ref": (-986, pyunits.kJ / pyunits.mol), - "entr_mol_form_sol_comp_ref": (83, pyunits.J / pyunits.K / pyunits.mol), - }, - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - "Sol": {"type": SolidPhase, "equation_of_state": Ideal}, - }, - "state_definition": FpcTP, - "state_bounds": {"temperature": (273.15, 300, 650), "pressure": (5e4, 1e5, 1e6)}, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, -} -# End thermo_config definition - -# Case 3 rxn config (with log_solubility_product) -case3_log_rxn_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "CaCO3_Ksp": { - "stoichiometry": { - ("Sol", "CaCO3"): -1, - ("Liq", "Ca_2+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-12 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298.0, pyunits.K), - "reaction_order": { - ("Sol", "CaCO3"): 0, - ("Liq", "Ca_2+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - "CaOH2_Ksp": { - "stoichiometry": { - ("Sol", "Ca(OH)2"): -1, - ("Liq", "Ca_2+"): 1, - ("Liq", "OH_-"): 2, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-5.26 / 55.2 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298.0, pyunits.K), - "reaction_order": { - ("Sol", "Ca(OH)2"): 0, - ("Liq", "Ca_2+"): 1, - ("Liq", "OH_-"): 2, - }, - } - # End parameter_data - }, - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - } - # End last reaction - } - # End equilibrium_reactions -} -# End reaction_config definition - - -# Defaults to pH of 7 with no lime added -def run_case3( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCaOH2=1e-20, - xCa=1e-20, - xH2CO3=1e-20, - xHCO3=1e-20, - xCO3=1e-20, - thermo_config=None, - rxn_config=None, - has_energy_balance=True, -): - print("==========================================================================") - print("Case 3: Water softening through explicit addition of lime") - print("xOH = " + str(xOH)) - print("xH = " + str(xH)) - print("xCaCO3 = " + str(xCaCO3)) - print("xCaOH2 = " + str(xCaOH2)) - print("xCa = " + str(xCa)) - print("xH2CO3 = " + str(xH2CO3)) - print("xHCO3 = " + str(xHCO3)) - print("xCO3 = " + str(xCO3)) - print() - - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **rxn_config - ) - - args = { - "property_package": model.fs.thermo_params, - "reaction_package": model.fs.rxn_params, - "has_rate_reactions": False, - "has_equilibrium_reactions": True, - "has_heat_transfer": False, - "has_heat_of_reaction": False, - "has_pressure_change": False, - } - if has_energy_balance == False: - args["energy_balance_type"] = EnergyBalanceType.none - - model.fs.unit = EquilibriumReactor(**args) - - total_flow_mol = 10 - - # Set flow_mol_phase_comp - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Ca_2+"].fix(xCa * total_flow_mol) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H_+"].fix(xH * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "OH_-"].fix(xOH * total_flow_mol) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2CO3"].fix( - xH2CO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "HCO3_-"].fix( - xHCO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "CO3_2-"].fix( - xCO3 * total_flow_mol - ) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "CaCO3"].fix( - xCaCO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "Ca(OH)2"].fix( - xCaOH2 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - (1 - xH - xOH - xCaCO3 - xCaOH2 - xCa - xH2CO3 - xHCO3 - xCO3) * total_flow_mol - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - if has_energy_balance == False: - model.fs.unit.outlet.temperature.fix(298.0) - - assert degrees_of_freedom(model) == 0 - - ## ==================== Start Scaling for this problem =========================== - # Modify some of the default scaling factors with function args - _set_eps_vals(model.fs.rxn_params, rxn_config, factor=1e-2, max_k_eq_ref=1e-12) - _set_equ_rxn_scaling( - model.fs.unit, model.fs.rxn_params, rxn_config, min_k_eq_ref=1e-2 - ) - _set_mat_bal_scaling_FpcTP(model.fs.unit, min_flow_mol_phase_comp=1e-2) - if has_energy_balance == True: - _set_ene_bal_scaling(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - model.fs.rxn_params.reaction_CaCO3_Ksp.s_norm.value = 1 - model.fs.rxn_params.reaction_CaOH2_Ksp.s_norm.value = 1 - - ## ==================== END Scaling for this problem =========================== - # Loosen the tolerances for initialization stage by passing a temporary - # config dict replacing default values (this does so without any - # changes to the global solver options in WaterTAP) - temp_config = {"tol": 1e-6, "constr_viol_tol": 1e-6} - model.fs.unit.initialize(optarg=temp_config, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - model.fs.rxn_params.reaction_CaCO3_Ksp.s_norm.value = ( - model.fs.rxn_params.reaction_CaCO3_Ksp.k_eq_ref.value - ) - model.fs.rxn_params.reaction_CaOH2_Ksp.s_norm.value = ( - model.fs.rxn_params.reaction_CaOH2_Ksp.k_eq_ref.value - ) - - results = solver.solve(model, tee=True) - - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - ## ==================== Check the results ================================ - print("comp\toutlet.tot_molfrac") - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i] - ) - ) - ) - print() - - # NOTE: Changed all to mole fraction - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp[i] - ) - ) - ) - print() - - Ca = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "Ca_2+" - ] - ) - OH = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "OH_-" - ] - ) - H = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "H_+" - ] - ) - CO3 = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "CO3_2-" - ] - ) - Ksp_CaCO3 = value( - model.fs.unit.control_volume.reactions[0.0].k_eq["CaCO3_Ksp"].expr - ) - Ksp_CaOH2 = value( - model.fs.unit.control_volume.reactions[0.0].k_eq["CaOH2_Ksp"].expr - ) - - print("Final pH = " + str(-log10(H * 55.2))) - - print() - print("Ksp_CaCO3 =\t" + str(Ksp_CaCO3)) - print("Ca*CO3 =\t" + str(Ca * CO3)) - print() - if Ksp_CaCO3 * 1.01 >= Ca * CO3: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp_CaCO3 / Ca / CO3) + ">=1") - assert False - - print() - print("Ksp_CaOH2 =\t" + str(Ksp_CaOH2)) - print("Ca*OH**2 =\t" + str(Ca * OH**2)) - print() - if Ksp_CaOH2 * 1.01 >= Ca * OH**2: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp_CaOH2 / Ca / OH**2) + ">=1") - assert False - - print("==========================================================================") - - return model - - -@pytest.mark.component -def test_case_3_seawater_with_ca(): - model = run_case3( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCaOH2=1e-20, - xCa=200 / 50000 / 2 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - thermo_config=case3_thermo_config, - rxn_config=case3_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_3_ultra_high_ca_forced_lime_precip(): - model = run_case3( - xOH=1e-1 / 55.2, - xH=1e-13 / 55.2, - xCaCO3=1e-20, - xCaOH2=1e-20, - xCa=2000 / 50000 / 2 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - thermo_config=case3_thermo_config, - rxn_config=case3_log_rxn_config, - has_energy_balance=True, - ) - - -@pytest.mark.component -def test_case_3_ultra_faux_added_lime_and_ash(): - added_lime_x = 100 / 50000 / 2 / 55.2 - added_ash_x = 2000 / 50000 / 2 / 55.2 - model = run_case3( - xOH=(1e-7 / 55.2) + 2 * added_lime_x, - xH=1e-7 / 55.2, - xCaCO3=1e-20, - xCaOH2=1e-20, - xCa=(2000 / 50000 / 2 / 55.2) + added_lime_x, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20 + added_ash_x, - thermo_config=case3_thermo_config, - rxn_config=case3_log_rxn_config, - has_energy_balance=True, - ) - - -# Case 4 Config -""" - Case 4: (phosphorus removal, most realistic test) - Aqueous Rxns: - H2O <---> H + OH - H2CO3 <---> H + HCO3 - HCO3 <---> H + CO3 - H2PO4 <---> H + HPO4 - HPO4 <---> H + PO4 - FeOH <---> Fe + OH - Fe(OH)2 <---> FeOH + OH - Fe(OH)3 <---> Fe(OH)2 + OH - Fe(OH)4 <---> Fe(OH)3 + OH - Solubility: - FePO4 <---> Fe + PO4 -""" -case4_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-285, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (69, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-230, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -10, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2PO4_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (96.98724, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-1296.3, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 90.4, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HPO4_2-": { - "type": Anion, - "charge": -2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (95.9793, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-1292.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - -33.4, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "PO4_3-": { - "type": Anion, - "charge": -3, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (94.97136, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-1277.4, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - -222, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Fe_3+": { - "type": Cation, - "charge": 3, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (55.845, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (233467, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_liq_comp_ref": (-48.5, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": ( - -315.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "FeOH_2+": { - "type": Cation, - "charge": 2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (72.8, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (305000, pyunits.J / pyunits.kmol / pyunits.K), - # NOTE: these parameters below are not well known - "enth_mol_form_liq_comp_ref": (-229.4, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "Fe(OH)2_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (89.8, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (375000, pyunits.J / pyunits.kmol / pyunits.K), - # NOTE: these parameters below are not well known - "enth_mol_form_liq_comp_ref": (-446.7, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "Fe(OH)3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (106.8, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (446000, pyunits.J / pyunits.kmol / pyunits.K), - # NOTE: these parameters below are not well known - "enth_mol_form_liq_comp_ref": (-638.5, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "Fe(OH)4_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (123.8, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (518000, pyunits.J / pyunits.kmol / pyunits.K), - # NOTE: these parameters below are not well known - "enth_mol_form_liq_comp_ref": (-830.0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "FePO4": { - "type": Component, - "valid_phase_types": PT.solidPhase, - "dens_mol_sol_comp": Constant, - "enth_mol_sol_comp": Constant, - "cp_mol_sol_comp": Constant, - "entr_mol_sol_comp": Constant, - "parameter_data": { - "mw": (150.8, pyunits.g / pyunits.mol), - "dens_mol_sol_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_sol_comp_coeff": (635000, pyunits.J / pyunits.kmol / pyunits.K), - "enth_mol_form_sol_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_sol_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - "Sol": {"type": SolidPhase, "equation_of_state": Ideal}, - }, - "state_definition": FpcTP, - "state_bounds": {"temperature": (273.15, 300, 650), "pressure": (5e4, 1e5, 1e6)}, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, -} -# End thermo_config definition - -# Case 4 rxn config (with log_solubility_product) -case4_log_rxn_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "FePO4_Ksp": { - "stoichiometry": { - ("Sol", "FePO4"): -1, - ("Liq", "Fe_3+"): 1, - ("Liq", "PO4_3-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-23 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298.0, pyunits.K), - "reaction_order": { - ("Sol", "FePO4"): 0, - ("Liq", "Fe_3+"): 1, - ("Liq", "PO4_3-"): 1, - }, - } - # End parameter_data - }, - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - "H3PO4_Ka2": { - "stoichiometry": { - ("Liq", "H2PO4_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HPO4_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (4.2, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-5.73 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2PO4_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HPO4_2-"): 1, - }, - } - # End parameter_data - }, - "H3PO4_Ka3": { - "stoichiometry": { - ("Liq", "HPO4_2-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "PO4_3-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-7.275 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HPO4_2-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "PO4_3-"): 1, - }, - } - # End parameter_data - }, - "FeOH_K": { - "stoichiometry": { - ("Liq", "FeOH_2+"): -1, - ("Liq", "Fe_3+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (1.768e-12 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "FeOH_2+"): -1, - ("Liq", "Fe_3+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - "FeOH2_K": { - "stoichiometry": { - ("Liq", "Fe(OH)2_+"): -1, - ("Liq", "FeOH_2+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (3.757e-11 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "Fe(OH)2_+"): -1, - ("Liq", "FeOH_2+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - "FeOH3_K": { - "stoichiometry": { - ("Liq", "Fe(OH)3"): -1, - ("Liq", "Fe(OH)2_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (9.765e-7 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "Fe(OH)3"): -1, - ("Liq", "Fe(OH)2_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - "FeOH4_K": { - "stoichiometry": { - ("Liq", "Fe(OH)4_-"): -1, - ("Liq", "Fe(OH)3"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (1.097e-6 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "Fe(OH)4_-"): -1, - ("Liq", "Fe(OH)3"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - } - # End last reaction - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Defaults to pH of 7 -def run_case4( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xH2CO3=1e-20, - xHCO3=1e-20, - xCO3=1e-20, - xH2PO4=1e-20, - xHPO4=1e-20, - xPO4=1e-20, - xFe=1e-20, - xFeOH=1e-20, - xFeOH2=1e-20, - xFeOH3=1e-20, - xFeOH4=1e-20, - xFePO4=1e-20, - thermo_config=None, - rxn_config=None, - has_energy_balance=True, - remove_precip_rxn=False, -): - print("==========================================================================") - print("Case 4: Phophorus removal through iron precipitation") - print("xOH = " + str(xOH)) - print("xH = " + str(xH)) - print("xH2CO3 = " + str(xH2CO3)) - print("xHCO3 = " + str(xHCO3)) - print("xCO3 = " + str(xCO3)) - print("xH2PO4 = " + str(xH2PO4)) - print("xHPO4 = " + str(xHPO4)) - print("xPO4 = " + str(xPO4)) - - print("xFe = " + str(xFe)) - print("xFeOH = " + str(xFeOH)) - print("xFeOH2 = " + str(xFeOH2)) - print("xFeOH3 = " + str(xFeOH3)) - print("xFeOH4 = " + str(xFeOH4)) - - print("xFePO4 = " + str(xFePO4)) - print() - - Ksp_hold = rxn_config["equilibrium_reactions"]["FePO4_Ksp"]["parameter_data"][ - "k_eq_ref" - ][0] - if remove_precip_rxn == True: - del rxn_config["equilibrium_reactions"]["FePO4_Ksp"] - - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **rxn_config - ) - - args = { - "property_package": model.fs.thermo_params, - "reaction_package": model.fs.rxn_params, - "has_rate_reactions": False, - "has_equilibrium_reactions": True, - "has_heat_transfer": False, - "has_heat_of_reaction": False, - "has_pressure_change": False, - } - if has_energy_balance == False: - args["energy_balance_type"] = EnergyBalanceType.none - - model.fs.unit = EquilibriumReactor(**args) - - total_flow_mol = 10 - - # Set flow_mol_phase_comp - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H_+"].fix(xH * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "OH_-"].fix(xOH * total_flow_mol) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2CO3"].fix( - xH2CO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "HCO3_-"].fix( - xHCO3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "CO3_2-"].fix( - xCO3 * total_flow_mol - ) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2PO4_-"].fix( - xH2PO4 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "HPO4_2-"].fix( - xHPO4 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "PO4_3-"].fix( - xPO4 * total_flow_mol - ) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Fe_3+"].fix(xFe * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "FeOH_2+"].fix( - xFeOH * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Fe(OH)2_+"].fix( - xFeOH2 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Fe(OH)3"].fix( - xFeOH3 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "Fe(OH)4_-"].fix( - xFeOH4 * total_flow_mol - ) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "FePO4"].fix( - xFePO4 * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - ( - 1 - - xH - - xOH - - xFePO4 - - xH2CO3 - - xHCO3 - - xCO3 - - xH2PO4 - - xHPO4 - - xPO4 - - xFe - - xFeOH - - xFeOH2 - - xFeOH3 - - xFeOH4 - ) - * total_flow_mol - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - if has_energy_balance == False: - model.fs.unit.outlet.temperature.fix(298.0) - - assert degrees_of_freedom(model) == 0 - - ## ==================== Start Scaling for this problem =========================== - # # TODO: NOTE: The _set_eps_vals function may need to change. Some cases run - # better without it included. - _set_eps_vals(model.fs.rxn_params, rxn_config) - _set_equ_rxn_scaling(model.fs.unit, model.fs.rxn_params, rxn_config) - _set_mat_bal_scaling_FpcTP(model.fs.unit) - if has_energy_balance == True: - _set_ene_bal_scaling(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - ## ==================== END Scaling for this problem =========================== - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - solver.options["tol"] = 1.0e-16 - solver.options["ma27_pivtol"] = 5e-1 - results = solver.solve(model, tee=True) - solver.options["ma27_pivtol"] = 5e-1 - del solver.options["tol"] - - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - ## ==================== Check the results ================================ - print("comp\toutlet.tot_molfrac") - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i] - ) - ) - ) - print() - - # NOTE: Changed all to mole fraction - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp[i] - ) - ) - ) - print() - - Fe = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "Fe_3+" - ] - ) - OH = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "OH_-" - ] - ) - H = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "H_+" - ] - ) - PO4 = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "PO4_3-" - ] - ) - if remove_precip_rxn == False: - Ksp = value(model.fs.unit.control_volume.reactions[0.0].k_eq["FePO4_Ksp"].expr) - else: - Ksp = Ksp_hold - - print("Final pH = " + str(-log10(H * 55.2))) - - print() - print("Ksp =\t" + str(Ksp)) - print("Fe*PO4 =\t" + str(Fe * PO4)) - print() - if Ksp * 1.01 >= Fe * PO4: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp / Fe / PO4) + ">=1") - if remove_precip_rxn == False: - assert False - - print("==========================================================================") - - return model - - -# seawater with standard amount of iron (5.38e-8 M) and phosphorus (3.22e-6 M) -# Boost Fe, PO4, and OH to induce precipitation -# Add 1e-4 M of each... -@pytest.mark.component -def test_case_4_seawater_added_Fe_OH_PO4(): - model = run_case4( - xOH=(1e-7 / 55.2) + (1e-4 / 55.2), - xH=1e-7 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - xH2PO4=1e-20, - xHPO4=(3.22e-6 / 55.2) + (1e-4 / 55.2), - xPO4=1e-20, - xFe=(5.38e-8 / 55.2) + (1e-4 / 55.2), - xFeOH=1e-20, - xFeOH2=1e-20, - xFeOH3=1e-20, - xFeOH4=1e-20, - xFePO4=1e-20, - thermo_config=case4_thermo_config, - rxn_config=case4_log_rxn_config, - has_energy_balance=True, - remove_precip_rxn=False, - ) - - -# seawater with standard amount of iron (5.38e-8 M) and phosphorus (3.22e-6 M) -# Boost Fe, PO4, and OH to induce precipitation -# Add 1e-4 M of Fe -# Add 1e-4 M of PO4 -# Add 0 M of OH -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case_4_seawater_added_Fe_PO4(): - model = run_case4( - xOH=(1e-7 / 55.2) + (0 / 55.2), - xH=1e-7 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - xH2PO4=1e-20, - xHPO4=(3.22e-6 / 55.2) + (1e-4 / 55.2), - xPO4=1e-20, - xFe=(5.38e-8 / 55.2) + (1e-4 / 55.2), - xFeOH=1e-20, - xFeOH2=1e-20, - xFeOH3=1e-20, - xFeOH4=1e-20, - xFePO4=1e-20, - thermo_config=case4_thermo_config, - rxn_config=case4_log_rxn_config, - has_energy_balance=True, - remove_precip_rxn=False, - ) - - -# seawater with standard amount of iron (5.38e-8 M) and phosphorus (3.22e-6 M) -# Boost Fe, PO4, and OH to induce precipitation -# Add 1e-4 M of Fe -# Add 0 M of PO4 -# Add 0 M of OH -@pytest.mark.component -def test_case_4_seawater_added_Fe_only(): - model = run_case4( - xOH=(1e-7 / 55.2) + (0 / 55.2), - xH=1e-7 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - xH2PO4=1e-20, - xHPO4=(3.22e-6 / 55.2) + (0 / 55.2), - xPO4=1e-20, - xFe=(5.38e-8 / 55.2) + (1e-4 / 55.2), - xFeOH=1e-20, - xFeOH2=1e-20, - xFeOH3=1e-20, - xFeOH4=1e-20, - xFePO4=1e-20, - thermo_config=case4_thermo_config, - rxn_config=case4_log_rxn_config, - has_energy_balance=True, - remove_precip_rxn=False, - ) - - -# This is for additional testing -if __name__ == "__main__": - # seawater with standard amount of iron (5.38e-8 M) and phosphorus (3.22e-6 M) - # - # This works, but convergence of the initialization stage is VERY, VERY poor!!! - # - # # TODO: Figure out how to better initialize this case. - # # TODO: NOTE: The _set_eps_vals function may need to change. Some cases run - # better without it included. - - model = run_case4( - xOH=1e-7 / 55.2, - xH=1e-7 / 55.2, - xH2CO3=1e-20, - xHCO3=0.00206 / 55.2, - xCO3=1e-20, - xH2PO4=1e-20, - xHPO4=3.22e-6 / 55.2, - xPO4=1e-20, - xFe=5.38e-8 / 55.2, - xFeOH=1e-20, - xFeOH2=1e-20, - xFeOH3=1e-20, - xFeOH4=1e-20, - xFePO4=1e-20, - thermo_config=case4_thermo_config, - rxn_config=case4_log_rxn_config, - has_energy_balance=True, - remove_precip_rxn=False, - ) - - exit() diff --git a/watertap/examples/chemistry/tests/test_pure_water_pH.py b/watertap/examples/chemistry/tests/test_pure_water_pH.py deleted file mode 100644 index d051dd6e9a..0000000000 --- a/watertap/examples/chemistry/tests/test_pure_water_pH.py +++ /dev/null @@ -1,701 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is to establish that the core chemistry packages in IDAES solve - a simple water dissociation problem and return the correct pH value. -""" -import enum - -# Importing testing libraries -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -# Imports from idaes core -from idaes.core import AqueousPhase -from idaes.core.base.components import Solvent, Cation, Anion - -# Imports from idaes generic models -import idaes.models.properties.modular_properties.pure.Perrys as Perrys -from idaes.models.properties.modular_properties.state_definitions import FTPx -from idaes.models.properties.modular_properties.eos.ideal import Ideal - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -# Import k-value functions -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -from idaes.core.util import scaling as iscale - -# Import pyomo methods to check the system units -from pyomo.util.check_units import assert_units_consistent - -# Import idaes methods to check the model during construction -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import ( - degrees_of_freedom, - fixed_variables_set, - activated_constraints_set, - number_variables, - number_total_constraints, - number_unused_variables, -) - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor - -# Import the core idaes objects for Flowsheets and types of balances -from idaes.core import FlowsheetBlock - -# Import log10 function from pyomo -from pyomo.environ import log10 - - -__authors__ = [ - "Austin Ladshaw", - "Ludovico Bianchi", - "Dan Gunter", -] -__author__ = __authors__[0] - - -class Variant(str, enum.Enum): - equilibrium = "equilibrium" - inherent = "inherent" - - def __str__(self): - return f"{self.__class__.__name__}.{self.name}" - - @property - def is_equilibrium(self): - return self is Variant.equilibrium - - -@pytest.fixture(scope="module", params=[Variant.equilibrium, Variant.inherent], ids=str) -def variant(request) -> Variant: - return request.param - - -@pytest.fixture(scope="module") -def base_units(): - return { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - } - - -@pytest.fixture(scope="module") -def solver(): - s = get_solver() - s.options["max_iter"] = 200 - return s - - -@pytest.fixture(scope="module") -def thermo_config(base_units): - # Configuration dictionary - data = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": ( - 4.6543, - pyunits.dimensionless, - ), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 647), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": base_units, - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - } - # End R1 - }, - } - return data - - -# Define the reaction_config for water dissociation -@pytest.fixture(scope="module") -def water_reaction_config(base_units): - return { - "base_units": base_units, - "equilibrium_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - } - # End R1 - } - # End equilibrium_reactions - } - # End reaction_config definition - - -def _get_without_inherent_reactions( - config_dict: dict, key="inherent_reactions" -) -> dict: - data = dict(config_dict) - del data[key] - return data - - -class TestPureWater: - @pytest.fixture - def model(self, thermo_config, variant: Variant, water_reaction_config): - if variant.is_equilibrium: - thermo_config = _get_without_inherent_reactions(thermo_config) - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - print(water_reaction_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **water_reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=variant.is_equilibrium, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1.0) - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.mark.unit - def test_build_model(self, model): - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 3 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 1 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.unit - def test_units_inherent(self, model): - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof(self, model): - assert degrees_of_freedom(model) == 0 - - @pytest.fixture - def expected_stats(self, variant): - expected = { - Variant.inherent: { - number_variables: 81, - number_total_constraints: 28, - number_unused_variables: 12, - }, - Variant.equilibrium: { - number_variables: 75, - number_total_constraints: 28, - number_unused_variables: 6, - }, - } - return expected[variant] - - @pytest.mark.unit - def test_stats(self, model, expected_stats): - for check_func, expected_result in expected_stats.items(): - check_result = check_func(model) - assert check_result == expected_result - - def _do_inherent_reaction_scaling(self, model): - for i in model.fs.unit.control_volume.inherent_reaction_extent_index: - scale = value( - model.fs.unit.control_volume.properties_out[0.0].k_eq[i[1]].expr - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.inherent_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].inherent_equilibrium_constraint[i[1]], - 0.1, - ) - return model - - def _do_equilibrium_reaction_scaling(self, model): - for i in model.fs.unit.control_volume.equilibrium_reaction_extent_index: - scale = value(model.fs.unit.control_volume.reactions[0.0].k_eq[i[1]].expr) - iscale.set_scaling_factor( - model.fs.unit.control_volume.equilibrium_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.reactions[0.0].equilibrium_constraint[ - i[1] - ], - 0.1, - ) - return model - - @pytest.fixture - def model_scaling(self, model, variant: Variant): - map_variant_reaction_scale_func = { - Variant.equilibrium: self._do_equilibrium_reaction_scaling, - Variant.inherent: self._do_inherent_reaction_scaling, - } - - scale_func = map_variant_reaction_scale_func[variant] - scale_func(model) - - # Next, try adding scaling for species - min_scale = 1e-3 - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if model.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min_scale: - scale = model.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min_scale - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - i - ], - 100 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - max_enth_mol_phase = 1 - min_scale = 1 - for phase in model.fs.unit.control_volume.properties_in[0.0].enth_mol_phase: - val = max( - min_scale, - abs( - value( - model.fs.unit.control_volume.properties_in[0.0] - .enth_mol_phase[phase] - .expr - ) - ), - ) - max_enth_mol_phase = max(val, max_enth_mol_phase) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_in[0.0]._enthalpy_flow_term[ - phase - ], - 1 / val, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0]._enthalpy_flow_term[ - phase - ], - 1 / val, - ) - - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.enthalpy_balances[0.0], 1 / max_enth_mol_phase - ) - - iscale.calculate_scaling_factors(model.fs.unit) - return model - - @pytest.mark.component - def test_scaling(self, model_scaling, variant: Variant): - model = model_scaling - - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - if variant.is_equilibrium: - # When using equilibrium reactions, there is another set of scaling factors calculated - assert isinstance( - model.fs.unit.control_volume.reactions[0.0].scaling_factor, Suffix - ) - - @pytest.fixture - def state_args(self): - return { - "mole_frac_comp": { - "H2O": 1, - "H_+": 10**-7 / 55.6, - "OH_-": 10**-7 / 55.6, - }, - "pressure": 101325, - "temperature": 298, - "flow_mol": 10, - } - - @pytest.fixture - def model_initialize(self, model, state_args, solver): - def _collect_data_to_check(m): - return { - fixed_variables_set: fixed_variables_set(m), - activated_constraints_set: activated_constraints_set(m), - } - - data_before = _collect_data_to_check(model) - model.fs.unit.initialize(state_args=state_args, optarg=solver.options) - - data_after = _collect_data_to_check(model) - return model, data_before, data_after - - @pytest.mark.component - def test_initialize(self, model_initialize): - model, data_before, data_after = model_initialize - - assert degrees_of_freedom(model) == 0 - - for key, value_before in data_before.items(): - value_after = data_after[key] - assert len(value_before) == len(value_after) - - @pytest.fixture - def model_solve(self, model, solver): - results = solver.solve(model, symbolic_solver_labels=True, tee=True) - return model, results - - @pytest.mark.component - def test_solve(self, model_solve): - _, results = model_solve - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - @pytest.mark.requires_idaes_solver - def test_solution(self, model_solve): - model, _ = model_solve - - assert pytest.approx(298, rel=1e-5) == value( - model.fs.unit.outlet.temperature[0] - ) - assert pytest.approx(10, rel=1e-5) == value(model.fs.unit.outlet.flow_mol[0]) - assert pytest.approx(101325, rel=1e-5) == value( - model.fs.unit.outlet.pressure[0] - ) - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - assert pytest.approx(55.2336, rel=1e-5) == total_molar_density - - pH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "H_+"] * total_molar_density) - ) - pOH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"] * total_molar_density) - ) - assert pytest.approx(7.000, rel=1e-3) == pH - assert pytest.approx(7.000, rel=1e-3) == pOH - assert pytest.approx(0.99999, rel=1e-5) == value( - model.fs.unit.outlet.mole_frac_comp[0.0, "H2O"] - ) - - -class TestPureWaterEDB(TestPureWater): - @pytest.fixture - def thermo_config(self, edb): - base = edb.get_base("default_thermo") - elements = ["H", "O"] - components = [] - # Add the components - for c in edb.get_components(element_names=elements): - # Should remove these since we don't have a vapor phase - # NOTE: The model still runs without removing these, but - # the 'stats' won't pass their checks due to the addition - # of new system variables that they inherently bring - c.remove("enth_mol_ig_comp") - c.remove("pressure_sat_comp") - base.add(c) - components.append(c.name) - # Add the reactions - for r in edb.get_reactions(component_names=components): - # Set a custom reaction order - r.set_reaction_order("Liq", {"H2O": 0, "H_+": 1, "OH_-": 1}) - r._data["type"] = "inherent" - base.add(r) - return base.idaes_config - - @pytest.fixture - def water_reaction_config(self, edb): - elements = ["H", "O"] - components = [c.name for c in edb.get_components(element_names=elements)] - base = edb.get_base("reaction") - # Add the reactions - for r in edb.get_reactions(component_names=components): - # Set a custom reaction order - r.set_reaction_order("Liq", {"H2O": 0, "H_+": 1, "OH_-": 1}) - # Need to remove this to avoid errors when using the generated config - r.remove_parameter("ds_rxn_ref") - base.add(r) - return base.idaes_config diff --git a/watertap/examples/chemistry/tests/test_recarbonation_process.py b/watertap/examples/chemistry/tests/test_recarbonation_process.py deleted file mode 100644 index f0244be6b8..0000000000 --- a/watertap/examples/chemistry/tests/test_recarbonation_process.py +++ /dev/null @@ -1,783 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This file is to run the example of running a phase change for the dissolution - of CO2 into water. -""" -# =================== Import Statements ============================== -import pytest - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -# Import pyomo methods to check the system units -from pyomo.util.check_units import assert_units_consistent -from pyomo.environ import units as pyunits - -from idaes.core.util import scaling as iscale - -from idaes.core.solvers import get_solver - -# Import idaes methods to check the model during construction -from idaes.core.util.model_statistics import ( - degrees_of_freedom, -) - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, - ConcentrationForm, -) - - -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor -from idaes.models.properties.modular_properties.pure.Perrys import Perrys -from idaes.models.properties.modular_properties.pure.NIST import NIST - -# Import the core idaes objects for Flowsheets and types of balances -from idaes.core import FlowsheetBlock - -# Import statements to be used in the starter config dict -from idaes.core import VaporPhase, AqueousPhase -from idaes.core.base.components import Solvent, Solute, Cation, Anion -from idaes.core.base.phases import PhaseType as PT -from idaes.models.properties.modular_properties.phase_equil.forms import fugacity -from idaes.models.properties.modular_properties.state_definitions import FpcTP -from idaes.models.properties.modular_properties.eos.ideal import Ideal -from idaes.models.properties.modular_properties.phase_equil import SmoothVLE -from idaes.models.properties.modular_properties.phase_equil.bubble_dew import ( - IdealBubbleDew, -) - -# Import log10 function from pyomo -from pyomo.environ import log10 - -import idaes.logger as idaeslog - -# Import scaling helper functions -from watertap.examples.chemistry.chem_scaling_utils import ( - _set_equ_rxn_scaling, - _set_mat_bal_scaling_FpcTP, - _set_ene_bal_scaling, -) - -__authors__ = [ - "Srikanth Allu", - "Austin Ladshaw", -] -__author__ = __authors__[0] - -# Create a thermo_config dictionary -thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "enth_mol_ig_comp": NIST, - "pressure_sat_comp": NIST, - "phase_equilibrium_form": {("Vap", "Liq"): fugacity}, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": ( - 4.6543, - pyunits.dimensionless, - ), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - }, - "CO2": { - "type": Solute, - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "enth_mol_ig_comp": NIST, - "pressure_sat_comp": NIST, - "phase_equilibrium_form": {("Vap", "Liq"): fugacity}, - "parameter_data": { - "mw": (44.0095, pyunits.g / pyunits.mol), - "pressure_crit": (73.825e5, pyunits.Pa), - "temperature_crit": (304.23, pyunits.K), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (0.000789, pyunits.kmol * pyunits.m**-3), - "2": (0.000956, pyunits.dimensionless), - "3": (500.78, pyunits.K), - "4": (0.94599, pyunits.dimensionless), - }, - "cp_mol_ig_comp_coeff": { - "A": (24.99735, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 55.18696, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - -33.69137, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - 7.948387, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - -0.136638, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-403.6075, pyunits.kJ / pyunits.mol), - "G": (228.2431, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "cp_mol_liq_comp_coeff": { - "1": (-8.3043e6, pyunits.J / pyunits.kmol / pyunits.K), - "2": (1.0437e5, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (4.333e2, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (6.0052e-1, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "enth_mol_form_liq_comp_ref": (-285.83, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (-393.52, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - "entr_mol_form_vap_comp_ref": (213.6, pyunits.J / pyunits.mol), - "pressure_sat_comp_coeff": { - "A": (6.81228, None), - "B": (1301.679, pyunits.K), - "C": (-3.494, pyunits.K), - }, - }, - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (61.0168, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (60.01, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - }, - }, - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - "Vap": {"type": VaporPhase, "equation_of_state": Ideal}, - }, - # Set base units of measurement - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Specifying state definition - "state_definition": FpcTP, - "state_bounds": { - "temperature": (273.15, 300, 500, pyunits.K), - "pressure": (5e4, 1e5, 1e6, pyunits.Pa), - }, - "pressure_ref": (101325, pyunits.Pa), - "temperature_ref": (300, pyunits.K), - # Defining phase equilibria - "phases_in_equilibrium": [("Vap", "Liq")], - "phase_equilibrium_state": {("Vap", "Liq"): SmoothVLE}, - "bubble_dew_method": IdealBubbleDew, -} - -reaction_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "CO2_to_H2CO3": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "CO2"): -1, - ("Liq", "H2CO3"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (1.7 * 10**-3, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): 1, - ("Liq", "CO2"): -1, - ("Liq", "H2O"): 0, - }, - } - # End parameter_data - }, - # End R2 - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - # End R3 - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - } - # End R4 - } - # End equilibrium_reactions -} - -# Get default solver for testing -solver = get_solver() - - -class TestCarbonationProcess: - @pytest.fixture(scope="class") - def equilibrium_config(self): - # Create a pyomo model object - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=True, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H_+"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "OH_-"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2CO3"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "HCO3_-"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "CO3_2-"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Vap", "CO2"].fix(0.0005 * 10) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "CO2"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Vap", "H2O"].fix(1e-15) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix((1 - 0.0005) * 10) - - return model - - @pytest.mark.unit - def test_build_model_equilibrium(self, equilibrium_config): - model = equilibrium_config - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 7 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 2 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - assert isinstance(model.fs.thermo_params.Vap, VaporPhase) - - @pytest.mark.unit - def test_units_equilibrium(self, equilibrium_config): - model = equilibrium_config - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof_equilibrium(self, equilibrium_config): - model = equilibrium_config - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_scaling_equilibrium(self, equilibrium_config): - model = equilibrium_config - - # Call scaling factor helper functions - _set_equ_rxn_scaling(model.fs.unit, model.fs.rxn_params, reaction_config) - _set_mat_bal_scaling_FpcTP(model.fs.unit) - _set_ene_bal_scaling(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - # When using equilibrium reactions, there are another set of scaling factors calculated - assert isinstance( - model.fs.unit.control_volume.reactions[0.0].scaling_factor, Suffix - ) - - # 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=1e5, small=1e-2 - ) - } - assert not badly_scaled_var_values - - @pytest.mark.component - def test_initialize_solver(self, equilibrium_config): - model = equilibrium_config - - # Exact state args (here for reference) - """ - state_args={"pressure": 101324.99999999999, - "temperature": 298.2092871397854, - "flow_mol_phase_comp": - { - ("Liq","H_+"): 8.296899016805206e-07, - ("Liq","OH_-"): 3.9555981844674506e-10, - ("Liq","H2CO3"): 8.484140196361887e-06, - ("Liq","HCO3_-"): 8.292773317392414e-07, - ("Liq","CO3_2-"): 8.505061416220037e-12, - ("Liq","CO2"): 0.004990670703742279, - ("Liq","H2O"): 9.994990674332442, - ("Vap","CO2"): 1.5870224558095792e-08, - ("Vap","H2O"): 1.1845964073198763e-08, - } - } - """ - - total_molar_conc = 55200 # approximation for water (mol/m^3) - total_molar_flow_rate = 10 # mol/s (based on inlet conditions) - total_volume_flow_rate = total_molar_flow_rate / total_molar_conc # m^3/s - input_co2_conc = 0.0005 * 10 / total_volume_flow_rate # based on inlet - - # Neutral pH Guess (works... but needs automation) - # Presume a neutral pH, calculate flow from assuming water solution - # Presume 90% of added vapor goes to Liquid - # Presume other 10% is distributed to species - # Use pKas to given approximate speciation - # Presume 1e-8 for remaining vapor species - state_args = { - "pressure": 101325, - "temperature": 298, - "flow_mol_phase_comp": { - ("Liq", "H_+"): 1.0e-4 * total_volume_flow_rate, - ("Liq", "OH_-"): 1.0e-4 * total_volume_flow_rate, - ("Liq", "H2CO3"): input_co2_conc - * total_volume_flow_rate - * 0.1 - * 0.01 - / 100, - ("Liq", "HCO3_-"): input_co2_conc - * total_volume_flow_rate - * 0.1 - * 0.98 - / 100, - ("Liq", "CO3_2-"): input_co2_conc - * total_volume_flow_rate - * 0.1 - * 0.01 - / 100, - ("Liq", "CO2"): input_co2_conc * total_volume_flow_rate * 0.9, - ("Liq", "H2O"): 10, - ("Vap", "CO2"): 1.0e-8, - ("Vap", "H2O"): 1.0e-8, - }, - } - - model.fs.unit.initialize( - state_args=state_args, optarg=solver.options, outlvl=idaeslog.DEBUG - ) - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve_equilibrium(self, equilibrium_config): - model = equilibrium_config - solver.options["max_iter"] = 100 - assert degrees_of_freedom(model) == 0 - results = solver.solve(model, tee=True) - print(results.solver.termination_condition) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution_equilibrium(self, equilibrium_config): - model = equilibrium_config - - assert pytest.approx(298.2, rel=1e-4) == value( - model.fs.unit.outlet.temperature[0] - ) - assert pytest.approx(101325, rel=1e-4) == value( - model.fs.unit.outlet.pressure[0] - ) - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - assert pytest.approx(55.165246, rel=1e-4) == total_molar_density - pH = -value( - log10( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp["Liq", "H_+"] - ) - * total_molar_density - ) - ) - pOH = -value( - log10( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp["Liq", "OH_-"] - ) - * total_molar_density - ) - ) - assert pytest.approx(5.339891, rel=1e-4) == pH - assert pytest.approx(8.654294, rel=1e-4) == pOH - - CO2_sorbed = value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "CO2") - ] - ) - assert pytest.approx(27.531571, rel=1e-4) == CO2_sorbed diff --git a/watertap/examples/chemistry/tests/test_remineralization.py b/watertap/examples/chemistry/tests/test_remineralization.py deleted file mode 100644 index ca8cc5a4e7..0000000000 --- a/watertap/examples/chemistry/tests/test_remineralization.py +++ /dev/null @@ -1,1901 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is to establish: (i) that IDAES can solve dilute system associated - with remineralization processes, (ii) that IDAES can correctly assign ions - to a set of apparent species, (iii) the solution of the system with the IDAES - assigned apparent species set is the same as the prior solve used to establish - that set, and (iv) that IDAES can mix-and-match inherent reactions with kinetic - reactions to emulate more realistic solution chemistry. Solutions are checked - with approximations to solution pH, alkalinity, and hardness after a typical - remineralization process. - - NOTE: Scaling for the kinetics is not yet perfected. You will still see AMPL - errors in the solve for kinetics due to the use of an 'unsafe' power - law rate function. These errors occur mostly when values for molefractions - become zero, which cannot be evaluated in a power law function. - - Inherent Reactions: - H2O <---> H + OH - H2CO3 <---> H + HCO3 - HCO3 <---> H + CO3 - H2O + CO2 <--> H2CO3 - - Additional Apparent species: - NaHCO3 - Ca(OH)2 - Ca(HCO3)2 - NaOH - CaCO3 - - Kinetic Reactions: - NaHCO3 --> Na + HCO3 - Ca(OH)2 --> Ca + 2 OH -""" -# Importing testing libraries -import pytest - -# Importing pyomo objects -from pyomo.environ import units as pyunits -from pyomo.environ import log10 -from pyomo.util.check_units import assert_units_consistent - -# Imports from idaes core -from idaes.core import AqueousPhase, FlowsheetBlock, EnergyBalanceType -from idaes.core.base.components import Solvent, Solute, Cation, Anion, Apparent -from idaes.core.base.phases import PhaseType as PT - -# Imports from idaes generic models -from idaes.models.properties.modular_properties.pure import Perrys, NIST -from idaes.models.properties.modular_properties.state_definitions import FTPx -from idaes.models.properties.modular_properties.eos.ideal import Ideal -from idaes.models.properties.modular_properties.pure.ConstantProperties import Constant -from idaes.models.properties.modular_properties.base.generic_property import StateIndex - -from idaes.models.properties.modular_properties.phase_equil.forms import fugacity - -# Importing the generic model information and objects -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) -from idaes.models.properties.modular_properties.reactions.rate_forms import ( - power_law_rate, -) -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) -from idaes.models.properties.modular_properties.reactions.rate_constant import arrhenius -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor -from idaes.models.unit_models.cstr import CSTR - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -# Import idaes methods to check the model during construction -from idaes.core.util import scaling as iscale -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import degrees_of_freedom - -import idaes.logger as idaeslog - -# Import scaling helper functions -from watertap.examples.chemistry.chem_scaling_utils import ( - _set_inherent_rxn_scaling, - _set_rate_rxn_scaling, - _set_mat_bal_scaling_FTPx, -) - -__author__ = "Austin Ladshaw" - -# Configuration dictionary -thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "enth_mol_ig_comp": NIST, - "pressure_sat_comp": NIST, - "phase_equilibrium_form": {("Vap", "Liq"): fugacity}, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": ( - 4.6543, - pyunits.dimensionless, - ), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - }, - "CO2": { - "type": Solute, - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "enth_mol_ig_comp": NIST, - "pressure_sat_comp": NIST, - "phase_equilibrium_form": {("Vap", "Liq"): fugacity}, - "parameter_data": { - "mw": (44.0095, pyunits.g / pyunits.mol), - "pressure_crit": (73.825e5, pyunits.Pa), - "temperature_crit": (304.23, pyunits.K), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (0.000789, pyunits.kmol * pyunits.m**-3), - "2": (0.000956, pyunits.dimensionless), - "3": (500.78, pyunits.K), - "4": (0.94599, pyunits.dimensionless), - }, - "cp_mol_ig_comp_coeff": { - "A": (24.99735, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 55.18696, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - -33.69137, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - 7.948387, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - -0.136638, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-403.6075, pyunits.kJ / pyunits.mol), - "G": (228.2431, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "cp_mol_liq_comp_coeff": { - "1": (-8.3043e6, pyunits.J / pyunits.kmol / pyunits.K), - "2": (1.0437e5, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (4.333e2, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (6.0052e-1, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "enth_mol_form_liq_comp_ref": (-285.83, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (-393.52, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - "entr_mol_form_vap_comp_ref": (213.6, pyunits.J / pyunits.mol), - "pressure_sat_comp_coeff": { - "A": (6.81228, None), - "B": (1301.679, pyunits.K), - "C": (-3.494, pyunits.K), - }, - }, - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Na_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (22.989769, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.252, pyunits.kmol * pyunits.m**-3), - "2": (0.347, pyunits.dimensionless), - "3": (1595.8, pyunits.K), - "4": (0.6598, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-240.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (59, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "Ca_2+": { - "type": Cation, - "charge": 2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (40.078, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (13.5, pyunits.kmol * pyunits.m**-3), - "2": (1, pyunits.dimensionless), - "3": (1, pyunits.K), - "4": (1, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-542.83, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -53, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (61.0168, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (60.01, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "NaHCO3": { - "type": Apparent, - "valid_phase_types": PT.aqueousPhase, - "dissociation_species": {"Na_+": 1, "HCO3_-": 1}, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca(OH)2": { - "type": Apparent, - "valid_phase_types": PT.aqueousPhase, - "dissociation_species": {"Ca_2+": 1, "OH_-": 2}, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "NaOH": { - "type": Apparent, - "valid_phase_types": PT.aqueousPhase, - "dissociation_species": {"Na_+": 1, "OH_-": 1}, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CaCO3": { - "type": Apparent, - "valid_phase_types": PT.aqueousPhase, - "dissociation_species": {"Ca_2+": 1, "CO3_2-": 1}, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca(HCO3)2": { - "type": Apparent, - "valid_phase_types": PT.aqueousPhase, - "dissociation_species": {"Ca_2+": 1, "HCO3_-": 2}, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - }, - # End Component list - "phases": {"Liq": {"type": AqueousPhase, "equation_of_state": Ideal}}, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 500), - "pressure": (5e4, 1e5, 1e6), - }, - "state_components": StateIndex.true, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Inherent reactions - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - # End R2 - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - # End R3 - "CO2_to_H2CO3": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "CO2"): -1, - ("Liq", "H2CO3"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (1.7 * 10**-3, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): 1, - ("Liq", "CO2"): -1, - ("Liq", "H2O"): 0, - }, - } - # End parameter_data - }, - } - # End equilibrium_reactions -} -# End thermo_config definition - -# This config is REQUIRED to use EquilibriumReactor even if we have no equilibrium reactions -reaction_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "dummy": { - "stoichiometry": {}, - "equilibrium_form": log_power_law_equil, - } - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Get default solver for testing -solver = get_solver() -solver.options["ma27_pivtol"] = 1e-1 - -# Start test class -class TestRemineralization: - @pytest.fixture(scope="class") - def remineralization_appr_equ(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=False, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - energy_balance_type=EnergyBalanceType.none, - ) - - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "CO3_2-"].fix(0.0) - model.fs.unit.inlet.mole_frac_comp[0, "H2CO3"].fix(0.0) - - # Add in our species as if they were Ca(OH)2 and NaHCO3 - # These are typical addatives for remineralization - Ca_OH_2 = 6e-4 # mol/L - NaHCO3 = 0.00206 # mol/L - total = 55.6 + Ca_OH_2 + NaHCO3 # mol/L - - model.fs.unit.inlet.mole_frac_comp[0, "Na_+"].fix(NaHCO3 / total) - model.fs.unit.inlet.mole_frac_comp[0, "Ca_2+"].fix(Ca_OH_2 / total) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(2 * Ca_OH_2 / total) - model.fs.unit.inlet.mole_frac_comp[0, "HCO3_-"].fix(NaHCO3 / total) - - # Add in a typical CO2 concentration for equilibrium reactor - model.fs.unit.inlet.mole_frac_comp[0, "CO2"].fix(0.0005) - - # Perform a summation of all non-H2O molefractions to find the H2O molefraction - sum = 0 - for i in model.fs.unit.inlet.mole_frac_comp: - if i[1] != "H2O": - sum += value(model.fs.unit.inlet.mole_frac_comp[i[0], i[1]]) - - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1 - sum) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.outlet.temperature.fix(298.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.mark.unit - def test_build_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 14 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - assert "CO2" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO2"), Solute) - - assert "Na_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Na_+"), Cation) - - assert "Ca_2+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Ca_2+"), Cation) - - assert "H2CO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H2CO3"), Solute) - - assert "HCO3_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("HCO3_-"), Anion) - - assert "CO3_2-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO3_2-"), Anion) - - assert "NaHCO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("NaHCO3"), Apparent) - - assert "Ca(OH)2" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Ca(OH)2"), Apparent) - - assert "NaOH" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("NaOH"), Apparent) - - assert "CaCO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CaCO3"), Apparent) - - assert "Ca(HCO3)2" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Ca(HCO3)2"), Apparent) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 1 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.unit - def test_units_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_scaling_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - - for i in model.fs.unit.control_volume.inherent_reaction_extent_index: - scale = value( - model.fs.unit.control_volume.properties_out[0.0].k_eq[i[1]].expr - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.inherent_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].inherent_equilibrium_constraint[i[1]], - 0.1, - ) - - # Next, try adding scaling for species - min = 1e-3 - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if model.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min: - scale = model.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - i - ], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert hasattr(model.fs.unit.control_volume, "scaling_factor") - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert hasattr( - model.fs.unit.control_volume.properties_out[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert hasattr( - model.fs.unit.control_volume.properties_in[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize_solver_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - - results = solver.solve(model, tee=True) - print(results.solver.termination_condition) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - pH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "H_+"] * total_molar_density) - ) - pOH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"] * total_molar_density) - ) - - assert pytest.approx(8.2018656, rel=1e-4) == pH - assert pytest.approx(5.7987456, rel=1e-4) == pOH - - # Calculate total hardness - TH = ( - 2 - * value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "Ca_2+") - ] - ) - / 1000 - ) - TH = TH * 50000 - assert pytest.approx(59.524889, rel=1e-5) == TH - - # Calculating carbonate alkalinity to determine the split of total hardness - CarbAlk = ( - 2 - * value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "CO3_2-") - ] - ) - / 1000 - ) - CarbAlk += ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "HCO3_-") - ] - ) - / 1000 - ) - CarbAlk = 50000 * CarbAlk - assert pytest.approx(161.6301239, rel=1e-5) == CarbAlk - - # Non-Carbonate Hardness only exists if there is excess hardness above alkalinity - if TH <= CarbAlk: - NCH = 0 - else: - NCH = TH - CarbAlk - CH = TH - NCH - assert pytest.approx(TH, rel=1e-5) == CH - - @pytest.mark.component - def test_validation_appr_equ(self, remineralization_appr_equ): - model = remineralization_appr_equ - - # Check the apparent species for valid distribution of ions - # i.e., if you use these as input values for apparent species to another - # process, then the model will result in same pH, hardness, and - # alkalinity, assuming same reaction sets apply - nahco3 = value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp_apparent["Liq", "NaHCO3"] - ) - assert pytest.approx(3.6490406403736244e-05, rel=1e-3) == nahco3 - - h2co3 = value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp_apparent["Liq", "H2CO3"] - ) - assert pytest.approx(8.127381781520279e-07, rel=1e-3) == h2co3 - - caoh = value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp_apparent["Liq", "Ca(OH)2"] - ) - assert pytest.approx(5.303805621810129e-09, rel=1e-3) == caoh - - naoh = value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp_apparent["Liq", "NaOH"] - ) - assert pytest.approx(1.8209732607155688e-08, rel=1e-3) == naoh - - caco3 = value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp_apparent["Liq", "CaCO3"] - ) - assert pytest.approx(1.581439142347598e-07, rel=1e-3) == caco3 - - cahco3 = value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp_apparent["Liq", "Ca(HCO3)2"] - ) - assert pytest.approx(1.0628273709826089e-05, rel=1e-3) == cahco3 - - -# Configuration dictionary -thermo_config_cstr = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "enth_mol_ig_comp": NIST, - "pressure_sat_comp": NIST, - "phase_equilibrium_form": {("Vap", "Liq"): fugacity}, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": ( - 4.6543, - pyunits.dimensionless, - ), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - }, - "CO2": { - "type": Solute, - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - "enth_mol_ig_comp": NIST, - "pressure_sat_comp": NIST, - "phase_equilibrium_form": {("Vap", "Liq"): fugacity}, - "parameter_data": { - "mw": (44.0095, pyunits.g / pyunits.mol), - "pressure_crit": (73.825e5, pyunits.Pa), - "temperature_crit": (304.23, pyunits.K), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (0.000789, pyunits.kmol * pyunits.m**-3), - "2": (0.000956, pyunits.dimensionless), - "3": (500.78, pyunits.K), - "4": (0.94599, pyunits.dimensionless), - }, - "cp_mol_ig_comp_coeff": { - "A": (24.99735, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 55.18696, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - -33.69137, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - 7.948387, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - -0.136638, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-403.6075, pyunits.kJ / pyunits.mol), - "G": (228.2431, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "cp_mol_liq_comp_coeff": { - "1": (-8.3043e6, pyunits.J / pyunits.kmol / pyunits.K), - "2": (1.0437e5, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (4.333e2, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (6.0052e-1, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "enth_mol_form_liq_comp_ref": (-285.83, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (-393.52, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - "entr_mol_form_vap_comp_ref": (213.6, pyunits.J / pyunits.mol), - "pressure_sat_comp_coeff": { - "A": (6.81228, None), - "B": (1301.679, pyunits.K), - "C": (-3.494, pyunits.K), - }, - }, - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Na_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (22.989769, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.252, pyunits.kmol * pyunits.m**-3), - "2": (0.347, pyunits.dimensionless), - "3": (1595.8, pyunits.K), - "4": (0.6598, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-240.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (59, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "Ca_2+": { - "type": Cation, - "charge": 2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (40.078, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (13.5, pyunits.kmol * pyunits.m**-3), - "2": (1, pyunits.dimensionless), - "3": (1, pyunits.K), - "4": (1, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-542.83, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -53, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (61.0168, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (60.01, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "NaHCO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (84.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca(OH)2": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (74.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 500), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Inherent reactions - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - # End R2 - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - # End R3 - "CO2_to_H2CO3": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "CO2"): -1, - ("Liq", "H2CO3"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.kJ / pyunits.mol), - "k_eq_ref": (1.7 * 10**-3, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): 1, - ("Liq", "CO2"): -1, - ("Liq", "H2O"): 0, - }, - } - # End parameter_data - }, - } - # End equilibrium_reactions -} -# End thermo_config definition - -reaction_config_cstr = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "rate_reactions": { - "R1": { - "stoichiometry": { - ("Liq", "NaHCO3"): -1, - ("Liq", "HCO3_-"): 1, - ("Liq", "Na_+"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "rate_constant": arrhenius, - "rate_form": power_law_rate, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.J / pyunits.mol), - "arrhenius_const": (1e-0, pyunits.mol / pyunits.m**3 / pyunits.s), - "energy_activation": (0, pyunits.J / pyunits.mol), - }, - }, - "R2": { - "stoichiometry": { - ("Liq", "Ca(OH)2"): -1, - ("Liq", "OH_-"): 2, - ("Liq", "Ca_2+"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "rate_constant": arrhenius, - "rate_form": power_law_rate, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0, pyunits.J / pyunits.mol), - "arrhenius_const": (0.5e-0, pyunits.mol / pyunits.m**3 / pyunits.s), - "energy_activation": (0, pyunits.J / pyunits.mol), - }, - }, - } - # End rate_reactions -} -# End reaction_config definition - - -# Start test class -class TestRemineralizationCSTR: - @pytest.fixture(scope="class") - def remineralization_cstr_kin(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config_cstr) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config_cstr - ) - model.fs.unit = CSTR( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_equilibrium_reactions=False, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - energy_balance_type=EnergyBalanceType.none, - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.outlet.temperature.fix(298.0) - - model.fs.unit.volume.fix(100) - model.fs.unit.inlet.flow_mol.fix(10) - - # Add in our species as if they were Ca(OH)2 and NaHCO3 - # These are typical addatives for remineralization - Ca_OH_2 = 6e-4 # mol/L - NaHCO3 = 0.00206 # mol/L - total = 55.6 + Ca_OH_2 + NaHCO3 # mol/L - zero = 1e-20 - - model.fs.unit.inlet.mole_frac_comp[0, "NaHCO3"].fix(NaHCO3 / total) - model.fs.unit.inlet.mole_frac_comp[0, "Ca(OH)2"].fix(Ca_OH_2 / total) - - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "CO3_2-"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "H2CO3"].fix(zero) - - model.fs.unit.inlet.mole_frac_comp[0, "Na_+"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "Ca_2+"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(zero) - model.fs.unit.inlet.mole_frac_comp[0, "HCO3_-"].fix(zero) - - model.fs.unit.inlet.mole_frac_comp[0, "CO2"].fix(0.0005) - - sum = 0 - for i in model.fs.unit.inlet.mole_frac_comp: - if i[1] != "H2O": - sum += value(model.fs.unit.inlet.mole_frac_comp[i[0], i[1]]) - - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1 - sum) - - return model - - @pytest.mark.unit - def test_build_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 11 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - - assert "CO2" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO2"), Solute) - - assert "Na_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Na_+"), Cation) - - assert "Ca_2+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Ca_2+"), Cation) - - assert "H2CO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H2CO3"), Solute) - - assert "HCO3_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("HCO3_-"), Anion) - - assert "CO3_2-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO3_2-"), Anion) - - assert "NaHCO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("NaHCO3"), Solute) - - assert "Ca(OH)2" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Ca(OH)2"), Solute) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 1 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.unit - def test_units_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_scaling_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - - _set_inherent_rxn_scaling(model.fs.unit, thermo_config_cstr, min_k_eq_ref=1e-2) - _set_rate_rxn_scaling(model.fs.rxn_params, model.fs.unit) - _set_mat_bal_scaling_FTPx(model.fs.unit) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert hasattr(model.fs.unit.control_volume, "scaling_factor") - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert hasattr( - model.fs.unit.control_volume.properties_out[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert hasattr( - model.fs.unit.control_volume.properties_in[0.0], "scaling_factor" - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize_solver_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - @pytest.mark.component - def test_solve_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - solver.options["halt_on_ampl_error"] = "yes" - results = solver.solve(model, tee=True, symbolic_solver_labels=True) - print(results.solver.termination_condition) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution_cstr_kin(self, remineralization_cstr_kin): - model = remineralization_cstr_kin - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - pH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "H_+"] * total_molar_density) - ) - pOH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"] * total_molar_density) - ) - - assert pytest.approx(8.144577167341051, rel=1e-4) == pH - assert pytest.approx(5.856040363828365, rel=1e-4) == pOH - - # Calculate total hardness - TH = ( - 2 - * value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "Ca_2+") - ] - ) - / 1000 - ) - TH = TH * 50000 - assert pytest.approx(49.60109933146309, rel=1e-4) == TH - - # Calculating carbonate alkalinity to determine the split of total hardness - CarbAlk = ( - 2 - * value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "CO3_2-") - ] - ) - / 1000 - ) - CarbAlk += ( - value( - model.fs.unit.control_volume.properties_out[0.0].conc_mol_phase_comp[ - ("Liq", "HCO3_-") - ] - ) - / 1000 - ) - CarbAlk = 50000 * CarbAlk - assert pytest.approx(142.42137166781512, rel=1e-4) == CarbAlk - - # Non-Carbonate Hardness only exists if there is excess hardness above alkalinity - if TH <= CarbAlk: - NCH = 0 - else: - NCH = TH - CarbAlk - CH = TH - NCH - assert pytest.approx(TH, rel=1e-5) == CH diff --git a/watertap/examples/chemistry/tests/test_seawater_alkalinity.py b/watertap/examples/chemistry/tests/test_seawater_alkalinity.py deleted file mode 100644 index 6bad1e302e..0000000000 --- a/watertap/examples/chemistry/tests/test_seawater_alkalinity.py +++ /dev/null @@ -1,1242 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is to establish that the core chemistry packages in IDAES - can solve a more complex aqueous speciation problem meant to mimic - the salinity, pH, and alkalinity of real seawater. Note that not all - possible reactions in seawater are being considered for this test, - only those most relevant to salinity and alkalinity calculations. - - Reactions: - H2O <---> H + OH - H2CO3 <---> H + HCO3 - HCO3 <---> H + CO3 - NaHCO3 <---> Na + HCO3 - Other species: - Cl -""" -# Importing testing libraries -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -# Imports from idaes core -from idaes.core import AqueousPhase -from idaes.core.base.components import Solvent, Solute, Cation, Anion -from idaes.core.base.phases import PhaseType as PT - -# Imports from idaes generic models -import idaes.models.properties.modular_properties.pure.Perrys as Perrys -from idaes.models.properties.modular_properties.state_definitions import FTPx -from idaes.models.properties.modular_properties.eos.ideal import Ideal - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -# Import k-value functions -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -from idaes.core.util import scaling as iscale - -# Import pyomo methods to check the system units -from pyomo.util.check_units import assert_units_consistent - -# Import idaes methods to check the model during construction -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import ( - degrees_of_freedom, - fixed_variables_set, - activated_constraints_set, - number_variables, - number_total_constraints, -) - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor - -# Import the core idaes objects for Flowsheets and types of balances -from idaes.core import FlowsheetBlock - -# Import log10 function from pyomo -from pyomo.environ import log10 - -__author__ = "Austin Ladshaw" - -EPS = 1e-20 - - -# Configuration dictionary -thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": ( - 4.6543, - pyunits.dimensionless, - ), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - # End parameter_data - }, - "H_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (1.00784, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "OH_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (17.008, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-230.000, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -10.75, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Na_+": { - "type": Cation, - "charge": 1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (22.989769, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.252, pyunits.kmol * pyunits.m**-3), - "2": (0.347, pyunits.dimensionless), - "3": (1595.8, pyunits.K), - "4": (0.6598, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-240.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": (59, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "Cl_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (35.453, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (4.985, pyunits.kmol * pyunits.m**-3), - "2": (0.36, pyunits.dimensionless), - "3": (1464.06, pyunits.K), - "4": (0.739, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-167.2, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (83993.8, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 56.5, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "H2CO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (62.03, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-699.7, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 187, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "HCO3_-": { - "type": Anion, - "charge": -1, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (61.0168, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-692, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 91.2, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CO3_2-": { - "type": Anion, - "charge": -2, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (60.01, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.4495, pyunits.kmol * pyunits.m**-3), - "2": (0.427, pyunits.dimensionless), - "3": (429.69, pyunits.K), - "4": (0.259, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-677.1, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (135749.9, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - -56.9, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "NaHCO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (84.007, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.252, pyunits.kmol * pyunits.m**-3), - "2": (0.347, pyunits.dimensionless), - "3": (1595.8, pyunits.K), - "4": (0.6598, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": { - "1": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "2": (0, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (0, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (0, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (0, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 650), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - # Inherent reactions - "inherent_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - # End R2 - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - # End R3 - "NaHCO3_K1": { - "stoichiometry": { - ("Liq", "NaHCO3"): -1, - ("Liq", "Na_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (13.43, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**0.27 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "NaHCO3"): -1, - ("Liq", "Na_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - } - # End R4 - } - # End equilibrium_reactions -} -# End thermo_config definition - -# Define the reaction_config for water dissociation -reaction_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "H2O_Kw": { - "stoichiometry": { - ("Liq", "H2O"): -1, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (55.830, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-14 / 55.2 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2O"): 0, - ("Liq", "H_+"): 1, - ("Liq", "OH_-"): 1, - }, - } - # End parameter_data - }, - # End R1 - "H2CO3_Ka1": { - "stoichiometry": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (7.7, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-6.35 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "H2CO3"): -1, - ("Liq", "H_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - }, - # End R2 - "H2CO3_Ka2": { - "stoichiometry": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (14.9, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**-10.33 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "HCO3_-"): -1, - ("Liq", "H_+"): 1, - ("Liq", "CO3_2-"): 1, - }, - } - # End parameter_data - }, - # End R3 - "NaHCO3_K1": { - "stoichiometry": { - ("Liq", "NaHCO3"): -1, - ("Liq", "Na_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_power_law_equil, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (13.43, pyunits.kJ / pyunits.mol), - "k_eq_ref": (10**0.27 / 55.2, pyunits.dimensionless), - "T_eq_ref": (298, pyunits.K), - # By default, reaction orders follow stoichiometry - # manually set reaction order here to override - "reaction_order": { - ("Liq", "NaHCO3"): -1, - ("Liq", "Na_+"): 1, - ("Liq", "HCO3_-"): 1, - }, - } - # End parameter_data - } - # End R4 - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Modify og configs to use either inherent or equilibrium reactions -thermo_only_config = {} -thermo_only_config.update(thermo_config) -del thermo_only_config["inherent_reactions"] - -# Get default solver for testing -solver = get_solver() - -# Start test class -class TestSeawaterAlkalinity: - @pytest.fixture(scope="class") - def inherent_reactions_config(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=False, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(EPS) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(EPS) - model.fs.unit.inlet.mole_frac_comp[0, "HCO3_-"].fix(EPS) - model.fs.unit.inlet.mole_frac_comp[0, "CO3_2-"].fix(EPS) - - total_nacl_inlet = 0.55 # mol/L - total_carbonate_inlet = 0.00206 # mol/L - frac_CO3_to_NaHCO3 = 1 - - model.fs.unit.inlet.mole_frac_comp[0, "Na_+"].fix(total_nacl_inlet / 54.8) - model.fs.unit.inlet.mole_frac_comp[0, "Cl_-"].fix(total_nacl_inlet / 54.8) - - model.fs.unit.inlet.mole_frac_comp[0, "NaHCO3"].fix( - (total_carbonate_inlet * frac_CO3_to_NaHCO3) / 54.8 - ) - model.fs.unit.inlet.mole_frac_comp[0, "H2CO3"].fix( - (total_carbonate_inlet * (1 - frac_CO3_to_NaHCO3)) / 54.8 - ) - - # Perform a summation of all non-H2O molefractions to find the H2O molefraction - sum = 0 - for i in model.fs.unit.inlet.mole_frac_comp: - # NOTE: i will be a tuple with format (time, component) - if i[1] != "H2O": - sum += value(model.fs.unit.inlet.mole_frac_comp[i[0], i[1]]) - - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1 - sum) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.fixture(scope="class") - def equilibrium_reactions_config(self): - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**thermo_only_config) - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **reaction_config - ) - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=True, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - ) - - model.fs.unit.inlet.mole_frac_comp[0, "H_+"].fix(EPS) - model.fs.unit.inlet.mole_frac_comp[0, "OH_-"].fix(EPS) - model.fs.unit.inlet.mole_frac_comp[0, "HCO3_-"].fix(EPS) - model.fs.unit.inlet.mole_frac_comp[0, "CO3_2-"].fix(EPS) - - total_nacl_inlet = 0.55 # mol/L - total_carbonate_inlet = 0.00206 # mol/L - frac_CO3_to_NaHCO3 = 1 - - model.fs.unit.inlet.mole_frac_comp[0, "Na_+"].fix(total_nacl_inlet / 54.8) - model.fs.unit.inlet.mole_frac_comp[0, "Cl_-"].fix(total_nacl_inlet / 54.8) - - model.fs.unit.inlet.mole_frac_comp[0, "NaHCO3"].fix( - (total_carbonate_inlet * frac_CO3_to_NaHCO3) / 54.8 - ) - model.fs.unit.inlet.mole_frac_comp[0, "H2CO3"].fix( - (total_carbonate_inlet * (1 - frac_CO3_to_NaHCO3)) / 54.8 - ) - - # Perform a summation of all non-H2O molefractions to find the H2O molefraction - sum = 0 - for i in model.fs.unit.inlet.mole_frac_comp: - # NOTE: i will be a tuple with format (time, component) - if i[1] != "H2O": - sum += value(model.fs.unit.inlet.mole_frac_comp[i[0], i[1]]) - - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(1 - sum) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.inlet.flow_mol.fix(10) - - return model - - @pytest.mark.unit - def test_build_model_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 9 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - assert "Na_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Na_+"), Cation) - assert "Cl_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Cl_-"), Anion) - assert "HCO3_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("HCO3_-"), Anion) - assert "CO3_2-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO3_2-"), Anion) - assert "H2CO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2CO3, Solute) - assert "NaHCO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.NaHCO3, Solute) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 1 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.unit - def test_build_model_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - - assert hasattr(model.fs.thermo_params, "component_list") - assert len(model.fs.thermo_params.component_list) == 9 - assert "H2O" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2O, Solvent) - assert "H_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("H_+"), Cation) - assert "OH_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("OH_-"), Anion) - assert "Na_+" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Na_+"), Cation) - assert "Cl_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("Cl_-"), Anion) - assert "HCO3_-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("HCO3_-"), Anion) - assert "CO3_2-" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.component("CO3_2-"), Anion) - assert "H2CO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.H2CO3, Solute) - assert "NaHCO3" in model.fs.thermo_params.component_list - assert isinstance(model.fs.thermo_params.NaHCO3, Solute) - - assert hasattr(model.fs.thermo_params, "phase_list") - assert len(model.fs.thermo_params.phase_list) == 1 - assert isinstance(model.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.unit - def test_units_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - assert_units_consistent(model) - - @pytest.mark.unit - def test_units_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - assert_units_consistent(model) - - @pytest.mark.unit - def test_dof_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - assert degrees_of_freedom(model) == 0 - - @pytest.mark.unit - def test_dof_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - assert degrees_of_freedom(model) == 0 - - @pytest.mark.unit - def test_stats_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - assert number_variables(model) == 281 - assert number_total_constraints(model) == 72 - - @pytest.mark.unit - def test_stats_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - assert number_variables(model) == 233 - assert number_total_constraints(model) == 72 - - @pytest.mark.component - def test_scaling_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - - for i in model.fs.unit.control_volume.inherent_reaction_extent_index: - scale = value( - model.fs.unit.control_volume.properties_out[0.0].k_eq[i[1]].expr - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.inherent_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].inherent_equilibrium_constraint[i[1]], - 0.1, - ) - - # Next, try adding scaling for species - min = 1e-3 - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if model.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min: - scale = model.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - i - ], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_scaling_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - - for i in model.fs.unit.control_volume.equilibrium_reaction_extent_index: - scale = value(model.fs.unit.control_volume.reactions[0.0].k_eq[i[1]].expr) - iscale.set_scaling_factor( - model.fs.unit.control_volume.equilibrium_reaction_extent[0.0, i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.reactions[0.0].equilibrium_constraint[ - i[1] - ], - 0.1, - ) - - # Next, try adding scaling for species - min = 1e-3 - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if model.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min: - scale = model.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - i - ], - 10 / scale, - ) - iscale.set_scaling_factor( - model.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].component_flow_balances[i[1]], - 10 / scale, - ) - iscale.constraint_scaling_transform( - model.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - iscale.calculate_scaling_factors(model.fs.unit) - - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - # When using equilibrium reactions, there are another set of scaling factors calculated - assert isinstance( - model.fs.unit.control_volume.reactions[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - - orig_fixed_vars = fixed_variables_set(model) - orig_act_consts = activated_constraints_set(model) - - model.fs.unit.initialize(optarg=solver.options) - - fin_fixed_vars = fixed_variables_set(model) - fin_act_consts = activated_constraints_set(model) - - assert degrees_of_freedom(model) == 0 - - assert len(fin_act_consts) == len(orig_act_consts) - assert len(fin_fixed_vars) == len(orig_fixed_vars) - - @pytest.mark.component - def test_initialize_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - - orig_fixed_vars = fixed_variables_set(model) - orig_act_consts = activated_constraints_set(model) - - model.fs.unit.initialize(optarg=solver.options) - - fin_fixed_vars = fixed_variables_set(model) - fin_act_consts = activated_constraints_set(model) - - assert degrees_of_freedom(model) == 0 - - assert len(fin_act_consts) == len(orig_act_consts) - assert len(fin_fixed_vars) == len(orig_fixed_vars) - - @pytest.mark.component - def test_solve_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - solver.options["max_iter"] = 20 - results = solver.solve(model) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solve_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - solver.options["max_iter"] = 20 - results = solver.solve(model) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution_inherent(self, inherent_reactions_config): - model = inherent_reactions_config - - assert pytest.approx(297.995, rel=1e-3) == value( - model.fs.unit.outlet.temperature[0] - ) - assert pytest.approx(10.00029, rel=1e-5) == value( - model.fs.unit.outlet.flow_mol[0] - ) - assert pytest.approx(101325, rel=1e-5) == value( - model.fs.unit.outlet.pressure[0] - ) - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - assert pytest.approx(54.61066231692383, rel=1e-5) == total_molar_density - - total_salt = ( - value(model.fs.unit.outlet.mole_frac_comp[0, "Na_+"]) - * total_molar_density - * 23 - ) - total_salt += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "Cl_-"]) - * total_molar_density - * 35.44 - ) - total_salt += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "NaHCO3"]) - * total_molar_density - * 84 - ) - psu = total_salt / (total_molar_density * 18) * 1000 - assert pytest.approx(32.66105, rel=1e-5) == psu - - total_carbonate = ( - value(model.fs.unit.outlet.mole_frac_comp[0, "NaHCO3"]) - * total_molar_density - ) - total_carbonate += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "H2CO3"]) * total_molar_density - ) - total_carbonate += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "HCO3_-"]) - * total_molar_density - ) - total_carbonate += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "CO3_2-"]) - * total_molar_density - ) - assert pytest.approx(0.0020528228525604694, rel=1e-5) == total_carbonate - - carbonate_alk = ( - value(model.fs.unit.outlet.mole_frac_comp[0, "HCO3_-"]) - * total_molar_density - ) - carbonate_alk += ( - 2 - * value(model.fs.unit.outlet.mole_frac_comp[0, "CO3_2-"]) - * total_molar_density - ) - carbonate_alk += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"]) * total_molar_density - ) - carbonate_alk -= ( - value(model.fs.unit.outlet.mole_frac_comp[0, "H_+"]) * total_molar_density - ) - carbonate_alk = carbonate_alk * 50000 - assert pytest.approx(79.418520, rel=1e-4) == carbonate_alk - - pH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "H_+"] * total_molar_density) - ) - pOH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"] * total_molar_density) - ) - assert pytest.approx(8.31763834, rel=1e-4) == pH - assert pytest.approx(5.6923942, rel=1e-4) == pOH - - @pytest.mark.component - def test_solution_equilibrium(self, equilibrium_reactions_config): - model = equilibrium_reactions_config - - assert pytest.approx(297.995, rel=1e-3) == value( - model.fs.unit.outlet.temperature[0] - ) - assert pytest.approx(10.0002, rel=1e-5) == value( - model.fs.unit.outlet.flow_mol[0] - ) - assert pytest.approx(101325, rel=1e-5) == value( - model.fs.unit.outlet.pressure[0] - ) - - total_molar_density = ( - value( - model.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"] - ) - / 1000 - ) - assert pytest.approx(54.61066231692384, rel=1e-5) == total_molar_density - - total_salt = ( - value(model.fs.unit.outlet.mole_frac_comp[0, "Na_+"]) - * total_molar_density - * 23 - ) - total_salt += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "Cl_-"]) - * total_molar_density - * 35.44 - ) - total_salt += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "NaHCO3"]) - * total_molar_density - * 84 - ) - psu = total_salt / (total_molar_density * 18) * 1000 - assert pytest.approx(32.66105, rel=1e-5) == psu - - total_carbonate = ( - value(model.fs.unit.outlet.mole_frac_comp[0, "NaHCO3"]) - * total_molar_density - ) - total_carbonate += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "H2CO3"]) * total_molar_density - ) - total_carbonate += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "HCO3_-"]) - * total_molar_density - ) - total_carbonate += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "CO3_2-"]) - * total_molar_density - ) - assert pytest.approx(0.0020528228525604694, rel=1e-5) == total_carbonate - - carbonate_alk = ( - value(model.fs.unit.outlet.mole_frac_comp[0, "HCO3_-"]) - * total_molar_density - ) - carbonate_alk += ( - 2 - * value(model.fs.unit.outlet.mole_frac_comp[0, "CO3_2-"]) - * total_molar_density - ) - carbonate_alk += ( - value(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"]) * total_molar_density - ) - carbonate_alk -= ( - value(model.fs.unit.outlet.mole_frac_comp[0, "H_+"]) * total_molar_density - ) - carbonate_alk = carbonate_alk * 50000 - assert pytest.approx(79.418520, rel=1e-4) == carbonate_alk - - pH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "H_+"] * total_molar_density) - ) - pOH = -value( - log10(model.fs.unit.outlet.mole_frac_comp[0, "OH_-"] * total_molar_density) - ) - assert pytest.approx(8.31763834, rel=1e-4) == pH - assert pytest.approx(5.6923942, rel=1e-4) == pOH diff --git a/watertap/examples/chemistry/tests/test_solids.py b/watertap/examples/chemistry/tests/test_solids.py deleted file mode 100644 index 3e7c6bcb65..0000000000 --- a/watertap/examples/chemistry/tests/test_solids.py +++ /dev/null @@ -1,692 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - This test is of the core IDAES components that allow for the declaration - and usage of solids phases in conjunction with aqueous phases. This test - is primarily being used to probe for issues that may exist in how IDAES - handles this new system and properties, since it is brand new to the - framework. Several tests of the same basic case are observed. - - Case 1: [Combine] A and B are aqueous, declare AB a solid, - add precipitation reaction (record IDAES debug) - - Several studies of this case are repeated for numerous inlet values to - assess IDAES's ability to capture both precipitation and dissolution - - Solubility Reaction: - AB <---> A + B - - Case 2: Repeat Case 1, but try using new 'log_solubility_product' -""" - -# Importing testing libraries -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -# Imports from idaes core -from idaes.core import AqueousPhase, SolidPhase, FlowsheetBlock, EnergyBalanceType -from idaes.core.base.components import Solvent, Solute, Component -from idaes.core.base.phases import PhaseType as PT - -# Imports from idaes generic models -from idaes.models.properties.modular_properties.pure.ConstantProperties import Constant -from idaes.models.properties.modular_properties.state_definitions import FTPx, FpcTP -from idaes.models.properties.modular_properties.eos.ideal import Ideal - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import built-in van't Hoff function -from idaes.models.properties.modular_properties.reactions.equilibrium_constant import ( - van_t_hoff, -) - -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - solubility_product, - log_solubility_product, -) - -# Import specific pyomo objects -from pyomo.environ import ( - ConcreteModel, - SolverStatus, - TerminationCondition, - value, - Suffix, -) - -from idaes.core.util import scaling as iscale -from idaes.core.util.initialization import fix_state_vars, revert_state_vars - -import idaes.logger as idaeslog - -# Import pyomo methods to check the system units -from pyomo.util.check_units import assert_units_consistent - -# Import idaes methods to check the model during construction -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import degrees_of_freedom - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Import the idaes object for the EquilibriumReactor unit model -from idaes.models.unit_models.equilibrium_reactor import EquilibriumReactor - -# Import scaling helper functions -from watertap.examples.chemistry.chem_scaling_utils import ( - _set_eps_vals, - _set_equ_rxn_scaling, - _set_mat_bal_scaling_FpcTP, - _set_mat_bal_scaling_FTPx, -) - -__author__ = "Austin Ladshaw" - -# Case 1 Config -case1_thermo_config = { - "components": { - "H2O": { - "type": Solvent, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - # End parameter_data - }, - "A": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - "parameter_data": { - "mw": (18, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - }, - "B": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - "parameter_data": { - "mw": (18, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_liq_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_liq_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_liq_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - }, - "AB": { - "type": Component, - "valid_phase_types": PT.solidPhase, - "dens_mol_sol_comp": Constant, - "enth_mol_sol_comp": Constant, - "cp_mol_sol_comp": Constant, - "entr_mol_sol_comp": Constant, - "parameter_data": { - "mw": (18, pyunits.g / pyunits.mol), - "dens_mol_sol_comp_coeff": (55.2, pyunits.kmol * pyunits.m**-3), - "cp_mol_sol_comp_coeff": (75.312, pyunits.J / pyunits.mol / pyunits.K), - "enth_mol_form_sol_comp_ref": (0, pyunits.kJ / pyunits.mol), - "entr_mol_form_sol_comp_ref": (0, pyunits.J / pyunits.K / pyunits.mol), - }, - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - "Sol": {"type": SolidPhase, "equation_of_state": Ideal}, - }, - # Default for testing = FpcTP - "state_definition": FpcTP, - # "state_definition": FTPx, - "state_bounds": { - # "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 650), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, -} -# End thermo_config definition - -# Actual solubility product -reaction_solubility = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "AB_Ksp": { - "stoichiometry": {("Sol", "AB"): -1, ("Liq", "A"): 1, ("Liq", "B"): 1}, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-10, pyunits.dimensionless), - "T_eq_ref": (300.0, pyunits.K), - "reaction_order": {("Sol", "AB"): 0, ("Liq", "A"): 1, ("Liq", "B"): 1}, - } - # End parameter_data - } - # End Reaction - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Actual solubility product -reaction_log_solubility = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "AB_Ksp": { - "stoichiometry": {("Sol", "AB"): -1, ("Liq", "A"): 1, ("Liq", "B"): 1}, - "heat_of_reaction": constant_dh_rxn, - "equilibrium_constant": van_t_hoff, - "equilibrium_form": log_solubility_product, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "dh_rxn_ref": (0.0, pyunits.J / pyunits.mol), - "k_eq_ref": (10**-10, pyunits.dimensionless), - "T_eq_ref": (300.0, pyunits.K), - "reaction_order": {("Sol", "AB"): 0, ("Liq", "A"): 1, ("Liq", "B"): 1}, - } - # End parameter_data - } - # End Reaction - } - # End equilibrium_reactions -} -# End reaction_config definition - -# Get default solver for testing -solver = get_solver() - - -def run_case1(xA, xB, xAB=1e-25, scaling=True, scaling_ref=1e-3, rxn_config=None): - print("==========================================================================") - print("Case 1: A and B are aqueous, AB is solid that forms from reaction") - print("xA = " + str(xA)) - print("xB = " + str(xB)) - print("xAB = " + str(xAB)) - print("scaling = " + str(scaling)) - print("including water = " + str(True)) - print() - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - model.fs.thermo_params = GenericParameterBlock(**case1_thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **rxn_config - ) - - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=True, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - energy_balance_type=EnergyBalanceType.none, - ) - - total_flow_mol = 10 - - # Set flow_mol_phase_comp - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "A"].fix(xA * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "B"].fix(xB * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "AB"].fix(xAB * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - (1 - xA - xB - xAB) * total_flow_mol - ) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.outlet.temperature.fix(298.0) - - assert degrees_of_freedom(model) == 0 - - assert_units_consistent(model) - - # Scaling - _set_eps_vals(model.fs.rxn_params, rxn_config, max_k_eq_ref=1e-12) - _set_equ_rxn_scaling( - model.fs.unit, model.fs.rxn_params, rxn_config, min_k_eq_ref=scaling_ref - ) - _set_mat_bal_scaling_FpcTP(model.fs.unit, min_flow_mol_phase_comp=scaling_ref * 10) - model.fs.rxn_params.reaction_AB_Ksp.s_scale.value = 10 - - iscale.calculate_scaling_factors(model.fs.unit) - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - assert isinstance( - model.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - assert isinstance( - model.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - # End scaling if statement - - init_options = {**solver.options} - init_options["bound_relax_factor"] = 1.0e-07 - model.fs.unit.initialize(optarg=init_options, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - solver.options["bound_relax_factor"] = 1.0e-07 - results = solver.solve(model, tee=True) - del solver.options["bound_relax_factor"] - - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - print("comp\toutlet.tot_molfrac") - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i] - ) - ) - ) - print() - - # NOTE: Changed all to mole fraction - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp[i] - ) - ) - ) - - A = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "A" - ] - ) - B = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "B" - ] - ) - Ksp = value(model.fs.unit.control_volume.reactions[0.0].k_eq["AB_Ksp"].expr) - - print() - if Ksp * 1.01 >= A * B: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp / A / B) + ">=1") - assert False - print("Ksp =\t" + str(Ksp)) - print("A*B =\t" + str(A * B)) - - print("==========================================================================") - - return model - - -def run_case2( - xA, xB, xAB=1e-25, scaling=True, scaling_ref=1e-3, rxn_config=None, state="FpcTP" -): - print("==========================================================================") - print( - "Case 2 (log form): A and B are aqueous, AB is solid that forms from reaction" - ) - print("xA = " + str(xA)) - print("xB = " + str(xB)) - print("xAB = " + str(xAB)) - print("scaling = " + str(scaling)) - print("including water = " + str(True)) - print() - model = ConcreteModel() - model.fs = FlowsheetBlock(dynamic=False) - - if state == "FpcTP": - case1_thermo_config["state_definition"] = FpcTP - elif state == "FTPx": - case1_thermo_config["state_definition"] = FTPx - case1_thermo_config["state_bounds"]["flow_mol"] = (0, 50, 100) - else: - print("Error! Undefined state...") - assert False - - model.fs.thermo_params = GenericParameterBlock(**case1_thermo_config) - - model.fs.rxn_params = GenericReactionParameterBlock( - property_package=model.fs.thermo_params, **rxn_config - ) - - model.fs.unit = EquilibriumReactor( - property_package=model.fs.thermo_params, - reaction_package=model.fs.rxn_params, - has_rate_reactions=False, - has_equilibrium_reactions=True, - has_heat_transfer=False, - has_heat_of_reaction=False, - has_pressure_change=False, - energy_balance_type=EnergyBalanceType.none, - ) - - total_flow_mol = 10 - - # Set flow_mol_phase_comp - if case1_thermo_config["state_definition"] == FpcTP: - - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "A"].fix(xA * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "B"].fix(xB * total_flow_mol) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Sol", "AB"].fix( - xAB * total_flow_mol - ) - model.fs.unit.inlet.flow_mol_phase_comp[0, "Liq", "H2O"].fix( - (1 - xA - xB - xAB) * total_flow_mol - ) - - if case1_thermo_config["state_definition"] == FTPx: - model.fs.unit.inlet.mole_frac_comp[0, "A"].fix(xA) - model.fs.unit.inlet.mole_frac_comp[0, "B"].fix(xB) - model.fs.unit.inlet.mole_frac_comp[0, "AB"].fix(xAB) - model.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix((1 - xA - xB - xAB)) - model.fs.unit.inlet.flow_mol.fix(total_flow_mol) - - model.fs.unit.inlet.pressure.fix(101325.0) - model.fs.unit.inlet.temperature.fix(298.0) - model.fs.unit.outlet.temperature.fix(298.0) - - assert degrees_of_freedom(model) == 0 - - assert_units_consistent(model) - - # Scaling - _set_eps_vals(model.fs.rxn_params, rxn_config, max_k_eq_ref=1e-12) - _set_equ_rxn_scaling( - model.fs.unit, model.fs.rxn_params, rxn_config, min_k_eq_ref=scaling_ref - ) - if case1_thermo_config["state_definition"] == FpcTP: - _set_mat_bal_scaling_FpcTP(model.fs.unit, min_flow_mol_phase_comp=scaling_ref) - if case1_thermo_config["state_definition"] == FTPx: - _set_mat_bal_scaling_FTPx(model.fs.unit, min_mole_frac_comp=scaling_ref) - - iscale.calculate_scaling_factors(model.fs.unit) - assert isinstance(model.fs.unit.control_volume.scaling_factor, Suffix) - - # Initialize model - - if case1_thermo_config["state_definition"] == FpcTP: - state_args = { - "flow_mol_phase_comp": { - ("Liq", "H2O"): model.fs.unit.inlet.flow_mol_phase_comp[ - 0, "Liq", "H2O" - ].value, - ("Liq", "A"): model.fs.unit.inlet.flow_mol_phase_comp[ - 0, "Liq", "A" - ].value, - ("Liq", "B"): model.fs.unit.inlet.flow_mol_phase_comp[ - 0, "Liq", "B" - ].value, - ("Sol", "AB"): model.fs.unit.inlet.flow_mol_phase_comp[ - 0, "Sol", "AB" - ].value, - }, - "pressure": 101325, - "temperature": 298, - "flow_mol": 10, - } - - if case1_thermo_config["state_definition"] == FTPx: - state_args = { - "mole_frac_comp": { - "H2O": model.fs.unit.inlet.mole_frac_comp[0, "H2O"].value, - "A": model.fs.unit.inlet.mole_frac_comp[0, "A"].value, - "B": model.fs.unit.inlet.mole_frac_comp[0, "B"].value, - "AB": model.fs.unit.inlet.mole_frac_comp[0, "AB"].value, - }, - "pressure": 101325, - "temperature": 298, - "flow_mol": 10, - } - - flags = fix_state_vars(model.fs.unit.control_volume.properties_out, state_args) - revert_state_vars(model.fs.unit.control_volume.properties_out, flags) - - model.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - - assert degrees_of_freedom(model) == 0 - - results = solver.solve(model, tee=True) - - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - print("comp\toutlet.tot_molfrac") - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i] - ) - ) - ) - print() - - # NOTE: Changed all to mole fraction - for i in model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - print( - str(i) - + "\t" - + str( - value( - model.fs.unit.control_volume.properties_out[ - 0.0 - ].mole_frac_phase_comp[i] - ) - ) - ) - - A = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "A" - ] - ) - B = value( - model.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[ - "Liq", "B" - ] - ) - Ksp = value(model.fs.unit.control_volume.reactions[0.0].k_eq["AB_Ksp"].expr) - - print() - if Ksp * 1.01 >= A * B: - print("Constraint is satisfied!") - else: - print("Constraint is VIOLATED!") - print("\tRelative error: " + str(Ksp / A / B) + ">=1") - assert False - print("Ksp =\t" + str(Ksp)) - print("A*B =\t" + str(A * B)) - - print("==========================================================================") - - return model - - -## ================================= Case 1 Tests =============================== -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case1_low_conc_no_precipitation(): - model = run_case1( - xA=1e-9, - xB=1e-9, - xAB=1e-25, - scaling=True, - scaling_ref=1e-6, - rxn_config=reaction_solubility, - ) - - -@pytest.mark.requires_idaes_solver -@pytest.mark.component -def test_case1_mid_conc_no_precipitation(): - model = run_case1( - xA=1e-9, - xB=1e-2, - xAB=1e-25, - scaling=True, - scaling_ref=1e-4, - rxn_config=reaction_solubility, - ) - - -@pytest.mark.component -def test_case1_high_conc_with_precipitation(): - model = run_case1( - xA=1e-2, xB=1e-2, xAB=1e-25, scaling=True, rxn_config=reaction_solubility - ) - - -@pytest.mark.component -def test_case1_low_conc_with_dissolution(): - model = run_case1( - xA=1e-9, xB=1e-9, xAB=1e-2, scaling=True, rxn_config=reaction_solubility - ) - - -@pytest.mark.component -def test_case1_mid_conc_with_dissolution(): - model = run_case1( - xA=1e-9, xB=1e-2, xAB=1e-2, scaling=True, rxn_config=reaction_solubility - ) - - -@pytest.mark.component -def test_case1_high_conc_for_all(): - model = run_case1( - xA=1e-2, xB=1e-2, xAB=1e-2, scaling=True, rxn_config=reaction_solubility - ) - - -## ================================= Case 2 Tests =============================== -@pytest.mark.component -def test_case2_low_conc_no_precipitation(): - model = run_case2( - xA=1e-9, xB=1e-9, xAB=1e-25, scaling=True, rxn_config=reaction_log_solubility - ) - - -@pytest.mark.component -def test_case2_mid_conc_no_precipitation(): - model = run_case2( - xA=1e-9, - xB=1e-2, - xAB=1e-25, - scaling=True, - scaling_ref=1e-5, - rxn_config=reaction_log_solubility, - ) - - -@pytest.mark.component -def test_case2_high_conc_with_precipitation(): - model = run_case2( - xA=1e-2, - xB=1e-2, - xAB=1e-25, - scaling=True, - scaling_ref=1e-5, - rxn_config=reaction_log_solubility, - ) - - -@pytest.mark.component -def test_case2_low_conc_with_dissolution(): - model = run_case2( - xA=1e-9, xB=1e-9, xAB=1e-2, scaling=True, rxn_config=reaction_log_solubility - ) - - -@pytest.mark.component -def test_case2a_mid_conc_with_dissolution(): - model = run_case2( - xA=1e-9, xB=1e-2, xAB=1e-2, scaling=True, rxn_config=reaction_log_solubility - ) - - -@pytest.mark.component -def test_case2b_mid_conc_with_dissolution(): - model = run_case2( - xA=1e-2, xB=1e-9, xAB=1e-2, scaling=True, rxn_config=reaction_log_solubility - ) - - -@pytest.mark.component -def test_case2_high_conc_for_all(): - model = run_case2( - xA=1e-2, xB=1e-2, xAB=1e-2, scaling=True, rxn_config=reaction_log_solubility - ) diff --git a/watertap/examples/chemistry/tests/test_water_softening.py b/watertap/examples/chemistry/tests/test_water_softening.py deleted file mode 100644 index 6beda005c2..0000000000 --- a/watertap/examples/chemistry/tests/test_water_softening.py +++ /dev/null @@ -1,546 +0,0 @@ -################################################################################# -# WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, -# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, -# National Renewable Energy Laboratory, and National Energy Technology -# Laboratory (subject to receipt of any required approvals from the U.S. Dept. -# of Energy). All rights reserved. -# -# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license -# information, respectively. These files are also available online at the URL -# "https://github.com/watertap-org/watertap/" -################################################################################# - -""" - Tests to check reactions of water softening by adding lime (CaOH2) to calcium - bicarbonate and magnesium bicarbonate with stoichiometric reactor and return - correct hardness value. - - Kinetic Reactions: - Ca(HCO3)2 + Ca(OH)2 --> 2 CaCO3 + 2 H2O - Mg(HCO3)2 + 2 Ca(OH)2 --> 2 CaCO3 + Mg(OH)2 + 2 H2O -""" -import pytest - -# Importing the object for units from pyomo -from pyomo.environ import units as pyunits - -from pyomo.environ import ( - ConcreteModel, - TerminationCondition, - SolverStatus, - value, - Suffix, -) - -from pyomo.util.check_units import assert_units_consistent - -from idaes.core import AqueousPhase, FlowsheetBlock, EnergyBalanceType - -import idaes.logger as idaeslog - -from idaes.models.unit_models.stoichiometric_reactor import ( - StoichiometricReactor, -) - -from idaes.core.util import scaling as iscale - -from idaes.core.solvers import get_solver -from idaes.core.util.model_statistics import ( - degrees_of_freedom, - fixed_variables_set, - activated_constraints_set, - number_variables, - number_total_constraints, - number_unused_variables, -) - -from idaes.core.base.components import Solvent, Solute -from idaes.core.base.phases import PhaseType as PT - -# Import the idaes objects for Generic Properties and Reactions -from idaes.models.properties.modular_properties.base.generic_property import ( - GenericParameterBlock, -) -from idaes.models.properties.modular_properties.base.generic_reaction import ( - GenericReactionParameterBlock, -) - -# Imports from idaes generic models -import idaes.models.properties.modular_properties.pure.Perrys as Perrys -from idaes.models.properties.modular_properties.pure.ConstantProperties import Constant -from idaes.models.properties.modular_properties.state_definitions import FTPx -from idaes.models.properties.modular_properties.eos.ideal import Ideal -from idaes.models.properties.modular_properties.reactions.rate_constant import arrhenius -from idaes.models.properties.modular_properties.reactions.rate_forms import ( - power_law_rate, -) - -# Importing the enum for concentration unit basis used in the 'get_concentration_term' function -from idaes.models.properties.modular_properties.base.generic_reaction import ( - ConcentrationForm, -) - -# Import the object/function for heat of reaction -from idaes.models.properties.modular_properties.reactions.dh_rxn import constant_dh_rxn - -# Import safe log power law equation -from idaes.models.properties.modular_properties.reactions.equilibrium_forms import ( - log_power_law_equil, -) - -__author__ = "Srikanth Allu, Austin Ladshaw" - -thermo_config = { - "components": { - "H2O": { - "type": Solvent, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Perrys, - "enth_mol_liq_comp": Perrys, - "cp_mol_liq_comp": Perrys, - "entr_mol_liq_comp": Perrys, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (18.0153, pyunits.g / pyunits.mol), - "pressure_crit": (220.64e5, pyunits.Pa), - "temperature_crit": (647, pyunits.K), - # Comes from Perry's Handbook: p. 2-98 - "dens_mol_liq_comp_coeff": { - "eqn_type": 1, - "1": (5.459, pyunits.kmol * pyunits.m**-3), - "2": (0.30542, pyunits.dimensionless), - "3": (647.13, pyunits.K), - "4": (0.081, pyunits.dimensionless), - }, - "enth_mol_form_liq_comp_ref": (-285.830, pyunits.kJ / pyunits.mol), - "enth_mol_form_vap_comp_ref": (0, pyunits.kJ / pyunits.mol), - # Comes from Perry's Handbook: p. 2-174 - "cp_mol_liq_comp_coeff": { - "1": (2.7637e5, pyunits.J / pyunits.kmol / pyunits.K), - "2": (-2.0901e3, pyunits.J / pyunits.kmol / pyunits.K**2), - "3": (8.125, pyunits.J / pyunits.kmol / pyunits.K**3), - "4": (-1.4116e-2, pyunits.J / pyunits.kmol / pyunits.K**4), - "5": (9.3701e-6, pyunits.J / pyunits.kmol / pyunits.K**5), - }, - "cp_mol_ig_comp_coeff": { - "A": (30.09200, pyunits.J / pyunits.mol / pyunits.K), - "B": ( - 6.832514, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-1, - ), - "C": ( - 6.793435, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-2, - ), - "D": ( - -2.534480, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**-3, - ), - "E": ( - 0.082139, - pyunits.J - * pyunits.mol**-1 - * pyunits.K**-1 - * pyunits.kiloK**2, - ), - "F": (-250.8810, pyunits.kJ / pyunits.mol), - "G": (223.3967, pyunits.J / pyunits.mol / pyunits.K), - "H": (0, pyunits.kJ / pyunits.mol), - }, - "entr_mol_form_liq_comp_ref": ( - 69.95, - pyunits.J / pyunits.K / pyunits.mol, - ), - "pressure_sat_comp_coeff": { - "A": (4.6543, None), # [1], temperature range 255.9 K - 373 K - "B": (1435.264, pyunits.K), - "C": (-64.848, pyunits.K), - }, - }, - # End parameter_data - }, - "Ca(OH)2": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (74.093, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "CaCO3": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Ca(HCO3)2": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Mg(OH)2": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "mw": (74.093, pyunits.g / pyunits.mol), - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - "Mg(HCO3)2": { - "type": Solute, - "valid_phase_types": PT.aqueousPhase, - # Define the methods used to calculate the following properties - "dens_mol_liq_comp": Constant, - "enth_mol_liq_comp": Constant, - "cp_mol_liq_comp": Constant, - "entr_mol_liq_comp": Constant, - # Parameter data is always associated with the methods defined above - "parameter_data": { - "dens_mol_liq_comp_coeff": (55, pyunits.kmol * pyunits.m**-3), - "enth_mol_form_liq_comp_ref": (-945.53, pyunits.kJ / pyunits.mol), - "cp_mol_liq_comp_coeff": (167039, pyunits.J / pyunits.kmol / pyunits.K), - "entr_mol_form_liq_comp_ref": ( - 100, - pyunits.J / pyunits.K / pyunits.mol, - ), - }, - # End parameter_data - }, - }, - # End Component list - "phases": { - "Liq": {"type": AqueousPhase, "equation_of_state": Ideal}, - }, - "state_definition": FTPx, - "state_bounds": { - "flow_mol": (0, 50, 100), - "temperature": (273.15, 300, 650), - "pressure": (5e4, 1e5, 1e6), - }, - "pressure_ref": 1e5, - "temperature_ref": 300, - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, -} -# End thermo_config definition - - -# This config is REQUIRED to use EquilibriumReactor even if we have no equilibrium reactions -reaction_config = { - "base_units": { - "time": pyunits.s, - "length": pyunits.m, - "mass": pyunits.kg, - "amount": pyunits.mol, - "temperature": pyunits.K, - }, - "equilibrium_reactions": { - "dummy": { - "stoichiometry": {}, - "equilibrium_form": log_power_law_equil, - } - }, - # End equilibrium_reactions - "rate_reactions": { - "R1": { - "stoichiometry": { - ("Liq", "Ca(HCO3)2"): -1, - ("Liq", "Ca(OH)2"): -1, - ("Liq", "CaCO3"): 2, - ("Liq", "H2O"): 2, - }, - "heat_of_reaction": constant_dh_rxn, - "rate_constant": arrhenius, - "rate_form": power_law_rate, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "arrhenius_const": (1, pyunits.mol / pyunits.m**3 / pyunits.s), - "energy_activation": (0, pyunits.J / pyunits.mol), - "dh_rxn_ref": (0, pyunits.J / pyunits.mol), - }, - }, - "R2": { - "stoichiometry": { - ("Liq", "Mg(HCO3)2"): -1, - ("Liq", "Ca(OH)2"): -2, - ("Liq", "CaCO3"): 2, - ("Liq", "Mg(OH)2"): 1, - ("Liq", "H2O"): 2, - }, - "heat_of_reaction": constant_dh_rxn, - "rate_constant": arrhenius, - "rate_form": power_law_rate, - "concentration_form": ConcentrationForm.moleFraction, - "parameter_data": { - "arrhenius_const": (1, pyunits.mol / pyunits.m**3 / pyunits.s), - "energy_activation": (0, pyunits.J / pyunits.mol), - "dh_rxn_ref": (0, pyunits.J / pyunits.mol), - }, - }, - }, -} -# End reaction_config definition - - -# ----------------------------------------------------------------------------- -# Get default solver for testing -solver = get_solver() - -# ----------------------------------------------------------------------------- -class TestWaterStoich(object): - @pytest.fixture(scope="class") - def water_stoich(self): - m = ConcreteModel() - m.fs = FlowsheetBlock(dynamic=False) - - m.fs.thermo_params = GenericParameterBlock(**thermo_config) - m.fs.rxn_params = GenericReactionParameterBlock( - property_package=m.fs.thermo_params, **reaction_config - ) - - m.fs.unit = StoichiometricReactor( - property_package=m.fs.thermo_params, - reaction_package=m.fs.rxn_params, - has_heat_transfer=False, - has_heat_of_reaction=False, - energy_balance_type=EnergyBalanceType.none, - has_pressure_change=False, - ) - - m.fs.unit.inlet.mole_frac_comp[0, "Mg(HCO3)2"].fix(0.00003) - m.fs.unit.inlet.mole_frac_comp[0, "Mg(OH)2"].fix(0.0) - m.fs.unit.inlet.mole_frac_comp[0, "Ca(HCO3)2"].fix(0.00003) - m.fs.unit.inlet.mole_frac_comp[0, "CaCO3"].fix(0.0) - m.fs.unit.inlet.mole_frac_comp[0, "H2O"].fix(0.99991) - - m.fs.unit.inlet.pressure.fix(101325.0) - m.fs.unit.inlet.temperature.fix(298.0) - m.fs.unit.inlet.flow_mol.fix(10) - - m.fs.unit.outlet.temperature.fix(298.0) - - m.fs.unit.outlet.mole_frac_comp[0, "Ca(HCO3)2"].fix(0.000015) - m.fs.unit.outlet.mole_frac_comp[0, "Mg(HCO3)2"].fix(0.000015) - m.fs.unit.outlet.mole_frac_comp[0, "Ca(OH)2"].fix(0.0000003) - - return m - - @pytest.mark.build - @pytest.mark.unit - def test_build(self, water_stoich): - - m = water_stoich - assert hasattr(m.fs.thermo_params, "component_list") - assert len(m.fs.thermo_params.component_list) == 6 - assert "H2O" in m.fs.thermo_params.component_list - assert isinstance(m.fs.thermo_params.H2O, Solvent) - assert "Ca(HCO3)2" in m.fs.thermo_params.component_list - assert isinstance(m.fs.thermo_params.component("Ca(HCO3)2"), Solute) - assert "Ca(OH)2" in m.fs.thermo_params.component_list - assert isinstance(m.fs.thermo_params.component("Ca(OH)2"), Solute) - assert "CaCO3" in m.fs.thermo_params.component_list - assert isinstance(m.fs.thermo_params.component("CaCO3"), Solute) - assert "Mg(HCO3)2" in m.fs.thermo_params.component_list - assert isinstance(m.fs.thermo_params.component("Mg(HCO3)2"), Solute) - - assert hasattr(m.fs.thermo_params, "phase_list") - assert len(m.fs.thermo_params.phase_list) == 1 - assert isinstance(m.fs.thermo_params.Liq, AqueousPhase) - - @pytest.mark.unit - def test_units_stoich(self, water_stoich): - m = water_stoich - assert_units_consistent(m) - - @pytest.mark.unit - def test_dof_stoich(self, water_stoich): - m = water_stoich - assert degrees_of_freedom(m) == 0 - - @pytest.mark.unit - def test_stats_stoich(self, water_stoich): - m = water_stoich - assert number_variables(m) == 123 - assert number_total_constraints(m) == 54 - assert number_unused_variables(m) == 59 - - @pytest.mark.component - def test_scaling_stoich(self, water_stoich): - m = water_stoich - - # Next, try adding scaling for species - min = 1e-3 - for i in m.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp: - # i[0] = phase, i[1] = species - if m.fs.unit.inlet.mole_frac_comp[0, i[1]].value > min: - scale = m.fs.unit.inlet.mole_frac_comp[0, i[1]].value - else: - scale = min - iscale.set_scaling_factor( - m.fs.unit.control_volume.properties_out[0.0].mole_frac_comp[i[1]], - 10 / scale, - ) - iscale.set_scaling_factor( - m.fs.unit.control_volume.properties_out[0.0].mole_frac_phase_comp[i], - 10 / scale, - ) - iscale.set_scaling_factor( - m.fs.unit.control_volume.properties_out[0.0].flow_mol_phase_comp[i], - 10 / scale, - ) - iscale.constraint_scaling_transform( - m.fs.unit.control_volume.properties_out[0.0].component_flow_balances[ - i[1] - ], - 10 / scale, - ) - iscale.constraint_scaling_transform( - m.fs.unit.control_volume.material_balances[0.0, i[1]], 10 / scale - ) - - iscale.set_scaling_factor( - m.fs.unit.control_volume.rate_reaction_extent[0.0, "R1"], 1 - ) - iscale.calculate_scaling_factors(m.fs.unit) - - assert isinstance(m.fs.unit.control_volume.scaling_factor, Suffix) - - assert isinstance( - m.fs.unit.control_volume.properties_out[0.0].scaling_factor, Suffix - ) - - assert isinstance( - m.fs.unit.control_volume.properties_in[0.0].scaling_factor, Suffix - ) - - @pytest.mark.component - def test_initialize(self, water_stoich): - m = water_stoich - - orig_fixed_vars = fixed_variables_set(m) - orig_act_consts = activated_constraints_set(m) - - # Manually fix the unknown inlet and force unfix the known outlets - # This customization is REQUIRED for StoichiometricReactor - # since IDAES assumes that inlets are knowns during a solve - m.fs.unit.inlet.mole_frac_comp[0, "Ca(OH)2"].fix(0.0000006) - m.fs.unit.outlet.mole_frac_comp[0, "Ca(OH)2"].unfix() - m.fs.unit.outlet.mole_frac_comp[0, "Ca(HCO3)2"].unfix() - m.fs.unit.outlet.mole_frac_comp[0, "Mg(HCO3)2"].unfix() - - m.fs.unit.initialize(optarg=solver.options, outlvl=idaeslog.DEBUG) - - # Undo the fixing we just did and return all values to there - # original states - m.fs.unit.outlet.mole_frac_comp[0, "Ca(OH)2"].fix(0.0000003) - m.fs.unit.inlet.mole_frac_comp[0, "Ca(OH)2"].unfix() - m.fs.unit.outlet.mole_frac_comp[0, "Ca(HCO3)2"].fix(0.000015) - m.fs.unit.outlet.mole_frac_comp[0, "Mg(HCO3)2"].fix(0.000015) - - fin_fixed_vars = fixed_variables_set(m) - fin_act_consts = activated_constraints_set(m) - - print(value(m.fs.unit.outlet.temperature[0])) - assert degrees_of_freedom(m) == 0 - - assert len(fin_act_consts) == len(orig_act_consts) - assert len(fin_fixed_vars) == len(orig_fixed_vars) - - @pytest.mark.component - def test_solve(self, water_stoich): - m = water_stoich - solver.options["max_iter"] = 4000 - results = solver.solve(m, tee=True) - assert results.solver.termination_condition == TerminationCondition.optimal - assert results.solver.status == SolverStatus.ok - - @pytest.mark.component - def test_solution(self, water_stoich): - m = water_stoich - total_molar_density = ( - value(m.fs.unit.control_volume.properties_out[0.0].dens_mol_phase["Liq"]) - / 1000 - ) - total_hardness1 = ( - 50000 - * 2 - * m.fs.unit.outlet.mole_frac_comp[0, "Ca(HCO3)2"].value - * total_molar_density - ) - total_hardness2 = ( - 50000 - * 2 - * m.fs.unit.outlet.mole_frac_comp[0, "Mg(HCO3)2"].value - * total_molar_density - ) - assert pytest.approx(55.23359, rel=1e-5) == total_molar_density - assert pytest.approx(165.70077, rel=1e-5) == total_hardness1 + total_hardness2