PYroMat Tutorial

Importing the package

Once PYroMat is installed, the package should be available at the Python command line as pyromat.

>>> import pyromat as pm

Retrieving substance objects

Once PYroMat (pm for short) is imported, the get function will retrieve objects that represent individual substances. For example, let's look at diatomic nitrogen, oxygen, and carbon dioxide.

>>> N2 = pm.get('ig.N2')
>>> O2 = pm.get('ig.O2')
>>> CO2 = pm.get('ig.CO2')

The argument to the get function is the species ID. It is comprised of two parts: the chemical compound expressed in Hill notation (e.g. "N2") and the data collection to which it belongs (e.g. "ig" for ideal gas). So far, there are only two collections: "ig" for ideal gas and "mp" for multi-phase. The collection helps organize the PYroMat substance models by their capabilities. For example, there is a "ig.N2" and a "mp.N2" found in PYroMat. The first is an ideal gas model, and the second is a multi-phase model for cryogenic nitrogen - same substance, but different model.

Of course, the problem, here, is that the get function requires users to already know the substance ID string that they are looking for. What if users want to browse or search for substances? Never fear; read on.

Retrieving data

The objects expose all the methods we need to access their properties. For example, this code retrieves molecular weight, enthalpy in kJ/kg, and entropy in kJ/kg/K. We'll talk more about units in just a moment.

>>> N2.mw()
28.0134
>>> N2.h()
array(0.0009843252384197784)
>>> N2.s()
array(6.839762903237966)

But wait! Enthalpy and entropy are functions of temperature and pressure!

All properties accept keyword arguments that allow users to define the state in terms of almost any two properties. For ideal gases, that includes, temperature, pressure, entropy, enthalpy, density, and specific volume. For multi-phase substances, that includes quality as well.

The table below lists the available properties of the PYroMat substances. Those marked with an asterisk (*) can be accepted as keyword arguments to define the state.

Python Property Units
cp Const. pressure specific heat unit_energy / unit_matter / unit_temperature
cv Const. volume specific heat unit_energy / unit_matter / unit_temperature
d* Density unit_matter / unit_volume
e* Internal energy unit_energy / unit_matter
gam Specific heat ratio Dimensionless.
h* Enthalpy unit_energy / unit_matter
mw Molecular weight unit_mass / unit_molar
p* Pressure unit_pressure
R Ideal gas constant unit_energy / unit_matter / unit_temperature
s* Entropy unit_energy / unit_matter / unit_temperature
T* Temperature unit_temperature
v* Specific volume unit_volume / unit_matter
x* Quality (mp) Dimensionless.
X Mole fraction (igmix) Dimensionless.
Y Mass fractions (igmix) Dimensionless.

The command-line examples below all return the enthalpy of diatomic nitrogen at 496.5K and 3bar, but using different approaches to calling the enthalpy function.

>>> N2.h(T=496.5, p=3.)     # keywords T and p **fastest**
array([207.43528961])
>>> N2.h(T=496.5)           # p reverts to its default 1.01325
array([207.43528961])
>>> N2.h(496.5, 3.)         # T and p as ordered args
array([207.43528961])
>>> N2.h(496.5)             # p reverts to its default
array([207.43528961])
>>> d = N2.d(T=496.5, p=3.)
>>> N2.h(T=496.5, d=d)      # density instead of pressure
array([207.43528961])       # **fastest for multi-phase**
>>> s = N2.s(T=496.5,p=3.)
>>> N2.h(s=s, p=3.)         # entropy and pressure **slowest**
array([207.43528961])

In general, PYroMat is intended to be used with keywords and values specified, but it is also designed to do its best to interpret your meaning when you leave things out. In the above examples, when the keywords are missing or when one of the properties is missing, the method reverted to defaults. When no keywords are specified, PYroMat assumes you are working in temperature and pressure (T,p) tuples. When one (or both) is omitted, it reverts to a default values (see the Configuration chapter of the PYroMat Handbook for more information).

This is a good point to note that the h() method returns a Numpy array instead of an ordinary float. All properties accept array-like arguments to temperature and pressure. PYroMat uses Numpy's array broadcasting rules for operating on bulk data. This can come in handy for simulations where T and p might be big multi-dimensional arrays in time and space.

>>> N2.s(T=[300,400,500,600], p=1.5)
array([6.72596156, 7.02564504, 7.25995304, 7.45405736])

For more information, use the in-line help help(N2) or help(N2.h), visit the Features page, see the User and Developer's Handbook, or use the pm.info() function.

In-line documentation

The in-line documentation describes the properties that are available and names the units used. For more detail on any individual property, just call up the in-line documentation for that method.

