Skip to content

Commit

Permalink
Simplify computation of equivalence ratio and mixture fraction
Browse files Browse the repository at this point in the history
  • Loading branch information
g3bk47 authored and bryanwweber committed Jul 1, 2020
1 parent 58debf1 commit 99de114
Show file tree
Hide file tree
Showing 9 changed files with 640 additions and 840 deletions.
6 changes: 3 additions & 3 deletions include/cantera/thermo/Phase.h
Original file line number Diff line number Diff line change
Expand Up @@ -877,20 +877,20 @@ class Phase
m_root = root;
}

//! Converts a compositionMap to a vector with entries for each species
//! Converts a compositionMap to a vector with entries for each species
//! Species that are not specified are set to zero in the vector
/*!
* @param[in] comp compositionMap containing the mixture composition
* @return vector with length m_kk
*/
vector_fp getCompositionFromMap(const compositionMap& comp) const;

//! Converts a mixture composition from mole fractions to mass fractions
//! Converts a mixture composition from mole fractions to mass fractions
//! @param[in] Y mixture composition in mass fractions (length m_kk)
//! @param[out] X mixture composition in mole fractions (length m_kk)
void massFractionsToMoleFractions(const double* Y, double* X) const;

//! Converts a mixture composition from mass fractions to mole fractions
//! Converts a mixture composition from mass fractions to mole fractions
//! @param[in] X mixture composition in mole fractions (length m_kk)
//! @param[out] Y mixture composition in mass fractions (length m_kk)
void moleFractionsToMassFractions(const double* X, double* Y) const;
Expand Down
173 changes: 97 additions & 76 deletions include/cantera/thermo/ThermoPhase.h

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions interfaces/cython/cantera/_cantera.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ cdef extern from "cantera/base/Solution.h" namespace "Cantera":


cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera":
ctypedef enum ThermoBasisType:
mass "Cantera::ThermoBasisType::mass",
molar "Cantera::ThermoBasisType::molar"
ctypedef enum ThermoBasis:
mass "Cantera::ThermoBasis::mass",
molar "Cantera::ThermoBasis::molar"

cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera":
cdef cppclass CxxThermoPhase "Cantera::ThermoPhase":
Expand Down Expand Up @@ -273,12 +273,12 @@ cdef extern from "cantera/thermo/ThermoPhase.h" namespace "Cantera":
void setState_Psat(double P, double x) except +translate_exception
void setState_TPQ(double T, double P, double Q) except +translate_exception

void setMixtureFraction(double mixFrac, const double* fuelComp, const double* oxComp, ThermoBasisType basis) except +translate_exception
double getMixtureFraction(const double* fuelComp, const double* oxComp, ThermoBasisType basis, string element) except +translate_exception
void setEquivalenceRatio(double phi, const double* fuelComp, const double* oxComp, ThermoBasisType basis) except +translate_exception
double getEquivalenceRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis) except +translate_exception
double getEquivalenceRatio() except +translate_exception
double getStoichAirFuelRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis) except +translate_exception
void setMixtureFraction(double mixFrac, const double* fuelComp, const double* oxComp, ThermoBasis basis) except +translate_exception
double mixtureFraction(const double* fuelComp, const double* oxComp, ThermoBasis basis, string element) except +translate_exception
void setEquivalenceRatio(double phi, const double* fuelComp, const double* oxComp, ThermoBasis basis) except +translate_exception
double equivalenceRatio(const double* fuelComp, const double* oxComp, ThermoBasis basis) except +translate_exception
double equivalenceRatio() except +translate_exception
double stoichAirFuelRatio(const double* fuelComp, const double* oxComp, ThermoBasis basis) except +translate_exception

cdef extern from "cantera/thermo/IdealGasPhase.h":
cdef cppclass CxxIdealGasPhase "Cantera::IdealGasPhase"
Expand Down
37 changes: 17 additions & 20 deletions interfaces/cython/cantera/examples/thermo/equivalenceRatio.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,39 @@

gas.TP = 300, ct.one_atm

# set the mixture composition according to the stoichiometric mixture (equivalence ratio = 1)
# set the mixture composition according to the stoichiometric mixture
# (equivalence ratio = 1)
gas.set_equivalence_ratio(1, fuel, oxidizer)

