The IGTools Module

The IGTools module (found in the package pyromat.igtools) provides the IgtMix class, which allows users to dynamically define mixtures of component ideal gases with a flexible interface convenient for the command line and scripts alike. IgtMix instances have property methods just like the rest of the PYroMat classes, but the total quanitity (the extent) of the mixture is specified. Therefore, properties are calculate in total and not per unit metter (not intensive).

In this example, we quickly compute the enthalpy of a mixture 35% hydrogen and balance argon by volume.

>>> from pyromat import igtools as igt
>>> mymix = igt.IgtMix('0.35 H2 + .65Ar', units='kmol')
>>> mymix.h(T=300)
array([43.67153007])

Table of methods

IgtMix instances provide the same properties as the core PYroMat classes

cp() spec. heat (unit_energy / unit_temperature)
cv() spec. heat (unit_energy / unit_temperature)
d() density (unit_matter / unit_volume)
e() internal energy (unit_energy)
f() free energy (unit_energy)
g() Gibbs energy (unit_energy)
gam() spec. heat ratio (dless)
h() enthalpy (unit_energy)
mw() molecular weight (unit_mass / unit_molar)
T() temperature (unit_temperature)
p() pressure (unit_pressure)
R() gas constant (unit_energy / unit_matter / unit_temperature)
s() entropy (unit_energy / unit_temperature)
v() specific volume (unit_volume / unit_matter)

There is not currently a state() method, but there are plans to add one.

For information about the mixture, see:

atoms() qty. of each element (unit_molar)
mass() total mass (unit_mass)
molar() total number of moles (unit_molar)
Tlim() lower & upper limits (unit_temperature)
X() mole fractions (dless)
Y() mass fractions (dless)

To operate on the mixture like a Numpy array, see:

insert() Add a new substance
nsubst() Number of substances in the mixture
ndim() Mixture array dimension
remove() Remove a substance
reshape() Change the mixture array's shape
shape() Mixture array shape

See also the indexing features described below.

To convert to other data types, see

todict() A dictionary with keys=subst, values=quantities
toigmix() List of igmix instances
tolist() Return a list of the substances

There is also fromigmix() function.

Introduction to IgtMix

The IgtMix class instances allow users to dynamically define mixtures of component ideal gases with a flexible interface convenient for the command line and scripts alike. IgtMix instances have property methods just like the rest of the PYroMat classes. Even though the extent of IgtMix instances is provided, the properties are still evaluated intensively.

In this example, we quickly compute the enthalpy of a mixture 35% hydrogen and balance argon by volume.

>>> mymix = IgtMix('0.35 H2 + .65 Ar', units='kmol')
>>> mymix.h(T=300)
array([43.67153007])

Defining a mixture

There are six ways to define a mixture:

(1) From a string: Strings are expected in the format: 'qty0 subst0 + qty1 subst1 + ...' Quantities specify an amount of each substance in the units configured in pm.config['unit_matter'] unless the optional 'units' keyword is set. Omitted quantities are presumed to be unity, and substances can be specified with or without their 'ig.' collection prefix. All whitespace is ignored.

>>> mymix = IgtMix('10 ig.N2')
>>> mymix = igt.IgtMix('2.4 N2 + Ar', units='kmol')
>>> print(mymix)
[2.4]N2 + [1.]Ar

NOTE: This method cannot be used to specify an ion like 'Ne+'. Instead, use the dictionary or list methods below.

(2) From keyword arguments: Keyword arguments accept abbreviated substance ID strings as keywords. These are the full substance ID strings with the ig. prefix omitted.

>>> air = IgtMix(N2=0.76, O2=0.23, Ar=0.01)
    

NOTE: This method cannot be used to specify an ion like 'Ne+'. Instead, use the dictionary or list methods below.

(3) A list of constituents with no quantities: This assigns zero matter to each mixture. Then, users are free to modify the mixture contents by indexing.

>>> mymix = IgtMix(['ig.N2', 'ig.O2', 'ig.Ar'])
>>> mymix['N2'] = 0.76
>>> mymix['O2'] = 0.23
>>> mymix['Ar'] = 0.01

