# 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`

### Getting objects

Once PYroMat (pm for short) is imported, the get function will retrieve objects that represent individual species. 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, e.g. "N2," and the data collection to which it belongs, "ig," for "ideal gas."

### 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 a set of primary properties. For ideal gases, that means temperature, T, pressure, p, or density, d. For a multi-phase mixture, users can also specify quality, x.

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 **best**
array([207.43528961])
>>> N2.h(T=496.5)           # p reverts to its default
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])
>>> N2.h(T=496.5, d=2.0358) # density instead of pressure
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.

### 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 ig in module __builtin__ object:

class ig(pyromat.reg.__basedata__)
|  Ideal gas class using the Shomate equation of state.
|  This class exposes properties through member methods.  All property
|  functions accept temperature in PYroMat's unit_temperature and
|  pressure in PYroMat's unit_pressure.  The following are the member
|  methods and their unit conventions:
|    cp() spec. heat       (unit_energy / unit_temperature / unit_matter)
|    cv() spec. heat       (unit_energy / unit_temperature / unit_matter)
|    d()  density          (unit_matter / unit_volume)
|    e()  internal energy  (unit_energy / unit_matter)
|    h()  enthalpy         (unit_energy / unit_matter)
|    gam()  spec. heat ratio (dless)
|    mw() molecular weight (unit_mass / unit_molar)
|    R()  gas constant     (unit_energy / unit_temperature / unit_matter)
|    s()  entropy          (unit_energy / unit_temperature / unit_matter)
|
|  There are also routines to invert properties; e.g. calculating
|  temperature from enthalpy or from entropy and pressure.
|    T_h()  temperature from enthalpy
|    T_d()  temperature from density and pressure
|    T_s()  temperature from entropy and pressure
|    p_s()  pressure from entropy and temperature
|    p_d()  pressure from density and temperature
|
|  Some meta-data on the species can be obtained using methods
|    Tlim() a two-element array with the min,max temperatures supported by
|           the data set.
|    contents()  returns a dictionary with a key entry for each atom in
|                the chemical formula and the corresponding integer
|                quantity of each.
|
:```
```>>> help(N2.h)

Help on method h:

h(self, T=None, p=None, hf=True) method of __builtin__.ig instance
Enthalpy
h(T,p)
Both arguments are optional, and will default to 'def_T' and 'def_p'
configuration parameters if they are left undefined.  Ideal gas enthalpy
is not actually a function of p, but it is permitted as an argument
for cross-compatibility between species' function calls.

There is an optional parameter, hf, that is a boolean indicating whether
the enthalpy of formation should be included in the enthalpy.  If hf is
false, h() will calculate the enthalpy to be zero a T=298.15K

Accepts unit_temperature
unit_pressure
Returns unit_energy / unit_matter```

### 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.

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