# this function can be used to compute the equivalence ratio for any mixture
# an optional argument "basis" indicates if fuel and oxidizer compositions are
# This function can be used to compute the equivalence ratio for any mixture.
# An optional argument "basis" indicates if fuel and oxidizer compositions are
# provided in terms of mass or mole fractions. Default is mole fractions.
# If fuel and oxidizer are given in mass fractions, use basis='mass'
# Before Cantera version 2.5, the implementation of get_equivalence_ratio
# was inconsistent. Therefore, use the 'behavior' argument to use the new
# correct implementation. The behavior="old" implementation is now deprecated
phi = gas.get_equivalence_ratio(fuel=fuel, oxidizer=oxidizer, behavior="new")
print("phi", phi)
phi = gas.equivalence_ratio(fuel, oxidizer)
print("phi = {:1.3f}".format(phi))

# The equivalence ratio can also be computed from the elemental composition
# assuming that there is no oxygen in the fuel and no C,H and S elements
# in the oxidizer this means, the composition of fuel and oxidizer can be omitted
phi = gas.get_equivalence_ratio(behavior="new")
# in the oxidizer so that the composition of fuel and oxidizer can be omitted
phi = gas.equivalence_ratio()

# in this example, the result is the same as above
print("phi", phi)
# In this example, the result is the same as above
print("phi = {:1.3f}".format(phi))

# the mixture fraction Z can be computed as follows:
Z = gas.get_mixture_fraction(fuel, oxidizer)
print("Z", Z)
Z = gas.mixture_fraction(fuel, oxidizer)
print("Z = {:1.3f}".format(Z))

# The mixture fraction is kg fuel / (kg fuel + kg oxidizer). Since the fuel in
# this example is pure methane and the oxidizer is air, the mixture fraction
# is the same as the mass fraction of methane in the mixture
print("mass fraction of CH4: ", gas["CH4"].Y)
print("mass fraction of CH4 = {:1.3f}".format(gas["CH4"].Y[0]))

# Mixture fraction and equivalence ratio are invariant to the reaction progress.
# For example, they stay constant if the mixture composition changes to the burnt
# state
gas.equilibrate('HP')
phi_burnt = gas.get_equivalence_ratio(fuel=fuel, oxidizer=oxidizer, behavior="new")
Z_burnt = gas.get_mixture_fraction(fuel, oxidizer)
print("phi(burnt):", phi_burnt)
print("Z(burnt):", Z)

phi_burnt = gas.equivalence_ratio(fuel, oxidizer)
Z_burnt = gas.mixture_fraction(fuel, oxidizer)
print("phi(burnt) = {:1.3f}".format(phi_burnt))
print("Z(burnt) = {:1.3f}".format(Z))
2 changes: 1 addition & 1 deletion interfaces/cython/cantera/onedim.py
Original file line number Diff line number Diff line change
Expand Up @@ -1399,7 +1399,7 @@ def mixture_fraction(self, m):
vals = np.empty(self.flame.n_points)
for i in range(self.flame.n_points):
self.set_gas_state(i)
vals[i] = self.gas.get_mixture_fraction(Yf, Yo, 'mass', m)
vals[i] = self.gas.mixture_fraction(Yf, Yo, 'mass', m)
return vals

class ImpingingJet(FlameBase):
Expand Down
128 changes: 63 additions & 65 deletions interfaces/cython/cantera/test/test_thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,67 +302,89 @@ def test_set_equivalence_ratio_sulfur(self):
gas = ct.Solution(thermo='IdealGas', kinetics='GasKinetics',
species=ct.Species.listFromFile('gri30.xml') + sulfur_species,
reactions=ct.Reaction.listFromFile('gri30.xml'))
gas.set_equivalence_ratio(2.0, 'CH3:0.5, SO:0.25, OH:0.125, N2:0.125', 'O2:0.5, SO2:0.25, CO2:0.125, CH:0.125')
self.assertNear(gas['SO2'].X[0], 31.0/212.0)
self.assertNear(gas['O2'].X[0], 31.0/106.0)
self.assertNear(gas['SO'].X[0], 11.0/106.0)
self.assertNear(gas['CO2'].X[0], 31.0/424.0)
self.assertNear(gas['CH3'].X[0], 11.0/53.0)
self.assertNear(gas['N2'].X[0], 11.0/212.0)
self.assertNear(gas['CH'].X[0], 31.0/424.0)
self.assertNear(gas['OH'].X[0], 11.0/212.0)

def test_get_equivalence_ratio(self):
fuel = 'CH3:0.5, SO:0.25, OH:0.125, N2:0.125'
ox = 'O2:0.5, SO2:0.25, CO2:0.125, CH:0.125'