(4) A dictionary of constituents with their quantities as values: Dictionary keys are interpreted to be substance identifiers, and the corresponding values are interpreted as quantities.

>>> air = IgtMix({'ig.N2':0.76, 'ig.O2':0.23, 'ig.Ar':.01})

(5) Algebraicaly: IgtMix instances can be combined with each other using addition and multiplication at the command line.

>>> air = IgtMix('.76 N2 + .23 O2 + .01 Ar')
>>> fuel = IgtMix('.23 CH4 + .44 C3H8')
>>> reactants = air + 0.4*fuel
>>> print(reactants)
[0.76]N2 + [0.23]O2 + [0.01]Ar + [0.092]CH4 + [0.176]C3H8

Any other data type in addition with an IgtMix is interpreted as a mixture definition as well. For example,

>>> reactants = '.76 N2 + .23 O2 + .01 Ar' + 0.4*fuel       # String
>>> reactants = {'N2':.75, 'O2':.23, 'Ar':.01} + 0.4*fuel   # Dictionary
>>> mymix = pm.get('ig.H2O') + 0.4 * air                    # Pure substance

(6) From an existing igmix instance: The fromigmix() function converts the PYroMat igmix instances into IgtMix instances.

>>> air = fromigmix(pm.get('ig.air'))
>>> print(air)
[0.00935019]Ar + [0.00031401]CO2 + [0.78085562]N2 + [0.20948019]O2

This is a useful trick if users want to make quick changes to an existing mixture. When the fromigmix() function is used, the igmix instance is split into its constituent gases to form the IgtMix instance, so the example above results in a mixture with argon, carbon dioxide, nitrogen, and oxygen. If an igmix instance is passed directly to the IgtMix class, it is treated the same as any other constituent gas, so the example below produces a mixture that only has one gas component.

>>> air = igt.IgtMix(pm.get('ig.air'))
>>> print(air)
[1.]air

Specifying substances

In the examples above, constituent gases can be identified three ways:

  1. By their full substance ID string
    >>> mymix = IgtMix('ig.N2')
    
  2. By their abbreviated substance ID string (with no leading ig.)
    >>> mymix = IgtMix('N2')
    
  3. Or by their full data instance
    >>> n2 = pm.get('ig.N2')
    >>> mymix = IgtMix(n2)
    

Any of these ('N2', 'ig.N2', or pm.get('ig.N2')) may be used as the substance identifier in the mixture list or dictionary methods above.


Arithmetic with mixtures

Because mixtures work in absolute quantities (as opposed to mass or mole fractions), they can be incrased, decreased, subtracted from, or added to using basic math operations at the command line. In this example, mix3 has 1 kmol of water, 2 kmol carbon dioxide, and 2 kmol each of nitrogen and argon.

>>> pm.config['unit_matter'] = 'kmol'
>>> mix1 = IgtMix({'H2O':2, 'CO2':4})
>>> mix2 = IgtMix({'N2':1, 'Ar':1})
>>> mix3 = 0.5*mix1 + 2*mix2

Addition and subtraction attempt to convert non-IgtMix instances to IgtMix instances. That means that strings, lists, dictionaries, and ordinary PYroMat instances may be folded into mixtures using plain command-line arithmetic.

>>> mix4 = mix2 + '0.5 CO'
>>> mix5 = mix2 + pm.get('ig.H2O')
>>> mix6 = mix2 + {'H2O':[0.5,0.12], 'C2H2':0.8}

The last examples show how mixture arrays can be defined. Array broadcasting is inherently supported, so even though H2O was the only substance with multiple values, all other values are broadcast,

>>> print(mix6)
N2   : array([1., 1.])
Ar   : array([1., 1.])
H2O  : array([0.5 , 0.12])
C2H2 : array([0.8, 0.8])

Quantities and units

When an IgtMix instance is initialized, the quantities are interpreted in PYroMat's currently configured 'unit_matter' unless the behavior is overridden by the optional units keyword.

>>> pm.config['unit_matter'] = 'kg'
>>> mymix = IgtMix({'H2O':5.2, 'CO2':4.7}, units='kmol')

