From 4bdc330bd4bd81211965b205f743b692bdc982f4 Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Tue, 11 Nov 2025 23:51:23 -0500 Subject: [PATCH 01/12] define SeawaterPropertySet to make it easier to extract all property names, descriptions, and units later --- .../property_models/seawater_prop_pack.py | 144 ++++++++++++++++-- 1 file changed, 133 insertions(+), 11 deletions(-) diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index 0066f3f7ff..4ff0266a46 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -12,10 +12,6 @@ """ Initial property package for seawater system """ - -# Import Python libraries -import idaes.logger as idaeslog - # Import Pyomo libraries from pyomo.environ import ( Constraint, @@ -43,6 +39,7 @@ MaterialBalanceType, EnergyBalanceType, ) +from idaes.core.base.property_set import PropertyMetadata, PropertySetBase from idaes.core.base.components import Solute, Solvent from idaes.core.base.phases import LiquidPhase from idaes.core.util.constants import Constants @@ -51,7 +48,7 @@ revert_state_vars, solve_indexed_blocks, ) -from watertap.core.solvers import get_solver +import idaes.logger as idaeslog from idaes.core.util.model_statistics import ( degrees_of_freedom, number_unfixed_variables, @@ -62,6 +59,9 @@ PropertyPackageError, ) import idaes.core.util.scaling as iscale + +# Import WaterTAP libraries +from watertap.core.solvers import get_solver from watertap.core.util.scaling import transform_property_constraints # Set up logger @@ -739,6 +739,7 @@ def build(self): @classmethod def define_metadata(cls, obj): """Define properties supported and units.""" + obj.define_property_set(SeawaterPropertySet) obj.add_properties( { "flow_mass_phase_comp": {"method": None}, @@ -759,12 +760,14 @@ def define_metadata(cls, obj): "cp_mass_phase": {"method": "_cp_mass_phase"}, "therm_cond_phase": {"method": "_therm_cond_phase"}, "diffus_phase_comp": {"method": "_diffus_phase_comp"}, - } - ) - - obj.define_custom_properties( - { - "dens_mass_solvent": {"method": "_dens_mass_solvent"}, + # } + # ) + + # obj.define_custom_properties( + # { + "dens_mass_solvent": {"method": "_dens_mass_solvent", + # "doc": "Mass density of pure water" + }, "osm_coeff": {"method": "_osm_coeff"}, "enth_flow": {"method": "_enth_flow"}, "dh_vap_mass": {"method": "_dh_vap_mass"}, @@ -1789,3 +1792,122 @@ def calculate_scaling_factors(self): # transforming constraints transform_property_constraints(self) + +class SeawaterPropertySet(PropertySetBase): + """ + This object defines all the standard properties supported by IDAES, and also allows for + definition of new properties as required for seawater properties. + """ + + flow_mass = PropertyMetadata( + name="flow_mass", + doc="Mass flow rate", + units=pyunits.kg / pyunits.s, + ) + temperature = PropertyMetadata( + name="temperature", + doc="Temperature", + units=pyunits.K, + ) + pressure = PropertyMetadata( + name="pressure", + doc="Pressure", + units=pyunits.Pa, + ) + mass_frac = PropertyMetadata( + name="mass_frac", + doc="Mass fraction", + units=pyunits.dimensionless, + ) + dens_mass = PropertyMetadata( + name="dens_mass", + doc="Mass density of solution", + units=pyunits.kg * pyunits.m**-3, + ) + + flow_vol = PropertyMetadata( + name="flow_vol", + doc="Total volumetric flow rate", + units=pyunits.m**3 / pyunits.s, + ) + conc_mass = PropertyMetadata( + name="conc_mass", + doc="Mass concentration", + units=pyunits.kg * pyunits.m**-3, + ) + flow_mol_phase_comp = PropertyMetadata( + name="flow_mol", + doc="Molar flowrate", + units=pyunits.mol / pyunits.s, + ) + mole_frac = PropertyMetadata( + name="mole_frac", + doc="Mole fraction", + units=pyunits.dimensionless, + ) + molality= PropertyMetadata( + name="molality", + doc="Molality", + units=pyunits.mole / pyunits.kg, + ) + visc_d= PropertyMetadata( + name="visc_d_phase", + doc="Dynamic viscosity", + units=pyunits.Pa * pyunits.s, + ) + pressure_osm= PropertyMetadata( + name="pressure_osm", + doc="Osmotic pressure", + units=pyunits.Pa, + ) + enth_mass= PropertyMetadata( + name="enth_mass", + doc="Specific enthalpy", + units=pyunits.J * pyunits.kg**-1, + ) + pressure_sat = PropertyMetadata( + name="pressure_sat", + doc="Vapor pressure", + units=pyunits.Pa, + ) + cp_mass = PropertyMetadata( + name="cp_mass", + doc="Specific heat capacity", + units=pyunits.J / (pyunits.kg * pyunits.K), + ) + therm_cond = PropertyMetadata( + name="therm_cond", + doc="Thermal conductivity", + units=pyunits.W / (pyunits.m * pyunits.K), + ) + diffus = PropertyMetadata( + name="diffus", + doc="Diffusivity", + units=pyunits.m**2 / pyunits.s, + ) + + dens_mass_solvent = PropertyMetadata( + name="dens_mass_solvent", + doc="Mass density of pure water", + units=pyunits.kg * pyunits.m**-3, + ) + osm_coeff = PropertyMetadata( + name="osm_coeff", + doc="Osmotic coefficient", + units=pyunits.dimensionless, + ) + enth_flow = PropertyMetadata( + name="enth_flow", + doc="Enthalpy flow", + units=pyunits.J/pyunits.s, + ) + dh_vap_mass = PropertyMetadata( + name="dh_vap_mass", + doc="Latent heat of vaporization", + units=pyunits.J * pyunits.kg**-1, + ) + boiling_point_elevation = PropertyMetadata( + name="boiling_point_elevation", + doc="Boiling point elevation", + units=pyunits.K, + ) From 6f668216c050e983afc2a10c597c83d3a8b076ab Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Tue, 11 Nov 2025 23:52:22 -0500 Subject: [PATCH 02/12] correct erroneous implementation of fix_initialization_states on seawater prop model --- watertap/property_models/seawater_prop_pack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index 4ff0266a46..f8958edbef 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -807,7 +807,7 @@ def fix_initialization_states(self): # Constraint on water concentration at outlet - unfix in these cases for b in self.values(): if b.config.defined_state is False: - b.conc_mol_comp["H2O"].unfix() + b.flow_mass_phase_comp["Liq", "H2O"].unfix() def initialize( self, From e737d35ba66cd71984ab01712f8b88ce101ad69c Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Tue, 11 Nov 2025 23:55:44 -0500 Subject: [PATCH 03/12] cleanup --- watertap/property_models/seawater_prop_pack.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index f8958edbef..bc8f0f160a 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -760,20 +760,11 @@ def define_metadata(cls, obj): "cp_mass_phase": {"method": "_cp_mass_phase"}, "therm_cond_phase": {"method": "_therm_cond_phase"}, "diffus_phase_comp": {"method": "_diffus_phase_comp"}, - # } - # ) - - # obj.define_custom_properties( - # { - "dens_mass_solvent": {"method": "_dens_mass_solvent", - # "doc": "Mass density of pure water" - }, + "dens_mass_solvent": {"method": "_dens_mass_solvent"}, "osm_coeff": {"method": "_osm_coeff"}, "enth_flow": {"method": "_enth_flow"}, "dh_vap_mass": {"method": "_dh_vap_mass"}, - "boiling_point_elevation_phase": { - "method": "_boiling_point_elevation_phase" - }, + "boiling_point_elevation_phase": {"method": "_boiling_point_elevation_phase"}, } ) From 1f60e87f28de51c624f84dcc715f6ab1efa2c605 Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 00:15:51 -0500 Subject: [PATCH 04/12] add list_properties and list_properties_as_dataframe to seawater model --- watertap/core/util/__init__.py | 1 + watertap/property_models/seawater_prop_pack.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/watertap/core/util/__init__.py b/watertap/core/util/__init__.py index a09ce4c7ef..d7b1e32df2 100644 --- a/watertap/core/util/__init__.py +++ b/watertap/core/util/__init__.py @@ -15,3 +15,4 @@ assert_no_degrees_of_freedom, assert_degrees_of_freedom, ) +from .property_helpers import print_property_metadata diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index bc8f0f160a..0880288f42 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -63,7 +63,7 @@ # Import WaterTAP libraries from watertap.core.solvers import get_solver from watertap.core.util.scaling import transform_property_constraints - +from watertap.core.util.property_helpers import print_property_metadata # Set up logger _log = idaeslog.getLogger(__name__) @@ -736,6 +736,18 @@ def build(self): self.set_default_scaling("diffus_phase_comp", 1e9) self.set_default_scaling("boiling_point_elevation_phase", 1e0, index="Liq") + def list_properties(self, return_df=False): + """ + Print seawater property package metadata. + """ + print_property_metadata(self) + + def list_properties_in_dataframe(self): + """ + Return seawater property package metadata as a pandas DataFrame. + """ + return print_property_metadata(self, return_df=True) + @classmethod def define_metadata(cls, obj): """Define properties supported and units.""" From 4b2b12855be10e1b78414803fbee3303ce237461 Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 00:24:46 -0500 Subject: [PATCH 05/12] add property_helpers and tests --- watertap/core/util/property_helpers.py | 40 +++++++++++++ .../core/util/tests/test_property_helpers.py | 58 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 watertap/core/util/property_helpers.py create mode 100644 watertap/core/util/tests/test_property_helpers.py diff --git a/watertap/core/util/property_helpers.py b/watertap/core/util/property_helpers.py new file mode 100644 index 0000000000..0d589442e9 --- /dev/null +++ b/watertap/core/util/property_helpers.py @@ -0,0 +1,40 @@ + +import pandas as pd + +def print_property_metadata(prop_pkg, return_df=False): + """ + Print all supported properties from a WaterTAP/IDAES property package. + + Args: + prop_pkg: The property model ParameterBlock (e.g., m.fs.properties) + return_df: If True, returns a Pandas DataFrame instead of printing. + """ + metadata = prop_pkg.get_metadata() + vars, units, docs = [], [], [] + + for v in metadata.properties: + vars.append(v._name) + units.append(str(v._units)) + docs.append(v._doc) + + if return_df: + return pd.DataFrame({ + "Property Description": docs, + "Model Attribute": vars, + "Units": units + }) + + # Pretty-print + name_col = "Model Attribute" + desc_col = "Property Description" + units_col = "Units" + + name_w = max(len(name_col), max(len(n) for n in vars)) + 2 + desc_w = max(len(desc_col), max(len(d) for d in docs)) + 2 + units_w = max(len(units_col), max(len(u) for u in units)) + 2 + + print(f"{desc_col:<{desc_w}}{name_col:<{name_w}}{units_col:<{units_w}}") + print("-" * (name_w + desc_w + units_w)) + + for n, d, u in zip(vars, docs, units): + print(f"{d:<{desc_w}}{n:<{name_w}}{u:<{units_w}}") diff --git a/watertap/core/util/tests/test_property_helpers.py b/watertap/core/util/tests/test_property_helpers.py new file mode 100644 index 0000000000..af9a0f6792 --- /dev/null +++ b/watertap/core/util/tests/test_property_helpers.py @@ -0,0 +1,58 @@ +################################################################################# +# WaterTAP Copyright (c) 2020-2024, The Regents of the University of California, +# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, +# National Renewable Energy Laboratory, and National Energy Technology +# Laboratory (subject to receipt of any required approvals from the U.S. Dept. +# of Energy). All rights reserved. +# +# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license +# information, respectively. These files are also available online at the URL +# "https://github.com/watertap-org/watertap/" +################################################################################# + +import pytest +import pandas as pd +from watertap.core.util.property_helpers import print_property_metadata + +# Dummy classes to mimic WaterTAP metadata structure +class DummyProp: + def __init__(self, name, units, doc): + self._name = name + self._units = units + self._doc = doc + +class DummyMetadata: + def __init__(self): + self.properties = [ + DummyProp("flow_mass", "kg/s", "Mass flow rate"), + DummyProp("temperature", "K", "Stream temperature"), + ] + +class DummyPropPkg: + def get_metadata(self): + return DummyMetadata() + +def test_print_property_metadata_dataframe(): + pkg = DummyPropPkg() + df = print_property_metadata(pkg, return_df=True) + + # Check type + assert isinstance(df, pd.DataFrame) + + # Check columns + expected_cols = ["Property Description", "Model Attribute", "Units"] + assert list(df.columns) == expected_cols + + # Check content + assert "flow_mass" in df["Model Attribute"].values + assert "temperature" in df["Model Attribute"].values + assert "kg/s" in df["Units"].values + +def test_print_property_metadata_pretty_print(capsys): + pkg = DummyPropPkg() + print_property_metadata(pkg, return_df=False) + + # Capture printed output + captured = capsys.readouterr() + assert "Property Description" in captured.out + assert "flow_mass" in captured.out From 00a78808b488c133b196e195fde580ae6a39b71c Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 00:25:48 -0500 Subject: [PATCH 06/12] add copyright to property_helpers --- watertap/core/util/property_helpers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/watertap/core/util/property_helpers.py b/watertap/core/util/property_helpers.py index 0d589442e9..3166a5499e 100644 --- a/watertap/core/util/property_helpers.py +++ b/watertap/core/util/property_helpers.py @@ -1,3 +1,14 @@ +################################################################################# +# WaterTAP Copyright (c) 2020-2024, The Regents of the University of California, +# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, +# National Renewable Energy Laboratory, and National Energy Technology +# Laboratory (subject to receipt of any required approvals from the U.S. Dept. +# of Energy). All rights reserved. +# +# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license +# information, respectively. These files are also available online at the URL +# "https://github.com/watertap-org/watertap/" +################################################################################# import pandas as pd From 966a7cb692a9896e4feb4fdbde0228b7da20170d Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 00:28:54 -0500 Subject: [PATCH 07/12] run black --- watertap/core/util/property_helpers.py | 11 +++++------ .../core/util/tests/test_property_helpers.py | 5 +++++ watertap/property_models/seawater_prop_pack.py | 16 ++++++++++------ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/watertap/core/util/property_helpers.py b/watertap/core/util/property_helpers.py index 3166a5499e..2386716440 100644 --- a/watertap/core/util/property_helpers.py +++ b/watertap/core/util/property_helpers.py @@ -12,10 +12,11 @@ import pandas as pd + def print_property_metadata(prop_pkg, return_df=False): """ Print all supported properties from a WaterTAP/IDAES property package. - + Args: prop_pkg: The property model ParameterBlock (e.g., m.fs.properties) return_df: If True, returns a Pandas DataFrame instead of printing. @@ -29,11 +30,9 @@ def print_property_metadata(prop_pkg, return_df=False): docs.append(v._doc) if return_df: - return pd.DataFrame({ - "Property Description": docs, - "Model Attribute": vars, - "Units": units - }) + return pd.DataFrame( + {"Property Description": docs, "Model Attribute": vars, "Units": units} + ) # Pretty-print name_col = "Model Attribute" diff --git a/watertap/core/util/tests/test_property_helpers.py b/watertap/core/util/tests/test_property_helpers.py index af9a0f6792..9f29f8d573 100644 --- a/watertap/core/util/tests/test_property_helpers.py +++ b/watertap/core/util/tests/test_property_helpers.py @@ -14,6 +14,7 @@ import pandas as pd from watertap.core.util.property_helpers import print_property_metadata + # Dummy classes to mimic WaterTAP metadata structure class DummyProp: def __init__(self, name, units, doc): @@ -21,6 +22,7 @@ def __init__(self, name, units, doc): self._units = units self._doc = doc + class DummyMetadata: def __init__(self): self.properties = [ @@ -28,10 +30,12 @@ def __init__(self): DummyProp("temperature", "K", "Stream temperature"), ] + class DummyPropPkg: def get_metadata(self): return DummyMetadata() + def test_print_property_metadata_dataframe(): pkg = DummyPropPkg() df = print_property_metadata(pkg, return_df=True) @@ -48,6 +52,7 @@ def test_print_property_metadata_dataframe(): assert "temperature" in df["Model Attribute"].values assert "kg/s" in df["Units"].values + def test_print_property_metadata_pretty_print(capsys): pkg = DummyPropPkg() print_property_metadata(pkg, return_df=False) diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index 0880288f42..7c442a1596 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -64,6 +64,7 @@ from watertap.core.solvers import get_solver from watertap.core.util.scaling import transform_property_constraints from watertap.core.util.property_helpers import print_property_metadata + # Set up logger _log = idaeslog.getLogger(__name__) @@ -776,7 +777,9 @@ def define_metadata(cls, obj): "osm_coeff": {"method": "_osm_coeff"}, "enth_flow": {"method": "_enth_flow"}, "dh_vap_mass": {"method": "_dh_vap_mass"}, - "boiling_point_elevation_phase": {"method": "_boiling_point_elevation_phase"}, + "boiling_point_elevation_phase": { + "method": "_boiling_point_elevation_phase" + }, } ) @@ -1796,6 +1799,7 @@ def calculate_scaling_factors(self): # transforming constraints transform_property_constraints(self) + class SeawaterPropertySet(PropertySetBase): """ This object defines all the standard properties supported by IDAES, and also allows for @@ -1848,22 +1852,22 @@ class SeawaterPropertySet(PropertySetBase): doc="Mole fraction", units=pyunits.dimensionless, ) - molality= PropertyMetadata( + molality = PropertyMetadata( name="molality", doc="Molality", units=pyunits.mole / pyunits.kg, ) - visc_d= PropertyMetadata( + visc_d = PropertyMetadata( name="visc_d_phase", doc="Dynamic viscosity", units=pyunits.Pa * pyunits.s, ) - pressure_osm= PropertyMetadata( + pressure_osm = PropertyMetadata( name="pressure_osm", doc="Osmotic pressure", units=pyunits.Pa, ) - enth_mass= PropertyMetadata( + enth_mass = PropertyMetadata( name="enth_mass", doc="Specific enthalpy", units=pyunits.J * pyunits.kg**-1, @@ -1902,7 +1906,7 @@ class SeawaterPropertySet(PropertySetBase): enth_flow = PropertyMetadata( name="enth_flow", doc="Enthalpy flow", - units=pyunits.J/pyunits.s, + units=pyunits.J / pyunits.s, ) dh_vap_mass = PropertyMetadata( name="dh_vap_mass", From 27a4cd29e1901ff49db0ca39d1d570685be9b28c Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 00:41:29 -0500 Subject: [PATCH 08/12] tweak doc string for seawaterpropertyset and BEP --- watertap/property_models/seawater_prop_pack.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index 7c442a1596..607c8aa720 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -1802,8 +1802,7 @@ def calculate_scaling_factors(self): class SeawaterPropertySet(PropertySetBase): """ - This object defines all the standard properties supported by IDAES, and also allows for - definition of new properties as required for seawater properties. + This object defines properties within the seawater property model. """ flow_mass = PropertyMetadata( @@ -1915,6 +1914,6 @@ class SeawaterPropertySet(PropertySetBase): ) boiling_point_elevation = PropertyMetadata( name="boiling_point_elevation", - doc="Boiling point elevation", + doc="Boiling point elevation temperature", units=pyunits.K, ) From b60568ec86fa8ab8b7bd0fd5fab0807036d2900c Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 01:10:35 -0500 Subject: [PATCH 09/12] add test for printout on sw prop model; black --- .../core/util/tests/test_property_helpers.py | 1 + .../property_models/seawater_prop_pack.py | 51 ++++++++++--------- .../tests/test_seawater_prop_pack.py | 44 ++++++++++++++++ 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/watertap/core/util/tests/test_property_helpers.py b/watertap/core/util/tests/test_property_helpers.py index 9f29f8d573..3dd8ff070e 100644 --- a/watertap/core/util/tests/test_property_helpers.py +++ b/watertap/core/util/tests/test_property_helpers.py @@ -36,6 +36,7 @@ def get_metadata(self): return DummyMetadata() +@pytest.mark.unit def test_print_property_metadata_dataframe(): pkg = DummyPropPkg() df = print_property_metadata(pkg, return_df=True) diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index 607c8aa720..d77f294d13 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -1805,8 +1805,8 @@ class SeawaterPropertySet(PropertySetBase): This object defines properties within the seawater property model. """ - flow_mass = PropertyMetadata( - name="flow_mass", + flow_mass_phase_comp = PropertyMetadata( + name="flow_mass_phase_comp", doc="Mass flow rate", units=pyunits.kg / pyunits.s, ) @@ -1820,13 +1820,13 @@ class SeawaterPropertySet(PropertySetBase): doc="Pressure", units=pyunits.Pa, ) - mass_frac = PropertyMetadata( + mass_frac_phase_comp = PropertyMetadata( name="mass_frac", doc="Mass fraction", units=pyunits.dimensionless, ) - dens_mass = PropertyMetadata( - name="dens_mass", + dens_mass_phase = PropertyMetadata( + name="dens_mass_phase", doc="Mass density of solution", units=pyunits.kg * pyunits.m**-3, ) @@ -1836,8 +1836,13 @@ class SeawaterPropertySet(PropertySetBase): doc="Total volumetric flow rate", units=pyunits.m**3 / pyunits.s, ) - conc_mass = PropertyMetadata( - name="conc_mass", + flow_vol_phase = PropertyMetadata( + name="flow_vol_phase", + doc="Volumetric flow rate of phase", + units=pyunits.m**3 / pyunits.s, + ) + conc_mass_phase_comp = PropertyMetadata( + name="conc_mass_phase_como", doc="Mass concentration", units=pyunits.kg * pyunits.m**-3, ) @@ -1846,28 +1851,28 @@ class SeawaterPropertySet(PropertySetBase): doc="Molar flowrate", units=pyunits.mol / pyunits.s, ) - mole_frac = PropertyMetadata( - name="mole_frac", + mole_frac_phase_comp = PropertyMetadata( + name="mole_frac_phase_comp", doc="Mole fraction", units=pyunits.dimensionless, ) - molality = PropertyMetadata( - name="molality", + molality_phase_comp = PropertyMetadata( + name="molality_phase_comp", doc="Molality", units=pyunits.mole / pyunits.kg, ) - visc_d = PropertyMetadata( + visc_d_phase = PropertyMetadata( name="visc_d_phase", doc="Dynamic viscosity", units=pyunits.Pa * pyunits.s, ) - pressure_osm = PropertyMetadata( - name="pressure_osm", + pressure_osm_phase = PropertyMetadata( + name="pressure_osm_phase", doc="Osmotic pressure", units=pyunits.Pa, ) - enth_mass = PropertyMetadata( - name="enth_mass", + enth_mass_phase = PropertyMetadata( + name="enth_mass_phase", doc="Specific enthalpy", units=pyunits.J * pyunits.kg**-1, ) @@ -1876,18 +1881,18 @@ class SeawaterPropertySet(PropertySetBase): doc="Vapor pressure", units=pyunits.Pa, ) - cp_mass = PropertyMetadata( + cp_mass_phase = PropertyMetadata( name="cp_mass", doc="Specific heat capacity", units=pyunits.J / (pyunits.kg * pyunits.K), ) - therm_cond = PropertyMetadata( - name="therm_cond", + therm_cond_phase = PropertyMetadata( + name="therm_cond_phase", doc="Thermal conductivity", units=pyunits.W / (pyunits.m * pyunits.K), ) - diffus = PropertyMetadata( - name="diffus", + diffus_phase_comp = PropertyMetadata( + name="diffus_phase_comp", doc="Diffusivity", units=pyunits.m**2 / pyunits.s, ) @@ -1912,8 +1917,8 @@ class SeawaterPropertySet(PropertySetBase): doc="Latent heat of vaporization", units=pyunits.J * pyunits.kg**-1, ) - boiling_point_elevation = PropertyMetadata( - name="boiling_point_elevation", + boiling_point_elevation_phase = PropertyMetadata( + name="boiling_point_elevation_phase", doc="Boiling point elevation temperature", units=pyunits.K, ) diff --git a/watertap/property_models/tests/test_seawater_prop_pack.py b/watertap/property_models/tests/test_seawater_prop_pack.py index f362b3cb94..021d78e5f2 100644 --- a/watertap/property_models/tests/test_seawater_prop_pack.py +++ b/watertap/property_models/tests/test_seawater_prop_pack.py @@ -10,6 +10,8 @@ # "https://github.com/watertap-org/watertap/" ################################################################################# import pytest +from pyomo.environ import ConcreteModel +from idaes.core import FlowsheetBlock import watertap.property_models.seawater_prop_pack as props from idaes.models.properties.tests.test_harness import ( PropertyTestHarness as PropertyTestHarness_idaes, @@ -332,3 +334,45 @@ def configure(self): ("flow_mass_phase_comp", ("Liq", "TDS")): 1239.69, ("enth_mass_phase", "Liq"): 3.8562e5, } + + +@pytest.mark.unit +def test_list_properties(capsys): + m = ConcreteModel() + m.fs = FlowsheetBlock(dynamic=False) + m.fs.props = props.SeawaterParameterBlock() + + # clear any existing captured output, call list_properties, then capture its output + capsys.readouterr() + m.fs.props.list_properties() + captured = capsys.readouterr().out + expected_output = """ +Property Description Model Attribute Units +----------------------------------------------------------------------------------- +Boiling point elevation temperature boiling_point_elevation_phase K +Mass concentration conc_mass_phase_comp kg*m**(-3) +Specific heat capacity cp_mass_phase J/(kg*K) +Mass density of solution dens_mass_phase kg*m**(-3) +Mass density of pure water dens_mass_solvent kg*m**(-3) +Latent heat of vaporization dh_vap_mass J*kg**(-1) +Diffusivity diffus_phase_comp m**2/s +Enthalpy flow enth_flow J/s +Specific enthalpy enth_mass_phase J*kg**(-1) +Mass flow rate flow_mass_phase_comp kg/s +Molar flowrate flow_mol_phase_comp mol/s +Total volumetric flow rate flow_vol m**3/s +Volumetric flow rate of phase flow_vol_phase m**3/s +Mass fraction mass_frac_phase_comp dimensionless +Molality molality_phase_comp mol/kg +Mole fraction mole_frac_phase_comp dimensionless +Osmotic coefficient osm_coeff dimensionless +Pressure pressure Pa +Osmotic pressure pressure_osm_phase Pa +Vapor pressure pressure_sat Pa +Temperature temperature K +Thermal conductivity therm_cond_phase W/(m*K) +Dynamic viscosity visc_d_phase Pa*s +""" + + # Strip trailing spaces/newlines for comparison + assert captured.strip() == expected_output.strip() From 7c7581b16e257865dee9ae092be85bb21ba5c4a2 Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 17:57:56 -0500 Subject: [PATCH 10/12] address Dan's suggestions --- watertap/core/util/__init__.py | 2 +- watertap/core/util/property_helpers.py | 49 ++++------- .../core/util/tests/test_property_helpers.py | 30 +++---- .../property_models/seawater_prop_pack.py | 16 ++-- .../tests/test_seawater_prop_pack.py | 82 +++++++++++-------- 5 files changed, 87 insertions(+), 92 deletions(-) diff --git a/watertap/core/util/__init__.py b/watertap/core/util/__init__.py index d7b1e32df2..383b9d6604 100644 --- a/watertap/core/util/__init__.py +++ b/watertap/core/util/__init__.py @@ -15,4 +15,4 @@ assert_no_degrees_of_freedom, assert_degrees_of_freedom, ) -from .property_helpers import print_property_metadata +from .property_helpers import get_property_metadata diff --git a/watertap/core/util/property_helpers.py b/watertap/core/util/property_helpers.py index 2386716440..24a07e7f1e 100644 --- a/watertap/core/util/property_helpers.py +++ b/watertap/core/util/property_helpers.py @@ -11,40 +11,21 @@ ################################################################################# import pandas as pd +from idaes.core.util.config import is_physical_parameter_block -def print_property_metadata(prop_pkg, return_df=False): - """ - Print all supported properties from a WaterTAP/IDAES property package. - - Args: - prop_pkg: The property model ParameterBlock (e.g., m.fs.properties) - return_df: If True, returns a Pandas DataFrame instead of printing. - """ +def get_property_metadata(prop_pkg): + """Get all supported properties from a WaterTAP/IDAES property package as a Pandas DataFrame.""" + try: + assert is_physical_parameter_block(prop_pkg) + except: + raise TypeError("get_property_metadata expected a PhysicalParameterBlock.") metadata = prop_pkg.get_metadata() - vars, units, docs = [], [], [] - - for v in metadata.properties: - vars.append(v._name) - units.append(str(v._units)) - docs.append(v._doc) - - if return_df: - return pd.DataFrame( - {"Property Description": docs, "Model Attribute": vars, "Units": units} - ) - - # Pretty-print - name_col = "Model Attribute" - desc_col = "Property Description" - units_col = "Units" - - name_w = max(len(name_col), max(len(n) for n in vars)) + 2 - desc_w = max(len(desc_col), max(len(d) for d in docs)) + 2 - units_w = max(len(units_col), max(len(u) for u in units)) + 2 - - print(f"{desc_col:<{desc_w}}{name_col:<{name_w}}{units_col:<{units_w}}") - print("-" * (name_w + desc_w + units_w)) - - for n, d, u in zip(vars, docs, units): - print(f"{d:<{desc_w}}{n:<{name_w}}{u:<{units_w}}") + df = pd.DataFrame( + { + "Description": [v._doc for v in metadata.properties], + "Name": [v._name for v in metadata.properties], + "Units": [str(v._units) for v in metadata.properties], + } + ) + return df diff --git a/watertap/core/util/tests/test_property_helpers.py b/watertap/core/util/tests/test_property_helpers.py index 3dd8ff070e..7d2b0887fb 100644 --- a/watertap/core/util/tests/test_property_helpers.py +++ b/watertap/core/util/tests/test_property_helpers.py @@ -12,7 +12,10 @@ import pytest import pandas as pd -from watertap.core.util.property_helpers import print_property_metadata +from idaes.core.base.property_base import ( + PhysicalParameterBlock as DummyPhysicalParameterBlock, +) +from watertap.core.util.property_helpers import get_property_metadata # Dummy classes to mimic WaterTAP metadata structure @@ -31,34 +34,27 @@ def __init__(self): ] -class DummyPropPkg: +class DummyPropPkg(DummyPhysicalParameterBlock): + def __init__(self): + self.component = ["dummy_component"] + def get_metadata(self): return DummyMetadata() @pytest.mark.unit -def test_print_property_metadata_dataframe(): +def test_get_property_metadata(): pkg = DummyPropPkg() - df = print_property_metadata(pkg, return_df=True) + df = get_property_metadata(pkg) # Check type assert isinstance(df, pd.DataFrame) # Check columns - expected_cols = ["Property Description", "Model Attribute", "Units"] + expected_cols = ["Description", "Name", "Units"] assert list(df.columns) == expected_cols # Check content - assert "flow_mass" in df["Model Attribute"].values - assert "temperature" in df["Model Attribute"].values + assert "flow_mass" in df["Name"].values + assert "temperature" in df["Name"].values assert "kg/s" in df["Units"].values - - -def test_print_property_metadata_pretty_print(capsys): - pkg = DummyPropPkg() - print_property_metadata(pkg, return_df=False) - - # Capture printed output - captured = capsys.readouterr() - assert "Property Description" in captured.out - assert "flow_mass" in captured.out diff --git a/watertap/property_models/seawater_prop_pack.py b/watertap/property_models/seawater_prop_pack.py index d77f294d13..13fe0f896d 100644 --- a/watertap/property_models/seawater_prop_pack.py +++ b/watertap/property_models/seawater_prop_pack.py @@ -63,7 +63,7 @@ # Import WaterTAP libraries from watertap.core.solvers import get_solver from watertap.core.util.scaling import transform_property_constraints -from watertap.core.util.property_helpers import print_property_metadata +from watertap.core.util.property_helpers import get_property_metadata # Set up logger _log = idaeslog.getLogger(__name__) @@ -737,17 +737,19 @@ def build(self): self.set_default_scaling("diffus_phase_comp", 1e9) self.set_default_scaling("boiling_point_elevation_phase", 1e0, index="Liq") - def list_properties(self, return_df=False): + def list_properties(self): """ - Print seawater property package metadata. + Return seawater property package metadata as a pandas DataFrame. """ - print_property_metadata(self) + df = get_property_metadata(self).reset_index(drop=True) + return df - def list_properties_in_dataframe(self): + def print_properties(self): """ - Return seawater property package metadata as a pandas DataFrame. + Print seawater property package metadata to the console. """ - return print_property_metadata(self, return_df=True) + df = get_property_metadata(self).reset_index(drop=True) + print(df.to_string(index=False)) # Pretty print without index @classmethod def define_metadata(cls, obj): diff --git a/watertap/property_models/tests/test_seawater_prop_pack.py b/watertap/property_models/tests/test_seawater_prop_pack.py index 021d78e5f2..04786a4696 100644 --- a/watertap/property_models/tests/test_seawater_prop_pack.py +++ b/watertap/property_models/tests/test_seawater_prop_pack.py @@ -12,6 +12,9 @@ import pytest from pyomo.environ import ConcreteModel from idaes.core import FlowsheetBlock +import re +import pandas as pd +from pandas.testing import assert_frame_equal import watertap.property_models.seawater_prop_pack as props from idaes.models.properties.tests.test_harness import ( PropertyTestHarness as PropertyTestHarness_idaes, @@ -343,36 +346,49 @@ def test_list_properties(capsys): m.fs.props = props.SeawaterParameterBlock() # clear any existing captured output, call list_properties, then capture its output - capsys.readouterr() - m.fs.props.list_properties() - captured = capsys.readouterr().out - expected_output = """ -Property Description Model Attribute Units ------------------------------------------------------------------------------------ -Boiling point elevation temperature boiling_point_elevation_phase K -Mass concentration conc_mass_phase_comp kg*m**(-3) -Specific heat capacity cp_mass_phase J/(kg*K) -Mass density of solution dens_mass_phase kg*m**(-3) -Mass density of pure water dens_mass_solvent kg*m**(-3) -Latent heat of vaporization dh_vap_mass J*kg**(-1) -Diffusivity diffus_phase_comp m**2/s -Enthalpy flow enth_flow J/s -Specific enthalpy enth_mass_phase J*kg**(-1) -Mass flow rate flow_mass_phase_comp kg/s -Molar flowrate flow_mol_phase_comp mol/s -Total volumetric flow rate flow_vol m**3/s -Volumetric flow rate of phase flow_vol_phase m**3/s -Mass fraction mass_frac_phase_comp dimensionless -Molality molality_phase_comp mol/kg -Mole fraction mole_frac_phase_comp dimensionless -Osmotic coefficient osm_coeff dimensionless -Pressure pressure Pa -Osmotic pressure pressure_osm_phase Pa -Vapor pressure pressure_sat Pa -Temperature temperature K -Thermal conductivity therm_cond_phase W/(m*K) -Dynamic viscosity visc_d_phase Pa*s -""" - - # Strip trailing spaces/newlines for comparison - assert captured.strip() == expected_output.strip() + # capsys.readouterr() + df = m.fs.props.list_properties() + # captured = capsys.readouterr().out + + # # Build DataFrame from captured table by splitting on two or more spaces + # lines = [ln for ln in captured.splitlines() if ln.strip() != ""] + # print(lines) + # # find header line + # header_idx = 0 + # header_cols = re.split(r"\s{2,}", lines[header_idx+1].strip()) + # data_lines = lines[header_idx + 2 :] # skip header and separator line + # rows = [re.split(r"\s{2,}", ln.strip()) for ln in data_lines] + + # df = pd.DataFrame(rows, columns=header_cols) + + # Expected dataframe rows (as parsed into columns) + expected_cols = ["Description", "Name", "Units"] + expected_rows = [ + ["Boiling point elevation temperature", "boiling_point_elevation_phase", "K"], + ["Mass concentration", "conc_mass_phase_comp", "kg*m**(-3)"], + ["Specific heat capacity", "cp_mass_phase", "J/(kg*K)"], + ["Mass density of solution", "dens_mass_phase", "kg*m**(-3)"], + ["Mass density of pure water", "dens_mass_solvent", "kg*m**(-3)"], + ["Latent heat of vaporization", "dh_vap_mass", "J*kg**(-1)"], + ["Diffusivity", "diffus_phase_comp", "m**2/s"], + ["Enthalpy flow", "enth_flow", "J/s"], + ["Specific enthalpy", "enth_mass_phase", "J*kg**(-1)"], + ["Mass flow rate", "flow_mass_phase_comp", "kg/s"], + ["Molar flowrate", "flow_mol_phase_comp", "mol/s"], + ["Total volumetric flow rate", "flow_vol", "m**3/s"], + ["Volumetric flow rate of phase", "flow_vol_phase", "m**3/s"], + ["Mass fraction", "mass_frac_phase_comp", "dimensionless"], + ["Molality", "molality_phase_comp", "mol/kg"], + ["Mole fraction", "mole_frac_phase_comp", "dimensionless"], + ["Osmotic coefficient", "osm_coeff", "dimensionless"], + ["Pressure", "pressure", "Pa"], + ["Osmotic pressure", "pressure_osm_phase", "Pa"], + ["Vapor pressure", "pressure_sat", "Pa"], + ["Temperature", "temperature", "K"], + ["Thermal conductivity", "therm_cond_phase", "W/(m*K)"], + ["Dynamic viscosity", "visc_d_phase", "Pa*s"], + ] + expected_df = pd.DataFrame(expected_rows, columns=expected_cols) + + # Compare dataframes + assert_frame_equal(df.reset_index(drop=True), expected_df.reset_index(drop=True)) From 81b73576588d7a74fd3f509aadfc076a8b62d264 Mon Sep 17 00:00:00 2001 From: Adam Atia Date: Wed, 12 Nov 2025 18:03:40 -0500 Subject: [PATCH 11/12] ensure rows won't be truncated. display all rows by default --- watertap/core/util/property_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/watertap/core/util/property_helpers.py b/watertap/core/util/property_helpers.py index 24a07e7f1e..dbff56f625 100644 --- a/watertap/core/util/property_helpers.py +++ b/watertap/core/util/property_helpers.py @@ -21,6 +21,7 @@ def get_property_metadata(prop_pkg): except: raise TypeError("get_property_metadata expected a PhysicalParameterBlock.") metadata = prop_pkg.get_metadata() + pd.set_option('display.max_rows', None) df = pd.DataFrame( { "Description": [v._doc for v in metadata.properties], From f1ac8790636809e0fce309dbf9913bab4c6211a1 Mon Sep 17 00:00:00 2001 From: MarcusHolly Date: Thu, 13 Nov 2025 15:56:12 -0500 Subject: [PATCH 12/12] Run black --- watertap/core/util/property_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watertap/core/util/property_helpers.py b/watertap/core/util/property_helpers.py index dbff56f625..3358013da8 100644 --- a/watertap/core/util/property_helpers.py +++ b/watertap/core/util/property_helpers.py @@ -21,7 +21,7 @@ def get_property_metadata(prop_pkg): except: raise TypeError("get_property_metadata expected a PhysicalParameterBlock.") metadata = prop_pkg.get_metadata() - pd.set_option('display.max_rows', None) + pd.set_option("display.max_rows", None) df = pd.DataFrame( { "Description": [v._doc for v in metadata.properties],