diff --git a/include/cantera/thermo/Phase.h b/include/cantera/thermo/Phase.h index d2d3fdb0148..b125cbe88b6 100644 --- a/include/cantera/thermo/Phase.h +++ b/include/cantera/thermo/Phase.h @@ -877,7 +877,7 @@ 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 @@ -885,12 +885,12 @@ class Phase */ 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; diff --git a/include/cantera/thermo/ThermoPhase.h b/include/cantera/thermo/ThermoPhase.h index 9925f77c04a..e0b0965f3e5 100644 --- a/include/cantera/thermo/ThermoPhase.h +++ b/include/cantera/thermo/ThermoPhase.h @@ -40,17 +40,12 @@ const int cSS_CONVENTION_VPSS = 1; const int cSS_CONVENTION_SLAVE = 2; //@} -/*! - * @name CONSTANTS - Specification of the input mixture composition - */ -//@{ //! Differentiate between mole fractions and mass fractions for input mixture composition -enum class ThermoBasisType : int +enum class ThermoBasis { mass, molar }; - //@} //! Base class for a phase with thermodynamic properties. @@ -1193,24 +1188,24 @@ class ThermoPhase : public Phase /*! * Fuel and oxidizer compositions are given either as * mole fractions or mass fractions (specified by `basis`) - * and do not need to be normalized. Pressure is kept constant. - * Elements C, S, H and O are considered for the oxidation. + * and do not need to be normalized. Pressure and temperature are + * kept constant. Elements C, S, H and O are considered for the oxidation. * * @param mixFrac mixture fraction (between 0 and 1) * @param fuelComp composition of the fuel - * @param oxComp composition of the oxidizer - * @param basis either ThermoPhase::molar or ThermoPhase::mass - * fuel and oxidizer composition are interpreted - * as mole or mass fractions (default: mole) + * @param oxComp composition of the oxidizer + * @param basis either ThermoPhase::molar or ThermoPhase::mass. + * Fuel and oxidizer composition are interpreted + * as mole or mass fractions (default: molar) */ void setMixtureFraction(double mixFrac, const double* fuelComp, const double* oxComp, - ThermoBasisType basis = ThermoBasisType::molar); - //! @copydoc ThermoPhase::setMixtureFraction(double mixFrac, const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) + ThermoBasis basis = ThermoBasis::molar); + //! @copydoc ThermoPhase::setMixtureFraction void setMixtureFraction(double mixFrac, const std::string& fuelComp, const std::string& oxComp, - ThermoBasisType basis = ThermoBasisType::molar); - //! @copydoc ThermoPhase::setMixtureFraction(double mixFrac, const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) + ThermoBasis basis = ThermoBasis::molar); + //! @copydoc ThermoPhase::setMixtureFraction void setMixtureFraction(double mixFrac, const compositionMap& fuelComp, const compositionMap& oxComp, - ThermoBasisType basis = ThermoBasisType::molar); + ThermoBasis basis = ThermoBasis::molar); //@} //! @name Compute Mixture Fraction @@ -1238,24 +1233,24 @@ class ThermoPhase : public Phase * and \f$ M_m \f$ the atomic weight of element \f$ m \f$. * * @param fuelComp composition of the fuel - * @param oxComp composition of the oxidizer - * @param returns mixture fraction - * @param basis either ThermoPhase::mole or ThermoPhase::mass - * fuel and oxidizer composition are interpreted - * as mole or mass fractions (default: mole) + * @param oxComp composition of the oxidizer + * @param basis either ThermoPhase::mole or ThermoPhase::mass. + * Fuel and oxidizer composition are interpreted + * as mole or mass fractions (default: molar) * @param element either "Bilger" to compute the mixture fraction - * in terms of the Bilger mixture fraction, or + * in terms of the Bilger mixture fraction, or * an element name, to compute the mixture fraction * bsaed on a single element (default: "Bilger") - */ - double getMixtureFraction(const double* fuelComp, const double* oxComp, - ThermoBasisType basis = ThermoBasisType::molar, const std::string& element = "Bilger") const; - //! @copydoc ThermoPhase::getMixtureFraction_X(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar, const std::string& element = "Bilger") - double getMixtureFraction(const std::string& fuelComp, const std::string& oxComp, - ThermoBasisType basis = ThermoBasisType::molar, const std::string& element = "Bilger") const; - //! @copydoc ThermoPhase::getMixtureFraction_X(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar, const std::string& element = "Bilger") - double getMixtureFraction(const compositionMap& fuelComp, const compositionMap& oxComp, - ThermoBasisType basis = ThermoBasisType::molar, const std::string& element = "Bilger") const; + * @returns mixture fraction (kg fuel / kg mixture) + */ + double mixtureFraction(const double* fuelComp, const double* oxComp, + ThermoBasis basis = ThermoBasis::molar, const std::string& element = "Bilger") const; + //! @copydoc ThermoPhase::mixtureFraction + double mixtureFraction(const std::string& fuelComp, const std::string& oxComp, + ThermoBasis basis = ThermoBasis::molar, const std::string& element = "Bilger") const; + //! @copydoc ThermoPhase::mixtureFraction + double mixtureFraction(const compositionMap& fuelComp, const compositionMap& oxComp, + ThermoBasis basis = ThermoBasis::molar, const std::string& element = "Bilger") const; //@} //! @name Set Mixture Composition by Equivalence Ratio @@ -1265,22 +1260,21 @@ class ThermoPhase : public Phase /*! * Fuel and oxidizer compositions are given either as * mole fractions or mass fractions (specified by `basis`) - * and do not need to be normalized. Pressure is kept constant. - * Elements C, S, H and O are considered for the oxidation. + * and do not need to be normalized. Pressure and temperature are + * kept constant. Elements C, S, H and O are considered for the oxidation. * * @param phi equivalence ratio * @param fuelComp composition of the fuel - * @param oxComp composition of the oxidizer - * @param basis either ThermoPhase::mole or ThermoPhase::mass - * fuel and oxidizer composition are interpreted - * as mole or mass fractions (default: mole) - * @param returns mixture fraction - */ - void setEquivalenceRatio(double phi, const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar); - //! @copydoc ThermoPhase::setEquivalenceRatio(double phi, const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) - void setEquivalenceRatio(double phi, const std::string& fuelComp, const std::string& oxComp, ThermoBasisType basis = ThermoBasisType::molar); - //! @copydoc ThermoPhase::setEquivalenceRatio(double phi, const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) - void setEquivalenceRatio(double phi, const compositionMap& fuelComp, const compositionMap& oxComp, ThermoBasisType basis = ThermoBasisType::molar); + * @param oxComp composition of the oxidizer + * @param basis either ThermoPhase::mole or ThermoPhase::mass. + * Fuel and oxidizer composition are interpreted + * as mole or mass fractions (default: molar) + */ + void setEquivalenceRatio(double phi, const double* fuelComp, const double* oxComp, ThermoBasis basis = ThermoBasis::molar); + //! @copydoc ThermoPhase::setEquivalenceRatio + void setEquivalenceRatio(double phi, const std::string& fuelComp, const std::string& oxComp, ThermoBasis basis = ThermoBasis::molar); + //! @copydoc ThermoPhase::setEquivalenceRatio + void setEquivalenceRatio(double phi, const compositionMap& fuelComp, const compositionMap& oxComp, ThermoBasis basis = ThermoBasis::molar); //@} //! @name Compute Equivalence Ratio @@ -1292,68 +1286,80 @@ class ThermoPhase : public Phase * The equivalence ratio \f$ \phi \f$ is computed from * \f[ \phi = \frac{Z}{1-Z}\frac{1-Z_{\mathrm{st}}}{Z_{\mathrm{st}}} \f] * where \f$ Z \f$ is the Bilger mixture fraction of the mixture - * given the specified fuel and oxidizer compositions and + * given the specified fuel and oxidizer compositions * \f$ Z_{\mathrm{st}} \f$ is the mixture fraction at stoichiometric * conditions. Fuel and oxidizer compositions are given either as * mole fractions or mass fractions (specified by `basis`) * and do not need to be normalized. * Elements C, S, H and O are considered for the oxidation. + * If fuel and oxidizer composition are unknown or not specified, + * use the version that takes no arguments. * * @param fuelComp composition of the fuel - * @param oxComp composition of the oxidizer - * @param basis either ThermoPhase::mole or ThermoPhase::mass - * fuel and oxidizer composition are interpreted - * as mole or mass fractions (default: mole) - * @param returns equivalence ratio - */ - double getEquivalenceRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) const; - //! @copydoc ThermoPhase::getEquivalenceRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) - double getEquivalenceRatio(const std::string& fuelComp, const std::string& oxComp, ThermoBasisType basis = ThermoBasisType::molar) const; - //! @copydoc ThermoPhase::getEquivalenceRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) - double getEquivalenceRatio(const compositionMap& fuelComp, const compositionMap& oxComp, ThermoBasisType basis = ThermoBasisType::molar) const; + * @param oxComp composition of the oxidizer + * @param basis either ThermoPhase::mole or ThermoPhase::mass. + * Fuel and oxidizer composition are interpreted + * as mole or mass fractions (default: molar) + * @returns equivalence ratio + * @see mixtureFraction for the definition of the Bilger mixture fraction + * @see equivalenceRatio() for the computation of \f$ \phi \f$ without arguments + */ + double equivalenceRatio(const double* fuelComp, const double* oxComp, ThermoBasis basis = ThermoBasis::molar) const; + //! @copydoc ThermoPhase::equivalenceRatio + double equivalenceRatio(const std::string& fuelComp, const std::string& oxComp, ThermoBasis basis = ThermoBasis::molar) const; + //! @copydoc ThermoPhase::equivalenceRatio + double equivalenceRatio(const compositionMap& fuelComp, const compositionMap& oxComp, ThermoBasis basis = ThermoBasis::molar) const; + //@} //! Compute the equivalence ratio for the current mixture //! from available oxygen and required oxygen /*! - * Computes the equivalence ratio \f$ \phi \f$ from + * Computes the equivalence ratio \f$ \phi \f$ from * \f[ \phi = \frac{Z_{\mathrm{mole},C} + Z_{\mathrm{mole},S} + \frac{1}{4}Z_{\mathrm{mole},H}} * {\frac{1}{2}Z_{\mathrm{mole},O}} \f] * where \f$ Z_{\mathrm{mole},m} \f$ is the elemental mole fraction * of element \f$ m \f$. In this special case, the equivalence ratio - * is independent of a fuel or oxidizer composition because it only + * is independent of a fuel or oxidizer composition because it only * considers the locally available oxygen compared to the required oxygen * for complete oxidation. It is the same as assuming that the oxidizer - * only contains O(and inert elements) and the fuel contains only - * H, C and S (and inert elements) + * only contains O (and inert elements) and the fuel contains only + * H, C and S (and inert elements). If either of these conditions is + * not met, use the version of this functions which takes the fuel and + * oxidizer compositions as input * - * @param returns equivalence ratio + * @returns equivalence ratio + * @see equivalenceRatio compute the equivalence ratio from specific + * fuel and oxidizer compositions */ - double getEquivalenceRatio() const; - //@} + double equivalenceRatio() const; //! @name Compute Stoichiometric Air to Fuel Ratio //! @{ - //! Compute the stoichiometric air to fuel ratio (kmol oxidizer to kmol fuel) + //! Compute the stoichiometric air to fuel ratio (kg oxidizer / kg fuel) //! given fuel and oxidizer compositions. /*! * Fuel and oxidizer compositions are given either as * mole fractions or mass fractions (specified by `basis`) * and do not need to be normalized. * Elements C, S, H and O are considered for the oxidation. + * Note that the stoichiometric air to fuel ratio \f$ \mathit{AFR}_{\mathrm{st}} \f$ + * does not depend on the current mixture composition. The current + * air to fuel ratio can be computed from \f$ \mathit{AFR} = \mathit{AFR}_{\mathrm{st}}/\phi \f$ + * where \f$ \phi \f$ is the equivalence ratio of the current mixture * * @param fuelComp composition of the fuel - * @param oxComp composition of the oxidizer - * @param basis either ThermoPhase::mole or ThermoPhase::mass - * fuel and oxidizer composition are interpreted - * as mole or mass fractions (default: mole) - * @param returns Stoichiometric Air to Fuel Ratio (kmol oxidizer / kmol fuel) - */ - double getStoichAirFuelRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) const; - //! @copydoc ThermoPhase::getStoichAirFuelRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) - double getStoichAirFuelRatio(const std::string& fuelComp, const std::string& oxComp, ThermoBasisType basis = ThermoBasisType::molar) const; - //! @copydoc ThermoPhase::getStoichAirFuelRatio(const double* fuelComp, const double* oxComp, ThermoBasisType basis = ThermoBasisType::molar) - double getStoichAirFuelRatio(const compositionMap& fuelComp, const compositionMap& oxComp, ThermoBasisType basis = ThermoBasisType::molar) const; + * @param oxComp composition of the oxidizer + * @param basis either ThermoPhase::mole or ThermoPhase::mass. + * Fuel and oxidizer composition are interpreted + * as mole or mass fractions (default: molar) + * @returns Stoichiometric Air to Fuel Ratio (kg oxidizer / kg fuel) + */ + double stoichAirFuelRatio(const double* fuelComp, const double* oxComp, ThermoBasis basis = ThermoBasis::molar) const; + //! @copydoc ThermoPhase::stoichAirFuelRatio + double stoichAirFuelRatio(const std::string& fuelComp, const std::string& oxComp, ThermoBasis basis = ThermoBasis::molar) const; + //! @copydoc ThermoPhase::stoichAirFuelRatio + double stoichAirFuelRatio(const compositionMap& fuelComp, const compositionMap& oxComp, ThermoBasis basis = ThermoBasis::molar) const; //@} private: @@ -1385,6 +1391,21 @@ class ThermoPhase : public Phase //! Sets the temperature and (if set_p is true) the pressure. void setState_conditional_TP(doublereal t, doublereal p, bool set_p); + //! Helper function for computing the amount of oxygen required for complete oxidation. + /*! + * @param y array of (possibly non-normalized) mass fractions (length m_kk) + * @returns amount of required oxygen in kmol O / kg mixture + */ + double o2Required(const double* y) const; + + //! Helper function for computing the amount of oxygen + //! available in the current mixture. + /*! + * @param y array of (possibly non-normalized) mass fractions (length m_kk) + * @returns amount of O in kmol O / kg mixture + */ + double o2Present(const double* y) const; + public: /** * @name Chemical Equilibrium diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index b796296f5cb..5cf58cda2fb 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -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": @@ -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" diff --git a/interfaces/cython/cantera/examples/thermo/equivalenceRatio.py b/interfaces/cython/cantera/examples/thermo/equivalenceRatio.py index 7fb98b850f2..08282fe91b8 100644 --- a/interfaces/cython/cantera/examples/thermo/equivalenceRatio.py +++ b/interfaces/cython/cantera/examples/thermo/equivalenceRatio.py @@ -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)) diff --git a/interfaces/cython/cantera/onedim.py b/interfaces/cython/cantera/onedim.py index caa109fcf7e..a5e2c2e3345 100644 --- a/interfaces/cython/cantera/onedim.py +++ b/interfaces/cython/cantera/onedim.py @@ -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): diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index 444cae2b8f0..7e67c84ad7f 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -302,35 +302,52 @@ 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") @@ -338,31 +355,36 @@ def test_get_set_equivalence_ratio_functions(self): 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 @@ -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) diff --git a/interfaces/cython/cantera/thermo.pyx b/interfaces/cython/cantera/thermo.pyx index 525286518ea..414f7f7fa5e 100644 --- a/interfaces/cython/cantera/thermo.pyx +++ b/interfaces/cython/cantera/thermo.pyx @@ -5,7 +5,7 @@ import warnings import weakref import numbers as _numbers -cdef enum ThermoBasis: +cdef enum ThermoBasisType: mass_basis = 0 molar_basis = 1 @@ -739,6 +739,27 @@ cdef class ThermoPhase(_SolutionBase): def __set__(self, C): self._setArray1(thermo_setConcentrations, C) + def __composition_to_array(self, comp, basis): + """take a mixture composition in mole or mass fraction as string, + dict or array and return array (for internal use)""" + if (isinstance(comp, str) and ':' not in comp + and comp in self.species_names): + comp += ':1.0' + + original_state = self.state + + if basis == 'mole': + self.TPX = None, None, comp + arr = np.copy(self.X) + elif basis == 'mass': + self.TPY = None, None, comp + arr = np.copy(self.Y) + else: + raise ValueError("basis must either be 'mass' or mole'.") + + self.state = original_state + return arr + def set_equivalence_ratio(self, phi, fuel, oxidizer, basis='mole'): """ Set the composition to a mixture of *fuel* and *oxidizer* at the @@ -749,11 +770,11 @@ cdef class ThermoPhase(_SolutionBase): basis='mole' means mole fractions (default), basis='mass' means mass fractions:: >>> gas.set_equivalence_ratio(0.5, 'CH4', 'O2:1.0, N2:3.76', basis='mole') - >>> gas.mole_fraction_dict() - {'CH4': 0.028376330528516805, 'N2': 0.7452356312613028, 'O2': 0.22638803821018033} + >>> gas.mass_fraction_dict() + {'CH4': 0.02837633052851681, 'N2': 0.7452356312613029, 'O2': 0.22638803821018036} >>> gas.set_equivalence_ratio(1.2, {'NH3':0.8, 'CO':0.2}, 'O2:1.0', basis='mole') - >>> gas.mole_fraction_dict() - {'CO': 0.14784006249290751, 'NH3': 0.3595664554540104, 'O2': 0.49259348205308207} + >>> gas.mass_fraction_dict() + {'CO': 0.14784006249290754, 'NH3': 0.35956645545401045, 'O2': 0.49259348205308207} :param phi: Equivalence ratio :param fuel: @@ -764,37 +785,12 @@ cdef class ThermoPhase(_SolutionBase): :param basis: determines if *fuel* and *oxidizer* are given in mole fractions (basis='mole') or mass fractions (basis='mass') """ - 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' - - original_state = self.state - - if basis == 'mole': - self.TPX = None, None, fuel - fu = self.X - self.TPX = None, None, oxidizer - ox = self.X - elif basis == 'mass': - self.TPY = None, None, fuel - fu = self.Y - self.TPY = None, None, oxidizer - ox = self.Y - else: - raise ValueError("basis must either be 'mass' or mole'.") - cdef np.ndarray[np.double_t, ndim=1] f = \ - np.ascontiguousarray(fu, dtype=np.double) + np.ascontiguousarray(self.__composition_to_array(fuel, basis), dtype=np.double) cdef np.ndarray[np.double_t, ndim=1] o = \ - np.ascontiguousarray(ox, dtype=np.double) + np.ascontiguousarray(self.__composition_to_array(oxidizer, basis), dtype=np.double) - self.state = original_state - - self.thermo.setEquivalenceRatio(phi, &f[0], &o[0], ThermoBasisType.mass if basis=='mass' else ThermoBasisType.molar) + self.thermo.setEquivalenceRatio(phi, &f[0], &o[0], ThermoBasis.mass if basis=='mass' else ThermoBasis.molar) def set_mixture_fraction(self, mixFrac, fuel, oxidizer, basis='mole'): """ @@ -802,7 +798,7 @@ cdef class ThermoPhase(_SolutionBase): specified mixture fraction *mixFrac* (kg fuel / kg mixture), holding temperature and pressure constant. Considers the oxidation of C to CO2, H to H2O and S to SO2. Other elements are assumed not to participate in - oxidation (i.e. N ends up as N2). The *basis* determines the composition + oxidation (i.e. N ends up as N2). The *basis* determines the composition of fuel and oxidizer: basis='mole' (default) means mole fractions, basis='mass' means mass fractions:: @@ -811,7 +807,7 @@ cdef class ThermoPhase(_SolutionBase): {'CH4': 0.5, 'N2': 0.38350014242997776, 'O2': 0.11649985757002226} >>> gas.set_mixture_fraction(0.5, {'NH3':0.8, 'CO':0.2}, 'O2:1.0') >>> gas.mass_fraction_dict() - {'CO': 0.14568206877899598, 'NH3': 0.354317931221004, 'O2': 0.5} + {'CO': 0.145682068778996, 'NH3': 0.354317931221004, 'O2': 0.5} :param mixFrac: Mixture fraction (kg fuel / kg mixture) :param fuel: @@ -821,151 +817,108 @@ cdef class ThermoPhase(_SolutionBase): dict. :param basis: determines if *fuel* and *oxidizer* are given in mole fractions (basis='mole') or mass fractions (basis='mass') - """ + cdef np.ndarray[np.double_t, ndim=1] f = \ + np.ascontiguousarray(self.__composition_to_array(fuel, basis), dtype=np.double) + cdef np.ndarray[np.double_t, ndim=1] o = \ + np.ascontiguousarray(self.__composition_to_array(oxidizer, basis), dtype=np.double) - if (isinstance(fuel, str) and ':' not in fuel - and fuel in self.species_names): - fuel += ':1.0' + self.thermo.setMixtureFraction(mixFrac, &f[0], &o[0], ThermoBasis.mass if basis=='mass' else ThermoBasis.molar) - if (isinstance(oxidizer, str) and ':' not in oxidizer - and oxidizer in self.species_names): - oxidizer += ':1.0' + def get_equivalence_ratio(self, oxidizers=[], ignore=[]): + """ + Get the composition of a fuel/oxidizer mixture. This gives the + equivalence ratio of an unburned mixture. This is not a quantity that is + conserved after oxidation. Considers the oxidation of C to CO2, H to H2O + and S to SO2. Other elements are assumed not to participate in oxidation + (i.e. N ends up as N2). - original_state = self.state + :param oxidizers: + List of oxidizer species names as strings. Default: with + ``oxidizers=[]``, every species that contains O but does not contain + H, C, or S is considered to be an oxidizer. + :param ignore: + List of species names as strings to ignore. - if basis == 'mole': - self.TPX = None, None, fuel - fu = self.X - self.TPX = None, None, oxidizer - ox = self.X - elif basis == 'mass': - self.TPY = None, None, fuel - fu = self.Y - self.TPY = None, None, oxidizer - ox = self.Y - else: - raise ValueError("basis must either be 'mass' or mole'.") + >>> gas.set_equivalence_ratio(0.5, 'CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') + >>> gas.get_equivalence_ratio() + 0.5 + >>> gas.get_equivalence_ratio(['O2']) # Only consider O2 as the oxidizer instead of O2 and NO + 0.488095238095 + >>> gas.X = 'CH4:1, O2:2, NO:0.1' + >>> gas.get_equivalence_ratio(ignore=['NO']) + 1.0 - cdef np.ndarray[np.double_t, ndim=1] f = \ - np.ascontiguousarray(fu, dtype=np.double) - cdef np.ndarray[np.double_t, ndim=1] o = \ - np.ascontiguousarray(ox, dtype=np.double) + .. deprecated:: 2.5 - self.state = original_state + To be deprecated with version 2.5, and removed thereafter. + Replaced by function `equivalence_ratio`. + """ + warnings.warn("To be removed after Cantera 2.5. " + "Replaced by function 'equivalence_ratio'.", DeprecationWarning) + + if not oxidizers: + # Default behavior, find all possible oxidizers + oxidizers = [] + for s in self.species(): + if all(y not in s.composition for y in ['C', 'H', 'S']): + oxidizers.append(s.name) + + alpha = 0 + mol_O = 0 + for k, s in enumerate(self.species()): + if s.name in ignore: + continue + elif s.name in oxidizers: + mol_O += s.composition.get('O', 0) * self.X[k] + else: + nC = s.composition.get('C', 0) + nH = s.composition.get('H', 0) + nO = s.composition.get('O', 0) + nS = s.composition.get('S', 0) - self.thermo.setMixtureFraction(mixFrac, &f[0], &o[0], ThermoBasisType.mass if basis=='mass' else ThermoBasisType.molar) + alpha += (2 * nC + nH / 2 + 2 * nS - nO) * self.X[k] + + if mol_O == 0: + return float('inf') + else: + return alpha / mol_O - def get_equivalence_ratio(self, oxidizers=[], ignore=[], fuel=None, oxidizer=None, basis='mole', behavior='old'): + def equivalence_ratio(self, fuel=None, oxidizer=None, basis='mole'): """ - Get the equivalence ratio of the current mixture. Currently, there are - two different modes: - behavior='old': This is the default mode for backward compatibility - but will be removed after Cantera 2.5 (currently deprecated). - In this mode, the equivalence ratio is not a conserved quantity. - behavior='new': Consistent definition of the equivalence ratio, which - is a conserved quantity. Considers the oxidation of C to CO2, H to H2O + Get the equivalence ratio of the current mixture, which is a + conserved quantity. Considers the oxidation of C to CO2, H to H2O and S to SO2. Other elements are assumed not to participate in oxidation (i.e. N ends up as N2). If fuel and oxidizer are not specified, the equivalence ratio is computed from the available oxygen and the required oxygen for complete oxidation. The *basis* determines the composition of fuel and oxidizer: basis='mole' (default) means mole fractions, basis='mass' means mass fractions:: - :param oxidizers: - Only for behavior='old'. List of oxidizer species names as strings. - Default: with ``oxidizers=[]``, every species that contains O but - does not contain H, C, or S is considered to be an oxidizer. - :param ignore: - Only for behavor='old'. List of species names as strings to ignore. - :param fuel: - Only for behavior='new'. Fuel species name or mole/,mass fractions - as string, array, or dict + :param fuel: + Fuel species name or mole/,mass fractions as string, array, or dict :param oxidizer: - Only for behavior='new'. Oxidizer species name or mole/mass fractions - as a string, array, or dict. + Oxidizer species name or mole/mass fractions as a string, array, or dict. :param basis: - Only for behavior='new'. Determines if *fuel* and *oxidizer* are - given in mole fractions (basis='mole') or mass fractions (basis='mass') + Determines if *fuel* and *oxidizer* are given in mole fractions (basis='mole') + or mass fractions (basis='mass') >>> gas.set_equivalence_ratio(0.5, 'CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') - >>> gas.get_equivalence_ratio('CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01', behavior='new') + >>> gas.equivalence_ratio('CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') 0.5 """ - - if behavior == 'old': - - warnings.warn("After Cantera 2.5, behavior='old' will be removed." - "Use behavior='new' for a consistent definition of" - "the equivalence ratio", DeprecationWarning) - - if not oxidizers: - # Default behavior, find all possible oxidizers - oxidizers = [] - for s in self.species(): - if all(y not in s.composition for y in ['C', 'H', 'S']): - oxidizers.append(s.name) - - alpha = 0 - mol_O = 0 - for k, s in enumerate(self.species()): - if s.name in ignore: - continue - elif s.name in oxidizers: - mol_O += s.composition.get('O', 0) * self.X[k] - else: - nC = s.composition.get('C', 0) - nH = s.composition.get('H', 0) - nO = s.composition.get('O', 0) - nS = s.composition.get('S', 0) - - alpha += (2 * nC + nH / 2 + 2 * nS - nO) * self.X[k] - - if mol_O == 0: - return float('inf') - else: - return alpha / mol_O - - elif behavior != 'new': - raise ValueError("'behavior' must either be 'old' or 'new'.") - if fuel is None and oxidizer is None: - return self.thermo.getEquivalenceRatio() - - 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' - - original_state = self.state - - if basis == 'mole': - self.TPX = None, None, fuel - fu = self.X - self.TPX = None, None, oxidizer - ox = self.X - elif basis == 'mass': - self.TPY = None, None, fuel - fu = self.Y - self.TPY = None, None, oxidizer - ox = self.Y - else: - raise ValueError("'basis' must either be 'mass' or mole'.") + return self.thermo.equivalenceRatio() cdef np.ndarray[np.double_t, ndim=1] f = \ - np.ascontiguousarray(fu, dtype=np.double) + np.ascontiguousarray(self.__composition_to_array(fuel, basis), dtype=np.double) cdef np.ndarray[np.double_t, ndim=1] o = \ - np.ascontiguousarray(ox, dtype=np.double) - - self.state = original_state + np.ascontiguousarray(self.__composition_to_array(oxidizer, basis), dtype=np.double) - return self.thermo.getEquivalenceRatio(&f[0], &o[0], ThermoBasisType.mass if basis=='mass' else ThermoBasisType.molar) + return self.thermo.equivalenceRatio(&f[0], &o[0], ThermoBasis.mass if basis=='mass' else ThermoBasis.molar) - def get_mixture_fraction(self, fuel, oxidizer, basis='mole', element="Bilger"): + def mixture_fraction(self, fuel, oxidizer, basis='mole', element="Bilger"): """ Get the mixture fraction of the current mixture (kg fuel / (kg oxidizer + kg fuel)) This is a quantity that is conserved after oxidation. Considers the @@ -989,52 +942,26 @@ cdef class ThermoPhase(_SolutionBase): the Bilger mixture fraction (default) >>> gas.set_mixture_fraction(0.5, 'CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') - >>> gas.get_mixture_fraction('CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') + >>> gas.mixture_fraction('CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') 0.5 """ - - 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' - - original_state = self.state - - if basis == 'mole': - self.TPX = None, None, fuel - fu = self.X - self.TPX = None, None, oxidizer - ox = self.X - elif basis == 'mass': - self.TPY = None, None, fuel - fu = self.Y - self.TPY = None, None, oxidizer - ox = self.Y - else: - raise ValueError("basis must either be 'mass' or mole'.") - cdef np.ndarray[np.double_t, ndim=1] f = \ - np.ascontiguousarray(fu, dtype=np.double) + np.ascontiguousarray(self.__composition_to_array(fuel, basis), dtype=np.double) cdef np.ndarray[np.double_t, ndim=1] o = \ - np.ascontiguousarray(ox, dtype=np.double) - - self.state = original_state + np.ascontiguousarray(self.__composition_to_array(oxidizer, basis), dtype=np.double) if isinstance(element, (str, bytes)): e_name = element else: e_name = self.element_name(self.element_index(element)) - return self.thermo.getMixtureFraction(&f[0], &o[0], ThermoBasisType.mass if basis=='mass' else ThermoBasisType.molar, stringify(e_name)) + return self.thermo.mixtureFraction(&f[0], &o[0], ThermoBasis.mass if basis=='mass' else ThermoBasis.molar, stringify(e_name)) - def get_stoich_air_fuel_ratio(self, fuel, oxidizer, basis='mole'): + def stoich_air_fuel_ratio(self, fuel, oxidizer, basis='mole'): """ - Get the stoichiometric air to fuel ratio (kmol air / kmol fuel). Considers the + Get the stoichiometric air to fuel ratio (kg oxidizer / kg fuel). Considers the oxidation of C to CO2, H to H2O and S to SO2. Other elements are assumed - not to participate in oxidation (i.e. N ends up as N2). + not to participate in oxidation (i.e. N ends up as N2). The *basis* determines the composition of fuel and oxidizer: basis='mole' (default) means mole fractions, basis='mass' means mass fractions:: @@ -1047,42 +974,15 @@ cdef class ThermoPhase(_SolutionBase): fractions (basis='mole') or mass fractions (basis='mass') >>> gas.set_mixture_fraction(0.5, 'CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') - >>> gas.get_stoich_air_fuel_ratio('CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') - 6.785529715762273 + >>> gas.stoich_air_fuel_ratio('CH3:0.5, CH3OH:.5, N2:0.125', 'O2:0.21, N2:0.79, NO:0.01') + 8.148040722239438 """ - - 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' - - original_state = self.state - - if basis == 'mole': - self.TPX = None, None, fuel - fu = self.X - self.TPX = None, None, oxidizer - ox = self.X - elif basis == 'mass': - self.TPY = None, None, fuel - fu = self.Y - self.TPY = None, None, oxidizer - ox = self.Y - else: - raise ValueError("basis must either be 'mass' or mole'.") - cdef np.ndarray[np.double_t, ndim=1] f = \ - np.ascontiguousarray(fu, dtype=np.double) + np.ascontiguousarray(self.__composition_to_array(fuel, basis), dtype=np.double) cdef np.ndarray[np.double_t, ndim=1] o = \ - np.ascontiguousarray(ox, dtype=np.double) - - self.state = original_state - - return self.thermo.getStoichAirFuelRatio(&f[0], &o[0], ThermoBasisType.mass if basis=='mass' else ThermoBasisType.molar) + np.ascontiguousarray(self.__composition_to_array(oxidizer, basis), dtype=np.double) + return self.thermo.stoichAirFuelRatio(&f[0], &o[0], ThermoBasis.mass if basis=='mass' else ThermoBasis.molar) def elemental_mass_fraction(self, m): r""" diff --git a/src/thermo/ThermoPhase.cpp b/src/thermo/ThermoPhase.cpp index 52cbd6911ba..294cd0dc297 100644 --- a/src/thermo/ThermoPhase.cpp +++ b/src/thermo/ThermoPhase.cpp @@ -689,135 +689,138 @@ void ThermoPhase::setState_SPorSV(double Starget, double p, throw CanteraError("ThermoPhase::setState_SPorSV (SP)", ErrString); } } -double ThermoPhase::getStoichAirFuelRatio(const compositionMap& fuelComp, + +double ThermoPhase::o2Required(const double* y) const +{ + // indices of fuel elements + size_t iC = elementIndex("C"); + size_t iS = elementIndex("S"); + size_t iH = elementIndex("H"); + + double o2req = 0.0; + double sum = 0.0; + for (size_t k = 0; k != m_kk; ++k) { + sum += y[k]; + double x = y[k] / molecularWeights()[k]; + if (iC != npos) { + o2req += x * nAtoms(k, iC); + } + if (iS != npos) { + o2req += x * nAtoms(k, iS); + } + if (iH != npos) { + o2req += x * 0.25 * nAtoms(k, iH); + } + } + if (sum == 0.0) { + throw CanteraError("ThermoPhase::o2Required", + "No composition specified"); + } + return o2req/sum; +} + +double ThermoPhase::o2Present(const double* y) const +{ + size_t iO = elementIndex("O"); + double o2pres = 0.0; + double sum = 0.0; + for (size_t k = 0; k != m_kk; ++k) { + sum += y[k]; + o2pres += y[k] / molecularWeights()[k] * nAtoms(k, iO); + } + if (sum == 0.0) { + throw CanteraError("ThermoPhase::o2Present", + "No composition specified"); + } + return 0.5 * o2pres / sum; +} + +double ThermoPhase::stoichAirFuelRatio(const compositionMap& fuelComp, const compositionMap& oxComp, - ThermoBasisType basis) const + ThermoBasis basis) const { vector_fp fuel(getCompositionFromMap(fuelComp)); vector_fp ox(getCompositionFromMap(oxComp)); - return getStoichAirFuelRatio(fuel.data(), ox.data(), basis); + return stoichAirFuelRatio(fuel.data(), ox.data(), basis); } -double ThermoPhase::getStoichAirFuelRatio(const std::string& fuelComp, +double ThermoPhase::stoichAirFuelRatio(const std::string& fuelComp, const std::string& oxComp, - ThermoBasisType basis) const + ThermoBasis basis) const { - return getStoichAirFuelRatio( + return stoichAirFuelRatio( parseCompString(fuelComp.find(":") != std::string::npos ? fuelComp : fuelComp+":1.0"), parseCompString(oxComp.find(":") != std::string::npos ? oxComp : oxComp+":1.0"), basis); } -double ThermoPhase::getStoichAirFuelRatio(const double* fuelComp, +double ThermoPhase::stoichAirFuelRatio(const double* fuelComp, const double* oxComp, - ThermoBasisType basis) const + ThermoBasis basis) const { vector_fp fuel, ox; - if (basis == ThermoBasisType::mass) { // convert input compositions to mole fractions + if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions fuel.resize(m_kk); ox.resize(m_kk); - massFractionsToMoleFractions(fuelComp, fuel.data()); - massFractionsToMoleFractions(oxComp, ox.data()); + moleFractionsToMassFractions(fuelComp, fuel.data()); + moleFractionsToMassFractions(oxComp, ox.data()); fuelComp = fuel.data(); oxComp = ox.data(); } - // indices of elements - size_t iC = elementIndex("C"); - size_t iS = elementIndex("S"); - size_t iO = elementIndex("O"); - size_t iH = elementIndex("H"); - - // non-normalized element mole fractions - double Co = 0.0, Cf = 0.0; - double So = 0.0, Sf = 0.0; - double Oo = 0.0, Of = 0.0; - double Ho = 0.0, Hf = 0.0; - double sum_Xf = 0.0, sum_Xo = 0.0; - - for (size_t k = 0; k != m_kk; ++k) { - auto Xf = fuelComp[k]; - auto Xo = oxComp[k]; - sum_Xf += Xf; - sum_Xo += Xo; - - if (iC != npos) { - double nC = nAtoms(k, iC); - Co += nC*Xo; - Cf += nC*Xf; - } - - if (iS != npos) { - double nS = nAtoms(k, iS); - So += nS*Xo; - Sf += nS*Xf; - } - - if (iH != npos) { - double nH = nAtoms(k, iH); - Ho += nH*Xo; - Hf += nH*Xf; - } - - if (iO != npos) { - double nO = nAtoms(k, iO); - Oo += nO*Xo; - Of += nO*Xf; - } - } + double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp); + double o2_required_ox = o2Required(oxComp) - o2Present(oxComp); - if (sum_Xf == 0.0 || sum_Xo == 0.0) { - throw CanteraError("ThermoPhase::getStoichAirFuelRatio", - "no fuel and/or oxidizer composition specified"); + if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) { + throw CanteraError("ThermoPhase::stoichAirFuelRatio", + "Fuel composition contains too much oxygen or " + "oxidizer contains not enough oxygen. " + "Fuel and oxidizer composition mixed up?"); } - double denominator = Oo - 2.0*Co - 2.0*So - Ho/2.0; - - if (denominator == 0.0) { + if (o2_required_ox == 0.0) { return std::numeric_limits::infinity(); } - double l = - (Of - 2.0*Cf - 2.0*Sf - Hf/2.0)*sum_Xo / (sum_Xf * denominator); - - return std::max(l, 0.0); + return o2_required_fuel / (-o2_required_ox); } void ThermoPhase::setEquivalenceRatio(double phi, const double* fuelComp, - const double* oxComp, ThermoBasisType basis) + const double* oxComp, ThermoBasis basis) { if (phi < 0.0) { throw CanteraError("ThermoPhase::setEquivalenceRatio", - "equivalence ratio phi must be >= 0"); + "Equivalence ratio phi must be >= 0"); } double p = pressure(); vector_fp fuel, ox; - if (basis == ThermoBasisType::mass) { // convert input compositions to mole fractions + if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions fuel.resize(m_kk); ox.resize(m_kk); - massFractionsToMoleFractions(fuelComp, fuel.data()); - massFractionsToMoleFractions(oxComp, ox.data()); + moleFractionsToMassFractions(fuelComp, fuel.data()); + moleFractionsToMassFractions(oxComp, ox.data()); fuelComp = fuel.data(); oxComp = ox.data(); } - double stoichAirFuelRatio = getStoichAirFuelRatio(fuelComp, oxComp, ThermoBasisType::molar); + double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass); double sum_f = std::accumulate(fuelComp, fuelComp+m_kk, 0.0); double sum_o = std::accumulate(oxComp, oxComp+m_kk, 0.0); - vector_fp X(m_kk); + vector_fp y(m_kk); for (size_t k = 0; k != m_kk; ++k) { - X[k] = phi * fuelComp[k]/sum_f + stoichAirFuelRatio * oxComp[k]/sum_o; + y[k] = phi * fuelComp[k]/sum_f + AFR_st * oxComp[k]/sum_o; } - setMoleFractions(X.data()); + setMassFractions(y.data()); setPressure(p); } void ThermoPhase::setEquivalenceRatio(double phi, const std::string& fuelComp, - const std::string& oxComp, ThermoBasisType basis) + const std::string& oxComp, ThermoBasis basis) { setEquivalenceRatio(phi, parseCompString(fuelComp.find(":") != std::string::npos ? fuelComp : fuelComp+":1.0"), @@ -826,40 +829,17 @@ void ThermoPhase::setEquivalenceRatio(double phi, const std::string& fuelComp, } void ThermoPhase::setEquivalenceRatio(double phi, const compositionMap& fuelComp, - const compositionMap& oxComp, ThermoBasisType basis) + const compositionMap& oxComp, ThermoBasis basis) { vector_fp fuel = getCompositionFromMap(fuelComp); vector_fp ox = getCompositionFromMap(oxComp); setEquivalenceRatio(phi, fuel.data(), ox.data(), basis); } -double ThermoPhase::getEquivalenceRatio() const +double ThermoPhase::equivalenceRatio() const { - // indices of elements - size_t iC = elementIndex("C"); - size_t iS = elementIndex("S"); - size_t iO = elementIndex("O"); - size_t iH = elementIndex("H"); - - double C = 0.0, S = 0.0, O = 0.0, H = 0.0; - for (size_t k = 0; k != m_kk; ++k) { - double X = moleFraction(k); - if (iC != npos) { - C += nAtoms(k, iC) * X; - } - if (iS != npos) { - S += nAtoms(k, iS) * X; - } - if (iO != npos) { - O += nAtoms(k, iO) * X; - } - if (iC != npos) { - H += nAtoms(k, iH) * X; - } - } - - double o2_required = H*0.25 + S + C; - double o2_present = 0.5*O; + double o2_required = o2Required(massFractions()); + double o2_present = o2Present(massFractions()); if (o2_present == 0.0) { // pure fuel return std::numeric_limits::infinity(); @@ -868,30 +848,30 @@ double ThermoPhase::getEquivalenceRatio() const return o2_required / o2_present; } -double ThermoPhase::getEquivalenceRatio(const compositionMap& fuelComp, +double ThermoPhase::equivalenceRatio(const compositionMap& fuelComp, const compositionMap& oxComp, - ThermoBasisType basis) const + ThermoBasis basis) const { vector_fp fuel(getCompositionFromMap(fuelComp)); vector_fp ox(getCompositionFromMap(oxComp)); - return getEquivalenceRatio(fuel.data(), ox.data(), basis); + return equivalenceRatio(fuel.data(), ox.data(), basis); } -double ThermoPhase::getEquivalenceRatio(const std::string& fuelComp, +double ThermoPhase::equivalenceRatio(const std::string& fuelComp, const std::string& oxComp, - ThermoBasisType basis) const + ThermoBasis basis) const { - return getEquivalenceRatio( + return equivalenceRatio( parseCompString(fuelComp.find(":") != std::string::npos ? fuelComp : fuelComp+":1.0"), parseCompString(oxComp.find(":") != std::string::npos ? oxComp : oxComp+":1.0"), basis); } -double ThermoPhase::getEquivalenceRatio(const double* fuelComp, +double ThermoPhase::equivalenceRatio(const double* fuelComp, const double* oxComp, - ThermoBasisType basis) const + ThermoBasis basis) const { - double Z = getMixtureFraction(fuelComp, oxComp, basis); + double Z = mixtureFraction(fuelComp, oxComp, basis); if (Z == 0.0) { return 0.0; // pure oxidizer @@ -902,38 +882,22 @@ double ThermoPhase::getEquivalenceRatio(const double* fuelComp, } vector_fp fuel, ox; - if (basis == ThermoBasisType::mass) { // convert input compositions to mole fractions + if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions fuel.resize(m_kk); ox.resize(m_kk); - massFractionsToMoleFractions(fuelComp, fuel.data()); - massFractionsToMoleFractions(oxComp, ox.data()); + moleFractionsToMassFractions(fuelComp, fuel.data()); + moleFractionsToMassFractions(oxComp, ox.data()); fuelComp = fuel.data(); oxComp = ox.data(); } - double stoichAirFuelRatio = getStoichAirFuelRatio(fuelComp, oxComp, ThermoBasisType::molar); - - double Xf_st = 1.0/(1.0+stoichAirFuelRatio); - - double sum_f = std::accumulate(fuelComp, fuelComp+m_kk, 0.0); - double sum_o = std::accumulate(oxComp, oxComp+m_kk, 0.0); - - double mmw_st = 0.0; - for (size_t k = 0; k != m_kk; ++k) { - mmw_st += molecularWeights()[k] * (Xf_st * fuelComp[k]/sum_f - + (1.0-Xf_st) * oxComp[k]/sum_o); - } - - double mmw_f = std::inner_product(fuelComp, fuelComp+m_kk, - molecularWeights().data(), 0.0) / sum_f; - - double Z_st = Xf_st * mmw_f/mmw_st; + double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass); - return std::max(Z / (1.0 - Z) * (1.0 - Z_st) / Z_st, 0.0); + return std::max(Z / (1.0 - Z) * AFR_st, 0.0); } void ThermoPhase::setMixtureFraction(double mixFrac, const compositionMap& fuelComp, - const compositionMap& oxComp, ThermoBasisType basis) + const compositionMap& oxComp, ThermoBasis basis) { vector_fp fuel(getCompositionFromMap(fuelComp)); vector_fp ox(getCompositionFromMap(oxComp)); @@ -941,7 +905,7 @@ void ThermoPhase::setMixtureFraction(double mixFrac, const compositionMap& fuelC } void ThermoPhase::setMixtureFraction(double mixFrac, const std::string& fuelComp, - const std::string& oxComp, ThermoBasisType basis) + const std::string& oxComp, ThermoBasis basis) { setMixtureFraction(mixFrac, parseCompString(fuelComp.find(":") != std::string::npos ? fuelComp : fuelComp+":1.0"), @@ -950,15 +914,15 @@ void ThermoPhase::setMixtureFraction(double mixFrac, const std::string& fuelComp } void ThermoPhase::setMixtureFraction(double mixFrac, const double* fuelComp, - const double* oxComp, ThermoBasisType basis) + const double* oxComp, ThermoBasis basis) { if (mixFrac < 0.0 || mixFrac > 1.0) { throw CanteraError("ThermoPhase::setMixtureFraction", - "mixture fraction must be between 0 and 1"); + "Mixture fraction must be between 0 and 1"); } vector_fp fuel, ox; - if (basis == ThermoBasisType::molar) { // convert input compositions to mass fractions + if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions fuel.resize(m_kk); ox.resize(m_kk); moleFractionsToMassFractions(fuelComp, fuel.data()); @@ -967,54 +931,54 @@ void ThermoPhase::setMixtureFraction(double mixFrac, const double* fuelComp, oxComp = ox.data(); } - double sum_Yf = std::accumulate(fuelComp, fuelComp+m_kk, 0.0); - double sum_Yo = std::accumulate(oxComp, oxComp+m_kk, 0.0); + double sum_yf = std::accumulate(fuelComp, fuelComp+m_kk, 0.0); + double sum_yo = std::accumulate(oxComp, oxComp+m_kk, 0.0); - if (sum_Yf == 0.0 || sum_Yo == 0.0) { + if (sum_yf == 0.0 || sum_yo == 0.0) { throw CanteraError("ThermoPhase::setMixtureFraction", - "no fuel and/or oxidizer composition specified"); + "No fuel and/or oxidizer composition specified"); } double p = pressure(); - vector_fp Y(m_kk); + vector_fp y(m_kk); for (size_t k = 0; k != m_kk; ++k) { - Y[k] = mixFrac * fuelComp[k]/sum_Yf + (1.0-mixFrac) * oxComp[k]/sum_Yo; + y[k] = mixFrac * fuelComp[k]/sum_yf + (1.0-mixFrac) * oxComp[k]/sum_yo; } - setMassFractions_NoNorm(Y.data()); + setMassFractions_NoNorm(y.data()); setPressure(p); } -double ThermoPhase::getMixtureFraction(const compositionMap& fuelComp, +double ThermoPhase::mixtureFraction(const compositionMap& fuelComp, const compositionMap& oxComp, - ThermoBasisType basis, + ThermoBasis basis, const std::string& element) const { vector_fp fuel(getCompositionFromMap(fuelComp)); vector_fp ox(getCompositionFromMap(oxComp)); - return getMixtureFraction(fuel.data(), ox.data(), basis, element); + return mixtureFraction(fuel.data(), ox.data(), basis, element); } -double ThermoPhase::getMixtureFraction(const std::string& fuelComp, +double ThermoPhase::mixtureFraction(const std::string& fuelComp, const std::string& oxComp, - ThermoBasisType basis, + ThermoBasis basis, const std::string& element) const { - return getMixtureFraction( + return mixtureFraction( parseCompString(fuelComp.find(":") != std::string::npos ? fuelComp : fuelComp+":1.0"), parseCompString(oxComp.find(":") != std::string::npos ? oxComp : oxComp+":1.0"), basis, element); } -double ThermoPhase::getMixtureFraction(const double* fuelComp, +double ThermoPhase::mixtureFraction(const double* fuelComp, const double* oxComp, - ThermoBasisType basis, + ThermoBasis basis, const std::string& element) const { vector_fp fuel, ox; - if (basis == ThermoBasisType::molar) { // convert input compositions to mass fractions + if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions fuel.resize(m_kk); ox.resize(m_kk); moleFractionsToMassFractions(fuelComp, fuel.data()); @@ -1023,102 +987,59 @@ double ThermoPhase::getMixtureFraction(const double* fuelComp, oxComp = ox.data(); } - double sum_Yf = std::accumulate(fuelComp, fuelComp+m_kk, 0.0); - double sum_Yo = std::accumulate(oxComp, oxComp+m_kk, 0.0); - if (element == "Bilger") // compute the mixture fraction based on the Bilger mixture fraction { - size_t iC = elementIndex("C"); - size_t iS = elementIndex("S"); - size_t iO = elementIndex("O"); - size_t iH = elementIndex("H"); - - const auto& M = molecularWeights(); - - // elemental compositions of the mixture, fuel and oxidizer - double Z_Cf = 0.0, Z_Sf = 0.0, Z_Of = 0.0, Z_Hf = 0.0; - double Z_Co = 0.0, Z_So = 0.0, Z_Oo = 0.0, Z_Ho = 0.0; - double Z_C = 0.0, Z_S = 0.0, Z_O = 0.0, Z_H = 0.0; - for (size_t k = 0; k != m_kk; ++k) { - double Yf = fuelComp[k] / M[k]; - double Yo = oxComp[k] / M[k]; - double Y = massFractions()[k] / M[k]; - - if (iC != npos) { - double nC = nAtoms(k, iC); - Z_Cf += Yf * nC; - Z_Co += Yo * nC; - Z_C += Y * nC; - } - if (iS != npos) { - double nS = nAtoms(k, iS); - Z_Sf += Yf * nS; - Z_So += Yo * nS; - Z_S += Y * nS; - } - if (iO != npos) { - double nO = nAtoms(k, iO); - Z_Of += Yf * nO; - Z_Oo += Yo * nO; - Z_O += Y * nO; - } - if (iH != npos) { - double nH = nAtoms(k, iH); - Z_Hf += Yf * nH; - Z_Ho += Yo * nH; - Z_H += Y * nH; - } + double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp); + double o2_required_ox = o2Required(oxComp) - o2Present(oxComp); + double o2_required_mix = o2Required(massFractions()) - o2Present(massFractions()); + + if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) { + throw CanteraError("ThermoPhase::mixtureFraction", + "Fuel composition contains too much oxygen or " + "oxidizer contains not enough oxygen. " + "Fuel and oxidizer composition mixed up?"); } - if (sum_Yf == 0.0 || sum_Yo == 0.0) { - throw CanteraError("ThermoPhase::getStoichAirFuelRatio", - "no fuel and/or oxidizer composition specified"); - } + double denominator = o2_required_fuel - o2_required_ox; - // in case input is not normalized - Z_Cf /= sum_Yf; - Z_Sf /= sum_Yf; - Z_Of /= sum_Yf; - Z_Hf /= sum_Yf; + if (denominator == 0.0) { + throw CanteraError("ThermoPhase::mixtureFraction", + "Fuel and oxidizer have the same composition"); + } - Z_Co /= sum_Yo; - Z_So /= sum_Yo; - Z_Oo /= sum_Yo; - Z_Ho /= sum_Yo; + double Z = (o2_required_mix - o2_required_ox) / denominator; - double denominator = 2.0*(Z_Cf - Z_Co) + 0.5*(Z_Hf - Z_Ho) - + 2.0*(Z_Sf - Z_So) - (Z_Of - Z_Oo); + return std::min(std::max(Z, 0.0), 1.0); + } else { + // compute the mixture fraction from a single element + double sum_yf = std::accumulate(fuelComp, fuelComp+m_kk, 0.0); + double sum_yo = std::accumulate(oxComp, oxComp+m_kk, 0.0); - if (denominator == 0.0) { - throw CanteraError("ThermoPhase::getMixtureFraction", - "fuel and oxidizer have the same composition"); + if (sum_yf == 0.0 || sum_yo == 0.0) { + throw CanteraError("ThermoPhase::mixtureFraction", + "No fuel and/or oxidizer composition specified"); } - double mf = (2.0*(Z_C - Z_Co) + 0.5*(Z_H - Z_Ho) + 2.0*(Z_S - Z_So) - (Z_O - Z_Oo)) - / denominator; + auto elementalFraction = [this](size_t m, const double* y) { + double Z_m = 0.0; + for (size_t k = 0; k != m_kk; ++k) { + Z_m += y[k] / molecularWeight(k) * nAtoms(k, m); + } + return Z_m; + }; - return std::min(std::max(mf, 0.0), 1.0); - } - else // compute the mixture fraction from the mass fractions of a single element - { size_t m = elementIndex(element); - checkElementIndex(m); - double Z_f = 0.0, Z_o = 0.0, Z = 0.0; - for (size_t k = 0; k != m_kk; ++k) { - double frac = nAtoms(k, m) * atomicWeight(m) / molecularWeight(k); - Z_f += frac * fuelComp[k]; - Z_o += frac * oxComp[k]; - Z += frac * massFractions()[k]; - } - Z_f /= sum_Yf; - Z_o /= sum_Yo; + double Z_m_fuel = elementalFraction(m, fuelComp)/sum_yf; + double Z_m_ox = elementalFraction(m, oxComp)/sum_yo; + double Z_m_mix = elementalFraction(m, massFractions()); - if (Z_f == Z_o) { - throw CanteraError("ThermoPhase::getMixtureFraction", - "fuel and oxidizer have the same composition for element {}", + if (Z_m_fuel == Z_m_ox) { + throw CanteraError("ThermoPhase::mixtureFraction", + "Fuel and oxidizer have the same composition for element {}", element); } - return std::min(std::max((Z - Z_o)/(Z_f - Z_o), 0.0), 1.0); + double Z = (Z_m_mix - Z_m_ox) / (Z_m_fuel - Z_m_ox); + return std::min(std::max(Z, 0.0), 1.0); } } diff --git a/test/thermo/ThermoPhase_Test.cpp b/test/thermo/ThermoPhase_Test.cpp index 1e84e186abc..0709c8ecc30 100644 --- a/test/thermo/ThermoPhase_Test.cpp +++ b/test/thermo/ThermoPhase_Test.cpp @@ -109,226 +109,189 @@ TEST_F(TestThermoMethods, setState_AnyMap) EXPECT_NEAR(thermo->temperature(), 298.15, 1e-6); } -TEST(TestMixtureMethods, getSet_EquilRatio_MixtureFraction) +class EquilRatio_MixFrac_Test : public testing::Test { - auto sol = newSolution("gri30.yaml", "gri30", "None"); - auto pgas = sol->thermo(); - auto& gas = *pgas; - - // start with some fuel and oxidizer compositions - compositionMap fuel; - compositionMap ox; - fuel["CH4"] = 0.2; - fuel["O2"] = 0.02; - fuel["N2"] = 0.1; - fuel["CO"] = 0.05; - fuel["CO2"] = 0.02; - ox["O2"] = 0.21; - ox["N2"] = 0.79; - ox["CO"] = 0.04; - ox["CH4"] = 0.01; - ox["CO2"] = 0.03; - - gas.setState_TPX(300.0, 1e5, fuel); - double Y_Cf = gas.elementalMassFraction(gas.elementIndex("C")); - double Y_Of = gas.elementalMassFraction(gas.elementIndex("O")); - gas.setState_TPX(300.0, 1e5, ox); - double Y_Co = gas.elementalMassFraction(gas.elementIndex("C")); - double Y_Oo = gas.elementalMassFraction(gas.elementIndex("O")); - - // set equivalence ratio to 1.3 - gas.setEquivalenceRatio(1.3, fuel, ox, ThermoBasisType::molar); - - // set mixture to burnt state to make sure that equivalence ratio and - // mixture fraction are independent of reaction progress - gas.equilibrate("HP"); - double phi = gas.getEquivalenceRatio(fuel, ox, ThermoBasisType::molar); - double phi_loc = gas.getEquivalenceRatio(); - double mf = gas.getMixtureFraction(fuel, ox, ThermoBasisType::molar, "Bilger"); - double mf_C = gas.getMixtureFraction(fuel, ox, ThermoBasisType::molar, "C"); - double mf_O = gas.getMixtureFraction(fuel, ox, ThermoBasisType::molar, "O"); - double l = gas.getStoichAirFuelRatio(fuel, ox, ThermoBasisType::molar); - - EXPECT_NEAR(phi, 1.3, 1e-4); - EXPECT_NEAR(phi_loc, 1.1726068608, 1e-4); - EXPECT_NEAR(mf, 0.13415725911, 1e-4); - EXPECT_NEAR(mf_C, (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), 1e-4); - EXPECT_NEAR(mf_O, (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), 1e-4); - EXPECT_NEAR(l, 6.5972850678733, 1e-4); - - // set mixture according to mixture fraction - gas.setMixtureFraction(mf, fuel, ox, ThermoBasisType::molar); - gas.equilibrate("HP"); - phi = gas.getEquivalenceRatio(fuel, ox, ThermoBasisType::molar); - phi_loc = gas.getEquivalenceRatio(); - mf = gas.getMixtureFraction(fuel, ox, ThermoBasisType::molar, "Bilger"); - mf_C = gas.getMixtureFraction(fuel, ox, ThermoBasisType::molar, "C"); - mf_O = gas.getMixtureFraction(fuel, ox, ThermoBasisType::molar, "O"); - l = gas.getStoichAirFuelRatio(fuel, ox, ThermoBasisType::molar); - double p = gas.pressure(); // make sure the pressure has not been altered - - EXPECT_NEAR(phi, 1.3, 1e-4); - EXPECT_NEAR(phi_loc, 1.1726068608, 1e-4); - EXPECT_NEAR(mf, 0.13415725911, 1e-4); - EXPECT_NEAR(mf_C, (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), 1e-4); - EXPECT_NEAR(mf_O, (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), 1e-4); - EXPECT_NEAR(l, 6.5972850678733, 1e-4); - EXPECT_NEAR(p, 1e5, 1e-4); - - // do the same for mass fractions as input - - // convert fuel and oxidizer compositions to (non-normalized) mass fractions - gas.setState_TPX(300, 1e5, fuel); - fuel.clear(); - for (size_t i=0; i!=gas.nSpecies(); ++i) - fuel[gas.speciesName(i)] = gas.massFraction(i)*3; - - gas.setState_TPX(300, 1e5, ox); - ox.clear(); - for (size_t i=0; i!=gas.nSpecies(); ++i) - ox[gas.speciesName(i)] = gas.massFraction(i)*7; - - gas.setState_TPY(300.0, 1e5, fuel); - Y_Cf = gas.elementalMassFraction(gas.elementIndex("C")); - Y_Of = gas.elementalMassFraction(gas.elementIndex("O")); - gas.setState_TPY(300.0, 1e5, ox); - Y_Co = gas.elementalMassFraction(gas.elementIndex("C")); - Y_Oo = gas.elementalMassFraction(gas.elementIndex("O")); - - gas.setEquivalenceRatio(1.3, fuel, ox, ThermoBasisType::mass); - - gas.equilibrate("HP"); - - phi = gas.getEquivalenceRatio(fuel, ox, ThermoBasisType::mass); - phi_loc = gas.getEquivalenceRatio(); - mf = gas.getMixtureFraction(fuel, ox, ThermoBasisType::mass, "Bilger"); - mf_C = gas.getMixtureFraction(fuel, ox, ThermoBasisType::mass, "C"); - mf_O = gas.getMixtureFraction(fuel, ox, ThermoBasisType::mass, "O"); - l = gas.getStoichAirFuelRatio(fuel, ox, ThermoBasisType::mass); - - EXPECT_NEAR(phi, 1.3, 1e-4); - EXPECT_NEAR(phi_loc, 1.1726068608, 1e-4); - EXPECT_NEAR(mf, 0.13415725911, 1e-4); - EXPECT_NEAR(mf_C, (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), 1e-4); - EXPECT_NEAR(mf_O, (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), 1e-4); - EXPECT_NEAR(l, 6.5972850678733, 1e-4); - - gas.setMixtureFraction(mf, fuel, ox, ThermoBasisType::mass); - - gas.equilibrate("HP"); - - phi = gas.getEquivalenceRatio(fuel, ox, ThermoBasisType::mass); - phi_loc = gas.getEquivalenceRatio(); - mf = gas.getMixtureFraction(fuel, ox, ThermoBasisType::mass, "Bilger"); - mf_C = gas.getMixtureFraction(fuel, ox, ThermoBasisType::mass, "C"); - mf_O = gas.getMixtureFraction(fuel, ox, ThermoBasisType::mass, "O"); - l = gas.getStoichAirFuelRatio(fuel, ox, ThermoBasisType::mass); - - p = gas.pressure(); // make sure the pressure has not been altered - - EXPECT_NEAR(phi, 1.3, 1e-4); - EXPECT_NEAR(phi_loc, 1.1726068608, 1e-4); - EXPECT_NEAR(mf, 0.13415725911, 1e-4); - EXPECT_NEAR(mf_C, (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), 1e-4); - EXPECT_NEAR(mf_O, (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), 1e-4); - EXPECT_NEAR(l, 6.5972850678733, 1e-4); - EXPECT_NEAR(p, 1e5, 1e-4); - - // test some special cases - - vector_fp v_ox(gas.nSpecies()); - vector_fp v_fuel(gas.nSpecies()); - v_ox[gas.speciesIndex("O2")] = 21.0; - v_ox[gas.speciesIndex("N2")] = 79.0; - v_fuel[gas.speciesIndex("CH4")] = 10.0; - - // special case 1: pure oxidizer - gas.setState_TPX(300.0, 1e5, v_ox.data()); - EXPECT_NEAR(gas.getEquivalenceRatio(v_fuel.data(), v_ox.data(), ThermoBasisType::mass), 0.0, 1e-4); - EXPECT_NEAR(gas.getEquivalenceRatio(v_fuel.data(), v_ox.data(), ThermoBasisType::molar), 0.0, 1e-4); - EXPECT_NEAR(gas.getEquivalenceRatio(), 0.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::molar, "Bilger"), 0.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::mass, "Bilger"), 0.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::molar, "C"), 0.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::mass, "C"), 0.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::molar, "O"), 0.0, 1e-4); - - double Yo2 = gas.massFraction(gas.speciesIndex("O2")); - double Ych4 = gas.massFraction(gas.speciesIndex("CH4")); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setMixtureFraction(0.0, v_fuel.data(), v_ox.data(), ThermoBasisType::molar); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setEquivalenceRatio(0.0, v_fuel.data(), v_ox.data(), ThermoBasisType::molar); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - - gas.setState_TPY(300.0, 1e5, v_ox.data()); - Yo2 = gas.massFraction(gas.speciesIndex("O2")); - Ych4 = gas.massFraction(gas.speciesIndex("CH4")); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setEquivalenceRatio(0.0, v_fuel.data(), v_ox.data(), ThermoBasisType::mass); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setMixtureFraction(0.0, v_fuel.data(), v_ox.data(), ThermoBasisType::mass); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - - // special case 2: pure fuel - gas.setState_TPX(300.0, 1e5, v_fuel.data()); - ASSERT_EQ(gas.getEquivalenceRatio(v_fuel.data(), v_ox.data(), ThermoBasisType::mass) > 1e10, true); - ASSERT_EQ(gas.getEquivalenceRatio(v_fuel.data(), v_ox.data(), ThermoBasisType::molar) > 1e10, true); - ASSERT_EQ(gas.getEquivalenceRatio() > 1e10, true); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::molar, "Bilger"), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::mass, "Bilger"), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::molar, "C"), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::mass, "C"), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::molar, "O"), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(v_fuel.data(), v_ox.data(), ThermoBasisType::mass, "O"), 1.0, 1e-4); - - Yo2 = gas.massFraction(gas.speciesIndex("O2")); - Ych4 = gas.massFraction(gas.speciesIndex("CH4")); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setMixtureFraction(1.0, v_fuel.data(), v_ox.data(), ThermoBasisType::mass); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setMixtureFraction(1.0, v_fuel.data(), v_ox.data(), ThermoBasisType::molar); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setEquivalenceRatio(1e10, v_fuel.data(), v_ox.data(), ThermoBasisType::mass); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - gas.setState_TPX(300.0, 1e5, "N2:1"); - gas.setEquivalenceRatio(1e10, v_fuel.data(), v_ox.data(), ThermoBasisType::molar); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("O2")), Yo2, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); - - // special case 3: stoichiometric mixture, input is string - std::string sfuel = "CH4"; - std::string sox = "O2:21,N2:79"; - gas.setEquivalenceRatio(1.0, sfuel, sox, ThermoBasisType::molar); - gas.setMixtureFraction(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::molar, "Bilger"), sfuel, sox, ThermoBasisType::molar); - EXPECT_NEAR(gas.getEquivalenceRatio(sfuel, sox, ThermoBasisType::molar), 1.0, 1e-4); - EXPECT_NEAR(gas.getEquivalenceRatio(), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::molar, "Bilger"), 0.05516607283, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::molar, "C"), 0.05516607283, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::molar, "O"), 0.05516607283, 1e-4); - EXPECT_NEAR(gas.getStoichAirFuelRatio(sfuel, sox, ThermoBasisType::molar), 9.52380952380, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), 0.05516607283, 1e-4); - - gas.setEquivalenceRatio(1.0, sfuel, sox, ThermoBasisType::mass); - gas.setMixtureFraction(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::mass, "Bilger"), sfuel, sox, ThermoBasisType::mass); - EXPECT_NEAR(gas.getEquivalenceRatio(sfuel, sox, ThermoBasisType::mass), 1.0, 1e-4); - EXPECT_NEAR(gas.getEquivalenceRatio(), 1.0, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::mass, "Bilger"), 0.0500096579, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::mass, "C"), 0.0500096579, 1e-4); - EXPECT_NEAR(gas.getMixtureFraction(sfuel, sox, ThermoBasisType::mass, "O"), 0.0500096579, 1e-4); - EXPECT_NEAR(gas.getStoichAirFuelRatio(sfuel, sox, ThermoBasisType::mass), 10.593805138247204, 1e-4); - EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), 0.0500096579, 1e-4); +public: + void initSolution() { + m_sol = newSolution("gri30.yaml", "gri30", "None"); + } + + void set_arbitrary_mixture(ThermoBasis basis) { + auto& gas = *m_sol->thermo(); + m_fuel.clear(); + m_fuel["CH4"] = 0.2; + m_fuel["O2"] = 0.02; + m_fuel["N2"] = 0.1; + m_fuel["CO"] = 0.05; + m_fuel["CO2"] = 0.02; + m_ox.clear(); + m_ox["O2"] = 0.21; + m_ox["N2"] = 0.79; + m_ox["CO"] = 0.04; + m_ox["CH4"] = 0.01; + m_ox["CO2"] = 0.03; + + if (basis == ThermoBasis::mass) { + // convert fuel and oxidizer compositions to (non-normalized) mass fractions + gas.setState_TPX(300, 1e5, m_fuel); + m_fuel.clear(); + for (size_t i=0; i!=gas.nSpecies(); ++i) { + m_fuel[gas.speciesName(i)] = gas.massFraction(i)*3; + } + + gas.setState_TPX(300, 1e5, m_ox); + m_ox.clear(); + for (size_t i=0; i!=gas.nSpecies(); ++i) { + m_ox[gas.speciesName(i)] = gas.massFraction(i)*7; + } + } + } + + void test_arbitrary_equilRatio_MixFrac(ThermoBasis basis) { + auto& gas = *m_sol->thermo(); + if (basis == ThermoBasis::mass) { + gas.setState_TPY(300.0, 1e5, m_fuel); + } else { + gas.setState_TPX(300.0, 1e5, m_fuel); + } + double Y_Cf = gas.elementalMassFraction(gas.elementIndex("C")); + double Y_Of = gas.elementalMassFraction(gas.elementIndex("O")); + if (basis == ThermoBasis::mass) { + gas.setState_TPY(300.0, 1e5, m_ox); + } else { + gas.setState_TPX(300.0, 1e5, m_ox); + } + double Y_Co = gas.elementalMassFraction(gas.elementIndex("C")); + double Y_Oo = gas.elementalMassFraction(gas.elementIndex("O")); + + gas.setEquivalenceRatio(1.3, m_fuel, m_ox, basis); + double T = gas.temperature(); + + // set mixture to burnt state to make sure that equivalence ratio and + // mixture fraction are independent of reaction progress + gas.equilibrate("HP"); + test_mixture_results(T, basis, 1.3, 1.1726068608195617, 0.13415725911057605, + (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), + (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), + 8.3901204498353561, m_fuel, m_ox); + + gas.setState_TP(300.0,1e5); + gas.setMixtureFraction(gas.mixtureFraction(m_fuel, m_ox, basis, "Bilger"), m_fuel, m_ox, basis); + T = gas.temperature(); + gas.equilibrate("HP"); + test_mixture_results(T, basis, 1.3, 1.1726068608195617, 0.13415725911057605, + (gas.elementalMassFraction(gas.elementIndex("C"))-Y_Co)/(Y_Cf-Y_Co), + (gas.elementalMassFraction(gas.elementIndex("O"))-Y_Oo)/(Y_Of-Y_Oo), + 8.3901204498353561, m_fuel, m_ox); + } + + template + void test_mixture_results(double Temp, ThermoBasis basis, double phi, double loc_phi, double mf_Bilger, + double mf_C, double mf_O, double AFR_st, const T& fuel, const T& ox) { + auto& gas = *m_sol->thermo(); + EXPECT_NEAR(gas.equivalenceRatio(fuel, ox, basis), phi, 1e-4); + EXPECT_NEAR(gas.equivalenceRatio(), loc_phi, 1e-4); + EXPECT_NEAR(gas.mixtureFraction(fuel, ox, basis, "Bilger"), mf_Bilger, 1e-4); + EXPECT_NEAR(gas.mixtureFraction(fuel, ox, basis, "C"), mf_C, 1e-4); + EXPECT_NEAR(gas.mixtureFraction(fuel, ox, basis, "O"), mf_O, 1e-4); + EXPECT_NEAR(gas.stoichAirFuelRatio(fuel, ox, basis), AFR_st, 1e-4); + EXPECT_NEAR(gas.pressure(), 1e5, 1e-4); + EXPECT_NEAR(Temp, 300.0, 1e-4); + } + + void test_pure_mixture(ThermoBasis basis, bool oxidizer, double phi, double mf) { + auto& gas = *m_sol->thermo(); + vector_fp v_ox(gas.nSpecies()); + vector_fp v_fuel(gas.nSpecies()); + v_ox[gas.speciesIndex("O2")] = 21.0; + v_ox[gas.speciesIndex("N2")] = 79.0; + v_fuel[gas.speciesIndex("CH4")] = 10.0; + + if (oxidizer) { + gas.setState_TPX(300.0, 1e5, v_ox.data()); + EXPECT_NEAR(gas.equivalenceRatio(v_fuel.data(), v_ox.data(), basis), phi, 1e-4); + EXPECT_NEAR(gas.equivalenceRatio(), 0.0, 1e-4); + } else { + gas.setState_TPX(300.0, 1e5, v_fuel.data()); + ASSERT_EQ(gas.equivalenceRatio(v_fuel.data(), v_ox.data(), basis) > phi, true); + ASSERT_EQ(gas.equivalenceRatio() > phi, true); + } + EXPECT_NEAR(gas.mixtureFraction(v_fuel.data(), v_ox.data(), basis, "Bilger"), mf, 1e-4); + EXPECT_NEAR(gas.mixtureFraction(v_fuel.data(), v_ox.data(), basis, "C"), mf, 1e-4); + + double Ych4 = gas.massFraction(gas.speciesIndex("CH4")); + gas.setState_TPX(300.0, 1e5, "N2:1"); + gas.setMixtureFraction(mf, v_fuel.data(), v_ox.data(), basis); + EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); + gas.setState_TPX(300.0, 1e5, "N2:1"); + gas.setEquivalenceRatio(phi, v_fuel.data(), v_ox.data(), basis); + EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), Ych4, 1e-4); + } + + void test_stoich_mixture(ThermoBasis basis, double mf, double AFR_st) { + auto& gas = *m_sol->thermo(); + std::string sfuel = "CH4"; + std::string sox = "O2:21,N2:79"; + gas.setState_TP(300.0, 1e5); + gas.setEquivalenceRatio(1.0, sfuel, sox, basis); + gas.setMixtureFraction(gas.mixtureFraction(sfuel, sox, basis, "Bilger"), sfuel, sox, basis); + test_mixture_results(gas.temperature(), basis, 1.0, 1.0, mf, mf, mf, AFR_st, sfuel, sox); + EXPECT_NEAR(gas.massFraction(gas.speciesIndex("CH4")), mf, 1e-4); + } + + shared_ptr m_sol; + compositionMap m_fuel; + compositionMap m_ox; +}; + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_Arbitrary_Mixture_Molar) +{ + initSolution(); + set_arbitrary_mixture(ThermoBasis::molar); + test_arbitrary_equilRatio_MixFrac(ThermoBasis::molar); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_Arbitrary_Mixture_Mass) +{ + initSolution(); + set_arbitrary_mixture(ThermoBasis::mass); + test_arbitrary_equilRatio_MixFrac(ThermoBasis::mass); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_PureOx_Molar) +{ + initSolution(); + test_pure_mixture(ThermoBasis::molar, true, 0.0, 0.0); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_PureOx_Mass) +{ + initSolution(); + test_pure_mixture(ThermoBasis::mass, true, 0.0, 0.0); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_PureFuel_Molar) +{ + initSolution(); + test_pure_mixture(ThermoBasis::molar, false, 1e10, 1.0); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_PureFuel_Mass) +{ + initSolution(); + test_pure_mixture(ThermoBasis::mass, false, 1e10, 1.0); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_StoichMix_Molar) +{ + initSolution(); + test_stoich_mixture(ThermoBasis::molar, 0.055166413925195397, 17.126971264726048); +} + +TEST_F(EquilRatio_MixFrac_Test, EquilRatio_MixFrac_StoichMix_Mass) +{ + initSolution(); + test_stoich_mixture(ThermoBasis::mass, 0.050011556441079318, 18.995378491732041); } }