1592 lines
58 KiB
Cython
1592 lines
58 KiB
Cython
# This file is part of Cantera. See License.txt in the top-level directory or
|
|
# at http://www.cantera.org/license.txt for license and copyright information.
|
|
|
|
import warnings
|
|
import weakref
|
|
|
|
cdef enum ThermoBasis:
|
|
mass_basis = 0
|
|
molar_basis = 1
|
|
|
|
|
|
cdef class Species:
|
|
"""
|
|
A class which stores data about a single chemical species that may be
|
|
needed to add it to a `Solution` or `Interface` object (and to the
|
|
underlying `ThermoPhase` and `Transport` objects).
|
|
|
|
:param name:
|
|
A string giving the name of the species, e.g. ``'CH4'``
|
|
:param composition:
|
|
The elemental composition of the species, given either as a dict or a
|
|
composition string, e.g. ``{'C':1, 'H':4}`` or ``'C:1, H:4'``.
|
|
:param charge:
|
|
The electrical charge, in units of the elementary charge. Default 0.0.
|
|
:param size:
|
|
The effective size [m] of the species. Default 1.0.
|
|
:param init:
|
|
Used internally when wrapping :ct:`Species` objects returned from C++
|
|
|
|
Example: creating an ideal gas phase with a single species::
|
|
|
|
ch4 = ct.Species('CH4', 'C:1, H:4')
|
|
ch4.thermo = ct.ConstantCp(300, 1000, 101325,
|
|
(300, -7.453347e7, 1.865912e5, 3.576053e4))
|
|
tran = ct.GasTransportData()
|
|
tran.set_customary_units('nonlinear', 3.75, 141.40, 0.0, 2.60, 13.00)
|
|
ch4.transport = tran
|
|
gas = ct.Solution(thermo='IdealGas', species=[ch4])
|
|
|
|
The static methods `fromCti`, `fromXml`, `listFromFile`, `listFromCti`, and
|
|
`listFromXml` can be used to create `Species` objects from existing
|
|
definitions in the CTI or XML formats. All of the following will produce a
|
|
list of 53 `Species` objects containing the species defined in the GRI 3.0
|
|
mechanism::
|
|
|
|
S = ct.Species.listFromFile('gri30.cti')
|
|
S = ct.Species.listFromCti(open('path/to/gri30.cti').read())
|
|
S = ct.Species.listFromXml(open('path/to/gri30.xml').read())
|
|
|
|
"""
|
|
def __cinit__(self, *args, init=True, **kwargs):
|
|
if init:
|
|
self._species.reset(new CxxSpecies())
|
|
self.species = self._species.get()
|
|
|
|
def __init__(self, name=None, composition=None, charge=None, size=None,
|
|
*args, init=True, **kwargs):
|
|
if not init:
|
|
return
|
|
|
|
if name is not None:
|
|
self.species.name = stringify(name)
|
|
|
|
if composition is not None:
|
|
self.species.composition = comp_map(composition)
|
|
|
|
if charge is not None:
|
|
self.species.charge = charge
|
|
|
|
if size is not None:
|
|
self.species.size = size
|
|
|
|
cdef _assign(self, shared_ptr[CxxSpecies] other):
|
|
self._species = other
|
|
self.species = self._species.get()
|
|
|
|
@staticmethod
|
|
def fromCti(text):
|
|
"""
|
|
Create a Species object from its CTI string representation.
|
|
"""
|
|
cxx_species = CxxGetSpecies(deref(CxxGetXmlFromString(stringify(text))))
|
|
assert cxx_species.size() == 1, cxx_species.size()
|
|
species = Species(init=False)
|
|
species._assign(cxx_species[0])
|
|
return species
|
|
|
|
@staticmethod
|
|
def fromXml(text):
|
|
"""
|
|
Create a Species object from its XML string representation.
|
|
"""
|
|
cxx_species = CxxNewSpecies(deref(CxxGetXmlFromString(stringify(text))))
|
|
species = Species(init=False)
|
|
species._assign(cxx_species)
|
|
return species
|
|
|
|
@staticmethod
|
|
def listFromFile(filename):
|
|
"""
|
|
Create a list of Species objects from all of the species defined in a
|
|
CTI or XML file.
|
|
|
|
Directories on Cantera's input file path will be searched for the
|
|
specified file.
|
|
|
|
In the case of an XML file, the ``<species>`` nodes are assumed to be
|
|
children of the ``<speciesData>`` node in a document with a ``<ctml>``
|
|
root node, as in the XML files produced by conversion from CTI files.
|
|
"""
|
|
cxx_species = CxxGetSpecies(deref(CxxGetXmlFile(stringify(filename))))
|
|
species = []
|
|
for a in cxx_species:
|
|
b = Species(init=False)
|
|
b._assign(a)
|
|
species.append(b)
|
|
return species
|
|
|
|
@staticmethod
|
|
def listFromXml(text):
|
|
"""
|
|
Create a list of Species objects from all the species defined in an XML
|
|
string. The ``<species>`` nodes are assumed to be children of the
|
|
``<speciesData>`` node in a document with a ``<ctml>`` root node, as in
|
|
the XML files produced by conversion from CTI files.
|
|
"""
|
|
cxx_species = CxxGetSpecies(deref(CxxGetXmlFromString(stringify(text))))
|
|
species = []
|
|
for a in cxx_species:
|
|
b = Species(init=False)
|
|
b._assign(a)
|
|
species.append(b)
|
|
return species
|
|
|
|
@staticmethod
|
|
def listFromCti(text):
|
|
"""
|
|
Create a list of Species objects from all the species defined in a CTI
|
|
string.
|
|
"""
|
|
# Currently identical to listFromXml since get_XML_from_string is able
|
|
# to distinguish between CTI and XML.
|
|
cxx_species = CxxGetSpecies(deref(CxxGetXmlFromString(stringify(text))))
|
|
species = []
|
|
for a in cxx_species:
|
|
b = Species(init=False)
|
|
b._assign(a)
|
|
species.append(b)
|
|
return species
|
|
|
|
property name:
|
|
""" The name of the species. """
|
|
def __get__(self):
|
|
return pystr(self.species.name)
|
|
|
|
property composition:
|
|
"""
|
|
A dict containing the elemental composition of the species. Keys are
|
|
element names; values are the corresponding atomicities.
|
|
"""
|
|
def __get__(self):
|
|
return comp_map_to_dict(self.species.composition)
|
|
|
|
property charge:
|
|
"""
|
|
The electrical charge on the species, in units of the elementary charge.
|
|
"""
|
|
def __get__(self):
|
|
return self.species.charge
|
|
|
|
property size:
|
|
""" The effective size [m] of the species. """
|
|
def __get__(self):
|
|
return self.species.size
|
|
|
|
property thermo:
|
|
"""
|
|
Get/Set the species reference-state thermodynamic data, as an instance
|
|
of class `SpeciesThermo`.
|
|
"""
|
|
def __get__(self):
|
|
if self.species.thermo.get() != NULL:
|
|
return wrapSpeciesThermo(self.species.thermo)
|
|
else:
|
|
return None
|
|
|
|
def __set__(self, SpeciesThermo spthermo):
|
|
self.species.thermo = spthermo._spthermo
|
|
|
|
property transport:
|
|
"""
|
|
Get/Set the species transport parameters, as an instance of class
|
|
`GasTransportData`.
|
|
"""
|
|
def __get__(self):
|
|
if self.species.transport.get() != NULL:
|
|
data = GasTransportData(init=False)
|
|
data._assign(self.species.transport)
|
|
return data
|
|
else:
|
|
return None
|
|
def __set__(self, GasTransportData tran):
|
|
self.species.transport = tran._data
|
|
|
|
def __repr__(self):
|
|
return '<Species {}>'.format(self.name)
|
|
|
|
|
|
cdef class ThermoPhase(_SolutionBase):
|
|
"""
|
|
A phase with an equation of state.
|
|
|
|
Class `ThermoPhase` may be used to represent the intensive thermodynamic
|
|
state of a phase of matter, which might be a gas, liquid, or solid.
|
|
|
|
Class `ThermoPhase` is not usually instantiated directly. It is used
|
|
as a base class for classes `Solution` and `Interface`.
|
|
"""
|
|
|
|
# Sets of parameters which set the full thermodynamic state
|
|
_full_states = {frozenset(k): k
|
|
for k in ('TDX', 'TDY', 'TPX', 'TPY', 'UVX', 'UVY', 'DPX',
|
|
'DPY', 'HPX', 'HPY', 'SPX', 'SPY', 'SVX', 'SVY')}
|
|
|
|
# The signature of this function causes warnings for Sphinx documentation
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if 'source' not in kwargs:
|
|
self.thermo_basis = mass_basis
|
|
self._references = weakref.WeakKeyDictionary()
|
|
|
|
def report(self, show_thermo=True, float threshold=1e-14):
|
|
"""
|
|
Generate a report describing the thermodynamic state of this phase. To
|
|
print the report to the terminal, simply call the phase object. The
|
|
following two statements are equivalent::
|
|
|
|
>>> phase()
|
|
>>> print(phase.report())
|
|
"""
|
|
return pystr(self.thermo.report(bool(show_thermo), threshold))
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
print(self.report(*args, **kwargs))
|
|
|
|
property name:
|
|
"""
|
|
The name assigned to this phase. The default is taken from the CTI/XML
|
|
input file.
|
|
"""
|
|
def __get__(self):
|
|
return pystr(self.thermo.name())
|
|
def __set__(self, name):
|
|
self.thermo.setName(stringify(name))
|
|
|
|
property ID:
|
|
"""
|
|
The ID of the phase. The default is taken from the CTI/XML input file.
|
|
"""
|
|
def __get__(self):
|
|
return pystr(self.thermo.id())
|
|
def __set__(self, id_):
|
|
self.thermo.setID(stringify(id_))
|
|
|
|
|
|
property basis:
|
|
"""
|
|
Determines whether intensive thermodynamic properties are treated on a
|
|
`mass` (per kg) or `molar` (per kmol) basis. This affects the values
|
|
returned by the properties `h`, `u`, `s`, `g`, `v`, `density`, `cv`,
|
|
and `cp`, as well as the values used with the state-setting properties
|
|
such as `HPX` and `UV`.
|
|
"""
|
|
def __get__(self):
|
|
if self.thermo_basis == mass_basis:
|
|
return 'mass'
|
|
else:
|
|
return 'molar'
|
|
|
|
def __set__(self, value):
|
|
if value == 'mass':
|
|
self.thermo_basis = mass_basis
|
|
elif value == 'molar':
|
|
self.thermo_basis = molar_basis
|
|
else:
|
|
raise ValueError("Valid choices are 'mass' or 'molar'."
|
|
" Got {!r}.".format(value))
|
|
|
|
cdef double _mass_factor(self):
|
|
""" Conversion factor from current basis to kg """
|
|
if self.thermo_basis == molar_basis:
|
|
return self.thermo.meanMolecularWeight()
|
|
else:
|
|
return 1.0
|
|
|
|
cdef double _mole_factor(self):
|
|
""" Conversion factor from current basis to moles """
|
|
if self.thermo_basis == mass_basis:
|
|
return 1.0/self.thermo.meanMolecularWeight()
|
|
else:
|
|
return 1.0
|
|
|
|
def equilibrate(self, XY, solver='auto', double rtol=1e-9,
|
|
int maxsteps=1000, int maxiter=100, int estimate_equil=0,
|
|
int loglevel=0):
|
|
"""
|
|
Set to a state of chemical equilibrium holding property pair
|
|
*XY* constant.
|
|
|
|
:param XY:
|
|
A two-letter string, which must be one of the set::
|
|
|
|
['TP','TV','HP','SP','SV','UV']
|
|
|
|
:param solver:
|
|
Specifies the equilibrium solver to use. May be one of the following:
|
|
|
|
* ''element_potential'' - a fast solver using the element potential
|
|
method
|
|
* 'gibbs' - a slower but more robust Gibbs minimization solver
|
|
* 'vcs' - the VCS non-ideal equilibrium solver
|
|
* "auto" - The element potential solver will be tried first, then
|
|
if it fails the Gibbs solver will be tried.
|
|
:param rtol:
|
|
the relative error tolerance.
|
|
:param maxsteps:
|
|
maximum number of steps in composition to take to find a converged
|
|
solution.
|
|
:param maxiter:
|
|
For the Gibbs minimization solver, this specifies the number of
|
|
'outer' iterations on T or P when some property pair other
|
|
than TP is specified.
|
|
:param estimate_equil:
|
|
Integer indicating whether the solver should estimate its own
|
|
initial condition. If 0, the initial mole fraction vector in the
|
|
ThermoPhase object is used as the initial condition. If 1, the
|
|
initial mole fraction vector is used if the element abundances are
|
|
satisfied. If -1, the initial mole fraction vector is thrown out,
|
|
and an estimate is formulated.
|
|
:param loglevel:
|
|
Set to a value > 0 to write diagnostic output.
|
|
"""
|
|
self.thermo.equilibrate(stringify(XY.upper()), stringify(solver), rtol,
|
|
maxsteps, maxiter, estimate_equil, loglevel)
|
|
|
|
####### Composition, species, and elements ########
|
|
|
|
property n_elements:
|
|
"""Number of elements."""
|
|
def __get__(self):
|
|
return self.thermo.nElements()
|
|
|
|
cpdef int element_index(self, element) except *:
|
|
"""
|
|
The index of element *element*, which may be specified as a string or
|
|
an integer. In the latter case, the index is checked for validity and
|
|
returned. If no such element is present, an exception is thrown.
|
|
"""
|
|
if isinstance(element, (str, unicode, bytes)):
|
|
index = self.thermo.elementIndex(stringify(element))
|
|
elif isinstance(element, (int, float)):
|
|
index = <int>element
|
|
else:
|
|
raise TypeError("'element' must be a string or a number."
|
|
" Got {!r}.".format(element))
|
|
|
|
if not 0 <= index < self.n_elements:
|
|
raise ValueError('No such element {!r}.'.format(element))
|
|
|
|
return index
|
|
|
|
def element_name(self, m):
|
|
"""Name of the element with index *m*."""
|
|
return pystr(self.thermo.elementName(m))
|
|
|
|
property element_names:
|
|
"""A list of all the element names."""
|
|
def __get__(self):
|
|
return [self.element_name(m) for m in range(self.n_elements)]
|
|
|
|
def atomic_weight(self, m):
|
|
"""Atomic weight [kg/kmol] of element *m*"""
|
|
return self.thermo.atomicWeight(self.element_index(m))
|
|
|
|
property atomic_weights:
|
|
"""Array of atomic weight [kg/kmol] for each element in the mixture."""
|
|
def __get__(self):
|
|
return np.array([self.thermo.atomicWeight(m) for m in range(self.n_elements)])
|
|
|
|
property n_species:
|
|
"""Number of species."""
|
|
def __get__(self):
|
|
return self.thermo.nSpecies()
|
|
|
|
property n_selected_species:
|
|
"""
|
|
Number of species selected for output (by slicing of Solution object)
|
|
"""
|
|
def __get__(self):
|
|
return self._selected_species.size or self.n_species
|
|
|
|
def species_name(self, k):
|
|
"""Name of the species with index *k*."""
|
|
return pystr(self.thermo.speciesName(k))
|
|
|
|
property species_names:
|
|
"""A list of all the species names."""
|
|
def __get__(self):
|
|
if self._selected_species.size:
|
|
indices = self._selected_species
|
|
else:
|
|
indices = range(self.n_species)
|
|
return [self.species_name(k) for k in indices]
|
|
|
|
cpdef int species_index(self, species) except *:
|
|
"""
|
|
The index of species *species*, which may be specified as a string or
|
|
an integer. In the latter case, the index is checked for validity and
|
|
returned. If no such species is present, an exception is thrown.
|
|
"""
|
|
if isinstance(species, (str, unicode, bytes)):
|
|
index = self.thermo.speciesIndex(stringify(species))
|
|
elif isinstance(species, (int, float)):
|
|
index = <int>species
|
|
else:
|
|
raise TypeError("'species' must be a string or a number."
|
|
" Got {!r}.".format(species))
|
|
|
|
if not 0 <= index < self.n_species:
|
|
raise ValueError('No such species {!r}.'.format(species))
|
|
|
|
return index
|
|
|
|
def species(self, k=None):
|
|
"""
|
|
Return the `Species` object for species *k*, where *k* is either the
|
|
species index or the species name. If *k* is not specified, a list of
|
|
all species objects is returned.
|
|
"""
|
|
if k is None:
|
|
return [self.species(i) for i in range(self.n_species)]
|
|
|
|
s = Species(init=False)
|
|
if isinstance(k, (str, unicode, bytes)):
|
|
s._assign(self.thermo.species(stringify(k)))
|
|
elif isinstance(k, (int, float)):
|
|
s._assign(self.thermo.species(<int>k))
|
|
else:
|
|
raise TypeError("Argument must be a string or a number."
|
|
" Got {!r}.".format(k))
|
|
return s
|
|
|
|
def modify_species(self, k, Species species):
|
|
self.thermo.modifySpecies(k, species._species)
|
|
if self.kinetics:
|
|
self.kinetics.invalidateCache()
|
|
|
|
def add_species(self, Species species):
|
|
"""
|
|
Add a new species to this phase. Missing elements will be added
|
|
automatically.
|
|
"""
|
|
if self._references:
|
|
raise CanteraError('Cannot add species to ThermoPhase object if it'
|
|
' is linked to a Reactor, Domain1D (flame), or Mixture object.')
|
|
self.thermo.addUndefinedElements()
|
|
self.thermo.addSpecies(species._species)
|
|
self.thermo.initThermo()
|
|
if self.kinetics:
|
|
self.kinetics.invalidateCache()
|
|
|
|
def n_atoms(self, species, element):
|
|
"""
|
|
Number of atoms of element *element* in species *species*. The element
|
|
and species may be specified by name or by index.
|
|
|
|
>>> phase.n_atoms('CH4','H')
|
|
4
|
|
"""
|
|
return self.thermo.nAtoms(self.species_index(species),
|
|
self.element_index(element))
|
|
|
|
cdef np.ndarray _getArray1(self, thermoMethod1d method):
|
|
cdef np.ndarray[np.double_t, ndim=1] data = np.empty(self.n_species)
|
|
method(self.thermo, &data[0])
|
|
if self._selected_species.size:
|
|
return data[self._selected_species]
|
|
else:
|
|
return data
|
|
|
|
cdef void _setArray1(self, thermoMethod1d method, values) except *:
|
|
cdef np.ndarray[np.double_t, ndim=1] data
|
|
|
|
values = np.squeeze(values)
|
|
if values.ndim == 0:
|
|
values = values[np.newaxis] # corner case for single-species phases
|
|
|
|
if len(values) == self.n_species:
|
|
data = np.ascontiguousarray(values, dtype=np.double)
|
|
elif len(values) == len(self._selected_species):
|
|
data = np.zeros(self.n_species, dtype=np.double)
|
|
for i,k in enumerate(self._selected_species):
|
|
data[k] = values[i]
|
|
else:
|
|
raise ValueError("Array has incorrect length."
|
|
" Got {}. Expected {} or {}.".format(
|
|
len(values), self.n_species, len(self._selected_species)))
|
|
method(self.thermo, &data[0])
|
|
|
|
property molecular_weights:
|
|
"""Array of species molecular weights (molar masses) [kg/kmol]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getMolecularWeights)
|
|
|
|
property mean_molecular_weight:
|
|
"""The mean molecular weight (molar mass) [kg/kmol]."""
|
|
def __get__(self):
|
|
return self.thermo.meanMolecularWeight()
|
|
|
|
property Y:
|
|
"""
|
|
Get/Set the species mass fractions. Can be set as an array, as a dictionary,
|
|
or as a string. Always returns an array::
|
|
|
|
>>> phase.Y = [0.1, 0, 0, 0.4, 0, 0, 0, 0, 0.5]
|
|
>>> phase.Y = {'H2':0.1, 'O2':0.4, 'AR':0.5}
|
|
>>> phase.Y = 'H2:0.1, O2:0.4, AR:0.5'
|
|
>>> phase.Y
|
|
array([0.1, 0, 0, 0.4, 0, 0, 0, 0, 0.5])
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getMassFractions)
|
|
def __set__(self, Y):
|
|
if isinstance(Y, (str, unicode, bytes)):
|
|
self.thermo.setMassFractionsByName(stringify(Y))
|
|
elif isinstance(Y, dict):
|
|
self.thermo.setMassFractionsByName(comp_map(Y))
|
|
else:
|
|
self._setArray1(thermo_setMassFractions, Y)
|
|
|
|
property X:
|
|
"""
|
|
Get/Set the species mole fractions. Can be set as an array, as a dictionary,
|
|
or as a string. Always returns an array::
|
|
|
|
>>> phase.X = [0.1, 0, 0, 0.4, 0, 0, 0, 0, 0.5]
|
|
>>> phase.X = {'H2':0.1, 'O2':0.4, 'AR':0.5}
|
|
>>> phase.X = 'H2:0.1, O2:0.4, AR:0.5'
|
|
>>> phase.X
|
|
array([0.1, 0, 0, 0.4, 0, 0, 0, 0, 0.5])
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getMoleFractions)
|
|
def __set__(self, X):
|
|
if isinstance(X, (str, unicode, bytes)):
|
|
self.thermo.setMoleFractionsByName(stringify(X))
|
|
elif isinstance(X, dict):
|
|
self.thermo.setMoleFractionsByName(comp_map(X))
|
|
else:
|
|
self._setArray1(thermo_setMoleFractions, X)
|
|
|
|
property concentrations:
|
|
"""Get/Set the species concentrations [kmol/m^3]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getConcentrations)
|
|
def __set__(self, C):
|
|
self._setArray1(thermo_setConcentrations, C)
|
|
|
|
def set_equivalence_ratio(self, phi, fuel, oxidizer):
|
|
"""
|
|
Set the composition to a mixture of *fuel* and *oxidizer* at the
|
|
specified equivalence ratio *phi*, holding temperature and pressure
|
|
constant. Considers the oxidation of C and H to CO2 and H2O. Other
|
|
elements are assumed not to participate in oxidation (i.e. N ends up as
|
|
N2)::
|
|
|
|
>>> gas.set_equivalence_ratio(0.5, 'CH4', 'O2:1.0, N2:3.76')
|
|
>>> gas.mole_fraction_dict()
|
|
{'CH4': 0.049900199, 'N2': 0.750499001, 'O2': 0.199600798}
|
|
|
|
>>> gas.set_equivalence_ratio(1.2, {'NH3;:0.8, 'CO':0.2}, 'O2:1.0')
|
|
>>> gas.mole_fraction_dict()
|
|
{'CO': 0.1263157894, 'NH3': 0.505263157, 'O2': 0.36842105}
|
|
|
|
:param phi: Equivalence ratio
|
|
:param fuel:
|
|
Fuel species name or molar composition as string, array, or dict.
|
|
:param oxidizer:
|
|
Oxidizer species name or molar composition as a string, array, or
|
|
dict.
|
|
"""
|
|
if (isinstance(fuel, str) and ':' not in fuel
|
|
and fuel in self.species_names):
|
|
fuel += ':1.0'
|
|
|
|
if (isinstance(oxidizer, str) and ':' not in oxidizer
|
|
and oxidizer in self.species_names):
|
|
oxidizer += ':1.0'
|
|
|
|
self.TPX = None, None, fuel
|
|
Xf = self.X
|
|
self.TPX = None, None, oxidizer
|
|
Xo = self.X
|
|
|
|
nO = np.array([self.n_atoms(k, 'O') for k in range(self.n_species)])
|
|
|
|
if 'C' in self.element_names:
|
|
nC = np.array([self.n_atoms(k, 'C') for k in range(self.n_species)])
|
|
else:
|
|
nC = np.zeros(self.n_species)
|
|
|
|
if 'H' in self.element_names:
|
|
nH = np.array([self.n_atoms(k, 'H') for k in range(self.n_species)])
|
|
else:
|
|
nH = np.zeros(self.n_species)
|
|
|
|
Cf = nC.dot(Xf)
|
|
Co = nC.dot(Xo)
|
|
Of = nO.dot(Xf)
|
|
Oo = nO.dot(Xo)
|
|
Hf = nH.dot(Xf)
|
|
Ho = nH.dot(Xo)
|
|
|
|
stoichAirFuelRatio = - (Of - 2*Cf - Hf/2.0) / (Oo - 2*Co - Ho/2.0)
|
|
Xr = phi * Xf + stoichAirFuelRatio * Xo
|
|
self.TPX = None, None, Xr
|
|
|
|
def elemental_mass_fraction(self, m):
|
|
r"""
|
|
Get the elemental mass fraction :math:`Z_{\mathrm{mass},m}` of element
|
|
:math:`m` as defined by:
|
|
|
|
.. math:: Z_{\mathrm{mass},m} = \sum_k \frac{a_{m,k} M_m}{M_k} Y_k
|
|
|
|
with :math:`a_{m,k}` being the number of atoms of element :math:`m` in
|
|
species :math:`k`, :math:`M_m` the atomic weight of element :math:`m`,
|
|
:math:`M_k` the molecular weight of species :math:`k`, and :math:`Y_k`
|
|
the mass fraction of species :math:`k`.
|
|
|
|
:param m:
|
|
Base element, may be specified by name or by index.
|
|
|
|
>>> phase.elemental_mass_fraction('H')
|
|
1.0
|
|
"""
|
|
return self.thermo.elementalMassFraction(self.element_index(m))
|
|
|
|
def elemental_mole_fraction(self, m):
|
|
r"""
|
|
Get the elemental mole fraction :math:`Z_{\mathrm{mole},m}` of element
|
|
:math:`m` (the number of atoms of element m divided by the total number
|
|
of atoms) as defined by:
|
|
|
|
.. math:: Z_{\mathrm{mole},m} = \frac{\sum_k a_{m,k} X_k}
|
|
{\sum_k \sum_j a_{j,k} X_k}
|
|
|
|
with :math:`a_{m,k}` being the number of atoms of element :math:`m` in
|
|
species :math:`k`, :math:`\sum_j` being a sum over all elements, and
|
|
:math:`X_k` being the mole fraction of species :math:`k`.
|
|
|
|
:param m:
|
|
Base element, may be specified by name or by index.
|
|
|
|
>>> phase.elemental_mole_fraction('H')
|
|
1.0
|
|
"""
|
|
return self.thermo.elementalMoleFraction(self.element_index(m))
|
|
|
|
def set_unnormalized_mass_fractions(self, Y):
|
|
"""
|
|
Set the mass fractions without normalizing to force sum(Y) == 1.0.
|
|
Useful primarily when calculating derivatives with respect to Y[k] by
|
|
finite difference.
|
|
"""
|
|
cdef np.ndarray[np.double_t, ndim=1] data
|
|
if len(Y) == self.n_species:
|
|
data = np.ascontiguousarray(Y, dtype=np.double)
|
|
else:
|
|
raise ValueError("Array has incorrect length."
|
|
" Got {}, expected {}.".format(len(Y), self.n_species))
|
|
self.thermo.setMassFractions_NoNorm(&data[0])
|
|
|
|
def set_unnormalized_mole_fractions(self, X):
|
|
"""
|
|
Set the mole fractions without normalizing to force sum(X) == 1.0.
|
|
Useful primarily when calculating derivatives with respect to X[k]
|
|
by finite difference.
|
|
"""
|
|
cdef np.ndarray[np.double_t, ndim=1] data
|
|
if len(X) == self.n_species:
|
|
data = np.ascontiguousarray(X, dtype=np.double)
|
|
else:
|
|
raise ValueError("Array has incorrect length."
|
|
" Got {}, expected {}.".format(len(X), self.n_species))
|
|
self.thermo.setMoleFractions_NoNorm(&data[0])
|
|
|
|
def mass_fraction_dict(self, double threshold=0.0):
|
|
Y = self.thermo.getMassFractionsByName(threshold)
|
|
return {pystr(item.first):item.second for item in Y}
|
|
|
|
def mole_fraction_dict(self, double threshold=0.0):
|
|
X = self.thermo.getMoleFractionsByName(threshold)
|
|
return {pystr(item.first):item.second for item in X}
|
|
|
|
######## Read-only thermodynamic properties ########
|
|
|
|
property P:
|
|
"""Pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.thermo.pressure()
|
|
|
|
property T:
|
|
"""Temperature [K]."""
|
|
def __get__(self):
|
|
return self.thermo.temperature()
|
|
|
|
property density:
|
|
"""Density [kg/m^3 or kmol/m^3] depending on `basis`."""
|
|
def __get__(self):
|
|
return self.thermo.density() / self._mass_factor()
|
|
|
|
property density_mass:
|
|
"""(Mass) density [kg/m^3]."""
|
|
def __get__(self):
|
|
return self.thermo.density()
|
|
|
|
property density_mole:
|
|
"""Molar density [kmol/m^3]."""
|
|
def __get__(self):
|
|
return self.thermo.molarDensity()
|
|
|
|
property v:
|
|
"""Specific volume [m^3/kg or m^3/kmol] depending on `basis`."""
|
|
def __get__(self):
|
|
return self._mass_factor() / self.thermo.density()
|
|
|
|
property volume_mass:
|
|
"""Specific volume [m^3/kg]."""
|
|
def __get__(self):
|
|
return 1.0 / self.thermo.density()
|
|
|
|
property volume_mole:
|
|
"""Molar volume [m^3/kmol]."""
|
|
def __get__(self):
|
|
return self.thermo.molarVolume()
|
|
|
|
property u:
|
|
"""Internal energy in [J/kg or J/kmol]."""
|
|
def __get__(self):
|
|
return self.thermo.intEnergy_mole() * self._mole_factor()
|
|
|
|
property int_energy_mole:
|
|
"""Molar internal energy [J/kmol]."""
|
|
def __get__(self):
|
|
return self.thermo.intEnergy_mole()
|
|
|
|
property int_energy_mass:
|
|
"""Specific internal energy [J/kg]."""
|
|
def __get__(self):
|
|
return self.thermo.intEnergy_mass()
|
|
|
|
property h:
|
|
"""Enthalpy [J/kg or J/kmol] depending on `basis`."""
|
|
def __get__(self):
|
|
return self.thermo.enthalpy_mole() * self._mole_factor()
|
|
|
|
property enthalpy_mole:
|
|
"""Molar enthalpy [J/kmol]."""
|
|
def __get__(self):
|
|
return self.thermo.enthalpy_mole()
|
|
|
|
property enthalpy_mass:
|
|
"""Specific enthalpy [J/kg]."""
|
|
def __get__(self):
|
|
return self.thermo.enthalpy_mass()
|
|
|
|
property s:
|
|
"""Entropy [J/kg/K or J/kmol/K] depending on `basis`."""
|
|
def __get__(self):
|
|
return self.thermo.entropy_mole() * self._mole_factor()
|
|
|
|
property entropy_mole:
|
|
"""Molar entropy [J/kmol/K]."""
|
|
def __get__(self):
|
|
return self.thermo.entropy_mole()
|
|
|
|
property entropy_mass:
|
|
"""Specific entropy [J/kg]."""
|
|
def __get__(self):
|
|
return self.thermo.entropy_mass()
|
|
|
|
property g:
|
|
"""Gibbs free energy [J/kg or J/kmol] depending on `basis`."""
|
|
def __get__(self):
|
|
return self.thermo.gibbs_mole() * self._mole_factor()
|
|
|
|
property gibbs_mole:
|
|
"""Molar Gibbs free energy [J/kmol]."""
|
|
def __get__(self):
|
|
return self.thermo.gibbs_mole()
|
|
|
|
property gibbs_mass:
|
|
"""Specific Gibbs free energy [J/kg]."""
|
|
def __get__(self):
|
|
return self.thermo.gibbs_mass()
|
|
|
|
property cv:
|
|
"""
|
|
Heat capacity at constant volume [J/kg/K or J/kmol/K] depending on
|
|
`basis`.
|
|
"""
|
|
def __get__(self):
|
|
return self.thermo.cv_mole() * self._mole_factor()
|
|
|
|
property cv_mole:
|
|
"""Molar heat capacity at constant volume [J/kmol/K]."""
|
|
def __get__(self):
|
|
return self.thermo.cv_mole()
|
|
|
|
property cv_mass:
|
|
"""Specific heat capacity at constant volume [J/kg/K]."""
|
|
def __get__(self):
|
|
return self.thermo.cv_mass()
|
|
|
|
property cp:
|
|
"""
|
|
Heat capacity at constant pressure [J/kg/K or J/kmol/K] depending
|
|
on `basis`.
|
|
"""
|
|
def __get__(self):
|
|
return self.thermo.cp_mole() * self._mole_factor()
|
|
|
|
property cp_mole:
|
|
"""Molar heat capacity at constant pressure [J/kmol/K]."""
|
|
def __get__(self):
|
|
return self.thermo.cp_mole()
|
|
|
|
property cp_mass:
|
|
"""Specific heat capacity at constant pressure [J/kg/K]."""
|
|
def __get__(self):
|
|
return self.thermo.cp_mass()
|
|
|
|
property critical_temperature:
|
|
"""Critical temperature [K]."""
|
|
def __get__(self):
|
|
return self.thermo.critTemperature()
|
|
|
|
property critical_pressure:
|
|
"""Critical pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.thermo.critPressure()
|
|
|
|
property critical_density:
|
|
"""Critical density [kg/m^3 or kmol/m^3] depending on `basis`."""
|
|
def __get__(self):
|
|
return self.thermo.critDensity() / self._mass_factor()
|
|
|
|
property P_sat:
|
|
"""Saturation pressure [Pa] at the current temperature."""
|
|
def __get__(self):
|
|
return self.thermo.satPressure(self.T)
|
|
|
|
property T_sat:
|
|
"""Saturation temperature [K] at the current pressure."""
|
|
def __get__(self):
|
|
return self.thermo.satTemperature(self.P)
|
|
|
|
######## Methods to get/set the complete thermodynamic state ########
|
|
|
|
property state:
|
|
"""
|
|
Get/Set the full thermodynamic state as a single array, arranged as
|
|
[temperature, density, mass fractions] for most phases. Useful mainly
|
|
in cases where it is desired to store many states in a multidimensional
|
|
array.
|
|
"""
|
|
def __get__(self):
|
|
cdef np.ndarray[np.double_t, ndim=1] state = np.empty(self.n_species + 2)
|
|
self.thermo.saveState(len(state), &state[0])
|
|
return state
|
|
|
|
def __set__(self, state):
|
|
cdef np.ndarray[np.double_t, ndim=1] cstate = np.asarray(state)
|
|
self.thermo.restoreState(len(state), &cstate[0])
|
|
|
|
property TD:
|
|
"""Get/Set temperature [K] and density [kg/m^3 or kmol/m^3]."""
|
|
def __get__(self):
|
|
return self.T, self.density
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
T = values[0] if values[0] is not None else self.T
|
|
D = values[1] if values[1] is not None else self.density
|
|
self.thermo.setState_TR(T, D * self._mass_factor())
|
|
|
|
property TDX:
|
|
"""
|
|
Get/Set temperature [K], density [kg/m^3 or kmol/m^3], and mole
|
|
fractions.
|
|
"""
|
|
def __get__(self):
|
|
return self.T, self.density, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
T = values[0] if values[0] is not None else self.T
|
|
D = values[1] if values[1] is not None else self.density
|
|
self.X = values[2]
|
|
self.thermo.setState_TR(T, D * self._mass_factor())
|
|
|
|
property TDY:
|
|
"""
|
|
Get/Set temperature [K] and density [kg/m^3 or kmol/m^3], and mass
|
|
fractions.
|
|
"""
|
|
def __get__(self):
|
|
return self.T, self.density, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
T = values[0] if values[0] is not None else self.T
|
|
D = values[1] if values[1] is not None else self.density
|
|
self.Y = values[2]
|
|
self.thermo.setState_TR(T, D * self._mass_factor())
|
|
|
|
property TP:
|
|
"""Get/Set temperature [K] and pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.T, self.P
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
T = values[0] if values[0] is not None else self.T
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.thermo.setState_TP(T, P)
|
|
|
|
property TPX:
|
|
"""Get/Set temperature [K], pressure [Pa], and mole fractions."""
|
|
def __get__(self):
|
|
return self.T, self.P, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
T = values[0] if values[0] is not None else self.T
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.X = values[2]
|
|
self.thermo.setState_TP(T, P)
|
|
|
|
property TPY:
|
|
"""Get/Set temperature [K], pressure [Pa], and mass fractions."""
|
|
def __get__(self):
|
|
return self.T, self.P, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
T = values[0] if values[0] is not None else self.T
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.Y = values[2]
|
|
self.thermo.setState_TP(T, P)
|
|
|
|
property UV:
|
|
"""
|
|
Get/Set internal energy [J/kg or J/kmol] and specific volume
|
|
[m^3/kg or m^3/kmol].
|
|
"""
|
|
def __get__(self):
|
|
return self.u, self.v
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
U = values[0] if values[0] is not None else self.u
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.thermo.setState_UV(U / self._mass_factor(),
|
|
V / self._mass_factor())
|
|
|
|
property UVX:
|
|
"""
|
|
Get/Set internal energy [J/kg or J/kmol], specific volume
|
|
[m^3/kg or m^3/kmol], and mole fractions.
|
|
"""
|
|
def __get__(self):
|
|
return self.u, self.v, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
U = values[0] if values[0] is not None else self.u
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.X = values[2]
|
|
self.thermo.setState_UV(U / self._mass_factor(),
|
|
V / self._mass_factor())
|
|
|
|
property UVY:
|
|
"""
|
|
Get/Set internal energy [J/kg or J/kmol], specific volume
|
|
[m^3/kg or m^3/kmol], and mass fractions.
|
|
"""
|
|
def __get__(self):
|
|
return self.u, self.v, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
U = values[0] if values[0] is not None else self.u
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.Y = values[2]
|
|
self.thermo.setState_UV(U / self._mass_factor(),
|
|
V / self._mass_factor())
|
|
|
|
property DP:
|
|
"""Get/Set density [kg/m^3] and pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.density, self.P
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
D = values[0] if values[0] is not None else self.density
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.thermo.setState_RP(D*self._mass_factor(), P)
|
|
|
|
property DPX:
|
|
"""Get/Set density [kg/m^3], pressure [Pa], and mole fractions."""
|
|
def __get__(self):
|
|
return self.density, self.P, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
D = values[0] if values[0] is not None else self.density
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.X = values[2]
|
|
self.thermo.setState_RP(D*self._mass_factor(), P)
|
|
|
|
property DPY:
|
|
"""Get/Set density [kg/m^3], pressure [Pa], and mass fractions."""
|
|
def __get__(self):
|
|
return self.density, self.P, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
D = values[0] if values[0] is not None else self.density
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.Y = values[2]
|
|
self.thermo.setState_RP(D*self._mass_factor(), P)
|
|
|
|
property HP:
|
|
"""Get/Set enthalpy [J/kg or J/kmol] and pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.h, self.P
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
H = values[0] if values[0] is not None else self.h
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.thermo.setState_HP(H / self._mass_factor(), P)
|
|
|
|
property HPX:
|
|
"""Get/Set enthalpy [J/kg or J/kmol], pressure [Pa] and mole fractions."""
|
|
def __get__(self):
|
|
return self.h, self.P, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
H = values[0] if values[0] is not None else self.h
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.X = values[2]
|
|
self.thermo.setState_HP(H / self._mass_factor(), P)
|
|
|
|
property HPY:
|
|
"""Get/Set enthalpy [J/kg or J/kmol], pressure [Pa] and mass fractions."""
|
|
def __get__(self):
|
|
return self.h, self.P, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
H = values[0] if values[0] is not None else self.h
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.Y = values[2]
|
|
self.thermo.setState_HP(H / self._mass_factor(), P)
|
|
|
|
property SP:
|
|
"""Get/Set entropy [J/kg/K or J/kmol/K] and pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.s, self.P
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
S = values[0] if values[0] is not None else self.s
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.thermo.setState_SP(S / self._mass_factor(), P)
|
|
|
|
property SPX:
|
|
"""Get/Set entropy [J/kg/K or J/kmol/K], pressure [Pa], and mole fractions."""
|
|
def __get__(self):
|
|
return self.s, self.P, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
S = values[0] if values[0] is not None else self.s
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.X = values[2]
|
|
self.thermo.setState_SP(S / self._mass_factor(), P)
|
|
|
|
property SPY:
|
|
"""Get/Set entropy [J/kg/K or J/kmol/K], pressure [Pa], and mass fractions."""
|
|
def __get__(self):
|
|
return self.s, self.P, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
S = values[0] if values[0] is not None else self.s
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.Y = values[2]
|
|
self.thermo.setState_SP(S / self._mass_factor(), P)
|
|
|
|
property SV:
|
|
"""
|
|
Get/Set entropy [J/kg/K or J/kmol/K] and specific volume [m^3/kg or
|
|
m^3/kmol].
|
|
"""
|
|
def __get__(self):
|
|
return self.s, self.v
|
|
def __set__(self, values):
|
|
assert len(values) == 2, 'incorrect number of values'
|
|
S = values[0] if values[0] is not None else self.s
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.thermo.setState_SV(S / self._mass_factor(),
|
|
V / self._mass_factor())
|
|
|
|
property SVX:
|
|
"""
|
|
Get/Set entropy [J/kg/K or J/kmol/K], specific volume [m^3/kg or
|
|
m^3/kmol], and mole fractions.
|
|
"""
|
|
def __get__(self):
|
|
return self.s, self.v, self.X
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
S = values[0] if values[0] is not None else self.s
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.X = values[2]
|
|
self.thermo.setState_SV(S / self._mass_factor(),
|
|
V / self._mass_factor())
|
|
|
|
property SVY:
|
|
"""
|
|
Get/Set entropy [J/kg/K or J/kmol/K], specific volume [m^3/kg or
|
|
m^3/kmol], and mass fractions.
|
|
"""
|
|
def __get__(self):
|
|
return self.s, self.v, self.Y
|
|
def __set__(self, values):
|
|
assert len(values) == 3, 'incorrect number of values'
|
|
S = values[0] if values[0] is not None else self.s
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.Y = values[2]
|
|
self.thermo.setState_SV(S / self._mass_factor(),
|
|
V / self._mass_factor())
|
|
|
|
# partial molar / non-dimensional properties
|
|
property partial_molar_enthalpies:
|
|
"""Array of species partial molar enthalpies [J/kmol]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getPartialMolarEnthalpies)
|
|
|
|
property partial_molar_entropies:
|
|
"""Array of species partial molar entropies [J/kmol/K]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getPartialMolarEntropies)
|
|
|
|
property partial_molar_int_energies:
|
|
"""Array of species partial molar internal energies [J/kmol]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getPartialMolarIntEnergies)
|
|
|
|
property chemical_potentials:
|
|
"""Array of species chemical potentials [J/kmol]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getChemPotentials)
|
|
|
|
property electrochemical_potentials:
|
|
"""Array of species electrochemical potentials [J/kmol]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getElectrochemPotentials)
|
|
|
|
property partial_molar_cp:
|
|
"""
|
|
Array of species partial molar specific heat capacities at constant
|
|
pressure [J/kmol/K].
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getPartialMolarCp)
|
|
|
|
property partial_molar_volumes:
|
|
"""Array of species partial molar volumes [m^3/kmol]."""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getPartialMolarVolumes)
|
|
|
|
property standard_enthalpies_RT:
|
|
"""
|
|
Array of nondimensional species standard-state enthalpies at the
|
|
current temperature and pressure.
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getEnthalpy_RT)
|
|
|
|
property standard_entropies_R:
|
|
"""
|
|
Array of nondimensional species standard-state entropies at the
|
|
current temperature and pressure.
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getEntropy_R)
|
|
|
|
property standard_int_energies_RT:
|
|
"""
|
|
Array of nondimensional species standard-state internal energies at the
|
|
current temperature and pressure.
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getIntEnergy_RT)
|
|
|
|
property standard_gibbs_RT:
|
|
"""
|
|
Array of nondimensional species standard-state Gibbs free energies at
|
|
the current temperature and pressure.
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getGibbs_RT)
|
|
|
|
property standard_cp_R:
|
|
"""
|
|
Array of nondimensional species standard-state specific heat capacities
|
|
at constant pressure at the current temperature and pressure.
|
|
"""
|
|
def __get__(self):
|
|
return self._getArray1(thermo_getCp_R)
|
|
|
|
######## Miscellaneous properties ########
|
|
property isothermal_compressibility:
|
|
"""Isothermal compressibility [1/Pa]."""
|
|
def __get__(self):
|
|
return self.thermo.isothermalCompressibility()
|
|
|
|
property thermal_expansion_coeff:
|
|
"""Thermal expansion coefficient [1/K]."""
|
|
def __get__(self):
|
|
return self.thermo.thermalExpansionCoeff()
|
|
|
|
property min_temp:
|
|
"""
|
|
Minimum temperature for which the thermodynamic data for the phase are
|
|
valid.
|
|
"""
|
|
def __get__(self):
|
|
return self.thermo.minTemp()
|
|
|
|
property max_temp:
|
|
"""
|
|
Maximum temperature for which the thermodynamic data for the phase are
|
|
valid.
|
|
"""
|
|
def __get__(self):
|
|
return self.thermo.maxTemp()
|
|
|
|
property reference_pressure:
|
|
"""Reference state pressure [Pa]."""
|
|
def __get__(self):
|
|
return self.thermo.refPressure()
|
|
|
|
property electric_potential:
|
|
"""Get/Set the electric potential [V] for this phase."""
|
|
def __get__(self):
|
|
return self.thermo.electricPotential()
|
|
def __set__(self, double value):
|
|
self.thermo.setElectricPotential(value)
|
|
|
|
def element_potentials(self):
|
|
"""
|
|
Get the array of element potentials. The element potentials are only
|
|
defined for equilibrium states. This method first sets the composition
|
|
to a state of equilibrium at constant T and P, then computes the
|
|
element potentials for this equilibrium state.
|
|
"""
|
|
self.equilibrate('TP')
|
|
cdef np.ndarray[np.double_t, ndim=1] data = np.zeros(self.n_elements)
|
|
self.thermo.getElementPotentials(&data[0])
|
|
return data
|
|
|
|
|
|
cdef class InterfacePhase(ThermoPhase):
|
|
""" A class representing a surface or edge phase"""
|
|
def __cinit__(self, *args, **kwargs):
|
|
if pystr(self.thermo.type()) not in ("Surf", "Edge"):
|
|
raise TypeError('Underlying ThermoPhase object is of the wrong type.')
|
|
self.surf = <CxxSurfPhase*>(self.thermo)
|
|
|
|
property site_density:
|
|
"""
|
|
Get/Set the site density. [kmol/m^2] for surface phases; [kmol/m] for
|
|
edge phases.
|
|
"""
|
|
def __get__(self):
|
|
return self.surf.siteDensity()
|
|
def __set__(self, double value):
|
|
self.surf.setSiteDensity(value)
|
|
|
|
property coverages:
|
|
"""Get/Set the fraction of sites covered by each species."""
|
|
def __get__(self):
|
|
cdef np.ndarray[np.double_t, ndim=1] data = np.empty(self.n_species)
|
|
self.surf.getCoverages(&data[0])
|
|
if self._selected_species.size:
|
|
return data[self._selected_species]
|
|
else:
|
|
return data
|
|
|
|
def __set__(self, theta):
|
|
if isinstance(theta, (dict, str, unicode, bytes)):
|
|
self.surf.setCoveragesByName(comp_map(theta))
|
|
return
|
|
|
|
if len(theta) != self.n_species:
|
|
raise ValueError("Array has incorrect length."
|
|
" Got {}, expected {}".format(len(theta), self.n_species))
|
|
cdef np.ndarray[np.double_t, ndim=1] data = \
|
|
np.ascontiguousarray(theta, dtype=np.double)
|
|
self.surf.setCoverages(&data[0])
|
|
|
|
def set_unnormalized_coverages(self, cov):
|
|
"""
|
|
Set the surface coverages without normalizing to force sum(cov) == 1.0.
|
|
Useful primarily when calculating derivatives with respect to cov[k] by
|
|
finite difference.
|
|
"""
|
|
cdef np.ndarray[np.double_t, ndim=1] data
|
|
if len(cov) == self.n_species:
|
|
data = np.ascontiguousarray(cov, dtype=np.double)
|
|
else:
|
|
raise ValueError("Array has incorrect length."
|
|
" Got {}, expected {}.".format(len(cov), self.n_species))
|
|
self.surf.setCoveragesNoNorm(&data[0])
|
|
|
|
|
|
cdef class PureFluid(ThermoPhase):
|
|
"""
|
|
A pure substance that can be a gas, a liquid, a mixed gas-liquid fluid,
|
|
or a fluid beyond its critical point.
|
|
"""
|
|
|
|
_full_states = {frozenset(k): k
|
|
for k in ('TD', 'TP', 'UV', 'DP', 'HP', 'SP', 'SV', 'TX',
|
|
'PX', 'ST', 'TV', 'PV', 'UP', 'VH', 'TH', 'SH')}
|
|
|
|
property X:
|
|
"""
|
|
Get/Set vapor fraction (quality). Can be set only when in the two-phase
|
|
region.
|
|
"""
|
|
def __get__(self):
|
|
return self.thermo.vaporFraction()
|
|
def __set__(self, X):
|
|
if (self.P >= self.critical_pressure or
|
|
abs(self.P-self.P_sat)/self.P > 1e-4):
|
|
raise ValueError('Cannot set vapor quality outside the'
|
|
'two-phase region')
|
|
self.thermo.setState_Psat(self.P, X)
|
|
|
|
property TX:
|
|
"""Get/Set the temperature [K] and vapor fraction of a two-phase state."""
|
|
def __get__(self):
|
|
return self.T, self.X
|
|
def __set__(self, values):
|
|
T = values[0] if values[0] is not None else self.T
|
|
X = values[1] if values[1] is not None else self.X
|
|
self.thermo.setState_Tsat(T, X)
|
|
|
|
property PX:
|
|
"""Get/Set the pressure [Pa] and vapor fraction of a two-phase state."""
|
|
def __get__(self):
|
|
return self.P, self.X
|
|
def __set__(self, values):
|
|
P = values[0] if values[0] is not None else self.P
|
|
X = values[1] if values[1] is not None else self.X
|
|
self.thermo.setState_Psat(P, X)
|
|
|
|
property ST:
|
|
"""Get/Set the entropy [J/kg/K] and temperature [K] of a PureFluid."""
|
|
def __get__(self):
|
|
return self.s, self.T
|
|
def __set__(self, values):
|
|
S = values[0] if values[0] is not None else self.s
|
|
T = values[1] if values[1] is not None else self.T
|
|
self.thermo.setState_ST(S / self._mass_factor(), T)
|
|
|
|
property TV:
|
|
"""
|
|
Get/Set the temperature [K] and specific volume [m^3/kg] of
|
|
a PureFluid.
|
|
"""
|
|
def __get__(self):
|
|
return self.T, self.v
|
|
def __set__(self, values):
|
|
T = values[0] if values[0] is not None else self.T
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.thermo.setState_TV(T, V / self._mass_factor())
|
|
|
|
property PV:
|
|
"""
|
|
Get/Set the pressure [Pa] and specific volume [m^3/kg] of
|
|
a PureFluid.
|
|
"""
|
|
def __get__(self):
|
|
return self.p, self.v
|
|
def __set__(self, values):
|
|
P = values[0] if values[0] is not None else self.P
|
|
V = values[1] if values[1] is not None else self.v
|
|
self.thermo.setState_PV(P, V / self._mass_factor())
|
|
|
|
property UP:
|
|
"""
|
|
Get/Set the specific internal energy [J/kg] and the
|
|
pressure [Pa] of a PureFluid.
|
|
"""
|
|
def __get__(self):
|
|
return self.u, self.P
|
|
def __set__(self, values):
|
|
U = values[0] if values[0] is not None else self.u
|
|
P = values[1] if values[1] is not None else self.P
|
|
self.thermo.setState_UP(U / self._mass_factor(), P)
|
|
|
|
property VH:
|
|
"""
|
|
Get/Set the specfic volume [m^3/kg] and the specific
|
|
enthalpy [J/kg] of a PureFluid.
|
|
"""
|
|
def __get__(self):
|
|
return self.v, self.h
|
|
def __set__(self, values):
|
|
V = values[0] if values[0] is not None else self.v
|
|
H = values[1] if values[1] is not None else self.h
|
|
self.thermo.setState_VH(V/self._mass_factor(), H/self._mass_factor())
|
|
|
|
property TH:
|
|
"""
|
|
Get/Set the temperature [K] and the specific enthalpy [J/kg]
|
|
of a PureFluid.
|
|
"""
|
|
def __get__(self):
|
|
return self.T, self.h
|
|
def __set__(self, values):
|
|
T = values[0] if values[0] is not None else self.T
|
|
H = values[1] if values[1] is not None else self.h
|
|
self.thermo.setState_TH(T, H / self._mass_factor())
|
|
|
|
property SH:
|
|
"""
|
|
Get/Set the specific entropy [J/kg/K] and the specific
|
|
enthalpy [J/kg] of a PureFluid.
|
|
"""
|
|
def __get__(self):
|
|
return self.s, self.h
|
|
def __set__(self, values):
|
|
S = values[0] if values[0] is not None else self.s
|
|
H = values[1] if values[1] is not None else self.h
|
|
self.thermo.setState_SH(S/self._mass_factor(), H/self._mass_factor())
|
|
|
|
property TDX:
|
|
"""
|
|
Get the temperature [K], density [kg/m^3 or kmol/m^3], and vapor
|
|
fraction.
|
|
"""
|
|
def __get__(self):
|
|
return self.T, self.density, self.X
|
|
|
|
property TPX:
|
|
"""Get the temperature [K], pressure [Pa], and vapor fraction."""
|
|
def __get__(self):
|
|
return self.T, self.P, self.X
|
|
|
|
property UVX:
|
|
"""
|
|
Get the internal energy [J/kg or J/kmol], specific volume
|
|
[m^3/kg or m^3/kmol], and vapor fraction.
|
|
"""
|
|
def __get__(self):
|
|
return self.u, self.v, self.X
|
|
|
|
property DPX:
|
|
"""Get the density [kg/m^3], pressure [Pa], and vapor fraction."""
|
|
def __get__(self):
|
|
return self.density, self.P, self.X
|
|
|
|
property HPX:
|
|
"""
|
|
Get the enthalpy [J/kg or J/kmol], pressure [Pa] and vapor fraction.
|
|
"""
|
|
def __get__(self):
|
|
return self.h, self.P, self.X
|
|
|
|
property SPX:
|
|
"""
|
|
Get the entropy [J/kg/K or J/kmol/K], pressure [Pa], and vapor fraction.
|
|
"""
|
|
def __get__(self):
|
|
return self.s, self.P, self.X
|
|
|
|
property SVX:
|
|
"""
|
|
Get the entropy [J/kg/K or J/kmol/K], specific volume [m^3/kg or
|
|
m^3/kmol], and vapor fraction.
|
|
"""
|
|
def __get__(self):
|
|
return self.s, self.v, self.X
|
|
|
|
|
|
class Element(object):
|
|
"""
|
|
An element or a named isotope defined in Cantera.
|
|
|
|
Class `Element` gets data for the elements and isotopes defined in
|
|
`src/thermo/Elements.cpp`. This class can be used in two ways. The
|
|
first way is to get information about all of the elements stored in
|
|
Cantera. The three attributes `num_elements_defined`,
|
|
`element_symbols`, and `element_names` can be accessed by::
|
|
|
|
>>> ct.Element.num_elements_defined
|
|
>>> ct.Element.element_symbols
|
|
>>> ct.Element.element_names
|
|
|
|
Otherwise, if the class `Element` is called with an argument, it
|
|
stores the data about that particular element. For example::
|
|
|
|
>>> ar_sym = ct.Element('Ar')
|
|
>>> ar_name = ct.Element('argon')
|
|
>>> ar_num = ct.Element(18)
|
|
|
|
would all create instances with the information for argon. The
|
|
available argument options to create an instance of the `Element`
|
|
class with the element information are the `name`, `symbol`, and
|
|
`atomic_number`. Once an instance of the class is made, the `name`,
|
|
`atomic_number`, `symbol`, and atomic `weight` can be accessed as
|
|
attributes of the instance of the `Element` class.
|
|
|
|
>>> ar_sym.name
|
|
'argon'
|
|
>>> ar_sym.weight
|
|
39.948
|
|
>>> ar_sym.atomic_number
|
|
18
|
|
>>> ar_sym.symbol
|
|
'Ar'
|
|
|
|
The elements available are listed below, in the `element_symbols`
|
|
and `element_names` attribute documentation.
|
|
"""
|
|
|
|
#: The number of named elements (not isotopes) defined in Cantera
|
|
num_elements_defined = numElementsDefined()
|
|
|
|
#: A list of the symbols of all the elements (not isotopes) defined
|
|
#: in Cantera
|
|
element_symbols = [pystr(getElementSymbol(<int>(m+1)))
|
|
for m in range(num_elements_defined)]
|
|
|
|
#: A list of the names of all the elements (not isotopes) defined
|
|
#: in Cantera
|
|
element_names = [pystr(getElementName(<int>m+1))
|
|
for m in range(num_elements_defined)]
|
|
|
|
def __init__(self, arg):
|
|
if isinstance(arg, (str, unicode, bytes)):
|
|
try:
|
|
# Assume the argument is the element symbol and try to get the name
|
|
self._name = pystr(getElementName(stringify(arg)))
|
|
except RuntimeError:
|
|
# If getting the name failed, the argument must be the name
|
|
self._symbol = pystr(getElementSymbol(stringify(arg)))
|
|
self._name = arg.lower()
|
|
else:
|
|
self._symbol = arg
|
|
|
|
self._atomic_number = getAtomicNumber(stringify(arg))
|
|
self._weight = getElementWeight(stringify(arg))
|
|
elif isinstance(arg, int):
|
|
self._atomic_number = arg
|
|
self._name = pystr(getElementName(<int>arg))
|
|
self._symbol = pystr(getElementSymbol(<int>arg))
|
|
self._weight = getElementWeight(<int>arg)
|
|
else:
|
|
raise TypeError('The input argument to Element must be a string '
|
|
'or an integer')
|
|
|
|
@property
|
|
def name(self):
|
|
"""The name of the element or isotope."""
|
|
return self._name
|
|
|
|
@property
|
|
def atomic_number(self):
|
|
"""The atomic number of the element or isotope."""
|
|
return self._atomic_number
|
|
|
|
@property
|
|
def symbol(self):
|
|
"""The symbol of the element or isotope."""
|
|
return self._symbol
|
|
|
|
@property
|
|
def weight(self):
|
|
"""The atomic weight of the element or isotope."""
|
|
return self._weight
|