diff --git a/data/inputs/critProperties.xml b/data/inputs/critProperties.xml
index 654d41c733..bf9f49d150 100644
--- a/data/inputs/critProperties.xml
+++ b/data/inputs/critProperties.xml
@@ -76,7 +76,7 @@
-
+
diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h
index 1658cac337..518c133e40 100644
--- a/include/cantera/thermo/MixtureFugacityTP.h
+++ b/include/cantera/thermo/MixtureFugacityTP.h
@@ -119,6 +119,13 @@ class MixtureFugacityTP : public ThermoPhase
throw NotImplementedError("MixtureFugacityTP::getdlnActCoeffdlnN_diag");
}
+
+ //! @name Molar Thermodynamic properties
+ //! @{
+
+ virtual double enthalpy_mole() const;
+ virtual double entropy_mole() const;
+
//@}
/// @name Partial Molar Properties of the Solution
//@{
@@ -272,37 +279,9 @@ class MixtureFugacityTP : public ThermoPhase
*/
virtual void setPressure(doublereal p);
-protected:
- /**
- * Calculate the density of the mixture using the partial molar volumes and
- * mole fractions as input
- *
- * The formula for this is
- *
- * \f[
- * \rho = \frac{\sum_k{X_k W_k}}{\sum_k{X_k V_k}}
- * \f]
- *
- * where \f$X_k\f$ are the mole fractions, \f$W_k\f$ are the molecular
- * weights, and \f$V_k\f$ are the pure species molar volumes.
- *
- * Note, the basis behind this formula is that in an ideal solution the
- * partial molar volumes are equal to the pure species molar volumes. We
- * have additionally specified in this class that the pure species molar
- * volumes are independent of temperature and pressure.
- */
- virtual void calcDensity();
-
-public:
- virtual void setState_TP(doublereal T, doublereal pres);
- virtual void setState_TR(doublereal T, doublereal rho);
- virtual void setState_TPX(doublereal t, doublereal p, const doublereal* x);
-
protected:
virtual void compositionChanged();
- void setMoleFractions_NoState(const doublereal* const x);
-protected:
//! Updates the reference state thermodynamic functions at the current T of
//! the solution.
/*!
@@ -318,6 +297,9 @@ class MixtureFugacityTP : public ThermoPhase
* - m_s0_R;
*/
virtual void _updateReferenceStateThermo() const;
+
+ //! Temporary storage - length = m_kk.
+ mutable vector_fp m_tmpV;
public:
/// @name Thermodynamic Values for the Species Reference States
@@ -430,7 +412,6 @@ class MixtureFugacityTP : public ThermoPhase
* setState_TP() routines. Infinite loops would result if it were not
* protected.
*
- * -> why is this not const?
*
* @param TKelvin Temperature in Kelvin
* @param pressure Pressure in Pascals (Newton/m**2)
@@ -509,6 +490,7 @@ class MixtureFugacityTP : public ThermoPhase
* @return The saturation pressure at the given temperature
*/
virtual doublereal satPressure(doublereal TKelvin);
+ virtual void getActivityConcentrations(double* c) const;
protected:
//! Calculate the pressure given the temperature and the molar volume
@@ -534,9 +516,46 @@ class MixtureFugacityTP : public ThermoPhase
virtual void updateMixingExpressions();
//@}
+ /// @name Critical State Properties.
+ //@{
-protected:
- virtual void invalidateCache();
+ virtual double critTemperature() const;
+ virtual double critPressure() const;
+ virtual double critVolume() const;
+ virtual double critCompressibility() const;
+ virtual double critDensity() const;
+ virtual void calcCriticalConditions(double& pc, double& tc, double& vc) const;
+
+ //! Solve the cubic equation of state
+ /*!
+ *
+ * Returns the number of solutions found. For the gas phase solution, it returns
+ * a positive number (1 or 2). If it only finds the liquid branch solution,
+ * it will return -1 or -2 instead of 1 or 2.
+ * If it returns 0, then there is an error.
+ * The cubic equation is solved using Nickall's method
+ * (Ref: The Mathematical Gazette(1993), 77(November), 354--359,
+ * https://www.jstor.org/stable/3619777)
+ *
+ * @param T temperature (kelvin)
+ * @param pres pressure (Pa)
+ * @param a "a" parameter in the non-ideal EoS [Pa-m^6/kmol^2]
+ * @param b "b" parameter in the non-ideal EoS [m^3/kmol]
+ * @param aAlpha a*alpha (temperature dependent function for P-R EoS, 1 for R-K EoS)
+ * @param Vroot Roots of the cubic equation for molar volume (m3/kmol)
+ * @param an constant used in cubic equation
+ * @param bn constant used in cubic equation
+ * @param cn constant used in cubic equation
+ * @param dn constant used in cubic equation
+ * @param tc Critical temperature (kelvin)
+ * @param vc Critical volume
+ * @returns the number of solutions found
+ */
+ int solveCubic(double T, double pres, double a, double b,
+ double aAlpha, double Vroot[3], double an,
+ double bn, double cn, double dn, double tc, double vc) const;
+
+ //@}
//! Storage for the current values of the mole fractions of the species
/*!
@@ -556,10 +575,6 @@ class MixtureFugacityTP : public ThermoPhase
//! Force the system to be on a particular side of the spinodal curve
int forcedState_;
- //! The last temperature at which the reference state thermodynamic
- //! properties were calculated at.
- mutable doublereal m_Tlast_ref;
-
//! Temporary storage for dimensionless reference state enthalpies
mutable vector_fp m_h0_RT;
@@ -571,6 +586,7 @@ class MixtureFugacityTP : public ThermoPhase
//! Temporary storage for dimensionless reference state entropies
mutable vector_fp m_s0_R;
+
};
}
diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h
new file mode 100644
index 0000000000..ba7577d125
--- /dev/null
+++ b/include/cantera/thermo/PengRobinson.h
@@ -0,0 +1,317 @@
+//! @file PengRobinson.h
+
+// This file is part of Cantera. See License.txt in the top-level directory or
+// at https://cantera.org/license.txt for license and copyright information.
+
+#ifndef CT_PENGROBINSON_H
+#define CT_PENGROBINSON_H
+
+#include "MixtureFugacityTP.h"
+#include "cantera/base/Array.h"
+
+namespace Cantera
+{
+/**
+ * Implementation of a multi-species Peng-Robinson equation of state
+ *
+ * @ingroup thermoprops
+ */
+class PengRobinson : public MixtureFugacityTP
+{
+public:
+
+ //! Construct and initialize a PengRobinson object directly from an
+ //! input file
+ /*!
+ * @param infile Name of the input file containing the phase YAML data.
+ * If blank, an empty phase will be created.
+ * @param id ID of the phase in the input file. If empty, the
+ * first phase definition in the input file will be used.
+ */
+ explicit PengRobinson(const std::string& infile="",
+ const std::string& id="");
+
+ virtual std::string type() const {
+ return "PengRobinson";
+ }
+
+ //! @name Molar Thermodynamic properties
+ //! @{
+
+ virtual double cp_mole() const;
+ virtual double cv_mole() const;
+
+ //! @}
+ //! @name Mechanical Properties
+ //! @{
+
+ //! Return the thermodynamic pressure (Pa).
+ /*!
+ * Since the mass density, temperature, and mass fractions are stored,
+ * this method uses these values to implement the
+ * mechanical equation of state \f$ P(T, \rho, Y_1, \dots, Y_K) \f$.
+ *
+ * \f[
+ * P = \frac{RT}{v-b_{mix}} - \frac{\left(\alpha a\right)_{mix}}{v^2 + 2b_{mix}v - b_{mix}^2}
+ * \f]
+ *
+ * where:
+ *
+ * \f[
+ * \alpha = \left[ 1 + \kappa \left(1-T_r^{0.5}\right)\right]^2
+ * \f]
+ *
+ * and
+ *
+ * \f[
+ * \kappa = \left(0.37464 + 1.54226\omega - 0.26992\omega^2\right), \qquad \qquad \text{For } \omega <= 0.491 \\
+ * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right), \qquad \text{For } \omega > 0.491
+ * \f]
+ *
+ * Coefficients \f$ a_mix, b_mix \f$ and \f$(a \alpha)_{mix}\f$ are calculated as
+ *
+ * \f[
+ * a_{mix} = \sum_i \sum_j X_i X_j a_{i, j} = \sum_i \sum_j X_i X_j \sqrt{a_i a_j}
+ * \f]
+ *
+ * \f[
+ * b_{mix} = \sum_i X_i b_i
+ * \f]
+ *
+ * \f[
+ * {a \alpha}_{mix} = \sum_i \sum_j X_i X_j {a \alpha}_{i, j} = \sum_i \sum_j X_i X_j \sqrt{a_i a_j} \sqrt{\alpha_i \alpha_j}
+ * \f]
+ */
+ virtual double pressure() const;
+
+ // @}
+
+ //! Returns the standard concentration \f$ C^0_k \f$, which is used to
+ //! normalize the generalized concentration.
+ /*!
+ * This is defined as the concentration by which the generalized
+ * concentration is normalized to produce the activity.
+ * The ideal gas mixture is considered as the standard or reference state here.
+ * Since the activity for an ideal gas mixture is simply the mole fraction,
+ * for an ideal gas, \f$ C^0_k = P/\hat R T \f$.
+ *
+ * @param k Optional parameter indicating the species. The default is to
+ * assume this refers to species 0.
+ * @return
+ * Returns the standard Concentration in units of m^3 / kmol.
+ */
+ virtual double standardConcentration(size_t k=0) const;
+
+ //! Get the array of non-dimensional activity coefficients at the current
+ //! solution temperature, pressure, and solution concentration.
+ /*!
+ * For all objects with the Mixture Fugacity approximation, we define the
+ * standard state as an ideal gas at the current temperature and pressure of
+ * the solution. The activities are based on this standard state.
+ *
+ * @param ac Output vector of activity coefficients. Length: m_kk.
+ */
+ virtual void getActivityCoefficients(double* ac) const;
+
+ /// @name Partial Molar Properties of the Solution
+ //@{
+
+ virtual void getChemPotentials(double* mu) const;
+ virtual void getPartialMolarEnthalpies(double* hbar) const;
+ virtual void getPartialMolarEntropies(double* sbar) const;
+ virtual void getPartialMolarIntEnergies(double* ubar) const;
+ virtual void getPartialMolarCp(double* cpbar) const;
+ virtual void getPartialMolarVolumes(double* vbar) const;
+
+ //! Calculate species-specific critical temperature
+ /*!
+ * The temperature dependent parameter in P-R EoS is calculated as
+ * \f$ T_{crit} = (0.0778 a)/(0.4572 b R) \f$
+ * Units: Kelvin
+ *
+ * @param a species-specific coefficients used in P-R EoS
+ * @param b species-specific coefficients used in P-R EoS
+ */
+ virtual double speciesCritTemperature(double a, double b) const;
+
+ //@}
+ //! @name Initialization Methods - For Internal use
+ /*!
+ * The following methods are used in the process of constructing
+ * the phase and setting its parameters from a specification in an
+ * input file. They are not normally used in application programs.
+ * To see how they are used, see importPhase().
+ */
+ //@{
+
+ virtual bool addSpecies(shared_ptr spec);
+ virtual void initThermo();
+
+ //! Retrieve a and b coefficients by looking up tabulated critical parameters
+ /*!
+ * If pureFluidParameters are not provided for any species in the phase,
+ * consult the critical properties tabulated in data/inputs/critProperties.xml.
+ * If the species is found there, calculate pure fluid parameters a_k and b_k as:
+ * \f[ a_k = 0.4278 R^2 T_c^2 / P_c \f]
+ *
+ * and:
+ * \f[ b_k = 0.08664 R T_c/ P_c \f]
+ *
+ * @param iName Name of the species
+ */
+ virtual vector_fp getCoeff(const std::string& iName);
+
+ //! Set the pure fluid interaction parameters for a species
+ /*!
+ * @param species Name of the species
+ * @param a "a" parameter in the Peng-Robinson model [Pa-m^6/kmol^2]
+ * @param b "b" parameter in the Peng-Robinson model [m^3/kmol]
+ * @param w acentric factor
+ */
+ void setSpeciesCoeffs(const std::string& species, double a, double b,
+ double w);
+
+ //! Set values for the interaction parameter between two species
+ /*!
+ * The "a" parameter for interactions between species *i* and *j* is
+ * assumed by default to be computed as:
+ * \f[ a_{ij} = \sqrt{a_{i, 0} a_{j, 0}} + \sqrt{a_{i, 1} a_{j, 1}} T \f]
+ *
+ * @param species_i Name of one species
+ * @param species_j Name of the other species
+ * @param a constant term in the "a" expression [Pa-m^6/kmol^2]
+ * @param alpha dimensionless function of T_r and \omega
+ */
+ void setBinaryCoeffs(const std::string& species_i,
+ const std::string& species_j, double a);
+
+protected:
+ // Special functions inherited from MixtureFugacityTP
+ virtual double sresid() const;
+ virtual double hresid() const;
+
+ virtual double liquidVolEst(double TKelvin, double& pres) const;
+ virtual double densityCalc(double TKelvin, double pressure, int phase, double rhoguess);
+
+ virtual double densSpinodalLiquid() const;
+ virtual double densSpinodalGas() const;
+ virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const;
+
+ // Special functions not inherited from MixtureFugacityTP
+
+ //! Calculate temperature derivative d(a*alpha)/dT
+ /*!
+ * These are stored internally.
+ */
+ double daAlpha_dT() const;
+
+ //! Calculate second derivative d2(a*alpha)/dT2
+ /*!
+ * These are stored internally.
+ */
+ double d2aAlpha_dT2() const;
+
+public:
+
+ //! Calculate dpdV and dpdT at the current conditions
+ /*!
+ * These are stored internally.
+ */
+ void calculatePressureDerivatives() const;
+
+ //! Update the a, b and alpha parameters
+ /*!
+ * The a and the b parameters depend on the mole fraction and the
+ * parameter alpha depends on the temperature. This function updates
+ * the internal numbers based on the state of the object.
+ */
+ virtual void updateMixingExpressions();
+
+ //! Calculate the a, b and the alpha parameters given the temperature
+ /*!
+ * This function doesn't change the internal state of the object, so it is a
+ * const function. It does use the stored mole fractions in the object.
+ *
+ * @param aCalc (output) Returns the a value
+ * @param bCalc (output) Returns the b value.
+ * @param aAlpha (output) Returns the (a*alpha) value.
+ */
+ void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const;
+
+ void calcCriticalConditions(double& pc, double& tc, double& vc) const;
+
+ //! Prepare variables and call the function to solve the cubic equation of state
+ int solveCubic(double T, double pres, double a, double b, double aAlpha,
+ double Vroot[3]) const;
+protected:
+ //! Value of b in the equation of state
+ /*!
+ * m_b is a function the mole fractions and species-specific b values.
+ */
+ double m_b;
+
+ //! Value of a and alpha in the equation of state
+ /*!
+ * m_aAlpha_mix is a function of the temperature and the mole fractions. m_a depends only on the mole fractions.
+ */
+ double m_a;
+ double m_aAlpha_mix;
+
+ // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk
+ vector_fp m_b_coeffs;
+ vector_fp m_kappa;
+ mutable vector_fp m_dalphadT;
+ mutable vector_fp m_d2alphadT2;
+ vector_fp m_alpha;
+
+ //Matrices for Binary coefficients a_{i,j} and {a*alpha}_{i.j} are saved in an Array form. Length = (m_kk, m_kk)
+ Array2D m_a_coeffs;
+ Array2D m_aAlpha_binary;
+
+ int m_NSolns;
+
+ double m_Vroot[3];
+
+ //! Temporary storage - length = m_kk.
+ mutable vector_fp m_pp;
+
+ // Partial molar volumes of the species
+ mutable vector_fp m_partialMolarVolumes;
+
+ //! The derivative of the pressure with respect to the volume
+ /*!
+ * Calculated at the current conditions. temperature and mole number kept
+ * constant
+ */
+ mutable double m_dpdV;
+
+ //! The derivative of the pressure with respect to the temperature
+ /*!
+ * Calculated at the current conditions. Total volume and mole number kept
+ * constant
+ */
+ mutable double m_dpdT;
+
+ //! Vector of derivatives of pressure with respect to mole number
+ /*!
+ * Calculated at the current conditions. Total volume, temperature and
+ * other mole number kept constant
+ */
+ mutable vector_fp m_dpdni;
+
+private:
+ //! Omega constants: a0 (= omega_a) and b0 (= omega_b) values used in Peng-Robinson equation of state
+ /*!
+ * These values are calculated by solving P-R cubic equation at the critical point.
+ */
+ static const double omega_a;
+
+ //! Omega constant for b
+ static const double omega_b;
+
+ //! Omega constant for the critical molar volume
+ static const double omega_vc;
+};
+}
+
+#endif
diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h
index d6317e33d9..f28b58bb13 100644
--- a/include/cantera/thermo/RedlichKwongMFTP.h
+++ b/include/cantera/thermo/RedlichKwongMFTP.h
@@ -46,13 +46,9 @@ class RedlichKwongMFTP : public MixtureFugacityTP
}
//! @name Molar Thermodynamic properties
- //! @{
-
- virtual doublereal enthalpy_mole() const;
- virtual doublereal entropy_mole() const;
+ //! @{
virtual doublereal cp_mole() const;
virtual doublereal cv_mole() const;
-
//! @}
//! @name Mechanical Properties
//! @{
@@ -71,32 +67,7 @@ class RedlichKwongMFTP : public MixtureFugacityTP
// @}
-protected:
- /**
- * Calculate the density of the mixture using the partial molar volumes and
- * mole fractions as input
- *
- * The formula for this is
- *
- * \f[
- * \rho = \frac{\sum_k{X_k W_k}}{\sum_k{X_k V_k}}
- * \f]
- *
- * where \f$X_k\f$ are the mole fractions, \f$W_k\f$ are the molecular
- * weights, and \f$V_k\f$ are the pure species molar volumes.
- *
- * Note, the basis behind this formula is that in an ideal solution the
- * partial molar volumes are equal to the species standard state molar
- * volumes. The species molar volumes may be functions of temperature and
- * pressure.
- */
- virtual void calcDensity();
-
- virtual void setTemperature(const doublereal temp);
- virtual void compositionChanged();
-
public:
- virtual void getActivityConcentrations(doublereal* c) const;
//! Returns the standard concentration \f$ C^0_k \f$, which is used to
//! normalize the generalized concentration.
@@ -149,16 +120,6 @@ class RedlichKwongMFTP : public MixtureFugacityTP
virtual void getPartialMolarCp(doublereal* cpbar) const;
virtual void getPartialMolarVolumes(doublereal* vbar) const;
- //@}
- /// @name Critical State Properties.
- //@{
-
- virtual doublereal critTemperature() const;
- virtual doublereal critPressure() const;
- virtual doublereal critVolume() const;
- virtual doublereal critCompressibility() const;
- virtual doublereal critDensity() const;
-
public:
//@}
//! @name Initialization Methods - For Internal use
@@ -245,11 +206,10 @@ class RedlichKwongMFTP : public MixtureFugacityTP
public:
virtual doublereal liquidVolEst(doublereal TKelvin, doublereal& pres) const;
- virtual doublereal densityCalc(doublereal TKelvin, doublereal pressure, int phase, doublereal rhoguess);
+ virtual doublereal densityCalc(doublereal T, doublereal pressure, int phase, doublereal rhoguess);
virtual doublereal densSpinodalLiquid() const;
virtual doublereal densSpinodalGas() const;
- virtual doublereal pressureCalc(doublereal TKelvin, doublereal molarVol) const;
virtual doublereal dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const;
//! Calculate dpdV and dpdT at the current conditions
@@ -258,15 +218,13 @@ class RedlichKwongMFTP : public MixtureFugacityTP
*/
void pressureDerivatives() const;
- virtual void updateMixingExpressions();
-
//! Update the a and b parameters
/*!
* The a and the b parameters depend on the mole fraction and the
* temperature. This function updates the internal numbers based on the
* state of the object.
*/
- void updateAB();
+ virtual void updateMixingExpressions();
//! Calculate the a and the b parameters given the temperature
/*!
@@ -283,21 +241,10 @@ class RedlichKwongMFTP : public MixtureFugacityTP
doublereal da_dt() const;
- void calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff,
- doublereal& pc, doublereal& tc, doublereal& vc) const;
+ void calcCriticalConditions(doublereal& pc, doublereal& tc, doublereal& vc) const;
- //! Solve the cubic equation of state
- /*!
- * The R-K equation of state may be solved via the following formula:
- *
- * V**3 - V**2(RT/P) - V(RTb/P - a/(P T**.5) + b*b) - (a b / (P T**.5)) = 0
- *
- * Returns the number of solutions found. If it only finds the liquid
- * branch solution, it will return a -1 or a -2 instead of 1 or 2. If it
- * returns 0, then there is an error.
- */
- int NicholsSolve(double TKelvin, double pres, doublereal a, doublereal b,
- doublereal Vroot[3]) const;
+ //! Prepare variables and call the function to solve the cubic equation of state
+ int solveCubic(double T, double pres, double a, double b, double Vroot[3]) const;
protected:
//! Form of the temperature parameterization
@@ -331,9 +278,6 @@ class RedlichKwongMFTP : public MixtureFugacityTP
//! Temporary storage - length = m_kk.
mutable vector_fp m_pp;
- //! Temporary storage - length = m_kk.
- mutable vector_fp m_tmpV;
-
// Partial molar volumes of the species
mutable vector_fp m_partialMolarVolumes;
@@ -358,7 +302,7 @@ class RedlichKwongMFTP : public MixtureFugacityTP
*/
mutable vector_fp dpdni_;
-public:
+private:
//! Omega constant for a -> value of a in terms of critical properties
/*!
* this was calculated from a small nonlinear solve
diff --git a/interfaces/cython/cantera/ctml_writer.py b/interfaces/cython/cantera/ctml_writer.py
index 5c65bd1c85..5e750088db 100644
--- a/interfaces/cython/cantera/ctml_writer.py
+++ b/interfaces/cython/cantera/ctml_writer.py
@@ -853,11 +853,11 @@ def build(self,a):
f= a.addChild("pureFluidParameters")
f['species'] = self._species
s = '%.10g, %.10g\n' % (self._acoeff[0], self._acoeff[1])
- ac = f.addChild("a_coeff",s)
+ ac = f.addChild("a_coeff", s)
ac["units"] = _upres+'-'+_ulen+'6/'+_umol+'2'
ac["model"] = "linear_a"
s = '%.10g\n' % self._bcoeff
- bc = f.addChild("b_coeff",s)
+ bc = f.addChild("b_coeff", s)
bc["units"] = _ulen+'3/'+_umol
@@ -872,12 +872,12 @@ def build(self,a):
f["species2"] = self._species2
f["species1"] = self._species1
s = '%.10g, %.10g\n' % (self._acoeff[0], self._acoeff[1])
- ac = f.addChild("a_coeff",s)
+ ac = f.addChild("a_coeff", s)
ac["units"] = _upres+'-'+_ulen+'6/'+_umol+'2'
ac["model"] = "linear_a"
if self._bcoeff:
s = '%.10g\n' % self._bcoeff
- bc = f.addChild("b_coeff",s)
+ bc = f.addChild("b_coeff", s)
bc["units"] = _ulen+'3/'+_umol
@@ -2397,7 +2397,6 @@ def build(self, p):
k = ph.addChild("kinetics")
k['model'] = self._kin
-
class ideal_interface(phase):
"""A chemically-reacting ideal surface solution of multiple species."""
def __init__(self,
diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py
index 05d74c1852..51a63eaccc 100644
--- a/interfaces/cython/cantera/test/test_thermo.py
+++ b/interfaces/cython/cantera/test/test_thermo.py
@@ -913,7 +913,6 @@ def test_nondimensional(self):
self.assertNear(np.dot(g.standard_gibbs_RT, g.X) - Smix_R,
g.gibbs_mole / (R*g.T))
-
class TestInterfacePhase(utilities.CanteraTest):
def setUp(self):
self.gas = ct.Solution('diamond.xml', 'gas')
diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp
index b16d3e8357..46d1fa435d 100644
--- a/src/thermo/MixtureFugacityTP.cpp
+++ b/src/thermo/MixtureFugacityTP.cpp
@@ -19,8 +19,7 @@ namespace Cantera
MixtureFugacityTP::MixtureFugacityTP() :
iState_(FLUID_GAS),
- forcedState_(FLUID_UNDEFINED),
- m_Tlast_ref(-1.0)
+ forcedState_(FLUID_UNDEFINED)
{
}
@@ -44,6 +43,23 @@ int MixtureFugacityTP::reportSolnBranchActual() const
return iState_;
}
+// ---- Molar Thermodynamic Properties ---------------------------
+double MixtureFugacityTP::enthalpy_mole() const
+{
+ double h_ideal = RT() * mean_X(m_h0_RT);
+ double h_nonideal = hresid();
+ return h_ideal + h_nonideal;
+}
+
+
+double MixtureFugacityTP::entropy_mole() const
+{
+ double s_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx()
+ - std::log(pressure()/refPressure()));
+ double s_nonideal = sresid();
+ return s_ideal + s_nonideal;
+}
+
// ---- Partial Molar Properties of the Solution -----------------
void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const
@@ -58,7 +74,6 @@ void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const
void MixtureFugacityTP::getStandardChemPotentials(doublereal* g) const
{
- _updateReferenceStateThermo();
copy(m_g0_RT.begin(), m_g0_RT.end(), g);
double tmp = log(pressure() / refPressure());
for (size_t k = 0; k < m_kk; k++) {
@@ -73,7 +88,6 @@ void MixtureFugacityTP::getEnthalpy_RT(doublereal* hrt) const
void MixtureFugacityTP::getEntropy_R(doublereal* sr) const
{
- _updateReferenceStateThermo();
copy(m_s0_R.begin(), m_s0_R.end(), sr);
double tmp = log(pressure() / refPressure());
for (size_t k = 0; k < m_kk; k++) {
@@ -83,7 +97,6 @@ void MixtureFugacityTP::getEntropy_R(doublereal* sr) const
void MixtureFugacityTP::getGibbs_RT(doublereal* grt) const
{
- _updateReferenceStateThermo();
copy(m_g0_RT.begin(), m_g0_RT.end(), grt);
double tmp = log(pressure() / refPressure());
for (size_t k = 0; k < m_kk; k++) {
@@ -93,7 +106,6 @@ void MixtureFugacityTP::getGibbs_RT(doublereal* grt) const
void MixtureFugacityTP::getPureGibbs(doublereal* g) const
{
- _updateReferenceStateThermo();
scale(m_g0_RT.begin(), m_g0_RT.end(), g, RT());
double tmp = log(pressure() / refPressure()) * RT();
for (size_t k = 0; k < m_kk; k++) {
@@ -103,7 +115,6 @@ void MixtureFugacityTP::getPureGibbs(doublereal* g) const
void MixtureFugacityTP::getIntEnergy_RT(doublereal* urt) const
{
- _updateReferenceStateThermo();
copy(m_h0_RT.begin(), m_h0_RT.end(), urt);
for (size_t i = 0; i < m_kk; i++) {
urt[i] -= 1.0;
@@ -112,13 +123,11 @@ void MixtureFugacityTP::getIntEnergy_RT(doublereal* urt) const
void MixtureFugacityTP::getCp_R(doublereal* cpr) const
{
- _updateReferenceStateThermo();
copy(m_cp0_R.begin(), m_cp0_R.end(), cpr);
}
void MixtureFugacityTP::getStandardVolumes(doublereal* vol) const
{
- _updateReferenceStateThermo();
for (size_t i = 0; i < m_kk; i++) {
vol[i] = RT() / pressure();
}
@@ -128,13 +137,11 @@ void MixtureFugacityTP::getStandardVolumes(doublereal* vol) const
void MixtureFugacityTP::getEnthalpy_RT_ref(doublereal* hrt) const
{
- _updateReferenceStateThermo();
copy(m_h0_RT.begin(), m_h0_RT.end(), hrt);
}
void MixtureFugacityTP::getGibbs_RT_ref(doublereal* grt) const
{
- _updateReferenceStateThermo();
copy(m_g0_RT.begin(), m_g0_RT.end(), grt);
}
@@ -146,25 +153,21 @@ void MixtureFugacityTP::getGibbs_ref(doublereal* g) const
const vector_fp& MixtureFugacityTP::gibbs_RT_ref() const
{
- _updateReferenceStateThermo();
return m_g0_RT;
}
void MixtureFugacityTP::getEntropy_R_ref(doublereal* er) const
{
- _updateReferenceStateThermo();
copy(m_s0_R.begin(), m_s0_R.end(), er);
}
void MixtureFugacityTP::getCp_R_ref(doublereal* cpr) const
{
- _updateReferenceStateThermo();
copy(m_cp0_R.begin(), m_cp0_R.end(), cpr);
}
void MixtureFugacityTP::getStandardVolumes_ref(doublereal* vol) const
{
- _updateReferenceStateThermo();
for (size_t i = 0; i < m_kk; i++) {
vol[i]= RT() / refPressure();
}
@@ -198,7 +201,7 @@ void MixtureFugacityTP::setStateFromXML(const XML_Node& state)
double rho = getFloat(state, "density", "density");
setState_TR(t, rho);
} else if (doTP) {
- double rho = Phase::density();
+ double rho = density();
setState_TR(t, rho);
}
}
@@ -222,113 +225,91 @@ bool MixtureFugacityTP::addSpecies(shared_ptr spec)
void MixtureFugacityTP::setTemperature(const doublereal temp)
{
+ Phase::setTemperature(temp);
_updateReferenceStateThermo();
- setState_TR(temperature(), density());
-}
-
-void MixtureFugacityTP::setPressure(doublereal p)
-{
- setState_TP(temperature(), p);
- }
-
-void MixtureFugacityTP::compositionChanged()
-{
- Phase::compositionChanged();
- getMoleFractions(moleFractions_.data());
-}
-
-void MixtureFugacityTP::setMoleFractions_NoState(const doublereal* const x)
-{
- Phase::setMoleFractions(x);
- getMoleFractions(moleFractions_.data());
+ // depends on mole fraction and temperature
updateMixingExpressions();
+ iState_ = phaseState(true);
}
-void MixtureFugacityTP::calcDensity()
-{
- throw NotImplementedError("MixtureFugacityTP::calcDensity");
-}
-
-void MixtureFugacityTP::setState_TP(doublereal t, doublereal pres)
+void MixtureFugacityTP::setPressure(doublereal p)
{
// A pretty tricky algorithm is needed here, due to problems involving
// standard states of real fluids. For those cases you need to combine the T
// and P specification for the standard state, or else you may venture into
// the forbidden zone, especially when nearing the triple point. Therefore,
// we need to do the standard state thermo calc with the (t, pres) combo.
- getMoleFractions(moleFractions_.data());
-
- Phase::setTemperature(t);
- _updateReferenceStateThermo();
- // Depends on the mole fractions and the temperature
- updateMixingExpressions();
+ double t = temperature();
+ double rhoNow = density();
if (forcedState_ == FLUID_UNDEFINED) {
- double rhoNow = Phase::density();
- double rho = densityCalc(t, pres, iState_, rhoNow);
+ double rho = densityCalc(t, p, iState_, rhoNow);
if (rho > 0.0) {
- Phase::setDensity(rho);
+ setDensity(rho);
iState_ = phaseState(true);
} else {
if (rho < -1.5) {
- rho = densityCalc(t, pres, FLUID_UNDEFINED , rhoNow);
+ rho = densityCalc(t, p, FLUID_UNDEFINED , rhoNow);
if (rho > 0.0) {
- Phase::setDensity(rho);
+ setDensity(rho);
iState_ = phaseState(true);
} else {
- throw CanteraError("MixtureFugacityTP::setState_TP", "neg rho");
+ throw CanteraError("MixtureFugacityTP::setPressure",
+ "neg rho");
}
} else {
- throw CanteraError("MixtureFugacityTP::setState_TP", "neg rho");
+ throw CanteraError("MixtureFugacityTP::setPressure",
+ "neg rho");
}
}
} else if (forcedState_ == FLUID_GAS) {
// Normal density calculation
if (iState_ < FLUID_LIQUID_0) {
- double rhoNow = Phase::density();
- double rho = densityCalc(t, pres, iState_, rhoNow);
+ double rho = densityCalc(t, p, iState_, rhoNow);
if (rho > 0.0) {
- Phase::setDensity(rho);
+ setDensity(rho);
iState_ = phaseState(true);
if (iState_ >= FLUID_LIQUID_0) {
- throw CanteraError("MixtureFugacityTP::setState_TP", "wrong state");
+ throw CanteraError("MixtureFugacityTP::setPressure",
+ "wrong state");
}
} else {
- throw CanteraError("MixtureFugacityTP::setState_TP", "neg rho");
+ throw CanteraError("MixtureFugacityTP::setPressure",
+ "neg rho");
}
}
} else if (forcedState_ > FLUID_LIQUID_0) {
if (iState_ >= FLUID_LIQUID_0) {
- double rhoNow = Phase::density();
- double rho = densityCalc(t, pres, iState_, rhoNow);
+ double rho = densityCalc(t, p, iState_, rhoNow);
if (rho > 0.0) {
- Phase::setDensity(rho);
+ setDensity(rho);
iState_ = phaseState(true);
if (iState_ == FLUID_GAS) {
- throw CanteraError("MixtureFugacityTP::setState_TP", "wrong state");
+ throw CanteraError("MixtureFugacityTP::setPressure",
+ "wrong state");
}
} else {
- throw CanteraError("MixtureFugacityTP::setState_TP", "neg rho");
+ throw CanteraError("MixtureFugacityTP::setPressure",
+ "neg rho");
}
}
}
}
-void MixtureFugacityTP::setState_TR(doublereal T, doublereal rho)
+void MixtureFugacityTP::compositionChanged()
{
+ Phase::compositionChanged();
getMoleFractions(moleFractions_.data());
- Phase::setTemperature(T);
- _updateReferenceStateThermo();
- Phase::setDensity(rho);
- // depends on mole fraction and temperature
updateMixingExpressions();
- iState_ = phaseState(true);
}
-void MixtureFugacityTP::setState_TPX(doublereal t, doublereal p, const doublereal* x)
+void MixtureFugacityTP::getActivityConcentrations(doublereal* c) const
{
- setMoleFractions_NoState(x);
- setState_TP(t,p);
+ getActivityCoefficients(c);
+ double p_RT = pressure() / RT();
+ for (size_t k = 0; k < m_kk; k++) {
+ c[k] *= moleFraction(k)*p_RT;
+ }
}
doublereal MixtureFugacityTP::z() const
@@ -491,7 +472,8 @@ doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa,
double densBase = 0.0;
if (! conv) {
molarVolBase = 0.0;
- throw CanteraError("MixtureFugacityTP::densityCalc", "Process did not converge");
+ throw CanteraError("MixtureFugacityTP::densityCalc",
+ "Process did not converge");
} else {
densBase = mmw / molarVolBase;
}
@@ -795,9 +777,9 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const
// If the temperature has changed since the last time these
// properties were computed, recompute them.
- if (m_Tlast_ref != Tnow) {
+ if (m_tlast != Tnow) {
m_spthermo.update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]);
- m_Tlast_ref = Tnow;
+ m_tlast = Tnow;
// update the species Gibbs functions
for (size_t k = 0; k < m_kk; k++) {
@@ -805,15 +787,254 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const
}
doublereal pref = refPressure();
if (pref <= 0.0) {
- throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", "neg ref pressure");
+ throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo",
+ "negative reference pressure");
}
}
}
-void MixtureFugacityTP::invalidateCache()
+double MixtureFugacityTP::critTemperature() const
{
- ThermoPhase::invalidateCache();
- m_Tlast_ref += 0.001234;
+ double pc, tc, vc;
+ calcCriticalConditions(pc, tc, vc);
+ return tc;
+}
+
+double MixtureFugacityTP::critPressure() const
+{
+ double pc, tc, vc;
+ calcCriticalConditions(pc, tc, vc);
+ return pc;
+}
+
+double MixtureFugacityTP::critVolume() const
+{
+ double pc, tc, vc;
+ calcCriticalConditions(pc, tc, vc);
+ return vc;
+}
+
+double MixtureFugacityTP::critCompressibility() const
+{
+ double pc, tc, vc;
+ calcCriticalConditions(pc, tc, vc);
+ return pc*vc/tc/GasConstant;
+}
+
+double MixtureFugacityTP::critDensity() const
+{
+ double pc, tc, vc;
+ calcCriticalConditions(pc, tc, vc);
+ double mmw = meanMolecularWeight();
+ return mmw / vc;
+}
+
+void MixtureFugacityTP::calcCriticalConditions(double& pc, double& tc, double& vc) const
+{
+ throw NotImplementedError("MixtureFugacityTP::calcCriticalConditions");
+}
+
+int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b,
+ double aAlpha, double Vroot[3], double an,
+ double bn, double cn, double dn, double tc, double vc) const
+{
+ fill_n(Vroot, 3, 0.0);
+ if (T <= 0.0) {
+ throw CanteraError("MixtureFugacityTP::solveCubic",
+ "negative temperature T = {}", T);
+ }
+
+ // Derive the center of the cubic, x_N
+ double xN = - bn /(3 * an);
+
+ // Derive the value of delta**2. This is a key quantity that determines the number of turning points
+ double delta2 = (bn * bn - 3 * an * cn) / (9 * an * an);
+ double delta = 0.0;
+
+ // Calculate a couple of ratios
+ // Cubic equation in z : z^3 - (1-B) z^2 + (A -2B -3B^2)z - (AB- B^2- B^3) = 0
+ double ratio1 = 3.0 * an * cn / (bn * bn);
+ double ratio2 = pres * b / (GasConstant * T); // B
+ if (fabs(ratio1) < 1.0E-7) {
+ double ratio3 = aAlpha / (GasConstant * T) * pres / (GasConstant * T); // A
+ if (fabs(ratio2) < 1.0E-5 && fabs(ratio3) < 1.0E-5) {
+ // A and B terms in cubic equation for z are almost zero, then z is near to 1
+ double zz = 1.0;
+ for (int i = 0; i < 10; i++) {
+ double znew = zz / (zz - ratio2) - ratio3 / (zz + ratio1);
+ double deltaz = znew - zz;
+ zz = znew;
+ if (fabs(deltaz) < 1.0E-14) {
+ break;
+ }
+ }
+ double v = zz * GasConstant * T / pres;
+ Vroot[0] = v;
+ return 1;
+ }
+ }
+
+ int nSolnValues; // Represents number of solutions to the cubic equation
+ double h2 = 4. * an * an * delta2 * delta2 * delta2; // h^2
+ if (delta2 > 0.0) {
+ delta = sqrt(delta2);
+ }
+
+ double h = 2.0 * an * delta * delta2;
+ double yN = 2.0 * bn * bn * bn / (27.0 * an * an) - bn * cn / (3.0 * an) + dn; // y_N term
+ double disc = yN * yN - h2; // discriminant
+
+ //check if y = h
+ if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) {
+ if (disc > 1e-10) {
+ throw CanteraError("MixtureFugacityTP::solveCubic",
+ "value of yN and h are too high, unrealistic roots may be obtained");
+ }
+ disc = 0.0;
+ }
+
+ if (disc < -1e-14) {
+ // disc<0 then we have three distinct roots.
+ nSolnValues = 3;
+ } else if (fabs(disc) < 1e-14) {
+ // disc=0 then we have two distinct roots (third one is repeated root)
+ nSolnValues = 2;
+ // We are here as p goes to zero.
+ } else if (disc > 1e-14) {
+ // disc> 0 then we have one real root.
+ nSolnValues = 1;
+ }
+
+ double tmp;
+ // One real root -> have to determine whether gas or liquid is the root
+ if (disc > 0.0) {
+ double tmpD = sqrt(disc);
+ double tmp1 = (- yN + tmpD) / (2.0 * an);
+ double sgn1 = 1.0;
+ if (tmp1 < 0.0) {
+ sgn1 = -1.0;
+ tmp1 = -tmp1;
+ }
+ double tmp2 = (- yN - tmpD) / (2.0 * an);
+ double sgn2 = 1.0;
+ if (tmp2 < 0.0) {
+ sgn2 = -1.0;
+ tmp2 = -tmp2;
+ }
+ double p1 = pow(tmp1, 1./3.);
+ double p2 = pow(tmp2, 1./3.);
+ double alpha = xN + sgn1 * p1 + sgn2 * p2;
+ Vroot[0] = alpha;
+ Vroot[1] = 0.0;
+ Vroot[2] = 0.0;
+ } else if (disc < 0.0) {
+ // Three real roots alpha, beta, gamma are obtained.
+ double val = acos(-yN / h);
+ double theta = val / 3.0;
+ double twoThirdPi = 2. * Pi / 3.;
+ double alpha = xN + 2. * delta * cos(theta);
+ double beta = xN + 2. * delta * cos(theta + twoThirdPi);
+ double gamma = xN + 2. * delta * cos(theta + 2.0 * twoThirdPi);
+ Vroot[0] = beta;
+ Vroot[1] = gamma;
+ Vroot[2] = alpha;
+
+ for (int i = 0; i < 3; i++) {
+ tmp = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
+ if (fabs(tmp) > 1.0E-4) {
+ for (int j = 0; j < 3; j++) {
+ if (j != i && fabs(Vroot[i] - Vroot[j]) < 1.0E-4 * (fabs(Vroot[i]) + fabs(Vroot[j]))) {
+ writelog("MixtureFugacityTP::solveCubic(T ={}, p ={}):"
+ " WARNING roots have merged: {}, {}\n",
+ T, pres, Vroot[i], Vroot[j]);
+ }
+ }
+ }
+ }
+ } else if (disc == 0.0) {
+ //Three equal roots are obtained, i.e. alpha = beta = gamma
+ if (yN < 1e-18 && h < 1e-18) {
+ // yN = 0.0 and h = 0 i.e. disc = 0
+ Vroot[0] = xN;
+ Vroot[1] = xN;
+ Vroot[2] = xN;
+ } else {
+ // h and yN need to figure out whether delta^3 is positive or negative
+ if (yN > 0.0) {
+ tmp = pow(yN/(2*an), 1./3.);
+ // In this case, tmp and delta must be equal.
+ if (fabs(tmp - delta) > 1.0E-9) {
+ throw CanteraError("MixtureFugacityTP::solveCubic",
+ "Inconsistency in solver: solver is ill-conditioned.");
+ }
+ Vroot[1] = xN + delta;
+ Vroot[0] = xN - 2.0*delta; // liquid phase root
+ } else {
+ tmp = pow(yN/(2*an), 1./3.);
+ // In this case, tmp and delta must be equal.
+ if (fabs(tmp - delta) > 1.0E-9) {
+ throw CanteraError("MixtureFugacityTP::solveCubic",
+ "Inconsistency in solver: solver is ill-conditioned.");
+ }
+ delta = -delta;
+ Vroot[0] = xN + delta;
+ Vroot[1] = xN - 2.0*delta; // gas phase root
+ }
+ }
+ }
+
+ // Find an accurate root, since there might be a heavy amount of roundoff error due to bad conditioning in this solver.
+ double res, dresdV = 0.0;
+ for (int i = 0; i < nSolnValues; i++) {
+ for (int n = 0; n < 20; n++) {
+ res = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
+ if (fabs(res) < 1.0E-14) { // accurate root is obtained
+ break;
+ }
+ dresdV = 3.0 * an * Vroot[i] * Vroot[i] + 2.0 * bn * Vroot[i] + cn; // derivative of the residual
+ double del = - res / dresdV;
+ Vroot[i] += del;
+ if (fabs(del) / (fabs(Vroot[i]) + fabs(del)) < 1.0E-14) {
+ break;
+ }
+ double res2 = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
+ if (fabs(res2) < fabs(res)) {
+ continue;
+ } else {
+ Vroot[i] -= del; // Go back to previous value of Vroot.
+ Vroot[i] += 0.1 * del; // under-relax by 0.1
+ }
+ }
+ if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) {
+ writelog("MixtureFugacityTP::solveCubic(T = {}, p = {}): "
+ "WARNING root didn't converge V = {}", T, pres, Vroot[i]);
+ writelogendl();
+ }
+ }
+
+ if (nSolnValues == 1) {
+ // Determine the phase of the single root.
+ // nSolnValues = 1 represents the gas phase by default.
+ if (T > tc) {
+ if (Vroot[0] < vc) {
+ // Supercritical phase
+ nSolnValues = -1;
+ }
+ } else {
+ if (Vroot[0] < xN) {
+ //Liquid phase
+ nSolnValues = -1;
+ }
+ }
+ } else {
+ // Determine if we have two distinct roots or three equal roots
+ // nSolnValues = 2 represents 2 equal roots by default.
+ if (nSolnValues == 2 && delta > 1e-14) {
+ //If delta > 0, we have two distinct roots (and one repeated root)
+ nSolnValues = -2;
+ }
+ }
+ return nSolnValues;
}
}
diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp
new file mode 100644
index 0000000000..f4032cdb20
--- /dev/null
+++ b/src/thermo/PengRobinson.cpp
@@ -0,0 +1,845 @@
+//! @file PengRobinson.cpp
+
+// This file is part of Cantera. See License.txt in the top-level directory or
+// at https://cantera.org/license.txt for license and copyright information.
+
+#include "cantera/thermo/PengRobinson.h"
+#include "cantera/thermo/ThermoFactory.h"
+#include "cantera/thermo/Species.h"
+#include "cantera/base/stringUtils.h"
+#include "cantera/base/ctml.h"
+
+#include
+
+#define _USE_MATH_DEFINES
+#include
+
+using namespace std;
+namespace bmt = boost::math::tools;
+
+namespace Cantera
+{
+
+const double PengRobinson::omega_a = 4.5723552892138218E-01;
+const double PengRobinson::omega_b = 7.77960739038885E-02;
+const double PengRobinson::omega_vc = 3.07401308698703833E-01;
+
+PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) :
+ m_b(0.0),
+ m_a(0.0),
+ m_aAlpha_mix(0.0),
+ m_NSolns(0),
+ m_dpdV(0.0),
+ m_dpdT(0.0)
+{
+ fill_n(m_Vroot, 3, 0.0);
+ initThermoFile(infile, id_);
+}
+
+void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double b, double w)
+{
+ size_t k = speciesIndex(species);
+ if (k == npos) {
+ throw CanteraError("PengRobinson::setSpeciesCoeffs",
+ "Unknown species '{}'.", species);
+ }
+
+ // Calculate value of kappa (independent of temperature)
+ // w is an acentric factor of species
+ if (w <= 0.491) {
+ m_kappa[k] = 0.37464 + 1.54226*w - 0.26992*w*w;
+ } else {
+ m_kappa[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w;
+ }
+
+ //Calculate alpha (temperature dependent interaction parameter)
+ double critTemp = speciesCritTemperature(a, b); // critical temperature of individual species
+ double sqt_T_r = sqrt(temperature() / critTemp);
+ double sqt_alpha = 1 + m_kappa[k] * (1 - sqt_T_r);
+ m_alpha[k] = sqt_alpha*sqt_alpha;
+
+ m_a_coeffs(k,k) = a;
+ double aAlpha_k = a*m_alpha[k];
+ m_aAlpha_binary(k,k) = aAlpha_k;
+
+ // standard mixing rule for cross-species interaction term
+ for (size_t j = 0; j < m_kk; j++) {
+ if (k == j) {
+ continue;
+ }
+ double a0kj = sqrt(m_a_coeffs(j,j) * a);
+ double aAlpha_j = a*m_alpha[j];
+ double a_Alpha = sqrt(aAlpha_j*aAlpha_k);
+ if (m_a_coeffs(j, k) == 0) {
+ m_a_coeffs(j, k) = a0kj;
+ m_aAlpha_binary(j, k) = a_Alpha;
+ m_a_coeffs(k, j) = a0kj;
+ m_aAlpha_binary(k, j) = a_Alpha;
+ }
+ }
+ m_b_coeffs[k] = b;
+}
+
+void PengRobinson::setBinaryCoeffs(const std::string& species_i,
+ const std::string& species_j, double a0)
+{
+ size_t ki = speciesIndex(species_i);
+ if (ki == npos) {
+ throw CanteraError("PengRobinson::setBinaryCoeffs",
+ "Unknown species '{}'.", species_i);
+ }
+ size_t kj = speciesIndex(species_j);
+ if (kj == npos) {
+ throw CanteraError("PengRobinson::setBinaryCoeffs",
+ "Unknown species '{}'.", species_j);
+ }
+
+ m_a_coeffs(ki, kj) = m_a_coeffs(kj, ki) = a0;
+ // Calculate alpha_ij
+ double alpha_ij = m_alpha[ki] * m_alpha[kj];
+ m_aAlpha_binary(ki, kj) = m_aAlpha_binary(kj, ki) = a0*alpha_ij;
+}
+
+// ------------Molar Thermodynamic Properties -------------------------
+
+double PengRobinson::cp_mole() const
+{
+ _updateReferenceStateThermo();
+ double T = temperature();
+ double mv = molarVolume();
+ double vpb = mv + (1 + M_SQRT2)*m_b;
+ double vmb = mv + (1 - M_SQRT2)*m_b;
+ calculatePressureDerivatives();
+ double cpref = GasConstant * mean_X(m_cp0_R);
+ double dHdT_V = cpref + mv * m_dpdT - GasConstant
+ + 1.0 / (2.0 * M_SQRT2 * m_b) * log(vpb / vmb) * T * d2aAlpha_dT2();
+ return dHdT_V - (mv + T * m_dpdT / m_dpdV) * m_dpdT;
+}
+
+double PengRobinson::cv_mole() const
+{
+ _updateReferenceStateThermo();
+ double T = temperature();
+ calculatePressureDerivatives();
+ return (cp_mole() + T * m_dpdT * m_dpdT / m_dpdV);
+}
+
+double PengRobinson::pressure() const
+{
+ _updateReferenceStateThermo();
+ // Get a copy of the private variables stored in the State object
+ double T = temperature();
+ double mv = molarVolume();
+ double denom = mv * mv + 2 * mv * m_b - m_b * m_b;
+ double pp = GasConstant * T / (mv - m_b) - m_aAlpha_mix / denom;
+ return pp;
+}
+
+double PengRobinson::standardConcentration(size_t k) const
+{
+ getStandardVolumes(m_tmpV.data());
+ return 1.0 / m_tmpV[k];
+}
+
+void PengRobinson::getActivityCoefficients(double* ac) const
+{
+ double mv = molarVolume();
+ double vpb2 = mv + (1 + M_SQRT2)*m_b;
+ double vmb2 = mv + (1 - M_SQRT2)*m_b;
+ double vmb = mv - m_b;
+ double pres = pressure();
+
+ for (size_t k = 0; k < m_kk; k++) {
+ m_pp[k] = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i);
+ }
+ }
+ double num = 0;
+ double den = 2 * M_SQRT2 * m_b * m_b;
+ double den2 = m_b * (mv * mv + 2 * mv * m_b - m_b * m_b);
+ double RTkelvin = RT();
+ for (size_t k = 0; k < m_kk; k++) {
+ num = 2 * m_b * m_pp[k] - m_aAlpha_mix * m_b_coeffs[k];
+ ac[k] = (-RTkelvin * log(pres * mv/ RTkelvin) + RTkelvin * log(mv / vmb)
+ + RTkelvin * m_b_coeffs[k] / vmb
+ - (num /den) * log(vpb2/vmb2)
+ - m_aAlpha_mix * m_b_coeffs[k] * mv/den2
+ );
+ }
+ for (size_t k = 0; k < m_kk; k++) {
+ ac[k] = exp(ac[k]/ RTkelvin);
+ }
+}
+
+// ---- Partial Molar Properties of the Solution -----------------
+
+void PengRobinson::getChemPotentials(double* mu) const
+{
+ getGibbs_ref(mu);
+ double RTkelvin = RT();
+ for (size_t k = 0; k < m_kk; k++) {
+ double xx = std::max(SmallNumber, moleFraction(k));
+ mu[k] += RTkelvin * (log(xx));
+ }
+
+ double mv = molarVolume();
+ double vmb = mv - m_b;
+ double vpb2 = mv + (1 + M_SQRT2)*m_b;
+ double vmb2 = mv + (1 - M_SQRT2)*m_b;
+
+ for (size_t k = 0; k < m_kk; k++) {
+ m_pp[k] = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i);
+ }
+ }
+ double pres = pressure();
+ double refP = refPressure();
+ double den = 2 * M_SQRT2 * m_b * m_b;
+ double den2 = m_b * (mv * mv + 2 * mv * m_b - m_b * m_b);
+
+ for (size_t k = 0; k < m_kk; k++) {
+ double num = 2 * m_b * m_pp[k] - m_aAlpha_mix * m_b_coeffs[k];
+
+ mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin)
+ + RTkelvin * log(mv / vmb)
+ + RTkelvin * m_b_coeffs[k] / vmb
+ - (num /den) * log(vpb2/vmb2)
+ - m_aAlpha_mix * m_b_coeffs[k] * mv/den2
+ );
+ }
+}
+
+void PengRobinson::getPartialMolarEnthalpies(double* hbar) const
+{
+ // First we get the reference state contributions
+ getEnthalpy_RT_ref(hbar);
+ scale(hbar, hbar+m_kk, hbar, RT());
+
+ // We calculate m_dpdni
+ double T = temperature();
+ double mv = molarVolume();
+ double vmb = mv - m_b;
+ double vpb2 = mv + (1 + M_SQRT2)*m_b;
+ double vmb2 = mv + (1 - M_SQRT2)*m_b;
+
+ for (size_t k = 0; k < m_kk; k++) {
+ m_pp[k] = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i);
+ }
+ }
+
+ double den = mv * mv + 2 * mv * m_b - m_b * m_b;
+ double den2 = den * den;
+ double RTkelvin = RT();
+ for (size_t k = 0; k < m_kk; k++) {
+ m_dpdni[k] = RTkelvin / vmb + RTkelvin * m_b_coeffs[k] / (vmb * vmb) - 2.0 * m_pp[k] / den
+ + 2 * vmb * m_aAlpha_mix * m_b_coeffs[k] / den2;
+ }
+
+ double daAlphadT = daAlpha_dT();
+ double fac = T * daAlphadT - m_aAlpha_mix;
+
+ calculatePressureDerivatives();
+ double fac2 = mv + T * m_dpdT / m_dpdV;
+ double fac3 = 2 * M_SQRT2 * m_b * m_b;
+ for (size_t k = 0; k < m_kk; k++) {
+ double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b - m_b_coeffs[k]) / fac3 * log(vpb2 / vmb2) * fac
+ + (mv * m_b_coeffs[k]) /(m_b * den) * fac;
+ hbar[k] = hbar[k] + hE_v;
+ hbar[k] -= fac2 * m_dpdni[k];
+ }
+}
+
+void PengRobinson::getPartialMolarEntropies(double* sbar) const
+{
+ getEntropy_R_ref(sbar);
+ scale(sbar, sbar+m_kk, sbar, GasConstant);
+ double T = temperature();
+ double mv = molarVolume();
+ double vmb = mv - m_b;
+ double vpb2 = mv + (1 + M_SQRT2)*m_b;
+ double vmb2 = mv + (1 - M_SQRT2)*m_b;
+ double refP = refPressure();
+ double daAlphadT = daAlpha_dT();
+ double coeff1 = 0;
+ double den1 = 2 * M_SQRT2 * m_b * m_b;
+ double den2 = mv * mv + 2 * mv * m_b - m_b * m_b;
+
+ // Calculate sum(n_j (a alpha)_i, k * (1/alpha_k d/dT(alpha_k))) -> m_pp
+ // Calculate sum(n_j (a alpha)_i, k * (1/alpha_i d/dT(alpha_i))) -> m_tmpV
+ for (size_t k = 0; k < m_kk; k++) {
+ m_pp[k] = 0.0;
+ m_tmpV[k] = 0;
+ for (size_t i = 0; i < m_kk; i++) {
+ m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i);
+ m_tmpV[k] += moleFractions_[i] * m_aAlpha_binary(k, i) * (m_dalphadT[i] / m_alpha[i]);
+ }
+ m_pp[k] = m_pp[k] * m_dalphadT[k] / m_alpha[k];
+ }
+
+
+ for (size_t k = 0; k < m_kk; k++) {
+ coeff1 = m_b * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_coeffs[k];
+ sbar[k] += GasConstant * log(GasConstant * T / (refP * mv))
+ + GasConstant
+ + GasConstant * log(mv / vmb)
+ + GasConstant * m_b_coeffs[k] / vmb
+ - coeff1 * log(vpb2 / vmb2) / den1
+ - m_b_coeffs[k] * mv * daAlphadT / den2 / m_b;
+ }
+ calculatePressureDerivatives();
+ getPartialMolarVolumes(m_partialMolarVolumes.data());
+ for (size_t k = 0; k < m_kk; k++) {
+ sbar[k] -= m_partialMolarVolumes[k] * m_dpdT;
+ }
+}
+
+void PengRobinson::getPartialMolarIntEnergies(double* ubar) const
+{
+ // u_i = h_i - p*v_i
+ double p = pressure();
+ getPartialMolarEnthalpies(ubar);
+ getPartialMolarVolumes(m_tmpV.data());
+ for (size_t k = 0; k < m_kk; k++) {
+ ubar[k] = ubar[k] - p*m_tmpV[k];
+ }
+}
+
+void PengRobinson::getPartialMolarCp(double* cpbar) const
+{
+ // First we get the reference state contributions
+ getCp_R(cpbar);
+ scale(cpbar, cpbar+m_kk, cpbar, GasConstant);
+
+ // We calculate d/dT(m_dpdni)
+ vector_fp grad_dpdni(m_kk, 0.0);
+ vector_fp grad_hi(m_kk, 0.0);
+ double T = temperature();
+ double mv = molarVolume();
+ double vmb = mv - m_b;
+ double vpb2 = mv + (1 + M_SQRT2)*m_b;
+ double vmb2 = mv + (1 - M_SQRT2)*m_b;
+
+ double grad_aAlpha = 0;
+ for (size_t k = 0; k < m_kk; k++) {
+ m_pp[k] = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ // Calculate temperature derivative of m_aAlpha_binary(i,j)
+ grad_aAlpha = m_dalphadT[i]/m_alpha[i] + m_dalphadT[k]/m_alpha[k];
+ grad_aAlpha = 0.5*m_aAlpha_binary(k, i)*grad_aAlpha;
+ m_pp[k] += moleFractions_[i] * grad_aAlpha;
+ }
+ }
+
+ double den = mv * mv + 2 * mv * m_b - m_b * m_b;
+ double den2 = den * den;
+ //double RTkelvin = RT();
+ for (size_t k = 0; k < m_kk; k++) {
+ grad_dpdni[k] = GasConstant / vmb + GasConstant * m_b_coeffs[k] / (vmb * vmb) - 2.0 * m_pp[k] / den
+ + 2 * vmb * daAlpha_dT() * m_b_coeffs[k] / den2;
+ }
+
+ double fac = T * d2aAlpha_dT2();
+ double fac2 = 2 * M_SQRT2 * m_b * m_b;
+
+ // Calculate d(hi)/dT
+ for (size_t k = 0; k < m_kk; k++) {
+ grad_hi[k] = cpbar[k] - GasConstant + mv*grad_dpdni[k]
+ + (2*m_b - m_b_coeffs[k])/fac2 * log(vpb2/vmb2) * fac
+ + (mv * m_b_coeffs[k]) /(m_b * den) * fac;
+ }
+
+ calculatePressureDerivatives();
+ double fac3 = mv + T * m_dpdT / m_dpdV;
+
+ for (size_t k = 0; k < m_kk; k++) {
+ cpbar[k] = moleFractions_[k]*grad_hi[k] - fac3*m_dpdT;
+ }
+}
+
+void PengRobinson::getPartialMolarVolumes(double* vbar) const
+{
+ for (size_t k = 0; k < m_kk; k++) {
+ m_pp[k] = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i);
+ }
+ }
+
+ double mv = molarVolume();
+ double vmb = mv - m_b;
+ double vpb = mv + m_b;
+ double fac = mv * mv + 2 * mv * m_b - m_b * m_b;
+ double fac2 = fac * fac;
+ double RTkelvin = RT();
+
+ for (size_t k = 0; k < m_kk; k++) {
+ double num = (RTkelvin + RTkelvin * m_b/ vmb + RTkelvin * m_b_coeffs[k] / vmb
+ + RTkelvin * m_b * m_b_coeffs[k] /(vmb * vmb)
+ - 2 * mv * m_pp[k] / fac
+ + 2 * mv * vmb * m_aAlpha_mix * m_b_coeffs[k] / fac2
+ );
+ double denom = (pressure() + RTkelvin * m_b / (vmb * vmb)
+ + m_aAlpha_mix/fac
+ - 2 * mv* vpb * m_aAlpha_mix / fac2
+ );
+ vbar[k] = num / denom;
+ }
+}
+
+double PengRobinson::speciesCritTemperature(double a, double b) const
+{
+ if (b <= 0.0) {
+ return 1000000.;
+ } else if (a <= 0.0) {
+ return 0.0;
+ } else {
+ return a * omega_b / (b * omega_a * GasConstant);
+ }
+}
+
+bool PengRobinson::addSpecies(shared_ptr spec)
+{
+ bool added = MixtureFugacityTP::addSpecies(spec);
+ if (added) {
+ m_a_coeffs.resize(m_kk, m_kk, 0.0);
+ m_b_coeffs.push_back(0.0);
+ m_aAlpha_binary.resize(m_kk, m_kk, 0.0);
+ m_kappa.push_back(0.0);
+
+ m_alpha.push_back(0.0);
+ m_dalphadT.push_back(0.0);
+ m_d2alphadT2.push_back(0.0);
+
+ m_pp.push_back(0.0);
+ m_tmpV.push_back(0.0);
+ m_partialMolarVolumes.push_back(0.0);
+ m_dpdni.push_back(0.0);
+ }
+ return added;
+}
+
+vector PengRobinson::getCoeff(const std::string& iName)
+{
+ vector_fp spCoeff{ NAN, NAN, NAN };
+
+ // Get number of species in the database
+ // open xml file critProperties.xml
+ XML_Node* doc = get_XML_File("critProperties.xml");
+ size_t nDatabase = doc->nChildren();
+
+ // Loop through all species in the database and attempt to match supplied
+ // species to each. If present, calculate pureFluidParameters a_k and b_k
+ // based on crit properties T_c and P_c:
+ for (size_t isp = 0; isp < nDatabase; isp++) {
+ XML_Node& acNodeDoc = doc->child(isp);
+ std::string iNameLower = toLowerCopy(iName);
+ std::string dbName = toLowerCopy(acNodeDoc.attrib("name"));
+
+ // Attempt to match provided species iName to current database species
+ // dbName:
+ if (iNameLower == dbName) {
+ // Read from database and calculate a and b coefficients
+ double vParams;
+ double T_crit = 0.0, P_crit = 0.0, w_ac = 0.0;
+
+ if (acNodeDoc.hasChild("Tc")) {
+ vParams = 0.0;
+ XML_Node& xmlChildCoeff = acNodeDoc.child("Tc");
+ if (xmlChildCoeff.hasAttrib("value")) {
+ std::string critTemp = xmlChildCoeff.attrib("value");
+ vParams = strSItoDbl(critTemp);
+ }
+ if (vParams <= 0.0) { //Assuming that Pc and Tc are non zero.
+ throw CanteraError("PengRobinson::getCoeff",
+ "Critical Temperature must be positive");
+ }
+ T_crit = vParams;
+ }
+ if (acNodeDoc.hasChild("Pc")) {
+ vParams = 0.0;
+ XML_Node& xmlChildCoeff = acNodeDoc.child("Pc");
+ if (xmlChildCoeff.hasAttrib("value")) {
+ std::string critPressure = xmlChildCoeff.attrib("value");
+ vParams = strSItoDbl(critPressure);
+ }
+ if (vParams <= 0.0) { //Assuming that Pc and Tc are non zero.
+ throw CanteraError("PengRobinson::getCoeff",
+ "Critical Pressure must be positive");
+ }
+ P_crit = vParams;
+ }
+ if (acNodeDoc.hasChild("omega")) {
+ vParams = 0.0;
+ XML_Node& xmlChildCoeff = acNodeDoc.child("omega");
+ if (xmlChildCoeff.hasChild("value")) {
+ std::string acentric_factor = xmlChildCoeff.attrib("value");
+ vParams = strSItoDbl(acentric_factor);
+ }
+ w_ac = vParams;
+ }
+
+ spCoeff[0] = omega_a * (GasConstant * GasConstant) * (T_crit * T_crit) / P_crit; //coeff a
+ spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b
+ spCoeff[2] = w_ac; // acentric factor
+ break;
+ }
+ }
+ return spCoeff;
+}
+
+void PengRobinson::initThermo()
+{
+ for (auto& item : m_species) {
+ // Read a and b coefficients and acentric factor w_ac from species 'input'
+ // information (i.e. as specified in a YAML input file)
+ if (item.second->input.hasKey("equation-of-state")) {
+ auto eos = item.second->input["equation-of-state"].getMapWhere(
+ "model", "Peng-Robinson");
+ double a0 = 0;
+ if (eos["a"].isScalar()) {
+ a0 = eos.convert("a", "Pa*m^6/kmol^2");
+ }
+ double b = eos.convert("b", "m^3/kmol");
+ // unitless acentric factor:
+ double w = eos["acentric_factor"].asDouble();
+
+ setSpeciesCoeffs(item.first, a0, b, w);
+ if (eos.hasKey("binary-a")) {
+ AnyMap& binary_a = eos["binary-a"].as();
+ const UnitSystem& units = binary_a.units();
+ for (auto& item2 : binary_a) {
+ double a0 = 0;
+ if (item2.second.isScalar()) {
+ a0 = units.convert(item2.second, "Pa*m^6/kmol^2");
+ }
+ setBinaryCoeffs(item.first, item2.first, a0);
+ }
+ }
+ } else {
+ // Check if a and b are already populated for this species (only the
+ // diagonal elements of a). If not, then search 'critProperties.xml'
+ // to find critical temperature and pressure to calculate a and b.
+ size_t k = speciesIndex(item.first);
+ if (m_a_coeffs(k, k) == 0.0) {
+ vector coeffs = getCoeff(item.first);
+
+ // Check if species was found in the database of critical
+ // properties, and assign the results
+ if (!isnan(coeffs[0])) {
+ setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]);
+ }
+ }
+ }
+ }
+}
+
+double PengRobinson::sresid() const
+{
+ double molarV = molarVolume();
+ double hh = m_b / molarV;
+ double zz = z();
+ double alpha_1 = daAlpha_dT();
+ double vpb = molarV + (1.0 + M_SQRT2) * m_b;
+ double vmb = molarV + (1.0 - M_SQRT2) * m_b;
+ double fac = alpha_1 / (2.0 * M_SQRT2 * m_b);
+ double sresid_mol_R = log(zz*(1.0 - hh)) + fac * log(vpb / vmb) / GasConstant;
+ return GasConstant * sresid_mol_R;
+}
+
+double PengRobinson::hresid() const
+{
+ double molarV = molarVolume();
+ double zz = z();
+ double aAlpha_1 = daAlpha_dT();
+ double T = temperature();
+ double vpb = molarV + (1 + M_SQRT2) * m_b;
+ double vmb = molarV + (1 - M_SQRT2) * m_b;
+ double fac = 1 / (2.0 * M_SQRT2 * m_b);
+ return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) * (T * aAlpha_1 - m_aAlpha_mix);
+}
+
+double PengRobinson::liquidVolEst(double T, double& presGuess) const
+{
+ double v = m_b * 1.1;
+ double atmp;
+ double btmp;
+ double aAlphatmp;
+ calculateAB(atmp, btmp, aAlphatmp);
+ double pres = std::max(psatEst(T), presGuess);
+ double Vroot[3];
+ bool foundLiq = false;
+ int m = 0;
+ while (m < 100 && !foundLiq) {
+ int nsol = solveCubic(T, pres, atmp, btmp, aAlphatmp, Vroot);
+ if (nsol == 1 || nsol == 2) {
+ double pc = critPressure();
+ if (pres > pc) {
+ foundLiq = true;
+ }
+ pres *= 1.04;
+ } else {
+ foundLiq = true;
+ }
+ }
+
+ if (foundLiq) {
+ v = Vroot[0];
+ presGuess = pres;
+ } else {
+ v = -1.0;
+ }
+ return v;
+}
+
+double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, double rhoGuess)
+{
+ // It's necessary to set the temperature so that m_aAlpha_mix is set correctly.
+ setTemperature(T);
+ double tcrit = critTemperature();
+ double mmw = meanMolecularWeight();
+ if (rhoGuess == -1.0) {
+ if (phaseRequested >= FLUID_LIQUID_0) {
+ double lqvol = liquidVolEst(T, presPa);
+ rhoGuess = mmw / lqvol;
+ }
+ } else {
+ // Assume the Gas phase initial guess, if nothing is specified to the routine
+ rhoGuess = presPa * mmw / (GasConstant * T);
+ }
+
+ double volGuess = mmw / rhoGuess;
+ m_NSolns = solveCubic(T, presPa, m_a, m_b, m_aAlpha_mix, m_Vroot);
+
+ double molarVolLast = m_Vroot[0];
+ if (m_NSolns >= 2) {
+ if (phaseRequested >= FLUID_LIQUID_0) {
+ molarVolLast = m_Vroot[0];
+ } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) {
+ molarVolLast = m_Vroot[2];
+ } else {
+ if (volGuess > m_Vroot[1]) {
+ molarVolLast = m_Vroot[2];
+ } else {
+ molarVolLast = m_Vroot[0];
+ }
+ }
+ } else if (m_NSolns == 1) {
+ if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) {
+ molarVolLast = m_Vroot[0];
+ } else {
+ return -2.0;
+ }
+ } else if (m_NSolns == -1) {
+ if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) {
+ molarVolLast = m_Vroot[0];
+ } else if (T > tcrit) {
+ molarVolLast = m_Vroot[0];
+ } else {
+ return -2.0;
+ }
+ } else {
+ molarVolLast = m_Vroot[0];
+ return -1.0;
+ }
+ return mmw / molarVolLast;
+}
+
+double PengRobinson::densSpinodalLiquid() const
+{
+ double Vroot[3];
+ double T = temperature();
+ int nsol = solveCubic(T, pressure(), m_a, m_b, m_aAlpha_mix, Vroot);
+ if (nsol != 3) {
+ return critDensity();
+ }
+
+ auto resid = [this, T](double v) {
+ double pp;
+ return dpdVCalc(T, v, pp);
+ };
+
+ boost::uintmax_t maxiter = 100;
+ std::pair vv = bmt::toms748_solve(
+ resid, Vroot[0], Vroot[1], bmt::eps_tolerance(48), maxiter);
+
+ double mmw = meanMolecularWeight();
+ return mmw / (0.5 * (vv.first + vv.second));
+}
+
+double PengRobinson::densSpinodalGas() const
+{
+ double Vroot[3];
+ double T = temperature();
+ int nsol = solveCubic(T, pressure(), m_a, m_b, m_aAlpha_mix, Vroot);
+ if (nsol != 3) {
+ return critDensity();
+ }
+
+ auto resid = [this, T](double v) {
+ double pp;
+ return dpdVCalc(T, v, pp);
+ };
+
+ boost::uintmax_t maxiter = 100;
+ std::pair vv = bmt::toms748_solve(
+ resid, Vroot[1], Vroot[2], bmt::eps_tolerance(48), maxiter);
+
+ double mmw = meanMolecularWeight();
+ return mmw / (0.5 * (vv.first + vv.second));
+}
+
+double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const
+{
+ double den = molarVol * molarVol + 2 * molarVol * m_b - m_b * m_b;
+ presCalc = GasConstant * T / (molarVol - m_b) - m_aAlpha_mix / den;
+
+ double vpb = molarVol + m_b;
+ double vmb = molarVol - m_b;
+ double dpdv = -GasConstant * T / (vmb * vmb) + 2 * m_aAlpha_mix * vpb / (den*den);
+ return dpdv;
+}
+
+void PengRobinson::calculatePressureDerivatives() const
+{
+ double T = temperature();
+ double mv = molarVolume();
+ double pres;
+
+ m_dpdV = dpdVCalc(T, mv, pres);
+ double vmb = mv - m_b;
+ double den = mv * mv + 2 * mv * m_b - m_b * m_b;
+ m_dpdT = (GasConstant / vmb - daAlpha_dT() / den);
+}
+
+void PengRobinson::updateMixingExpressions()
+{
+ double temp = temperature();
+
+ // Update individual alpha
+ for (size_t j = 0; j < m_kk; j++) {
+ double critTemp_j = speciesCritTemperature(m_a_coeffs(j,j), m_b_coeffs[j]);
+ double sqt_alpha = 1 + m_kappa[j] * (1 - sqrt(temp / critTemp_j));
+ m_alpha[j] = sqt_alpha*sqt_alpha;
+ }
+
+ //Update aAlpha_i, j
+ for (size_t i = 0; i < m_kk; i++) {
+ for (size_t j = 0; j < m_kk; j++) {
+ m_aAlpha_binary(i, j) = sqrt(m_alpha[i] * m_alpha[j]) * m_a_coeffs(i,j);
+ }
+ }
+ calculateAB(m_a,m_b,m_aAlpha_mix);
+}
+
+void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) const
+{
+ bCalc = 0.0;
+ aCalc = 0.0;
+ aAlphaCalc = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ bCalc += moleFractions_[i] * m_b_coeffs[i];
+ for (size_t j = 0; j < m_kk; j++) {
+ double a_vec_Curr = m_a_coeffs(i, j);
+ aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j];
+ aAlphaCalc += m_aAlpha_binary(i, j) * moleFractions_[i] * moleFractions_[j];
+ }
+ }
+}
+
+double PengRobinson::daAlpha_dT() const
+{
+ double daAlphadT = 0.0, temp, k, Tc, sqtTr, coeff1, coeff2;
+ for (size_t i = 0; i < m_kk; i++) {
+ // Calculate first derivative of alpha for individual species
+ Tc = speciesCritTemperature(m_a_coeffs(i,i), m_b_coeffs[i]);
+ sqtTr = sqrt(temperature() / Tc); //we need species critical temperature
+ coeff1 = 1 / (Tc*sqtTr);
+ coeff2 = sqtTr - 1;
+ k = m_kappa[i];
+ m_dalphadT[i] = coeff1 * (k*k*coeff2 - k);
+ }
+ //Calculate mixture derivative
+ for (size_t i = 0; i < m_kk; i++) {
+ for (size_t j = 0; j < m_kk; j++) {
+ temp = 0.5 * sqrt((m_a_coeffs(i, i) * m_a_coeffs(j, j)) / (m_alpha[i] * m_alpha[j]));
+ daAlphadT += moleFractions_[i] * moleFractions_[j] * temp
+ * (m_dalphadT[j] * m_alpha[i] + m_dalphadT[i] * m_alpha[j]);
+ }
+ }
+ return daAlphadT;
+}
+
+double PengRobinson::d2aAlpha_dT2() const
+{
+ for (size_t i = 0; i < m_kk; i++) {
+ double Tcrit_i = speciesCritTemperature(m_a_coeffs(i, i), m_b_coeffs[i]);
+ double sqt_Tr = sqrt(temperature() / Tcrit_i); //we need species critical temperature
+ double coeff1 = 1 / (Tcrit_i*Tcrit_i*sqt_Tr);
+ double coeff2 = sqt_Tr - 1;
+ // Calculate first and second derivatives of alpha for individual species
+ double k = m_kappa[i];
+ m_dalphadT[i] = coeff1 * (k*k*coeff2 - k);
+ m_d2alphadT2[i] = (k*k + k) * coeff1 / (2*sqt_Tr*sqt_Tr);
+ }
+
+ //Calculate mixture derivative
+ double d2aAlphadT2 = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ double alphai = m_alpha[i];
+ for (size_t j = 0; j < m_kk; j++) {
+ double alphaj = m_alpha[j];
+ double alphaij = alphai * alphaj;
+ double temp = 0.5 * sqrt((m_a_coeffs(i, i) * m_a_coeffs(j, j)) / (alphaij));
+ double num = (m_dalphadT[j] * alphai + m_dalphadT[i] * alphaj);
+ double fac1 = -(0.5 / alphaij) * num * num;
+ double fac2 = alphaj * m_d2alphadT2[i] + alphai * m_d2alphadT2[j] + 2. * m_dalphadT[i] * m_dalphadT[j];
+ d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp * (fac1 + fac2);
+ }
+ }
+ return d2aAlphadT2;
+}
+
+void PengRobinson::calcCriticalConditions(double& pc, double& tc, double& vc) const
+{
+ if (m_b <= 0.0) {
+ tc = 1000000.;
+ pc = 1.0E13;
+ vc = omega_vc * GasConstant * tc / pc;
+ return;
+ }
+ if (m_a <= 0.0) {
+ tc = 0.0;
+ pc = 0.0;
+ vc = 2.0 * m_b;
+ return;
+ }
+ tc = m_a * omega_b / (m_b * omega_a * GasConstant);
+ pc = omega_b * GasConstant * tc / m_b;
+ vc = omega_vc * GasConstant * tc / pc;
+}
+
+int PengRobinson::solveCubic(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const
+{
+ // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve.
+ double bsqr = b * b;
+ double RT_p = GasConstant * T / pres;
+ double aAlpha_p = aAlpha / pres;
+ double an = 1.0;
+ double bn = (b - RT_p);
+ double cn = -(2 * RT_p * b - aAlpha_p + 3 * bsqr);
+ double dn = (bsqr * RT_p + bsqr * b - aAlpha_p * b);
+
+ double tc = a * omega_b / (b * omega_a * GasConstant);
+ double pc = omega_b * GasConstant * tc / b;
+ double vc = omega_vc * GasConstant * tc / pc;
+
+ int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc, vc);
+
+ return nSolnValues;
+}
+
+}
diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp
index 50ca199855..b31b44586d 100644
--- a/src/thermo/RedlichKwongMFTP.cpp
+++ b/src/thermo/RedlichKwongMFTP.cpp
@@ -115,23 +115,6 @@ void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i,
// ------------Molar Thermodynamic Properties -------------------------
-doublereal RedlichKwongMFTP::enthalpy_mole() const
-{
- _updateReferenceStateThermo();
- doublereal h_ideal = RT() * mean_X(m_h0_RT);
- doublereal h_nonideal = hresid();
- return h_ideal + h_nonideal;
-}
-
-doublereal RedlichKwongMFTP::entropy_mole() const
-{
- _updateReferenceStateThermo();
- doublereal sr_ideal = GasConstant * (mean_X(m_s0_R)
- - sum_xlogx() - std::log(pressure()/refPressure()));
- doublereal sr_nonideal = sresid();
- return sr_ideal + sr_nonideal;
-}
-
doublereal RedlichKwongMFTP::cp_mole() const
{
_updateReferenceStateThermo();
@@ -173,39 +156,6 @@ doublereal RedlichKwongMFTP::pressure() const
return pp;
}
-void RedlichKwongMFTP::calcDensity()
-{
- // Calculate the molarVolume of the solution (m**3 kmol-1)
- const doublereal* const dtmp = moleFractdivMMW();
- getPartialMolarVolumes(m_tmpV.data());
- double invDens = dot(m_tmpV.begin(), m_tmpV.end(), dtmp);
-
- // Set the density in the parent State object directly, by calling the
- // Phase::setDensity() function.
- Phase::setDensity(1.0/invDens);
-}
-
-void RedlichKwongMFTP::setTemperature(const doublereal temp)
-{
- Phase::setTemperature(temp);
- _updateReferenceStateThermo();
- updateAB();
-}
-
-void RedlichKwongMFTP::compositionChanged()
-{
- MixtureFugacityTP::compositionChanged();
- updateAB();
-}
-
-void RedlichKwongMFTP::getActivityConcentrations(doublereal* c) const
-{
- getActivityCoefficients(c);
- for (size_t k = 0; k < m_kk; k++) {
- c[k] *= moleFraction(k)*pressure()/RT();
- }
-}
-
doublereal RedlichKwongMFTP::standardConcentration(size_t k) const
{
getStandardVolumes(m_tmpV.data());
@@ -427,87 +377,6 @@ void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* vbar) const
}
}
-doublereal RedlichKwongMFTP::critTemperature() const
-{
- double pc, tc, vc;
- double a0 = 0.0;
- double aT = 0.0;
- for (size_t i = 0; i < m_kk; i++) {
- for (size_t j = 0; j spec)
{
bool added = MixtureFugacityTP::addSpecies(spec);
@@ -842,7 +711,7 @@ doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGu
bool foundLiq = false;
int m = 0;
while (m < 100 && !foundLiq) {
- int nsol = NicholsSolve(TKelvin, pres, atmp, btmp, Vroot);
+ int nsol = solveCubic(TKelvin, pres, atmp, btmp, Vroot);
if (nsol == 1 || nsol == 2) {
double pc = critPressure();
if (pres > pc) {
@@ -868,28 +737,20 @@ doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa,
// It's necessary to set the temperature so that m_a_current is set correctly.
setTemperature(TKelvin);
double tcrit = critTemperature();
- doublereal mmw = meanMolecularWeight();
+ double mmw = meanMolecularWeight();
if (rhoguess == -1.0) {
- if (phaseRequested != FLUID_GAS) {
- if (TKelvin > tcrit) {
- rhoguess = presPa * mmw / (GasConstant * TKelvin);
- } else {
- if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) {
- rhoguess = presPa * mmw / (GasConstant * TKelvin);
- } else if (phaseRequested >= FLUID_LIQUID_0) {
+ if (phaseRequested >= FLUID_LIQUID_0) {
double lqvol = liquidVolEst(TKelvin, presPa);
rhoguess = mmw / lqvol;
}
- }
} else {
- // Assume the Gas phase initial guess, if nothing is specified to
- // the routine
+ // Assume the Gas phase initial guess, if nothing is specified to the routine
rhoguess = presPa * mmw / (GasConstant * TKelvin);
- }
}
+
doublereal volguess = mmw / rhoguess;
- NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, Vroot_);
+ NSolns_ = solveCubic(TKelvin, presPa, m_a_current, m_b_current, Vroot_);
doublereal molarVolLast = Vroot_[0];
if (NSolns_ >= 2) {
@@ -929,7 +790,7 @@ doublereal RedlichKwongMFTP::densSpinodalLiquid() const
{
double Vroot[3];
double T = temperature();
- int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, Vroot);
+ int nsol = solveCubic(T, pressure(), m_a_current, m_b_current, Vroot);
if (nsol != 3) {
return critDensity();
}
@@ -951,7 +812,7 @@ doublereal RedlichKwongMFTP::densSpinodalGas() const
{
double Vroot[3];
double T = temperature();
- int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, Vroot);
+ int nsol = solveCubic(T, pressure(), m_a_current, m_b_current, Vroot);
if (nsol != 3) {
return critDensity();
}
@@ -969,14 +830,6 @@ doublereal RedlichKwongMFTP::densSpinodalGas() const
return mmw / (0.5 * (vv.first + vv.second));
}
-doublereal RedlichKwongMFTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const
-{
- doublereal sqt = sqrt(TKelvin);
- double pres = GasConstant * TKelvin / (molarVol - m_b_current)
- - m_a_current / (sqt * molarVol * (molarVol + m_b_current));
- return pres;
-}
-
doublereal RedlichKwongMFTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const
{
doublereal sqt = sqrt(TKelvin);
@@ -1006,11 +859,6 @@ void RedlichKwongMFTP::pressureDerivatives() const
}
void RedlichKwongMFTP::updateMixingExpressions()
-{
- updateAB();
-}
-
-void RedlichKwongMFTP::updateAB()
{
double temp = temperature();
if (m_formTempParam == 1) {
@@ -1042,7 +890,7 @@ void RedlichKwongMFTP::updateAB()
}
}
}
- throw CanteraError("RedlichKwongMFTP::updateAB",
+ throw CanteraError("RedlichKwongMFTP::updateMixingExpressions",
"Missing Redlich-Kwong coefficients for species: {}", to_string(b));
}
}
@@ -1086,11 +934,21 @@ doublereal RedlichKwongMFTP::da_dt() const
return dadT;
}
-void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff,
- doublereal& pc, doublereal& tc, doublereal& vc) const
+void RedlichKwongMFTP::calcCriticalConditions(doublereal& pc, doublereal& tc, doublereal& vc) const
{
+ double a0 = 0.0;
+ double aT = 0.0;
+ for (size_t i = 0; i < m_kk; i++) {
+ for (size_t j = 0; j 0.1) {
- throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge");
+ throw CanteraError("RedlichKwongMFTP::calcCriticalConditions",
+ "didn't converge");
}
}
@@ -1128,21 +987,14 @@ void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, double
vc = omega_vc * GasConstant * tc / pc;
}
-int RedlichKwongMFTP::NicholsSolve(double TKelvin, double pres, doublereal a, doublereal b,
- doublereal Vroot[3]) const
+int RedlichKwongMFTP::solveCubic(double T, double pres, double a, double b, double Vroot[3]) const
{
- Vroot[0] = 0.0;
- Vroot[1] = 0.0;
- Vroot[2] = 0.0;
- if (TKelvin <= 0.0) {
- throw CanteraError("RedlichKwongMFTP::NicholsSolve", "neg temperature");
- }
-
+
// Derive the coefficients of the cubic polynomial to solve.
doublereal an = 1.0;
- doublereal bn = - GasConstant * TKelvin / pres;
- doublereal sqt = sqrt(TKelvin);
- doublereal cn = - (GasConstant * TKelvin * b / pres - a/(pres * sqt) + b * b);
+ doublereal bn = - GasConstant * T / pres;
+ doublereal sqt = sqrt(T);
+ doublereal cn = - (GasConstant * T * b / pres - a/(pres * sqt) + b * b);
doublereal dn = - (a * b / (pres * sqt));
double tmp = a * omega_b / (b * omega_a * GasConstant);
@@ -1150,182 +1002,9 @@ int RedlichKwongMFTP::NicholsSolve(double TKelvin, double pres, doublereal a, do
double tc = pow(tmp, pp);
double pc = omega_b * GasConstant * tc / b;
double vc = omega_vc * GasConstant * tc / pc;
- // Derive the center of the cubic, x_N
- doublereal xN = - bn /(3 * an);
-
- // Derive the value of delta**2. This is a key quantity that determines the
- // number of turning points
- doublereal delta2 = (bn * bn - 3 * an * cn) / (9 * an * an);
- doublereal delta = 0.0;
-
- // Calculate a couple of ratios
- doublereal ratio1 = 3.0 * an * cn / (bn * bn);
- doublereal ratio2 = pres * b / (GasConstant * TKelvin);
- if (fabs(ratio1) < 1.0E-7) {
- doublereal ratio3 = a / (GasConstant * sqt) * pres / (GasConstant * TKelvin);
- if (fabs(ratio2) < 1.0E-5 && fabs(ratio3) < 1.0E-5) {
- doublereal zz = 1.0;
- for (int i = 0; i < 10; i++) {
- doublereal znew = zz / (zz - ratio2) - ratio3 / (zz + ratio1);
- doublereal deltaz = znew - zz;
- zz = znew;
- if (fabs(deltaz) < 1.0E-14) {
- break;
- }
- }
- doublereal v = zz * GasConstant * TKelvin / pres;
- Vroot[0] = v;
- return 1;
- }
- }
-
- int nSolnValues;
- double h2 = 4. * an * an * delta2 * delta2 * delta2;
- if (delta2 > 0.0) {
- delta = sqrt(delta2);
- }
-
- doublereal h = 2.0 * an * delta * delta2;
- doublereal yN = 2.0 * bn * bn * bn / (27.0 * an * an) - bn * cn / (3.0 * an) + dn;
- doublereal desc = yN * yN - h2;
-
- if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) {
- if (desc != 0.0) {
- // this is for getting to other cases
- throw CanteraError("RedlichKwongMFTP::NicholsSolve", "numerical issues");
- }
- desc = 0.0;
- }
- if (desc < 0.0) {
- nSolnValues = 3;
- } else if (desc == 0.0) {
- nSolnValues = 2;
- // We are here as p goes to zero.
- } else if (desc > 0.0) {
- nSolnValues = 1;
- }
-
- // One real root -> have to determine whether gas or liquid is the root
- if (desc > 0.0) {
- doublereal tmpD = sqrt(desc);
- doublereal tmp1 = (- yN + tmpD) / (2.0 * an);
- doublereal sgn1 = 1.0;
- if (tmp1 < 0.0) {
- sgn1 = -1.0;
- tmp1 = -tmp1;
- }
- doublereal tmp2 = (- yN - tmpD) / (2.0 * an);
- doublereal sgn2 = 1.0;
- if (tmp2 < 0.0) {
- sgn2 = -1.0;
- tmp2 = -tmp2;
- }
- doublereal p1 = pow(tmp1, 1./3.);
- doublereal p2 = pow(tmp2, 1./3.);
- doublereal alpha = xN + sgn1 * p1 + sgn2 * p2;
- Vroot[0] = alpha;
- Vroot[1] = 0.0;
- Vroot[2] = 0.0;
- tmp = an * Vroot[0] * Vroot[0] * Vroot[0] + bn * Vroot[0] * Vroot[0] + cn * Vroot[0] + dn;
- } else if (desc < 0.0) {
- doublereal tmp = - yN/h;
- doublereal val = acos(tmp);
- doublereal theta = val / 3.0;
- doublereal oo = 2. * Pi / 3.;
- doublereal alpha = xN + 2. * delta * cos(theta);
- doublereal beta = xN + 2. * delta * cos(theta + oo);
- doublereal gamma = xN + 2. * delta * cos(theta + 2.0 * oo);
- Vroot[0] = beta;
- Vroot[1] = gamma;
- Vroot[2] = alpha;
-
- for (int i = 0; i < 3; i++) {
- tmp = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
- if (fabs(tmp) > 1.0E-4) {
- for (int j = 0; j < 3; j++) {
- if (j != i && fabs(Vroot[i] - Vroot[j]) < 1.0E-4 * (fabs(Vroot[i]) + fabs(Vroot[j]))) {
- warn_user("RedlichKwongMFTP::NicholsSolve",
- "roots have merged: {}, {} (T = {}, p = {})",
- Vroot[i], Vroot[j], TKelvin, pres);
- }
- }
- }
- }
- } else if (desc == 0.0) {
- if (yN == 0.0 && h == 0.0) {
- Vroot[0] = xN;
- Vroot[1] = xN;
- Vroot[2] = xN;
- } else {
- // need to figure out whether delta is pos or neg
- if (yN > 0.0) {
- tmp = pow(yN/(2*an), 1./3.);
- if (fabs(tmp - delta) > 1.0E-9) {
- throw CanteraError("RedlichKwongMFTP::NicholsSolve", "unexpected");
- }
- Vroot[1] = xN + delta;
- Vroot[0] = xN - 2.0*delta; // liquid phase root
- } else {
- tmp = pow(yN/(2*an), 1./3.);
- if (fabs(tmp - delta) > 1.0E-9) {
- throw CanteraError("RedlichKwongMFTP::NicholsSolve", "unexpected");
- }
- delta = -delta;
- Vroot[0] = xN + delta;
- Vroot[1] = xN - 2.0*delta; // gas phase root
- }
- }
- for (int i = 0; i < 2; i++) {
- tmp = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
- }
- }
-
- // Unfortunately, there is a heavy amount of roundoff error due to bad
- // conditioning in this
- double res, dresdV = 0.0;
- for (int i = 0; i < nSolnValues; i++) {
- for (int n = 0; n < 20; n++) {
- res = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
- if (fabs(res) < 1.0E-14) {
- break;
- }
- dresdV = 3.0 * an * Vroot[i] * Vroot[i] + 2.0 * bn * Vroot[i] + cn;
- double del = - res / dresdV;
- Vroot[i] += del;
- if (fabs(del) / (fabs(Vroot[i]) + fabs(del)) < 1.0E-14) {
- break;
- }
- double res2 = an * Vroot[i] * Vroot[i] * Vroot[i] + bn * Vroot[i] * Vroot[i] + cn * Vroot[i] + dn;
- if (fabs(res2) < fabs(res)) {
- continue;
- } else {
- Vroot[i] -= del;
- Vroot[i] += 0.1 * del;
- }
- }
- if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) {
- warn_user("RedlichKwongMFTP::NicholsSolve",
- "root did not converge: V = {} (T = {}, p = {})",
- Vroot[i], TKelvin, pres);
- }
- }
-
- if (nSolnValues == 1) {
- if (TKelvin > tc) {
- if (Vroot[0] < vc) {
- nSolnValues = -1;
- }
- } else {
- if (Vroot[0] < xN) {
- nSolnValues = -1;
- }
- }
- } else {
- if (nSolnValues == 2 && delta > 0.0) {
- nSolnValues = -2;
- }
- }
+ int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, pc);
+
return nSolnValues;
}
diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp
index f1a4c52a98..9faeff0f4a 100644
--- a/src/thermo/ThermoFactory.cpp
+++ b/src/thermo/ThermoFactory.cpp
@@ -23,6 +23,7 @@
#include "cantera/thermo/IonsFromNeutralVPSSTP.h"
#include "cantera/thermo/PureFluidPhase.h"
#include "cantera/thermo/RedlichKwongMFTP.h"
+#include "cantera/thermo/PengRobinson.h"
#include "cantera/thermo/SurfPhase.h"
#include "cantera/thermo/EdgePhase.h"
#include "cantera/thermo/MetalPhase.h"
@@ -88,6 +89,7 @@ ThermoFactory::ThermoFactory()
addAlias("liquid-water-IAPWS95", "PureLiquidWater");
reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); });
addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo");
+ reg("Peng-Robinson", []() { return new PengRobinson(); });
}
ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model)
diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml
new file mode 100644
index 0000000000..4f67f4e03a
--- /dev/null
+++ b/test/data/co2_PR_example.yaml
@@ -0,0 +1,129 @@
+units: {length: cm, quantity: mol, activation-energy: cal/mol}
+
+phases:
+- name: CO2-PR
+ species: [CO2, H2O, H2, CO, CH4, O2, N2]
+ thermo: Peng-Robinson
+ kinetics: gas
+ reactions: all
+ state: {T: 300, P: 1 atm, mole-fractions: {CO2: 0.99, H2: 0.01}}
+
+
+species:
+- name: CO2
+ composition: {C: 1, O: 2}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13,
+ -4.83719697e+04, 9.90105222]
+ - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14,
+ -4.8759166e+04, 2.27163806]
+ note: L7/88
+ equation-of-state:
+ model: Peng-Robinson
+ a: 3.958134E+11
+ b: 26.6275
+ acentric_factor: 0.228
+- name: H2O
+ composition: {H: 2, O: 1}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12,
+ -3.02937267e+04, -0.849032208]
+ - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14,
+ -3.00042971e+04, 4.9667701]
+ note: L8/89
+ equation-of-state:
+ model: Peng-Robinson
+ a: 5.998873E+11
+ b: 18.9714
+ acentric_factor: 0.344
+- name: H2
+ composition: {H: 2}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [2.34433112, 7.98052075e-03, -1.9478151e-05, 2.01572094e-08, -7.37611761e-12,
+ -917.935173, 0.683010238]
+ - [3.3372792, -4.94024731e-05, 4.99456778e-07, -1.79566394e-10, 2.00255376e-14,
+ -950.158922, -3.20502331]
+ note: TPIS78
+ equation-of-state:
+ model: Peng-Robinson
+ a: 2.668423E+10
+ b: 16.5478
+ acentric_factor: -0.22
+- name: CO
+ composition: {C: 1, O: 1}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [3.57953347, -6.1035368e-04, 1.01681433e-06, 9.07005884e-10, -9.04424499e-13,
+ -1.4344086e+04, 3.50840928]
+ - [2.71518561, 2.06252743e-03, -9.98825771e-07, 2.30053008e-10, -2.03647716e-14,
+ -1.41518724e+04, 7.81868772]
+ note: TPIS79
+ equation-of-state:
+ model: Peng-Robinson
+ a: 1.607164E+11
+ b: 24.6549
+ acentric_factor: 0.049
+- name: CH4
+ composition: {C: 1, H: 4}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11,
+ -1.02466476e+04, -4.64130376]
+ - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13,
+ -9468.34459, 18.437318]
+ note: L8/88
+ equation-of-state:
+ model: Peng-Robinson
+ a: 2.496344E+11
+ b: 26.8028
+ acentric_factor: 0.01
+- name: O2
+ composition: {O: 2}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12,
+ -1063.94356, 3.65767573]
+ - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14,
+ -1088.45772, 5.45323129]
+ note: TPIS89
+ equation-of-state:
+ model: Peng-Robinson
+ a: 1.497732E+11
+ b: 19.8281
+ acentric_factor: 0.022
+- name: N2
+ composition: {N: 2}
+ thermo:
+ model: NASA7
+ temperature-ranges: [300.0, 1000.0, 5000.0]
+ data:
+ - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12,
+ -1020.8999, 3.950372]
+ - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15,
+ -922.7977, 5.980528]
+ note: '121286'
+ equation-of-state:
+ model: Peng-Robinson
+ a: 1.485031E+11
+ b: 28.0810
+ acentric_factor: 0.04
+
+
+reactions:
+- equation: CO2 + H2 <=> CO + H2O # Reaction 1
+ rate-constant: {A: 1.2E+3, b: 0, Ea: 0}
diff --git a/test/data/thermo-models.yaml b/test/data/thermo-models.yaml
index 7c1f998ceb..87ebe7cae6 100644
--- a/test/data/thermo-models.yaml
+++ b/test/data/thermo-models.yaml
@@ -164,6 +164,11 @@ phases:
thermo: Redlich-Kwong
state: {T: 300, P: 200 atm, mole-fractions: {CO2: 0.9998, H2O: 0.0002}}
+- name: CO2-PR
+ species: [{pr-species: [CO2, H2O, H2]}]
+ thermo: Peng-Robinson
+ state: {T: 300, P: 200 atm, mole-fractions: {CO2: 0.9998, H2O: 0.0002}}
+
- name: nitrogen
species: [N2]
thermo: pure-fluid
@@ -559,6 +564,60 @@ rk-species:
b: 27.80
+pr-species:
+- name: H2
+ composition: {H: 2}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [2.34433112, 0.00798052075, -1.9478151e-05, 2.01572094e-08, -7.37611761e-12,
+ -917.935173, 0.683010238]
+ - [3.3372792, -4.94024731e-05, 4.99456778e-07, -1.79566394e-10, 2.00255376e-14,
+ -950.158922, -3.20502331]
+ equation-of-state:
+ model: Peng-Robinson
+ units: {length: cm, quantity: mol}
+ a: 2.668423E+10
+ b: 16.5478
+ acentric_factor: -0.22
+- name: H2O
+ composition: {H: 2, O: 1}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [4.19864056, -0.0020364341, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12,
+ -30293.7267, -0.849032208]
+ - [3.03399249, 0.00217691804, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14,
+ -30004.2971, 4.9667701]
+ equation-of-state:
+ model: Peng-Robinson
+ units: {length: cm, quantity: mol}
+ a: 5.998873E+11
+ b: 18.9714
+ acentric_factor: 0.344
+ binary-a:
+ H2: 4 bar*cm^6/mol^2
+ CO2: 7.897e7 bar*cm^6/mol^2
+- name: CO2
+ composition: {C: 1, O: 2}
+ thermo:
+ model: NASA7
+ temperature-ranges: [200.0, 1000.0, 3500.0]
+ data:
+ - [2.35677352, 0.00898459677, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13,
+ -48371.9697, 9.90105222]
+ - [3.85746029, 0.00441437026, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14,
+ -48759.166, 2.27163806]
+ equation-of-state:
+ model: Peng-Robinson
+ units: {length: cm, quantity: mol}
+ a: 3.958134E+11
+ b: 26.6275
+ acentric_factor: 0.228
+
+
HMW-species:
- name: H2O(L)
composition: {H: 2, O: 1}
diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp
new file mode 100644
index 0000000000..c0c1a3031d
--- /dev/null
+++ b/test/thermo/PengRobinson_Test.cpp
@@ -0,0 +1,344 @@
+#include "gtest/gtest.h"
+#include "cantera/thermo/PengRobinson.h"
+#include "cantera/thermo/ThermoFactory.h"
+
+
+namespace Cantera
+{
+
+class PengRobinson_Test : public testing::Test
+{
+public:
+ PengRobinson_Test() {
+ test_phase.reset(newPhase("../data/thermo-models.yaml", "CO2-PR"));
+ }
+
+ //vary the composition of a co2-h2 mixture:
+ void set_r(const double r) {
+ vector_fp moleFracs(7);
+ moleFracs[0] = r;
+ moleFracs[2] = 1-r;
+ test_phase->setMoleFractions(&moleFracs[0]);
+ }
+
+ std::unique_ptr test_phase;
+};
+
+TEST_F(PengRobinson_Test, construct_from_yaml)
+{
+ PengRobinson* peng_robinson_phase = dynamic_cast(test_phase.get());
+ EXPECT_TRUE(peng_robinson_phase != NULL);
+}
+
+TEST_F(PengRobinson_Test, chem_potentials)
+{
+ test_phase->setState_TP(298.15, 101325.);
+ /* Chemical potential should increase with increasing co2 mole fraction:
+ * mu = mu_0 + RT ln(gamma_k*X_k).
+ * where gamma_k is the activity coefficient. Run regression test against values
+ * calculated using the model.
+ */
+ const double expected_result[9] = {
+ -457338129.70445037,
+ -457327078.87912911,
+ -457317214.31077951,
+ -457308354.65227401,
+ -457300353.74028891,
+ -457293092.45485628,
+ -457286472.73969948,
+ -457280413.14238912,
+ -457274845.44186872
+ };
+
+ double xmin = 0.6;
+ double xmax = 0.9;
+ int numSteps = 9;
+ double dx = (xmax-xmin)/(numSteps-1);
+ vector_fp chemPotentials(7);
+ for(int i=0; i < numSteps; ++i)
+ {
+ set_r(xmin + i*dx);
+ test_phase->getChemPotentials(&chemPotentials[0]);
+ EXPECT_NEAR(expected_result[i], chemPotentials[0], 1.e-6);
+ }
+}
+
+TEST_F(PengRobinson_Test, chemPotentials_RT)
+{
+ test_phase->setState_TP(298., 1.);
+
+ // Test that chemPotentials_RT*RT = chemPotentials
+ const double RT = GasConstant * 298.;
+ vector_fp mu(7);
+ vector_fp mu_RT(7);
+ double xmin = 0.6;
+ double xmax = 0.9;
+ int numSteps = 9;
+ double dx = (xmax-xmin)/(numSteps-1);
+
+ for(int i=0; i < numSteps; ++i)
+ {
+ const double r = xmin + i*dx;
+ set_r(r);
+ test_phase->getChemPotentials(&mu[0]);
+ test_phase->getChemPotentials_RT(&mu_RT[0]);
+ EXPECT_NEAR(mu[0], mu_RT[0]*RT, 1.e-6);
+ EXPECT_NEAR(mu[2], mu_RT[2]*RT, 1.e-6);
+ }
+}
+
+TEST_F(PengRobinson_Test, activityCoeffs)
+{
+ test_phase->setState_TP(298., 1.);
+
+ // Test that mu0 + RT log(activityCoeff * MoleFrac) == mu
+ const double RT = GasConstant * 298.;
+ vector_fp mu0(7);
+ vector_fp activityCoeffs(7);
+ vector_fp chemPotentials(7);
+ double xmin = 0.6;
+ double xmax = 0.9;
+ int numSteps = 9;
+ double dx = (xmax-xmin)/(numSteps-1);
+
+ for(int i=0; i < numSteps; ++i)
+ {
+ const double r = xmin + i*dx;
+ set_r(r);
+ test_phase->getChemPotentials(&chemPotentials[0]);
+ test_phase->getActivityCoefficients(&activityCoeffs[0]);
+ test_phase->getStandardChemPotentials(&mu0[0]);
+ EXPECT_NEAR(chemPotentials[0], mu0[0] + RT*std::log(activityCoeffs[0] * r), 1.e-6);
+ EXPECT_NEAR(chemPotentials[2], mu0[2] + RT*std::log(activityCoeffs[2] * (1-r)), 1.e-6);
+ }
+}
+
+TEST_F(PengRobinson_Test, standardConcentrations)
+{
+ EXPECT_DOUBLE_EQ(test_phase->pressure()/(test_phase->temperature()*GasConstant),
+ test_phase->standardConcentration(0));
+ EXPECT_DOUBLE_EQ(test_phase->pressure()/(test_phase->temperature()*GasConstant),
+ test_phase->standardConcentration(1));
+}
+
+TEST_F(PengRobinson_Test, activityConcentrations)
+{
+ // Check to make sure activityConcentration_i == standardConcentration_i * gamma_i * X_i
+ vector_fp standardConcs(7);
+ vector_fp activityCoeffs(7);
+ vector_fp activityConcentrations(7);
+ double xmin = 0.6;
+ double xmax = 0.9;
+ int numSteps = 9;
+ double dx = (xmax-xmin)/(numSteps-1);
+
+ for(int i=0; i < numSteps; ++i)
+ {
+ const double r = xmin + i*dx;
+ set_r(r);
+ test_phase->getActivityCoefficients(&activityCoeffs[0]);
+ standardConcs[0] = test_phase->standardConcentration(0);
+ standardConcs[2] = test_phase->standardConcentration(2);
+ test_phase->getActivityConcentrations(&activityConcentrations[0]);
+
+ EXPECT_NEAR(standardConcs[0] * r * activityCoeffs[0], activityConcentrations[0], 1.e-6);
+ EXPECT_NEAR(standardConcs[2] * (1-r) * activityCoeffs[2], activityConcentrations[2], 1.e-6);
+ }
+}
+
+TEST_F(PengRobinson_Test, setTP)
+{
+ // Check to make sure that the phase diagram is accurately reproduced for a few select isobars
+
+ // All sub-cooled liquid:
+ const double rho1[6] = {
+ 6.6603507723749249e+002,
+ 1.6824762614489907e+002,
+ 1.6248354709581241e+002,
+ 1.5746729362032696e+002,
+ 1.5302217175386241e+002,
+ 1.4902908974486667e+002
+ };
+ // Phase change between temperatures 4 & 5:
+ const double rho2[6] = {
+ 7.5732259810273172e+002,
+ 7.2766981078381912e+002,
+ 6.935475475396446e+002,
+ 6.5227027102964917e+002,
+ 5.9657442842753153e+002,
+ 3.9966973266966875e+002
+ };
+ // Supercritical; no discontinuity in rho values:
+ const double rho3[6] = {
+ 8.0601205067780199e+002,
+ 7.8427655940884574e+002,
+ 7.6105347579146576e+002,
+ 7.3605202492828505e+002,
+ 7.0887891410210011e+002,
+ 6.7898591969734434e+002
+ };
+
+ for(int i=0; i<6; ++i)
+ {
+ const double temp = 294 + i*2;
+ set_r(0.999);
+ test_phase->setState_TP(temp, 5542027.5);
+ EXPECT_NEAR(test_phase->density(),rho1[i],1.e-8);
+
+ test_phase->setState_TP(temp, 7388370.);
+ EXPECT_NEAR(test_phase->density(),rho2[i],1.e-8);
+
+ test_phase->setState_TP(temp, 9236712.5);
+ EXPECT_NEAR(test_phase->density(),rho3[i],1.e-8);
+ }
+}
+
+TEST_F(PengRobinson_Test, getPressure)
+{
+ // Check to make sure that the P-R equation is accurately reproduced for a few selected values
+
+ /* This test uses CO2 as the only species (mole fraction 100%).
+ * Values of a_coeff, b_coeff are calculated based on the the critical temperature
+ * and pressure values of CO2 as follows:
+ * a_coeff = 0.457235(RT_crit)^2/p_crit
+ * b_coeff = 0.077796(RT_crit)/p_crit
+ * The temperature dependent parameter in P-R EoS is calculated as
+ * \alpha = [1 + \kappa(1 - sqrt{T/T_crit}]^2
+ * kappa is a function calulated based on the accentric factor.
+ */
+
+ double a_coeff = 3.958095109E+5;
+ double b_coeff = 26.62616317/1000;
+ double acc_factor = 0.228;
+ double pres_theoretical, kappa, alpha, mv;
+ const double rho = 1.0737;
+
+ //Calculate kappa value
+ kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor;
+
+ for (int i = 0; i < 15; i++)
+ {
+ const double temp = 296 + i * 50;
+ set_r(1.0);
+ test_phase->setState_TR(temp, rho);
+ const double Tcrit = test_phase->critTemperature();
+ mv = 1 / rho * test_phase->meanMolecularWeight();
+ //Calculate pressure using Peng-Robinson EoS
+ alpha = pow(1 + kappa*(1 - sqrt(temp / Tcrit)), 2.0);
+ pres_theoretical = GasConstant*temp / (mv - b_coeff)
+ - a_coeff*alpha / (mv*mv + 2*b_coeff*mv - b_coeff*b_coeff);
+ EXPECT_NEAR(test_phase->pressure(), pres_theoretical, 3);
+ }
+}
+
+TEST_F(PengRobinson_Test, gibbsEnergy)
+{
+ // Test that g == h - T*s
+ const double T = 298.;
+ double xmin = 0.6;
+ double xmax = 0.9;
+ int numSteps = 9;
+ double dx = (xmax - xmin) / (numSteps - 1);
+ double gibbs_theoretical;
+
+ for (int i = 0; i < numSteps; ++i)
+ {
+ const double r = xmin + i * dx;
+ test_phase->setState_TP(T, 1e5);
+ set_r(r);
+ gibbs_theoretical = test_phase->enthalpy_mole() - T * (test_phase->entropy_mole());
+ EXPECT_NEAR(test_phase->gibbs_mole(), gibbs_theoretical, 1.e-6);
+ }
+}
+
+TEST_F(PengRobinson_Test, totalEnthalpy)
+{
+ // Test that hbar = \sum (h_k*x_k)
+ double hbar, sum = 0.0;
+ vector_fp partialEnthalpies(7);
+ vector_fp moleFractions(7);
+ double xmin = 0.6;
+ double xmax = 0.9;
+ int numSteps = 9;
+ double dx = (xmax - xmin) / (numSteps - 1);
+
+ for (int i = 0; i < numSteps; ++i)
+ {
+ sum = 0.0;
+ const double r = xmin + i * dx;
+ test_phase->setState_TP(298., 1e5);
+ set_r(r);
+ hbar = test_phase->enthalpy_mole();
+ test_phase->getMoleFractions(&moleFractions[0]);
+ test_phase->getPartialMolarEnthalpies(&partialEnthalpies[0]);
+ for (int k = 0; k < 7; k++)
+ {
+ sum += moleFractions[k] * partialEnthalpies[k];
+ }
+ EXPECT_NEAR(hbar, sum, 1.e-6);
+ }
+}
+
+TEST_F(PengRobinson_Test, cpValidate)
+{
+ // Test that cp = dH/dT at constant pressure using finite difference method
+
+ double p = 1e5;
+ double Tmin = 298;
+ int numSteps = 1001;
+ double dT = 1e-5;
+ double r = 1.0;
+ vector_fp hbar(numSteps);
+ vector_fp cp(numSteps);
+ double dh_dT;
+
+ test_phase->setState_TP(Tmin, p);
+ set_r(r);
+ hbar[0] = test_phase->enthalpy_mole(); // J/kmol
+ cp[0] = test_phase->cp_mole(); // unit is J/kmol/K
+
+ for (int i = 0; i < numSteps; ++i)
+ {
+ const double T = Tmin + i * dT;
+ test_phase->setState_TP(T, p);
+ set_r(r);
+ hbar[i] = test_phase->enthalpy_mole(); // J/kmol
+ cp[i] = test_phase->cp_mole(); // unit is J/kmol/K
+
+ if (i > 0)
+ {
+ dh_dT = (hbar[i] - hbar[i - 1]) / dT;
+ EXPECT_NEAR((cp[i] / dh_dT), 1.0, 1e-5);
+ }
+ }
+}
+
+TEST_F(PengRobinson_Test, CoolPropValidate)
+{
+ // Validate the P-R EoS in Cantera with P-R EoS from CoolProp
+
+ const double rhoCoolProp[10] = {
+ 9.067928191884574,
+ 8.318322900591179,
+ 7.6883521740498155,
+ 7.150504298001246,
+ 6.685330199667018,
+ 6.278630757480957,
+ 5.919763108091383,
+ 5.600572727499541,
+ 5.314694056926007,
+ 5.057077678380463
+ };
+
+ double p = 5e5;
+
+ // Calculate density using Peng-Robinson EoS from Cantera
+ for(int i=0; i<10; i++)
+ {
+ const double temp = 300 + i*25;
+ set_r(1.0);
+ test_phase->setState_TP(temp, p);
+ EXPECT_NEAR(test_phase->density(),rhoCoolProp[i],1.e-5);
+ }
+}
+};
diff --git a/test/thermo/cubicSolver_Test.cpp b/test/thermo/cubicSolver_Test.cpp
new file mode 100644
index 0000000000..404b34f53e
--- /dev/null
+++ b/test/thermo/cubicSolver_Test.cpp
@@ -0,0 +1,111 @@
+#include "gtest/gtest.h"
+#include "cantera/thermo/PengRobinson.h"
+#include "cantera/thermo/ThermoFactory.h"
+
+namespace Cantera
+{
+
+class cubicSolver_Test : public testing::Test
+{
+public:
+ cubicSolver_Test() {
+ test_phase.reset(newPhase("../data/co2_PR_example.yaml"));
+ }
+
+ //vary the composition of a co2-h2 mixture
+ void set_r(const double r) {
+ vector_fp moleFracs(7);
+ moleFracs[0] = r;
+ moleFracs[2] = 1-r;
+ test_phase->setMoleFractions(&moleFracs[0]);
+ }
+
+ std::unique_ptr test_phase;
+};
+
+TEST_F(cubicSolver_Test, solve_cubic)
+{
+ /* This tests validates the cubic solver by considering CO2 as an example.
+ * Values of a_coeff, b_coeff and accentric factor are hard-coded.
+ * The temperature dependent parameter in P-R EoS is calculated as
+ * \alpha = [1 + \kappa(1 - sqrt{T/T_crit}]^2
+ * kappa is a function calulated based on the accentric factor.
+ *
+ * Three different states are considered as follows:
+ * 1. T = 300 T, P = 1 bar => Vapor (1 real root of the cubic equation)
+ * 2. T = 300 K, P = 80 bar => Supercritical (1 real root of the cubic equation)
+ * 3. T = Tc, P = Pc => Near critical region
+ */
+
+ // Define a Peng-Robinson phase
+ PengRobinson* peng_robinson_phase = dynamic_cast(test_phase.get());
+ EXPECT_TRUE(peng_robinson_phase != NULL);
+
+ set_r(1.0);
+ double a_coeff = 3.958134E+5;
+ double b_coeff = 26.6275/1000;
+ double acc_factor = 0.228;
+ const double Tcrit = test_phase->critTemperature();
+ const double pCrit = test_phase->critPressure();
+ double kappa = 0.37464 + 1.54226 * acc_factor - 0.26992 * acc_factor * acc_factor;
+ double temp, pres, alpha, rho, p;
+ int nSolnValues;
+ double Vroot[3];
+
+ const double expected_result[3] = {
+ 24.809417072270659,
+ 0.063638901847459045,
+ 0.10521518550521104
+ };
+
+ //Vapor phase -> nSolnValues = 1
+ temp = 300;
+ pres = 1e5;
+ //calculate alpha
+ alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2);
+ //Find cubic roots
+ nSolnValues = peng_robinson_phase->solveCubic(temp, pres, a_coeff, b_coeff, alpha * a_coeff, Vroot);
+ EXPECT_NEAR(expected_result[0], Vroot[0], 1.e-6);
+ EXPECT_NEAR(nSolnValues, 1 , 1.e-6);
+
+ // Obtain pressure using EoS and compare against the given pressure value
+ set_r(1.0);
+ rho = test_phase->meanMolecularWeight()/Vroot[0];
+ peng_robinson_phase->setState_TR(temp, rho);
+ p = peng_robinson_phase->pressure();
+ EXPECT_NEAR(p, pres, 1);
+
+ //Liquid phase, supercritical -> nSolnValues = -1
+ temp = 300;
+ pres = 80e5;
+ //calculate alpha
+ alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2);
+ //Find cubic roots
+ nSolnValues = peng_robinson_phase->solveCubic(temp, pres, a_coeff, b_coeff, alpha * a_coeff, Vroot);
+ EXPECT_NEAR(expected_result[1], Vroot[0], 1.e-6);
+ EXPECT_NEAR(nSolnValues, -1, 1.e-6);
+
+ // Obtain pressure using EoS and compare against the given pressure value
+ set_r(1.0);
+ rho = test_phase->meanMolecularWeight()/Vroot[0];
+ peng_robinson_phase->setState_TR(temp, rho);
+ p = peng_robinson_phase->pressure();
+ EXPECT_NEAR(p, pres, 1);
+
+ //Near critical point -> nSolnValues = -2
+ temp = Tcrit;
+ //calculate alpha
+ alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2);
+ //Find cubic roots
+ nSolnValues = peng_robinson_phase->solveCubic(Tcrit, pCrit, a_coeff, b_coeff, alpha * a_coeff, Vroot);
+ EXPECT_NEAR(expected_result[2], Vroot[0], 1.e-6);
+ EXPECT_NEAR(nSolnValues, -2, 1.e-6);
+
+ // Obtain pressure using EoS and compare against the given pressure value
+ set_r(1.0);
+ rho = test_phase->meanMolecularWeight()/Vroot[0];
+ peng_robinson_phase->setState_TR(Tcrit, rho);
+ p = peng_robinson_phase->pressure();
+ EXPECT_NEAR(p, pCrit, 1);
+}
+};
diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp
index abdda8a3b4..6ba3e8701e 100644
--- a/test/thermo/thermoFromYaml.cpp
+++ b/test/thermo/thermoFromYaml.cpp
@@ -332,6 +332,21 @@ TEST(ThermoFromYaml, RedlichKwong_CO2)
EXPECT_NEAR(thermo->cp_mass(), 3358.492543261, 1e-8);
}
+
+TEST(ThermoFromYaml, PengRobinson_CO2)
+{
+ auto thermo = newThermo("thermo-models.yaml", "CO2-PR");
+ EXPECT_NEAR(thermo->density(), 924.3096421928459, 1e-8);
+ EXPECT_NEAR(thermo->enthalpy_mass(), -9206196.3008209914, 1e-6);
+ EXPECT_NEAR(thermo->cp_mass(), 2203.2614945393584, 1e-8);
+
+ thermo->setState_TPX(350, 180*OneAtm, "CO2:0.6, H2O:0.02, H2:0.38");
+ EXPECT_NEAR(thermo->density(), 606.92307568968181, 1e-8);
+ EXPECT_NEAR(thermo->enthalpy_mass(), -9067591.6182085164, 1e-6);
+ EXPECT_NEAR(thermo->cp_mass(), 3072.2397990253207, 1e-8);
+ EXPECT_NEAR(thermo->cv_mole(), 32420.064214752296, 1e-8);
+}
+
TEST(ThermoFromYaml, PureFluid_nitrogen)
{
auto thermo = newThermo("thermo-models.yaml", "nitrogen");