>>> help(N2)
Help on ig2 in module builtins object:

class ig2(pyromat.reg.__basedata__)
 |  Ideal gas class using the NASA polynomial equation of state.
 |      
 |  ** Available Properties **
 |  IG has property methods:
 |    T()  temperature      (unit_temperature)
 |    p()  pressure         (unit_pressure)
 |    d()  density          (unit_matter / unit_volume)
 |    v()  specific volume  (unit_volume / unit_matter)
 |    cp() spec. heat       (unit_energy / unit_temperature / unit_matter)
 |    cv() spec. heat       (unit_energy / unit_temperature / unit_matter)
 |    gam()  spec. heat ratio (dless)
 |    e()  internal energy  (unit_energy / unit_matter)
 |    h()  enthalpy         (unit_energy / unit_matter)
 |    s()  entropy          (unit_energy / unit_temperature / unit_matter)
 |    state()       Calculates all properties!
 |  
 |  These accept any of the following keyword arguments: T, p, d, v, e, h, s
 |    h(T=452.)
 |    h(d=1.3, p=3.4)
 |    h(s=6.2, p=3.4)
 |  
 |  In the back end, most properties require only temperature except entropy,
 |  which requires temperature and pressure.  When other properties are given,
 |  temperature is needed, so additional calculations are necessary.  In the 
 |  worst case, specifying entropy, enthalpy, or internal energy even requires
 |  iteration.  For performance, once temperature and pressure become available,
 |  they should always be used.  For enthalpy, pressure is not needed, so it 
 |  may be omitted with no ill effect.  
 |  
 |  ** Other Properties **
 |  There are some properties that do not depend on the state; they only need
 |  to be converted to the relevant unit system.
 |    mw()      molecular weight (unit_mass / unit_molar)
 |    R()       gas constant
 |    Tlim()    a two-element array with the min,max temperatures 
 |              supported by the data set.
 |    atoms()   returns a dictionary with a key entry for each atom in
 |              the chemical formula and the corresponding integer 
 |              quantity of each.
 |                
 |  For more information on any of these methods, access the in-line 
 |  documentation using Python's built-in "help()" function.
:
>>> help(N2.h)
Help on method h:

h(*varg, **kwarg) method of builtins.ig2 instance
    Enthalpy
        h(...)
    
    All ideal gas properties accept two other properties as flexible inputs
    Below are the recognized keywords, their meaning, and the config entries
    that determine their units.
        T   temperature         unit_temperature
        p   pressure            unit_pressure
        d   density             unit_matter / unit_volume
        v   specific volume     unit_volume / unit_matter
        e   internal energy     unit_energy / unit_matter
        h   enthalpy            unit_energy / unit_matter
        s   entropy             unit_energy / unit_matter / unit_temperature
    
    If no keywords are specified, the positional arguments are interpreted
    as (T,p).  To configure their defaults, use the def_T and def_p config
    entries.
    
    Returns enthalpy in unit_energy / unit_matter

The info and search functions are powerful tools for finding what you need inside of PYroMat. search accepts a series of arguments to help users narrow down PYroMat's collection of substances. For example,

>>> import pyromat as pm
>>> pm.search('water')
{<ig2, ig.H2S>, <ig2, ig.CHN>, <ig, ig.BrH>, <ig2, ig.H2O>, <ig, ig.ClH>, <mp1, mp.H2O>, <ig, ig.DHO>}
>>> pm.search(name='water')     # This is equivalent.

In the above example, the "name" argument allowed us to search for substances that contained the string "water" in any of their common names or somewhere in their ID string. The result came back as a Python set containing all of the substance instances matching the search criteria.

The search function can do more than just look at a substance's name. Users can also specify the collection ('ig' or 'mp'), the PYroMat class, the CAS identifier string, the InChI identifier string, or the molecular makeup of the substance. Any of these can be combined