def test_sulfur_results(gas, fuel, ox, basis):
gas.set_equivalence_ratio(2.0, fuel, ox, basis)
Z = gas.mixture_fraction(fuel, ox, basis)
self.assertNear(gas.stoich_air_fuel_ratio(fuel, ox, basis)/((1.0-Z)/Z), 2.0)
gas.set_mixture_fraction(Z, fuel, ox, basis)
self.assertNear(gas['SO2'].X[0], 31.0/212.0)
self.assertNear(gas['O2'].X[0], 31.0/106.0)
self.assertNear(gas['SO'].X[0], 11.0/106.0)
self.assertNear(gas['CO2'].X[0], 31.0/424.0)
self.assertNear(gas['CH3'].X[0], 11.0/53.0)
self.assertNear(gas['N2'].X[0], 11.0/212.0)
self.assertNear(gas['CH'].X[0], 31.0/424.0)
self.assertNear(gas['OH'].X[0], 11.0/212.0)
self.assertNear(gas.equivalence_ratio(fuel, ox, basis), 2.0)

test_sulfur_results(gas, fuel, ox, 'mole')

gas.TPX = None, None, fuel
fuel = gas.Y
gas.TPX = None, None, ox
ox = gas.Y
test_sulfur_results(gas, fuel, ox, 'mass')

def test_equivalence_ratio(self):
gas = ct.Solution('gri30.xml')
for phi in np.linspace(0.5, 2.0, 5):
gas.set_equivalence_ratio(phi, 'CH4:0.8, CH3OH:0.2', 'O2:1.0, N2:3.76')
self.assertNear(phi, gas.get_equivalence_ratio(fuel='CH4:0.8, CH3OH:0.2', oxidizer='O2:1.0, N2:3.76', behavior='new'))
self.assertNear(phi, gas.equivalence_ratio('CH4:0.8, CH3OH:0.2', 'O2:1.0, N2:3.76'))
# Check sulfur species
sulfur_species = [k for k in ct.Species.listFromFile('nasa_gas.xml') if k.name in ("SO", "SO2")]
gas = ct.Solution(thermo='IdealGas', kinetics='GasKinetics',
species=ct.Species.listFromFile('gri30.xml') + sulfur_species)
for phi in np.linspace(0.5, 2.0, 5):
gas.set_equivalence_ratio(phi, 'CH3:0.5, SO:0.25, OH:0.125, N2:0.125', 'O2:0.5, SO2:0.25, CO2:0.125')
self.assertNear(phi, gas.get_equivalence_ratio(fuel='CH3:0.5, SO:0.25, OH:0.125, N2:0.125', oxidizer='O2:0.5, SO2:0.25, CO2:0.125', behavior='new'))
self.assertNear(phi, gas.equivalence_ratio('CH3:0.5, SO:0.25, OH:0.125, N2:0.125', 'O2:0.5, SO2:0.25, CO2:0.125'))
gas.X = 'CH4:1' # pure fuel
self.assertEqual(gas.get_equivalence_ratio(behavior='new'), np.inf)
self.assertEqual(gas.equivalence_ratio(), np.inf)

def test_get_set_equivalence_ratio_functions(self):

fuel = "CH4:0.2,O2:0.02,N2:0.1,CO:0.05,CO2:0.02"
ox = "O2:0.21,N2:0.79,CO:0.04,CH4:0.01,CO2:0.03"

gas = ct.Solution('gri30.xml')
gas.TPX = 300, 1e5, fuel
Y_Cf = gas.elemental_mass_fraction("C")
Y_Of = gas.elemental_mass_fraction("O")
gas.TPX = 300, 1e5, ox
Y_Co = gas.elemental_mass_fraction("C")
Y_Oo = gas.elemental_mass_fraction("O")

gas = ct.Solution("gri30.yaml")
gas.set_equivalence_ratio(1.3, fuel, ox)
gas.TP = 300, 1e5

# set mixture to burnt state to make sure that equivalence ratio and
# mixture fraction are independent of reaction progress
gas.equilibrate("HP");

phi = gas.get_equivalence_ratio(fuel=fuel, oxidizer=ox, behavior='new')
phi_loc = gas.get_equivalence_ratio(behavior='new')
mf = gas.get_mixture_fraction(fuel, ox)
mf_C = gas.get_mixture_fraction(fuel, ox, element="C")
mf_O = gas.get_mixture_fraction(fuel, ox, element="O")
l = gas.get_stoich_air_fuel_ratio(fuel, ox)