Changes to the 'unit_matter' configuration parameter do not affect the mixture's definition. Instead, it only changes the way the mixture's quantities are represented in a print() operation.


Arrays and indexing

Quantities are always handled as arrays, so a single IgtMix instance can actually manage arrays of mixtures with an arbitrary shape. In this example the mixture instance is an array of mixtures of neon, its first ion, and the free electron.

>>> x = np.linspace(0,1,11)
>>> mymix = IgtMix{{'Ne':1-x, 'Ne+':x, 'e':x}, units='kmol')
>>> print(mymix)
Ne  : array([1. , 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0. ])
Ne+ : array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
e-  : array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
    

The shape() method returns a tuple like Numpy's shape attribute, but it describes only the array of mixtures. It excludes the axis with the constituent substances. To obtain the number of substances, use the nsubst() method.

>>> mymix.shape()
(11,)
>>> mymix.nsubst()
3
    

To change the shape of the array, see reshape().

IgtMix instances may be indexed like a normal Numpy array, but with the special rule that the first dimension of the array is always indexed by a substance ID. From the example above,

>>> mymix['Ne']
array([1. , 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0. ])
>>> mymix['Ne',4]
0.6

When the first index is a slice, the value returned is a sub-mixture instead of an array.

>>> print(mymix[:,4])
0.6Ne + 0.4Ne+ + 0.4e- (kmol)
>>> print(mymix[:,3:6])
Ne  : array([0.7 , 0.65, 0.5 ])
Ne+ : array([0.3 , 0.35, 0.5 ])
e-  : array([0.3 , 0.35, 0.5 ])

Assignment works as well. The example below overwrites the fifth mixture.

>>> mymix[:,4] = {'Ne':0.65, 'Ne+':0.35, 'e':0.35}
>>> print(mymix)
Ne  : array([1.  , 0.9 , 0.8 , 0.7 , 0.65, 0.5 , 0.4 , 0.3 , ...
Ne+ : array([0.  , 0.1 , 0.2 , 0.3 , 0.35, 0.5 , 0.6 , 0.7 , ...
e-  : array([0.  , 0.1 , 0.2 , 0.3 , 0.35, 0.5 , 0.6 , 0.7 , ...

Iterating

IgtMix instances act much like an ordered dictionary with PYroMat ideal gas class isntances as keys and quantitiy arrays as values. The items() method allows simultaneous iteration over the substances and quantities.

>>> for subst,qty in mymix.items():
...     # subst is now a PYroMat substance instance
...     # qty is now a Numpy array of the substance's quantity
     
>>> for subst in mymix:
...     # subst is now a PYroMat substance instance

The IgtMix class

Initializers

Instances of the IgtMix class are created by the class initializer or by the fromigmix() function. The class initializer signature appears

IgtMix(contents=None, units=None, ...)

Called with no arguments, this creates an empty mixture - one with no substances. It can be expanded later using the other class methods. The contents keyword contains a string, list, dictionary, PYroMat substance, or another IgtMix instance. See the examples above for usage.

Any other keyword arguments are interpreted as string substance identifiers, and the corresponding values are interpreted as quantities.


Property Methods

The IgtMix class calculates properties extensively, (as opposed to intensively, which is how the internal PYroMat classes work). That means that energies and enthalpy are calculated in absolute energy units (as opposed to energy per unit matter). This is true of all properties with only a few exceptions (like molecular weight and gas constant).

Properties all have the call signature

IgtMix.__property__(...)

where "__property__" is the appropriate property name (e.g. cp, s, h, etc...). Like the other PYroMat ideal gas classes, IgtMix parse many combinations of properties to determine the thermodynamic state. However, these must be specified in extensive units. The table below shows the property combinations that are (and are not) supported by the property methods.

Supported IgtMix property argument combinations
*=supported, X=invalid, o=not supported
Tpdvhes
TX***XX*
p*X*****
d**XX***
v**XX***
hX***XX*
eX***XX*
s******X

For example, the example below calculate the constant-pressure specific heat of a mixture with entropy and pressure specified in units unit_energy/unit_temperature and unit_pressure respectively.

>>> m.cp(s=3.24, p=.05)

Mixture methods

IgtMix.atoms()

Returns a dictionary containing the number of each atom present in the mixture. The keys of the dictionary are the element string, and the values are the quantity arrays expressed in unit_molar. For example,

>>> pm.config['unit_matter'] = 'kmol'
>>> m = igt.IgtMix('H2O + 5C2H2')
>>> m.atoms()
{'H': array([12.]), 'O': array([1.]), 'C': array([10.])}

NOTE: Many users may be in the habit of setting the 'unit_matter' without updating 'unit_molar' or 'unit_mass' accordingly. This will yield confusing results!

mas = IgtMix.mass()
mol = IgtMix.molar()

Calculates the total mixture quantities in unit_mass and unit_molar units respectively. These represent the extent of the mixture by the sum of all mass or moles of all substances in the mixture. Both methods always return an array with the same shape as the mixture array.

NOTE: Many users may be in the habit of setting the 'unit_matter' setting without updating 'unit_molar' or 'unit_mass' accordingly. This will yield confusing results!

Tmin, Tmax = IgtMix.Tlim()

Returns a tuple containing the lower and upper temperature limits for the mixture in unit_temperature. The upper and lower temperature limits are established from the temperature limits of the constituent substances. The range reported is the widest over which all constituents report valid data.

X = IgtMix.X()
Y = IgtMix.Y()

The X() and Y() methods return dictionaries containing the mole and mass fractions of the constituents. The substance ID strings are the dictionary keys, and the values are arrays with the same shape as the mixture shape. For example,

m = igt.IgtMix('4.5 O2 + 2 N2', units='kmol')
>>> m.X()
{'ig.O2': array([0.69230769]), 'ig.N2': array([0.30769231])}
>>> m.Y()
{'ig.O2': array([0.7198954]), 'ig.N2': array([0.2801046])}

Note that these dictionaries can be passed directly to the IgtMix initializer to create a new mixture with an extent of exactly one unit molar or unit mass. This is equivalent to making the properties evaluate intensively.


Array methods

IgtMix.insert(sid, qty, units=None)

Inserts a new substance into the mixture. If the quantity or units are not specified, they default to zero and the current unit_matter. Returns nothing.

sidSubstance identifier [string or ig instance] (mandatory)
qtyQuantity [scalar or array-like] (default=0)
unitsQuantity unit matter [string] (default=None)
>>> m = IgtMix('2 N2')
>>> m.insert('O2', [0,1])
>>> print(m)
N2 : array([2., 2.])
O2 : array([0., 1.])
n = IgtMix.nsubst()

Returns the integer number of substances currently in the mixture.

>>> m = IgtMix('3 C3H8 + 4.7 CH4')
>>> m.nsubst()
2
n = IgtMix.ndim()

Returns the integer number of dimensions in the mixture array. This is equivalent to len(m.shape()).

>>> m = IgtMix({'C2H2':[[0, 0.1],[0, 0.1]], 'C3H8':[[0,0],[0.1,0.1]]})
>>> print(m)
C2H2 : array([[0. , 0.1],
       [0. , 0.1]])
C3H8 : array([[0. , 0. ],
       [0.1, 0.1]])
>>> m.ndim()
2
IgtMix.remove(sid)

Removes a substance from the mixture. Returns nothing.

sidSubstance identifier [string or ig instance] (mandatory)
>>> m = IgtMix('3 C3H8 + 4.7 CH4')
>>> m.remove('C3H8')
>>> print(m)
[4.7]CH4
IgtMix.reshape(shape)

Reshapes the mixture array in place. Like Numpy reshape operations, this cannot change the total number of elements in the mixture array. However, this operation can be used to change the number of dimensions.

shapeNew shape [tuple] (mandatory)
>>> m = IgtMix({'C2H2':[[0, 0.1],[0, 0.1]], 'C3H8':[[0,0],[0.1,0.1]]})
>>> print(m)
C2H2 : array([[0. , 0.1],
       [0. , 0.1]])
C3H8 : array([[0. , 0. ],
       [0.1, 0.1]])
>>> m.reshape((4,))
>>> print(m)
C2H2 : array([0. , 0.1, 0. , 0.1])
C3H8 : array([0. , 0. , 0.1, 0.1])
>>> m.reshape((1,2,2))
>>> print(m)
C2H2 : array([[[0. , 0.1],
        [0. , 0.1]]])
C3H8 : array([[[0. , 0. ],
        [0.1, 0.1]]])
shape = IgtMix.shape()

Returns the shape of the current mixture array as a tuple. This is similar to the Numpy shape attribute, but it is a method instead.

>>> m = IgtMix({'C2H2':[[0, 0.1],[0, 0.1]], 'C3H8':[[0,0],[0.1,0.1]]})
>>> print(m)
C2H2 : array([[0. , 0.1],
       [0. , 0.1]])
C3H8 : array([[0. , 0. ],
       [0.1, 0.1]])
>>> m.shape()
(2,2)

Data conversion methods

mdict = IgtMix.todict()

Returns a dictionary with the constituent gas id strings as keys and their quantity arrays converted to the unit_matter as values.

>>> m = IgtMix({'C2H2':[[0, 0.1],[0, 0.1]], 'C3H8':[[0,0],[0.1,0.1]]})
>>> print(m)
C2H2 : array([[0. , 0.1],
       [0. , 0.1]])
C3H8 : array([[0. , 0. ],
       [0.1, 0.1]])
>>> m.todict()
{'ig.C2H2': array([[0. , 0.1],
       [0. , 0.1]]), 'ig.C3H8': array([[0. , 0. ],
       [0.1, 0.1]])}
mmix = IgtMix.toigmix(sid)

Returns a list of igmix instances, each corresponding to one of the mixtures in the mixture array. The 1D list is a flattened version of the mixture array, so it has length equal to the total number of mixtures in the mixture array.

All built-in PYroMat substances require a substance ID string, so the required sid parameter allows users to name the new mixture substance. If the mixture array only contains a single mixture, then the substance ID string is used verbatim. Otherwise, an underscore and integer are added, consistent with the naming convention used in PYroMat for dissimilar substances with identical Hill formulae.

sidThe substance id of the new mixtures [tuple] (mandatory)

This method allows users to dynamically form mixtures using the command line features of the IGTools package while still benefiting from the faster performance of the igmix class. In the example below, note that the IgtMix specific heat is divided by the mass of the mixture to convert it back to an intensive property.

>>> m = IgtMix({'C2H2':[[1, 2],[1, 2]], 'C3H8':[[1,1],[2,2]]}, units='kmol')
>>> m2 = m.toigmix('ig.c')
>>> print(m2)
[<igmix, ig.c_0>, <igmix, ig.c_1>, <igmix, ig.c_2>, <igmix, ig.c_3>]
>>> m.cp(T=400) / m.mass()
array([[2.00136859, 1.94695158],
       [2.04718287, 2.00136859]])
>>> for this in m2:
...     this.cp(T=400)
... 
array([2.00136859])
array([1.94695158])
array([2.04718287])
array([2.00136859])
subst = IgtMix.tolist(mode='sid')

Generates a list of the mixture contents without the quantities. Optionally, the user may specify a string indicating the mode of the output.

'sid'(default) The full substance ID string (e.g. "ig.N2")
'hill'Hill chemical notation (e.g. "N2")
'instance'The PYroMat class instances (e.g. <ig2, ig.N2>)
>>> m.tolist()
['ig.C2H2', 'ig.C3H8']
>>> m.tolist('hill')
['C2H2', 'C3H8']
>>> m.tolist('instance')
[<ig2, ig.C2H2>, <ig2, ig.C3H8>]
m = fromigmix(subst)

Returns an IgtMix instance built from a PYroMat core igmix instance. This is a convenient way to modify existing mixtures quickly from the command line. Note that fromigmix() is not a method, but a function provided in the root of the IGTools module.

>>> air = pm.get('ig.air')
>>> mix = fromigmix(air)
>>> print(mix)
[0.01289563]Ar + [0.00047711]CO2 + [0.75520558]N2 + [0.23142168]O2

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

©2024 Released under the GPLv3 License