>>> pm.search(collection='ig', pmclass='igmix')     # Class AND collection
{<igmix, ig.air>, <igmix, ig.f5>, <igmix, ig.h35>}
>>> pm.search(inchi = 'InChI=1S/H2O/h1H2')          # Inchi string
{<ig2, ig.H2O>, <mp1, mp.H2O>}
>>> pm.search(contains='C')     # All substances with carbon in their formula
... huge set ...
>>> pm.search(contains={'C':2}) # All substances with precisely two carbon
{<ig2, ig.C2H4O2>, <ig2, ig.C2H4O4>, <ig2, ig.C2H5>, <ig2, ig.C2H6>, <ig2, ig.C2H6N2>, <ig2, ig.C2H6O>, ...
>>> pm.search(contains={'C':2, 'Cl':None})  # Precisely two carbon and any clorine
{<ig2, ig.C2HCl>, <ig2, ig.C2Cl6>, <ig2, ig.C2Cl4>, <ig2, ig.C2Cl2>}

It is also possible to build multi-step "and" and "or" operations with searches. In an "and" operation between two searches, the conditions specified in both searches must be true for all returned values. If the results of a previous search is passed to the members keyword of a search, that set (and not the set of all PYroMat substances) will be searched. This has the effect of producing an "and" operation between the two searches.

>>> result = pm.search(contains='C')
>>> pm.search(contains='D', members=result)                   # Returns substances with carbon and deuterium
{<ig2, ig.C6D6>, <ig2, ig.C6D5>, <ig2, ig.C12D10>, <ig2, ig.C12D9>}     
>>> pm.search(contains={'Br':1}).union( pm.search(contains={'Hg':1}))   # One bromine OR one mercury
{<ig, ig.BrF5>, <ig2, ig.Br>, <ig2, ig.HBr>, <ig, ig.BrF5S>, <ig, ig.CBrN>, <ig, ig.BrH>, <ig, ...
    

In the second example above, we have combined two searches using the set's union method. This just combines them (while eliminating redundant entries) in an "or" operation. These, and the other built-in Python set operations allow users to construct searches with arbitrary complexity.

Finally, once we have a search we are happy with, we may want to display the results more elegantly than just seeing a set of the substance IDs. The info function accepts these search result sets as an argument, and it displays a table of the results. When it is called with no arguments, it merely displays a table of all the substances supported by PYroMat. Alternately, the info function also accepts all the same arguments as search, so if you want, you can just let info perform the search for you.

>>> pm.info(name='water')
  PYroMat
Thermodynamic computational tools for Python
version: 2.2.0
------------------------------------------------------------------------
 ID     : class : name              : properties
------------------------------------------------------------------------
 ig.BrH :  ig   : Hydrogen bromide  : T p d v cp cv gam e h s mw R    
 ig.CHN :  ig2  : Hydrogen cyanide  : T p d v cp cv gam e h s mw R    
 ig.ClH :  ig   : Hydrogen chloride : T p d v cp cv gam e h s mw R    
 ig.DHO :  ig   : Water-d           : T p d v cp cv gam e h s mw R    
 ig.H2O :  ig2  : Water             : T p d v cp cv gam e h s mw R    
 ig.H2S :  ig2  : Hydrogen sulfide  : T p d v cp cv gam e h s mw R    
 mp.H2O :  mp1  : Water             : T p d v cp cv gam e h s mw R  

>>> pm.info(pm.search(inchi = 'InChI=1S/H2O/h1H2'))
  PYroMat
Thermodynamic computational tools for Python
version: 2.2.0
------------------------------------------------------------
 ID     : class : name  : properties
------------------------------------------------------------
 ig.H2O :  ig2  : Water : T p d v cp cv gam e h s mw R    
 mp.H2O :  mp1  : Water : T p d v cp cv gam e h s mw R   
    

If an exact match with a substance ID string is found, or if the search results only contain a single substance, detailed information is printed on that substance instead of the table.

>>> pm.info('mp.C2H2F4')    # pm.info(name='R-134a') also works
***
Information summary for substance: "mp.C2H2F4"
***

    C H F 
     2 2 4

             Names : R-134a
                     1,1,1,2-tetrafluoroethane
                     Norfurane
                     CF3CH2F
                     Freon 134
                     HFA-134a
                     HFC-134a
  Molecular Weight : 102.032
        CAS number : 359-35-3
      InChI string : InChI=1S/C2H2F4/c3-1(4)2(5)6/h1-2H
        Data class : mp1
       Loaded from : /home/chris/Documents/pyromat/src/pyromat/data/mp/C2_H2_F4.hpd
      Last updated : 12:18 June 30, 2022

The PYroMat R134a multiphase data are adapted from [1]. The equation of
state used to calculate most properties is their original work, but the
saturation conditions are derived from Wagner [2].
The data are implemented with the MP1 class. In-line help describes the
available funcitons and their proper use.
Adaptation to PYroMat was by Chris Martin in June 2018 thanks, in part, to
a grant from Penn State's Open Education Resources Initiative.
[1] R. Tilner-Roth and H. Baehr, An International Standard Formulation for
the Thermodynamic Properties of Tetrafluoroethane (HFC-134a) for
Temperatures from 170K to 455K and Pressure up to 70MPa, Journal of
Physical and Chemical Reference Data, v23, pp 657, 1994. doi:
10.1063/1.555958
[2] W. Wagner, Eine mathematisch statistische Methode zum Aufstellen
thermodynamischer Gleichungen-gezeigt am Beispiel der Dampfdruckkurve
reiner fluider Stoffe. Fortschr.-Ber. VDI-Z, v3, n39. 1974.

Working with units

By default, all energy is in kJ, and intensive properties are by mass (in kg). Volume is in cubic meters, and molar units are in kmols. Pressure is in bar and temperature is in Kelvin. These units are pretty broadly used, but if developing thermodynamic codes teaches us anything, it is that no system of units is pleasing to every audience. PYroMat has a configuration object that, among other things, allows the user to change the system of units.

>>> N2.s(T=700.,p=50.)
array(6.580384332173014)
>>> pm.config['unit_temperature'] = 'F'
>>> N2.s(T=800.33,p=50.)*1.8    # 800.33F == 700K
6.580384332173014

To know what the current settings are, just print the pm.config object.

>>> pm.config
...
     unit_energy : 'kJ'
      unit_force : 'N'
     unit_length : 'm'
       unit_mass : 'kg'
     unit_matter : 'kg'
      unit_molar : 'kmol'
   unit_pressure : 'bar'
unit_temperature : 'K'
       unit_time : 's'
     unit_volume : 'm3'
...

Most of these are self-explanatory, but the subtle distinctions between unit_mass, unit_molar, and unit_matter definitely deserves some explanation. Mass and molar specify how mass and mole count will be specified in properties like molecular weight, but matter specifies how extensive properties like entropy and enthalpy will be reported. Unit matter can be in mass or molar units.

>>> N2.mw()
28.0134
>>> N2.s()
array(6.835995552261296)
>>> pm.config['unit_matter'] = 'kmol'
>>> N2.s()
array(191.4994778037166)
>>> N2.s()/N2.mw()
6.8359955522612958

For a list of all units supported, the units module offers the show() funciton. This shows a space separated list of every string argument that the unit configuration parameters will accept. Matter is not shown because it can be either mass or molar units.

>>> pm.units.show()
          force : lb lbf kN N oz kgf 
         energy : BTU kJ J cal eV kcal BTU_ISO 
    temperature : K R eV C F 
       pressure : mmHg psi inHg MPa inH2O kPa Pa bar atm GPa torr mmH2O ksi 
          molar : Ncum NL Nm3 kmol scf n mol sci Ncc lbmol 
         volume : cumm cc mL L mm3 in3 gal UKgal cuin ft3 cuft USgal m3 cum 
         length : ft nm cm mm m km um mile in 
           mass : mg kg g oz lb lbm slug 
           time : s ms min hr ns year day us 

Working with arrays

PYroMat natively supports Numpy arrays. Temperature and pressure arguments can be any array-like iterable.

>>> import numpy as np
>>> T = np.arange(300., 1000.,100.)
>>> N2.h(T)
array([    53.90750325,   2971.32964933,   5910.75444792,   8893.9164816 ,
        11936.61265198,  15046.59932493,  18223.31173227])

When parameters are mixed together, they must obey Numpy's rules for array broadcasting. For example, when T is a list and p is a scalar, the same pressure will be used at each temperature specified.

>>> T = np.array([500., 550., 600.])
>>> p = 40.5
>>> N2.s(T,p)
array([ 175.96458798,  178.79561365,  181.40193451])

If we repeat the same steps, but with multiple pressures, we run into trouble. What would it mean, anyway, to have three temperatures and two pressures?

>>> p = np.array([40.5, 50.])
>>> N2.s(T,p)
Traceback (most recent call last):
  File "", line 1, in 
  File "/home/chris/Documents/PYroMat/pyromat/registry/ig.py", line 758, in s
    # to match p's dimensions at the end
ValueError: shape mismatch: objects cannot be broadcast to a single shape

If T and p are to be arrays, they must be exactly the same shape or they must proceed in different dimensions.

>>> T = T.reshape((T.size,1))
>>> N2.s(T,p)
array([[ 175.96458798,  174.21255642],
       [ 178.79561365,  177.04358209],
       [ 181.40193451,  179.64990295]])
>>> T
array([[ 500.],
       [ 550.],
       [ 600.]])
>>> p
array([ 40.5,  50. ])

This is just like populating a table for each combination of the temperature and pressure values. There are other funny multi-dimensional combinations that are supported, but we'll leave that to the Numpy documentation to cover.


Contact:
Christopher R. Martin, Ph.D.
Associate Professor of Mechanical Engineering
The Pennsylvania State University, Altoona College
crm28@psu.edu

©2022 Released under the GPLv3 License