gas.set_mixture_fraction(mf, fuel,ox)
phi2 = gas.get_equivalence_ratio(fuel=fuel, oxidizer=ox, behavior='new')

self.assertNear(phi, 1.3)
self.assertNear(phi2, 1.3)
self.assertNear(phi_loc, 1.1726068608)
self.assertNear(mf, 0.13415725911)
self.assertNear(mf_C, (gas.elemental_mass_fraction("C")-Y_Co)/(Y_Cf-Y_Co))
self.assertNear(mf_O, (gas.elemental_mass_fraction("O")-Y_Oo)/(Y_Of-Y_Oo))
self.assertNear(l, 6.5972850678733)
def test_equil_results(gas, fuel, ox, Y_Cf, Y_Of, Y_Co, Y_Oo, basis):
gas.TP = 300, 1e5
gas.set_equivalence_ratio(1.3, fuel, ox, basis)
T = gas.T

# set mixture to burnt state to make sure that equivalence ratio and
# mixture fraction are independent of reaction progress
gas.equilibrate("HP");

phi = gas.equivalence_ratio(fuel, ox, basis)
phi_loc = gas.equivalence_ratio()
mf = gas.mixture_fraction(fuel, ox, basis)
mf_C = gas.mixture_fraction(fuel, ox, basis, element="C")
mf_O = gas.mixture_fraction(fuel, ox, basis, element="O")
l = gas.stoich_air_fuel_ratio(fuel, ox, basis)

gas.set_mixture_fraction(mf, fuel,ox, basis)
phi2 = gas.equivalence_ratio(fuel, ox, basis)

self.assertNear(phi, 1.3)
self.assertNear(phi2, 1.3)
self.assertNear(phi_loc, 1.1726068608195617)
self.assertNear(mf, 0.13415725911057605)
self.assertNear(mf_C, (gas.elemental_mass_fraction("C")-Y_Co)/(Y_Cf-Y_Co))
self.assertNear(mf_O, (gas.elemental_mass_fraction("O")-Y_Oo)/(Y_Of-Y_Oo))
self.assertNear(l, 8.3901204498353561)
self.assertNear(gas.P, 1e5)
self.assertNear(T, 300.0)

test_equil_results(gas, fuel, ox, Y_Cf, Y_Of, Y_Co, Y_Oo, 'mole')

# do the same for mass-based functions

Expand All @@ -375,31 +397,7 @@ def test_get_set_equivalence_ratio_functions(self):
Y_Co = gas.elemental_mass_fraction("C")
Y_Oo = gas.elemental_mass_fraction("O")

gas.set_equivalence_ratio(1.3, fuel, ox, basis='mass')

gas.equilibrate("HP");

phi = gas.get_equivalence_ratio(fuel=fuel, oxidizer=ox, basis='mass', behavior='new')
phi_loc = gas.get_equivalence_ratio(behavior='new')
mf = gas.get_mixture_fraction(fuel, ox, basis='mass', element="Bilger")
mf_C = gas.get_mixture_fraction(fuel, ox, basis='mass', element="C")
mf_O = gas.get_mixture_fraction(fuel, ox, basis='mass', element="O")
l = gas.get_stoich_air_fuel_ratio(fuel, ox, basis='mass')

gas.set_mixture_fraction(mf, fuel,ox, basis='mass')
phi2 = gas.get_equivalence_ratio(fuel=fuel, oxidizer=ox, basis='mass', behavior='new')

# make sure the pressure was held constant
p = gas.P

self.assertNear(phi, 1.3)
self.assertNear(phi2, 1.3)
self.assertNear(phi_loc, 1.1726068608)
self.assertNear(mf, 0.13415725911)
self.assertNear(mf_C, (gas.elemental_mass_fraction("C")-Y_Co)/(Y_Cf-Y_Co))
self.assertNear(mf_O, (gas.elemental_mass_fraction("O")-Y_Oo)/(Y_Of-Y_Oo))
self.assertNear(l, 6.5972850678733)
self.assertNear(p, 1e5)
test_equil_results(gas, fuel, ox, Y_Cf, Y_Of, Y_Co, Y_Oo, 'mass')

def test_full_report(self):
report = self.phase.report(threshold=0.0)
Expand Down
Loading

0 comments on commit 99de114

Please sign in to comment.