Skip to content

Conversation

@adam-a-a
Copy link
Contributor

@adam-a-a adam-a-a commented Nov 12, 2025

Fixes/Resolves:

NA

Summary/Motivation:

While preparing materials for Academy, realized it isn't very easy to list all properties on a property model.

One would need to dive into source code or technical documentation.

This PR attempts to add list_properties and list_properties_as_dataframe methods to the SeawaterParameterBlock, using a new utility helper function, print_property_metadata.

This also required defining a separate PropertySet for Seawater--otherwise, you'll get back a long list of all supported IDAES properties that aren't necessarily supported in the seawater prop model.

Can extend this to other property models once we cover this PR.

Example implementation:

m = ConcreteModel()
m.fs = FlowsheetBlock(dynamic=False)
m.fs.seawater_properties = SeawaterParameterBlock()
m.fs.seawater_properties.list_properties()

Output:

Property Description         Model Attribute          Units          
---------------------------------------------------------------------
Boiling point elevation      boiling_point_elevation  K              
Mass concentration           conc_mass                kg*m**(-3)     
Specific heat capacity       cp_mass                  J/(kg*K)       
Mass density of solution     dens_mass                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                   m**2/s         
Enthalpy flow                enth_flow                J/s            
Specific enthalpy            enth_mass                J*kg**(-1)     
Mass flow rate               flow_mass                kg/s           
Molar flowrate               flow_mol_phase_comp      mol/s          
Total volumetric flow rate   flow_vol                 m**3/s         
Mass fraction                mass_frac                dimensionless  
Molality                     molality                 mol/kg         
Mole fraction                mole_frac                dimensionless  
Osmotic coefficient          osm_coeff                dimensionless  
Pressure                     pressure                 Pa             
Osmotic pressure             pressure_osm             Pa             
Vapor pressure               pressure_sat             Pa             
Temperature                  temperature              K              
Thermal conductivity         therm_cond               W/(m*K)        
Dynamic viscosity            visc_d                   Pa*s      

Changes proposed in this PR:

  • add helper function to print or return df of properties
  • add methods to seawater parameterblock that leverage the helper function
  • add tests for helper function
  • add new SeawaterPropertySet that inherits from PropertySetBase instead of StandardPropertySet, which will include properties that aren't in the seawater prop model when printing out

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

@adam-a-a adam-a-a added the Priority:High High Priority Issue or PR label Nov 12, 2025
@adam-a-a adam-a-a self-assigned this Nov 12, 2025
import pandas as pd


def print_property_metadata(prop_pkg, return_df=False):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a simpler interface would be get_property_metadata. Wrapping that in a print statement is very little burden on the caller and then you don't need this keyword.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean make it a method on parameterblock so that I don't need prop_pkg keyword? Or did you mean remove the need for returning df in this function and focus on getting the metadata?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he means rename this get_property_metadata and return the df and then the user can do what they want?

I like the idea of this being a method on ParameterBlock. But could you not just use this function and then @classmethod to avoid adding this code to every property model?

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()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the method (below). I suppose we can add a check for the _metadata attribute and raise an exception if it doesn't exist. Do you know of a better check to ensure the property model is a property model? Another idea is to check whether the prop_pkg input is a ParameterBlock.

    @classmethod
    def get_metadata(cls):
        """Get property parameter metadata.

        If the metadata is not defined, this will instantiate a new
        metadata object and call `define_metadata()` to set it up.

        If the metadata is already defined, it will be simply returned.

        Returns:
            PropertyClassMetadata: The metadata
        """
        if cls._metadata is None:
            pcm = PropertyClassMetadata()
            cls.define_metadata(pcm)
            cls._metadata = pcm

            # Check that the metadata was actually populated
            # Check requires looking at private attributes
            # pylint: disable-next=protected-access
            if pcm._properties is None or pcm._default_units is None:
                raise PropertyPackageError(
                    "Property package did not populate all expected metadata."
                )
        return cls._metadata


if return_df:
return pd.DataFrame(
{"Property Description": docs, "Model Attribute": vars, "Units": units}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest column headers with no spaces. "Name", "Description", "Units". The prefix "Property" is implied in all cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok will do!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be a dataframe? Why not just a dict?

{"Property Description": docs, "Model Attribute": vars, "Units": units}
)

# Pretty-print
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so again, I would just return the data frame. People know how to print a dataframe in a basic way in a terminal (i.e. just pass obj to print()) and a notebook will do it for you. If you really must print for the user, I'd suggest a separate method print_property_metadata that consists of basically :

pprint.pprint(get_property_metadata(*args).to_string(index=False))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue with the way it's printing now or any advantage to doing it differently?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue we should not return a dataframe in the first place.

The reason is we don't want to maintain code specific to pandas for printing stuff.

I think we we do want to display/print information we should use native python display tools otherwise we might run into system quirks ... (I wounder if pyomo table printer would work for this - also can look in my PR for WaterTAP flowsheet block for displaying a table - it uses native pyomo printer... ?)

assert "kg/s" in df["Units"].values


def test_print_property_metadata_pretty_print(capsys):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just test that there are, say, between 100 and 1000 characters in the output.

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):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these seem completely generic, so should be on a base class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but I started with this first before attempting to add to a base class (which would probably be best on IDAES repo but would slow things down for us)


# transforming constraints
transform_property_constraints(self)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is normal in IDAES but I find this way of creating data very verbose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me too, but given the current IDAES functionality and the way metadata is handled and reported back, this was the cleaner way of producing the list of property names actually supported on this property model, without the noise. Open to suggestions if there's a slicker approach, but this is meant a quick fix, with planned refinement in subsequent PRs (this quarter).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is redundant, don't the pyomo vars in the property block contain all of the same information? Would it not be easier to make a list of the "vars" you want to display and simply grab it?

capsys.readouterr()
m.fs.props.list_properties()
captured = capsys.readouterr().out
expected_output = """
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tets for string matches are not a good idea imho. at a minimum use a regex to look for some things least likely to change.

Copy link
Contributor Author

@adam-a-a adam-a-a Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was expecting this sort of response! I was aiming for a quick test to make sure my seawater prop model was listing the correct properties. Can reconsider this, but I was hoping to get something merged asap and revise in subsequent PR(s)

Copy link
Contributor

@MarcusHolly MarcusHolly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the intent is just to get something merged in preparation for Friday, then I think this is good as is. None of Dan's comments strike me as urgent, so these (and other) refinements can be made in future PRs.

Copy link
Contributor

@avdudchenko avdudchenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, I am not 100% sure about relying on pandas data frames for data storage and printing.

My biggest question is can't we pull most of the information here from already build pyomo vars defining the supported properties?

I almost wounder if this just need a registration class that lets you track vars you want to report and provide ways to both interact with?


if return_df:
return pd.DataFrame(
{"Property Description": docs, "Model Attribute": vars, "Units": units}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be a dataframe? Why not just a dict?

{"Property Description": docs, "Model Attribute": vars, "Units": units}
)

# Pretty-print
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue we should not return a dataframe in the first place.

The reason is we don't want to maintain code specific to pandas for printing stuff.

I think we we do want to display/print information we should use native python display tools otherwise we might run into system quirks ... (I wounder if pyomo table printer would work for this - also can look in my PR for WaterTAP flowsheet block for displaying a table - it uses native pyomo printer... ?)


# transforming constraints
transform_property_constraints(self)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is redundant, don't the pyomo vars in the property block contain all of the same information? Would it not be easier to make a list of the "vars" you want to display and simply grab it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority:High High Priority Issue or PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants