Thermodynamic Computational Tools For Python: Christopher Martin
Thermodynamic Computational Tools For Python: Christopher Martin
PYro
Christopher Martin
Assistant Professor of Mechanical Engineering
The Pennsylvania State University, Altoona College
April 2016
ii
https://www.behance.net/janetmontgomery
Contents
1 Introduction 1
1.1 What is PYro? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.1 Linux Maual Installation . . . . . . . . . . . . . . . . . . 9
1.3.2 Windows Maual Installation . . . . . . . . . . . . . . . . . 9
1.4 License . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2 Examples 11
3 Package Structure 13
3.1 PYro Base Module . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.1.1 config . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.1.2 info() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.1.3 get() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 reg: Registry Module . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.1 registry . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.2 regload() . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2.3 basedata . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.3 dat: Data Module . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3.1 data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3.2 clear() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3.3 load() . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3.4 updatefiles() . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3.5 new() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.4 utility: Miscellanea Module . . . . . . . . . . . . . . . . . . . . 17
4 Configuration 19
4.1 config Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2 Writing a Config File . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.1 config file . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2.2 config verbose . . . . . . . . . . . . . . . . . . . . . . . 22
4.2.3 dat dir . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2.4 dat overwrite . . . . . . . . . . . . . . . . . . . . . . . . 22
iii
iv CONTENTS
Introduction
1
2 CHAPTER 1. INTRODUCTION
Ye coders beware
the fate most unfair,
within all your projects is lurking.
With a flexible framework that allows users to define their own data and even
their own classes, the hope is that over time PYro will graudally be extended to
do the jobs that people need most, but in a way that is “Pythonic” and exten-
sible. In that way, PYro is distinguished from other excellent thermodynamic
resources (like Cantera) by its emphasis on being application agnostic.
PYro is easily installed using a standard setup script, convenient to use from
the command line, but also highly configurable. To get a feel for how easy PYro
is to use, look at section 1.2.
What can PYro do? The PYro classes supply methods for calculating most
common thermodynamic properties from temperature and pressure such as den-
sity, enthalpy, entropy, internal energy, molecular weight, specific heats, specific
heat ratio, and specific volume.
While version 1.1 included only ideal gas data, version 1.2 includes properties
of water and steam. There are plans to include refrigerants in later releases.
The addition of the psolve() function to all classes makes property inversion
possible. In other words, it is possible to determine the pressure and tempera-
ture at which a substance will have a given entropy and enthalpy. This comes
in handy for cycle analysis and flame temperature calculations.
What can’t PYro do? In addition to many others, all of the species sup-
ported by the GRIMech reaction database are included, but the corresponding
chemical kinetic data are not. While this could possibly change one day, Cantera
already offers excellent chemical kinetics support, so adding this functionality
is not a priority. This is an example where Cantera is probably just a better
tool for the job than PYro.
It is also worth noting here that psolve() is not as robust as it should be for
steam in the vicinity of phase changes. The abrupt discontinuity in properties
causes problems for the Newton algorithim or which psolve is built. This will
definitely be addressed in the future.
4 CHAPTER 1. INTRODUCTION
>>> O2 = pyro.get(’O2’)
>>> O2.cp(T=432)
0.95049900128261733
>>> T = np.arange(300,4000,10)
>>> plt.plot(T, O2.h(T))
>>> plt.xlabel(’Temperature (K)’);plt.ylabel(’Enthalpy (kJ/kg)’)
>>> plt.grid(’on’)
Data are available for mixtures too. In addition to the typical methods for
calculating properties, the mixture class provides methods to interrogate the
mass, molar, and fractional mass and molar contents.
>>> air = pyro.get(’air’)
>>> air.M()
{u’N2’: 21.88, u’Ar’: 0.373, u’CO2’: 0.013, u’O2’: 6.704}
>>> air.N()
{u’N2’: 0.7810547809262709, u’Ar’: 0.009337138279763693,
u’CO2’: 0.00029539076790238467, u’O2’: 0.20950785654462042}
>>> air.Y()
{u’N2’: 0.7552640662754573, u’Ar’: 0.012875388332758024,
u’CO2’: 0.0004487400759406282, u’O2’: 0.23141180531584396}
>>> air.X()
{u’N2’: 0.780902374928423, u’Ar’: 0.009335316338574262,
u’CO2’: 0.0002953331287638292, u’O2’: 0.20946697560423902}
>>> air.d( T=400, P=2.0 )
1.741805229300913
The info() function prints a summary of all available species.
>>> pyro.info()
PYro
Thermodynamic computational tools for Python
version: 1.2
-------------------------------
ID Modified Type
-------------------------------
Ar 4/7/2016 igfit
Ar+ 4/7/2016 igtab
Ar2 4/7/2016 igtab
C 4/7/2016 igfit
C2H 4/7/2016 igfit
C2H2 4/7/2016 igfit
C2H3 4/7/2016 igfit
C2H4 4/7/2016 igfit
C2H5 4/7/2016 igfit
C2H6 4/7/2016 igfit
C3H7 4/7/2016 igfit
C3H8 4/7/2016 igfit
CH 4/7/2016 igfit
CH2 4/7/2016 igfit
CH2(S) 4/7/2016 igfit
-------------------------------
ID Modified Type
-------------------------------
CH2CHO 4/7/2016 igfit
6 CHAPTER 1. INTRODUCTION
N 4/7/2016 igfit
N2 4/7/2016 igfit
N2O 4/7/2016 igfit
NCO 4/7/2016 igfit
-------------------------------
ID Modified Type
-------------------------------
NH 4/7/2016 igfit
NH2 4/7/2016 igfit
NH3 4/7/2016 igfit
NNH 4/7/2016 igfit
NO 4/7/2016 igfit
NO2 4/7/2016 igfit
Ne 4/7/2016 igtab
Ne+ 4/7/2016 igtab
O 4/7/2016 igfit
O2 4/7/2016 igfit
OH 4/7/2016 igfit
Rn 4/7/2016 igtab
S 4/7/2016 igtab
S2 4/7/2016 igtab
S3 4/7/2016 igtab
-------------------------------
ID Modified Type
-------------------------------
S4 4/7/2016 igtab
Xe 4/7/2016 igtab
air 11/6/2015 mixture
steam 12/24/2015 if97
When called with the name of a species, it returns more specific information
information.
>>> pyro.info(’HCO’)
***
Information summary for substance: "HCO"
***
Uses class: igfit
>>> pyro.info(’air’)
***
Information summary for substance: "air"
***
Uses class: mixture
The composition of air is taken from section 14 page 20 of the the CRC
Handbook of Chemistry and Physics 96th ed, 2015-2016.
www.hbcpnetbase.com
Trace gases (those present in less than 0.01% by volume) are neglected, as
they will not contribute substantially to the thermodynamic properties.
Original work is credited to:
COESA, "U.S. Standard Atmosphere, 1976", U.S. Government Printer Office,
Washington D.C., 1976.
Adapted by Chris Martin (c) 2015.
1.3 Installation
Installation is accomplished through the setup.py script in the root directory
of the package distribution. The following steps should produce a working in-
stallation of PYro.
cd C:\path\to\package
python.exe setup.py install
On a Linux installation, you may need to use sudo to execute the setup
script if you don’t have write privileges to the Python installation directory.
1.4 License
PYro is released under the GNU General Public License v3. Details can be
found here: http://www.gnu.org/licenses/gpl-3.0.en.html.
Chapter 2
Examples
11
12 CHAPTER 2. EXAMPLES
Chapter 3
Package Structure
13
14 CHAPTER 3. PACKAGE STRUCTURE
Most of the functions that users will ever need in order to interact with PYro
are exposed in the base module. Everything else is contained in four subordinate
modules; utility, a collection of miscellaneous functions, reg, a registry of all
data classes used in the PYro package, and dat, which manages raw data and
associates them with their respective classes.
3.1.1 config
PYro’s behavior depends on a number of configurable parameters that are con-
tained in the config dictionary. The values of these parameters are loaded
when the package is imported using the load_config() function in the utility
module. Some of the parameters are user configurable (like file and directory lo-
cations) and others are merely intended for reference (like the package version).
Details on these parameters are provided in chapter 4.
3.1.2 info()
The info() function retrieves information on the various species currently
loaded into memory. When it is evoked without any arguments, it prints a
table to stdout listing the names, date modified, and class type of the species
in memory. When called with the string name of a substance as an argument,
info() prints detailed information on the species data.
3.1.3 get()
The get() function accepts the string name of a species to be retrieved from
memory. If such a species exists in memory, get() returns its class object,
which in turn exposes methods for calculating properties.
3.2. REG: REGISTRY MODULE 15
3.2.1 registry
The registry dictionary is a mapping between a data type string, and the cor-
responding class definition for handling that data type. For example, a typical
PYro installation will give the following output:
>>> pyro.reg.registry[’igfit’]
<class ’pyro.reg.igfit’>
The data type strings are necessarily the same as the class name.
When data are loaded in the dat module, they use this dictionary to find
their corresponding class definitions. Exactly how this is accomplished is de-
scribed in detail in section 3.3.
3.2.2 regload()
The regload() function is responsible for populating the registry dictionary.
It is called automatically when the reg module is first imported, but developers
may wish to call it from the command line to incorporate changes.
The entires of the registry_dir configuration parameter constitute a list
of all locations where registry files are supposed to be found. regload() checks
the contents of each directory, in the order listed, for *.py files without a leading
underscore or period (‘ ’ or ‘.’). Any children of the __basedata__ class created
in these files are added to the registry dictionary, and all other objects are
ignored.
By default, the only registry directory is pyro/registry, but system ad-
ministrators may want to allow users to include their own registry directories.
Great care should be taken, however, to prevent standard users from accessing
registry directories that will be used by other users, as this creates a security
risk. Chapter 4 explains more on this.
3.2.3 basedata
This is a prototype class for all PYro data classes. In order for the reg module to
recognize a data class, issubclass( thisclass, __basedata__) must return
16 CHAPTER 3. PACKAGE STRUCTURE
true. To help developers write their own classes, __basedata__ has detailed
documentation, there is an _example.py file in the pyro/registry directory,
and the entire process is described in more detail in chapter 6.
3.3.1 data
Each species loaded into PYro resides in the data dictionary, so that the fol-
lowing lines are equivalent:
>>> pyro.get(’CO2’)
>>> pyro.dat.data[’CO2’]
3.3.2 clear()
This function empties the data dictionary, and can be useful for developers.
3.3.3 load()
The load() function is extremely important to PYro for standard users and
developers alike. It is executed automatically when the dat module is loaded.
When called without arguments, it scans all directories listed in the data_dir
parameter in the config dictionary for files with a “*.hpd” extension and no
leading underscore or decimal (‘ ’ or ‘.’). Files that meet these criteria are
passed to the load_file() function in the utility module which, if all goes
well, returns a dictionary containing the class data. Finally, the dictionary is
passed to the appropriate class initializer in the reg module, and the resulting
object is added to the data dictionary.
For developers and users interested in adding their own data files, the load()
function can do quite a bit more. If called with the “check” directive set to True,
rather than loading into the data dictionary, it performs a mock load operation
and compares the results with the data currently in memory. It checks for data
entries that have been newly created, deleted, or altered since load. It also lists
any files that exhibit redundant definitions for the same entry, and lists any
3.4. UTILITY: MISCELLANEA MODULE 17
3.3.4 updatefiles()
When called with no arguments, updatefiles() initiates an interactive process
for bringing the current data dictionary in agreement with the files in the various
data directories. The load() function is called with the check parameter set to
list all discrepancies.
Files with redundant entry definitions and files for entries that have been
removed can be suppressed or deleted. Edits to the data in memory can be
written to the appropriate files, and files for new data entries can be created
automatically.
3.3.5 new()
Useful for users who want to build scripts for creating new data, the new() func-
tion creates a new data entry from a data dictionary as if it had been loaded from
a file. When used in conjunction with the updatefiles() funciton, new data
sets can be easy to implement without ever needing a detailed understanding of
how PYro stores and retrieves data.
Configuration
19
20 CHAPTER 4. CONFIGURATION
At load, PYro searches for configuration files that allow users to make persis-
tent changes to the package’s behavior. The values set in these files are loaded
into the config dictionary in the base package, where they can be accessed or
altered from the command line.
The load_config() function found in the utility module is responsible
for dealing with all configuration files. It is called automatically when the PYro
package is first loaded, but it can also be called from the command line to
re-parse the configuration files. More information is provided in section 4.3.
3. No special allowance need be made for parameters that allow more than
one value (such as config_file, dat_dir, etc. . . ).
install_dir : ’/home/chris/py/pyro’
config_file : [’/home/chris/py/pyro/defaults.py’, ’/etc/pyro_conf.py’]
config_verbose : [False, True]
reg_exist_fatal : [False]
reg_verbose : [True]
dat_verbose : [True]
dat_overwrite : [True]
dat_exist_fatal : [False]
reg_overwrite : [True]
reg_dir : [’/home/chris/py/pyro/registry’]
version : ’1.0’
dat_dir : [’/home/chris/py/pyro/data’, ’/my/data/directory’, ’/my/other/data/directory’]
my_param : [’This is my parameter’]
dat_recursive : [True]
Note that the dat_dir and config_file entries alike were simply appended to
the default list even though one was written as a string and the other as a list
of strings.
The following is a brief description of each default entry in alphabetical
order.
The first file loaded is always defaults.py in the package installation di-
rectory. If the system admin chooses to add a configuration file (like the
/etc/pyro_conf.py example above), this is where it should be added.
After parsing each file, load_config() checks for new entries to config_file
list, so configuration files can be daisy-chained to one another. To prevent cir-
cular references, a history of all files loaded so far is also kept, and redundant
references are ignored with a warning.
4.2.8 def P
The _vectorize() function provided by the __basetest__ class will use this
value if the pressure is set to None. As a result, data classes that use _vectorize()
will have a consistent but configurable default condition.
4.2.9 def T
Just like with the def_P parameter, the _vectorize() function provided by
the __basetest__ class will use this value if the temperature is set to None.
As a result, data classes that use _vectorize() will have a consistent but
configurable default condition.
4.2.15 version
This read-only string indicates the package version.
24 CHAPTER 4. CONFIGURATION
>>> import os
>>> os.remove(’~/*’)
so that the contents of the user’s home directory will be deleted if that user
has the misfortune to include my configuration script. This same vulnerability
exists in the registry class definitions as well.
In most implementations, this will never be an issue. The configuration and
registry files are deliberately insecure to allow developers as much power and
flexibility as possible. While this may change in future releases, the current
design philosophy emphasizes flexibility over security.
There are multiple solutions, but all of them rely on the system admin to be
aware of the problem.
1. Use the default install, which only references the built-in files and direc-
tories.
2. Make sure user writable config files are only accessed by the users that
own them. This can be done by flexible references, e.g.
config_file = ’~/pyro_conf.py’
reg_dir = ’~/pyroreg’
These measures are already taken in the default installation. Future releases
may have measures to protect against these types of problems, but it is im-
portant to keep these potential problems in mind while tweaking the package’s
configuration.
26 CHAPTER 4. CONFIGURATION
Chapter 5
27
28 CHAPTER 5. WRITING NEW DATA FILES
PYro data files are in a json file format, which is particularly useful for
flexibly representing data structures with plain text. They are loaded as dic-
tionaries using Python’s json package for dealing with these files. The keys
of those dictionaries can vary however the data author desires. These files are
given a *.hpd extension short for “PYro Data.”
As described in section ??, the load() function is responsible for seeking
out and loading all data files, but for the actual process of parsing and checking
data files, load() calls the load_file() function in the utility module. The
utility function, load_file(), returns a dictionary that results from parsing
the data file, and throws errors or warnings if the file does not meet certain
requirements.
The load() function passes the dictionary returned by load_file() to the
appropriate class initializer (more on that later), which is trusted to do whatever
it needs with the file’s data.
The following sections establish more detail on how data files are structured:
• How can users construct their own files from the command line?
classes will have many more, but these are the essential few for interacting with
the PYro package.
5.2.1 id
This is the species identifier string. This is how PYro will reference the data;
for example when using the get() or info() functions. For diatomic oxygen,
the id value is ’O2’.
5.2.2 doc
This is a documentation string. It is expected to hold information about this
particular species. While it can be empty, it is typically a good idea to cite
any sources from which the species data are taken, and this is an ideal place
to include any special copyright information. This value is used by the info()
function to describe the species.
5.2.3 class
This is the class identifier string. The string contained in this field is used to
look up the class for which the data is intended in the PYro registry (see section
3.2). To populate the data dictionary, load() executes commands equivalent
to
This provides a glimpse of how PYro interacts with the data classes; their
initializers accept the data dictionary as their only argument.
5.2.4 fromfile
The fromfile entry is the only piece of data that is directly manipulated by
the PYro package. Regardless of whether it is defined in the file, the load()
function forces this parameter to the absolute path file name of the data file
from which the data was loaded. This little bit of information can be essential
for untangling redundant file problems.
P
N k
k=0 Ck,1 T : T < Tsplit
cp (T ) = (5.1)
PN
Ck,2 T k
: T ≥ Tsplit
k=0
From this principal piece of information, all other properties are derived
using ideal gas relationships. In order to implement this approach, the following
data elements are used:
5.3.1 C1 and C2
These lists of floating point numbers represent the coefficients of the low- and
high-temperature segments. They are arranged such that the index for each list
element corresponds to the power of temperature for which it is the coefficient.
Or, in code:
>>> T = 400
>>> cp = 0
>>> for k in range(len(C1)):
... cp += C1[k] * (T**k)
...
It should be noted that this is not actually the way the polynomial evaluation
is implemented in the class.
5.3.2 h1 and h2
These floating point constants serve as the integration constants when evaluating
enthalpy. They are related to, but not the same as the enthalpy of formation.
When using the lower temperature curve,
ˆ
h(T ) = cp (T )dT
N
X Ck k+1
= h1 + T (5.2)
k+1
k=0
5.3.3 mw
This is the scalar molecular weight of the species. It is primarily used to calculate
the ideal gas constant,
R
R= . (5.3)
mw
5.4. IGTAB DATA STRUCTURE 31
5.3.4 s1
These floating point constants serve as the integration constants when evaluating
entropy. Entropy is obtained from the maxwell relation,
c
p RT
ds = dT − dp (5.4)
T p p T
5.3.6 P ref
This is the pressure at which the ideal gas are taken. For most properties,
ideal gases are insensitive to pressure, but for entropy it is essential to know the
reference pressure. For most data, this is atmospheric pressure, 1.01325 bar.
5.4.1 cp
This data element is a list of specific heats corresponding to the temperatures
found in the T data element. Since the gases belonging to this class are assumed
ideal, specific heat has no pressure dependency.
5.4.2 h
This data element is a list of enthalpies corresponding to the temperatures found
in the T data element. Since the gases belonging to this class are assumed ideal,
enthalpy has no pressure dependency.
5.4.3 mw
This is the scalar molecular weight of the species. It is primarily used to calculate
the ideal gas constant.
5.4.4 s
This data element is a list of entropies corresponding to the temperatures found
in the T data element. These entropies are interpreted as the entropy of the
substance at reference pressure. The actual entropy is calculated by
p
s(T, p) = s◦ (T ) − RT ln (5.7)
p◦
5.4.5 T
This is a list of all the temperatures used in the ideal gas table. The values in
the list must be monotonically increasing, and the length of all lists used in the
table must be identical.
5.4.6 P ref
This is the pressure at which the ideal gas are taken. For most properties,
ideal gases are insensitive to pressure, but for entropy it is essential to know the
reference pressure. For most data, this is atmospheric pressure, 1.01325 bar.
5.5.1 contents
The contents dictionary is a mapping between the species present in the mix-
ture and their respective quantities. The keys are the id strings of each species
present. If the bymass data element is True, then the floating point values indi-
cate the respective masses present in the mixture. Otherwise, the values indicate
a mole or volume quantity present. These numbers need not be normalized into
percentages.
5.5.2 bymass
This is a boolean constant indicating whether the entries of the contents should
be interpreted as mases or molar quantities.
Of course, users can determine the state of the data dictionary themselves
before changes are actually implemented. The command
>>> CHK = hp.dat.load(verbose=True, check=True)
prints a summary of all *.hpd files discovered (including suppressed ones) and
their comparison with the data in memory. The CHK dictionary keys include
detailed data for the comparison: lists of species identifiers added, changed, or
suppressed; a suppressed dictionary mapping species id strings to the redun-
dant files defining them; and the data dictionary that would have resulted had
the load operation been run normally.
Chapter 6
35
36 CHAPTER 6. WRITING NEW DATA CLASSES
Through the class registry module described in section 3.2, users are afforded
substantial freedom in writing their own PYro data classes. Provided that au-
thors obey a few guidelines, the PYro system will automatically detect new class
definition files and handle the rest.
Authors can create new data class definitions by adding *.py files to any of
the directories listed in the reg_dir configuration entry (see section 4). Each
file with a *.py extension and without a leading ’_’ or ’.’ is executed. Any
class definitions built on the __basedata__ class (exposed in the reg module)
are added to the registry.
These files are executed in a context where the pyro package is available as
a global for access to any other modules (e.g. pyro.utility.np for access to
NumPy). Similarly, the __basedata__ is a global. Authors who need access to
additional modules or want to create other global references may do so without
concern since only objects that have __basedata__ as a parent are imported
into the registry.
6.1 basedata
The best introduction to the anatomy of a PYro class is probably through the
__basedata__ class itself. Authors who write their own classes are encouraged
to define them as children of the __basedata__ class, since it has prototypes
for all the important functions of a PYro class, and exposes some useful tools
(especially _vectorize()).
6.1.1 init ()
The responsibilities of a PYro data class begin with the __init__() function.
It accepts the class’s data dictionary as its only argument, which is then copied
into the object’s data member. It completes its responsibilities by executing
the __basetest__() and then the __test__() functions.
Authors writing their own __init__() functions for classes should probably
evoke the __basetest__()’s __init__() function with the line
def __init__(self,data):
... special code ...
super(myclass, self).__init__(args)
This is an easy way to include all the error checking code already included in
__basetest__().
Alternately, authors may wish to explicitly include code from the __init__()
function
def __init__(self,data):
... special code ...
self.data = data.copy()
self.__doc__ = self.data[’doc’]
6.2. AUTHORS’ RESPONSIBILITIES 37
self.__basetest__()
self.__test__()
Authors who use this approach may want to be aware that their classes will not
track with updates to the __basetest__ class.
mandatory += [’new’,’data’,’elements’]
Additional checks on the robustness of the data or other class integrity checks
can be implemented in the __test__() function, which is intended to be user
defined. By default, it does nothing. For example, authors might want to
raise an error from inside __test__() if some part of the data does not meet
expectations.
6.1.3 vectorize()
The _vectorize() function is made available to authors to help with the com-
mon task of dealing with property arguments of different dimensions. The
built-in classess build all of their calculations on NumPy arrays, and use the
_vectorize() function to automatically enforce that temperature and pres-
sure inputs are of compatible dimensions. See the function docstring for more
information.
2. Place the containing file in a directory found in the reg_dir search path.
3. Compose the class’s definition of the mandatory list to include each data
element required for the class to function properly.
5. Compose class methods that can access the resulting data dicitonary to
calculate properties.
38 CHAPTER 6. WRITING NEW DATA CLASSES
If authors want the class to be compatible with the built-in mixture class,
they should be sure to include the following member methods:
• cp() calculates constant-pressure specific heat in kJ/kg/K
• cv() calculates constant-volume specific heat in kJ/kg/K
39
40 APPENDIX A. A VERY BRIEF REVIEW OF THERMODYNAMICS
A.1 Properties
One way to discuss a substance’s properties is as a set of descriptors for the
substance at a particular thermodynamic state. Broadly speaking, this might
even include even qualitative descriptors like color and smell, but when we are
talking about thermodynamics we are typically talking about different ways of
describing the arrangement and motion of the atoms that make up the sub-
stance. It makes good sense to do that in such a way that is immediately useful
for whatever application we have in mind. If it is important to calculate how
long water takes to cool, internal energy may be an important tool, but if one is
designing a steam turbine, then enthalpy and entropy may be quite important.
p = ρRT (A.1)
to relate pressure, density, and temperature. In fact, if you look closely at the
ideal gas law in equation A.1, you can even see the idea living in the math. For
a given temperature, pressure can be increased by increasing the density of the
gas. For extensibility, PYro accepts pressure as an argument to the property
functions for ideal gas data types (igtab and igfit), but it will only rarely be
used (see entropy).
As of version 1.2, PYro does not install with multiphase data. That will prob-
ably change soon, so it’s worth talking about what happens when substances
stop being gases and start being other things.
When a substance changes phase, the remarkable shift in character of the
substance represents a fundamental change in how the molecules are arranged.
Solids have tightly spaced molecules that are in intimate contact with one an-
other; in sharp contrast to gases. Similarly, liquids also exhibit strong inter-
molecular forces that will tend to maintain their volume (like solids), but not
their shape (not like solids). As a result, pressure in these substances usually
plays only a minor role (if any) for determining internal energy.
What is very interesting is the intense quantity of energy released when
substances shift from gas-to-liquid-to-solid. Called latent heat, this energy is
42 APPENDIX A. A VERY BRIEF REVIEW OF THERMODYNAMICS
what gets released when free-flying gas molecules whack into a liquid, shed
their momentum, and are trapped as a sluggish liquid. Latent heat shows up
in internal energy as an incredibly mathematically inconvenient step change in
internal energy when we move between the phases. PYro doesn’t include it now,
but it will one day.
R
R= . (A.2)
mw
M is often reported without units, because it is adapted to different unit systems
by means of a redefinition of moles. PYro uses kg for mass, so it assumes kmol
as the standard unit for molecular count.
A.1.3 Enthalpy
Enthalpy is an example of the thermodynamic properties defined for convenience
of application. For a gas happily bouncing about in the confines of a container,
enthalpy will do us little good, but when a gas is flowing and pushing on things,
we need to account for both the internal energy and the mechanical work done
by the flow. Put another way, the act of moving a fluid in bulk from one pressure
to another implies work just like moving electrons from one voltage to another
or weights from one height to another.
Enthalpy is defined as
p
h=e+ , (A.3)
ρ
and shares the kJ/kg units of its internal energy parent. But, from whence does
it come?
Consider a steady flow through a volume in space. If the energy in the
volume is to be constant, then the rate of accumulation of energy in that vol-
ume must be zero. Energy will flow into the volume with individual particles
carrying their own internal energy and the motion of materials under pressure
will communicate work through fluid power. If we imagine a tiny surface area,
dS, with an outward-facing unit surface normal, ~n, the rate at which energy is
carried out by bulk flow is ρe~u · ~ndS. The rate at which the flow communicates
A.1. PROPERTIES 43
fluid power through the small surface is p~u · ~ndS. A sum of those contributors
over the entire surface must be zero.
‹ ‹
ρe~u · ~ndS + p~u · ~ndS = 0 (A.4)
S S
δq
c= , (A.7)
dT
where δq is a small quantity of heat added to the substance per mass (kJ/kg),
and dT is the resulting tiny rise in temperature. The use of δq (as opposed to
dq) is the result of an old debate. The argument goes that it’s because q isn’t
a property of the substance. Don’t worry about it.
For complicated substances, hotter molecules may tend to gyrate around
differently when they are cool, so different fractions of their energy will appear
as temperature. As a result c can be quite a strong function of temperature.
However, nice simple substances might exhibit nearly constant c over a wide
range of temperatures. If such a simple substance is a gas, it is called a perfect
gas.
The internal energy of an ideal gas doesn’t depend on pressure, but letting a
gas expand to a lower pressure will definitely cool it. The first law tells us why;
the fluid is doing work, and that energy had to come from somewhere. That
leads us to a conundrum when we are talking about the specific heat of gases;
what is happening to the pressure?
The general definition for specific heat offered by A.7 simply isn’t specific
enough because it doesn’t tell us what is happening to the pressure or volume
of the substance while we are changing the temperature. That may not be
intuitive, so let’s illustrate the idea with two examples.
44 APPENDIX A. A VERY BRIEF REVIEW OF THERMODYNAMICS
First, imagine we want to measure the specific heat of a substance while hold-
ing its volume constant. When there is no motion at the substance’s boundaries
no work is done, so the first law is quite simple
δq = de
∂e
= dT (A.8)
∂T v=const
In other words, the specific heat measured at a constant volume tells us the
fraction of internal energy that we actually observe as temperature. Of course,
if the substance is an ideal gas, then it doesn’t matter how we perform the
partial derivative because internal energy is ONLY a function of temperature.
What if we repeated the same measurement, but while holding pressure
constant? This time, the substance will need to expand to prevent the pressure
from increasing as the material is heated. In solids and liquids, this effect is
nearly irrelevant, but in gases, it is really very important.
δq = de + pdv
∂e ∂v
= +p dT
∂T ∂T p=const
∂e ∂p/ρ
= + dT
∂T ∂T p=const
∂h
= dT (A.9)
∂T p=const
Here, v is the specific volume or volume per unit mass. Note that if the substance
is an ideal gas it is irrelevant to the partial derivative whether pressure is held
constant.
The idea is that adding heat to a gas can go into making it hotter or making
it expand. Usually, it does both, but there is no way to know how unless the
process is well defined. Therefore, the specific heats of gases are commonly
reported as both constant-volume and constant-pressure specific heats,
∂e
cv = (A.10)
∂T v=const
∂h
cp = . (A.11)
∂T p=const
To make things a little more convenient, their ratio is also commonly treated
as a property,
cp
k= . (A.12)
cv
The specific heat ratio is also often expressed as γ, but while typographically
elegant, Greek letters just aren’t very convenient at the command line.
A.1. PROPERTIES 45
For ideal gases, cv and cp enjoy a very simple relationship which can be
derived from the definition of enthalpy,
dh
cp =
dT
d(e + RT )
=
dT
= cv + R (A.13)
This teaches us that R can be regarded as a kind of measure of the energy that
goes into expanding a gas as it heats.
A.1.5 Entropy
Perhaps the most conceptually inaccessible of all the commonly used thermo-
dynamic properties, entropy is the one that isn’t like the others. Energy and
enthalpy all show up from thinking about conserving energy, but entropy be-
comes useful because of the second law. In a nutshell, we need to enforce that
heat flows from hot to cold and that useful engines reject waste heat.
The definition of entropy is probably best motivated by the Clausius in-
equality. When we add or remove heat to a mechanism undergoing a “cycle”
(like an engine) the temperature of the substance to which the heat is being
cyclically added and removed really seems to matter. The Clausius inequality
tells us that when we follow the substance all the way through one cycle, unless
˛
δq
≤0 (A.14)
T
the system will not be able to operate continuously. People’s best attempts
to violate this rule just make the engine accumulate (or lose) energy until it
stopped functioning or the rule was obeyed anyway. For the best heat engine
in the world,
˛
δq
=0 (A.15)
T int.rev.
There’s no need to stop there, because the substance’s other properties tell us
where the heat goes.
T ds = de + pdv (A.17)
T ds = dh − vdp (A.18)
We see here that enthalpy will have the same units as R and specific heat,
kJ/kg/K.
Equation A.18 is where we find our source for integrating entropy for ideal
gases. When we have an ideal gas on our hands,
cp R
ds = dT − dp. (A.19)
T p
If the substance is not an ideal gas, then we’re stuck with Equations A.16
and A.18 to figure out a substance’s entropy. On the other hand, when the
substance is a perfect gas, things get very simple.
T p
s(T, p) = s0 + cp ln − Rln (A.22)
T0 p◦
A.2 Mixtures
As of version 1.2, PYro includes a mixture class and data for a handful of
common gas mixtures, but it does not include tools for calculating the properties
of mixtures whose compositions are changing (like in a reaction). Instead, that
is left to the user. For the sake of better understanding the mixture class and
helping users to do their own calculations with mixtures, a brief discussion on
the properties of mixtures might be helpful.
the densities at the same temperature and pressure and sum them, but this
gives us an unrealistically huge answer. Why? If we calculate the density of
each constituent using the pressure we measured from the gas as a whole, we
are drastically over-estimating the number of molecules of each.
P
Mk X
ρ(T, p) = k 6= ρk (T, p) (A.29)
V
k
Let’s show two ways to approach this problem with the same result. While
the constituent gases may all exhibit the same temperature, each of them will
only contribute to a fraction of the pressure. Called the partial pressure, it is
the fraction of the pressure that is contributed by each constituent, the sum of
which is what we actually measure. It turns out that the partial pressure of
each component is just the mole fraction, Xk , times the total pressure, p.
P
Mk
ρ(T, p) = k
V
X
= ρk (T, pXk ) (A.30)
k
Note that for an ideal gas, these results are actually identical. It is because at
their core, these are the same argument. They both suppose that density and
pressure at a given temperature are just proportional to the number of molecules
bouncing around, so the portion of a volume occupied by a gas is too. Happily,
this thinking extends to ideal solutions of liquids as well.
Unfortunately, as things get less “ideal,” the interactions in mixtures can
become more complicated, and a specialized model might be called for. PYro’s
mixture class relies on the ideal solution and ideal gas assumption to calculate
densities, so beware.