From 83080a16abebc64c25380d6fd79adbb20a3a3e1d Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 16 May 2019 11:09:46 -0600 Subject: [PATCH 001/110] Implementation of Peng-Robinson equation of state. Implemetation of Peng-Robinson equation of state added factories for FlowDevice and Wall objects Updating `thermo/PengRobinsonMFPT` class addition. -Adds `test/data/co2_PR_exmample.cti` for testing. -Corrects typo in `src/thermo/PengRobinsonMFTP::setSpeciesCoeffs` -Minor formatting changes (indentations) -Change how `Vroot_` is initialized. Updating ctml_writer.py to include acentric_factor Updating a typo in PengRobinsonMFTP.cpp Updating ctml_writer.py to add units for b_coeff Updating ctml_writer.py to add units for b_coeff with correct indentation Correcting a mistake in the getActivityCoefficient subroutine Updating PengRobinson_Test.cpp with accurate hard-coded values Adding a test getPressure in PengRobinsonMFTP_Test.cpp file Fixing tabs and spaces Replacing 'doublereal' with 'double' Fixing tabs and spaces in the CTI file Adding comments and renaming few parameter names Fixing tabs and spaces Following modifications in the code are performed: 1. Fixing typos 2. Adding more detailed documentation 3. Deleting redundant variables Fixing typos and comments in the cubic solver Fixing typos in PengRobinsonMFTP.h Replacing (molecularWt/rho) calculations by molarVolume() function Modifying tests with tighter tolerances Modifying comments and reference for cubic solver Replacing sqrt(2) with math constant M_SQRT2 Modification in cubic solver with additional comments and removal of redundant code --- include/cantera/thermo/PengRobinsonMFTP.h | 430 +++++++ interfaces/cython/cantera/ctml_writer.py | 55 +- src/thermo/PengRobinsonMFTP.cpp | 1300 +++++++++++++++++++++ src/thermo/ThermoFactory.cpp | 2 + test/data/co2_PR_example.cti | 168 +++ test/thermo/PengRobinsonMFTP_Test.cpp | 205 ++++ 6 files changed, 2155 insertions(+), 5 deletions(-) create mode 100644 include/cantera/thermo/PengRobinsonMFTP.h create mode 100644 src/thermo/PengRobinsonMFTP.cpp create mode 100644 test/data/co2_PR_example.cti create mode 100644 test/thermo/PengRobinsonMFTP_Test.cpp diff --git a/include/cantera/thermo/PengRobinsonMFTP.h b/include/cantera/thermo/PengRobinsonMFTP.h new file mode 100644 index 0000000000..2d47674264 --- /dev/null +++ b/include/cantera/thermo/PengRobinsonMFTP.h @@ -0,0 +1,430 @@ +//! @file PengRobinsonMFTP.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_PENGROBINSONMFTP_H +#define CT_PENGROBINSONMFTP_H + +#include "MixtureFugacityTP.h" +#include "cantera/base/Array.h" + +namespace Cantera +{ +/** + * Implementation of a multi-species Peng-Robinson equation of state + * + * @ingroup thermoprops + */ +class PengRobinsonMFTP : public MixtureFugacityTP +{ +public: + //! @name Constructors and Duplicators + //! @{ + + //! Base constructor. + PengRobinsonMFTP(); + + //! Construct and initialize a PengRobinsonMFTP object directly from an + //! ASCII input file + /*! + * @param infile Name of the input file containing the phase XML data + * to set up the object + * @param id ID of the phase in the input file. Defaults to the empty + * string. + */ + PengRobinsonMFTP(const std::string& infile, const std::string& id=""); + + //! Construct and initialize a PengRobinsonMFTP object directly from an + //! XML database + /*! + * @param phaseRef XML phase node containing the description of the phase + * @param id id attribute containing the name of the phase. (default + * is the empty string) + */ + PengRobinsonMFTP(XML_Node& phaseRef, const std::string& id = ""); + + virtual std::string type() const { + return "PengRobinson"; + } + + //! @name Molar Thermodynamic properties + //! @{ + + virtual double enthalpy_mole() const; + virtual double entropy_mole() const; + 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) for omega <= 0.491 + * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 + * \f] + * + *Coefficients a_mix, b_mix and (a \alpha)_{mix} are caclulated 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; + + // @} + +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 double temp); + virtual void compositionChanged(); + +public: + virtual void getActivityConcentrations(double* c) 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. In many cases, this + * quantity will be the same for all species in a phase. + * 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 m3 kmol-1. + */ + 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 + //@{ + + //! Get the array of non-dimensional species chemical potentials. + //! These are partial molar Gibbs free energies. + /*! + * \f$ \mu_k / \hat R T \f$. + * Units: unitless + * + * We close the loop on this function here calling getChemPotentials() and + * then dividing by RT. No need for child classes to handle. + * + * @param mu Output vector of non-dimensional species chemical potentials + * Length: m_kk. + */ + virtual void getChemPotentials_RT(double* mu) const; + + 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 the temperature dependent interaction parameter alpha needed for P-R EoS + /* + * 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. + * Units: unitless + */ + virtual void calculateAlpha(const std::string& species, double a, double b, double w); + //@} + /// @name Critical State Properties. + //@{ + + virtual double critTemperature() const; + virtual double critPressure() const; + virtual double critVolume() const; + virtual double critCompressibility() const; + virtual double critDensity() const; + virtual double speciesCritTemperature(double a, double b) const; + +public: + //@} + //! @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 setParametersFromXML(const XML_Node& thermoNode); + virtual void setToEquilState(const double* lambda_RT); + virtual void initThermoXML(XML_Node& phaseNode, const std::string& id); + + //! 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 build/data/thermo/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 std::vector getCoeff(const std::string& iName); + + //! Set the pure fluid interaction parameters for a species + /*! + * The "a" parameter for species *i* in the Peng-Robinson model is assumed + * to be a linear function of temperature: + * \f[ a = a_0 + a_1 T \f] + * + * @param species Name of the species + * @param a0 constant term in the expression for the "a" parameter + * of the specified species [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the expression for the + * "a" parameter of the specified species [Pa-m^6/kmol^2/K] + * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] + * @param alpha dimensionless function of T_r and \omega + * @param omega 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] + * + * This function overrides the defaults with the specified parameters: + * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] + * + * @param species_i Name of one species + * @param species_j Name of the other species + * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the "a" expression + * [Pa-m^6/kmol^2/K] + */ + void setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1); + +private: + //! Read the pure species PengRobinson input parameters + /*! + * @param pureFluidParam XML_Node for the pure fluid parameters + */ + void readXMLPureFluid(XML_Node& pureFluidParam); + + //! Read the cross species PengRobinson input parameters + /*! + * @param crossFluidParam XML_Node for the cross fluid parameters + */ + void readXMLCrossFluid(XML_Node& crossFluidParam); + + // @} + +protected: + // Special functions inherited from MixtureFugacityTP + virtual double sresid() const; + virtual double hresid() const; + +public: + 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 pressureCalc(double TKelvin, double molarVol) const; + virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + + //! Calculate dpdV and dpdT at the current conditions + /*! + * These are stored internally. + */ + 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(); + + //! Calculate the a and the b 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 temp Temperature (TKelvin) + * @param aCalc (output) Returns the a value + * @param bCalc (output) Returns the b value. + */ + void calculateAB(double temp, double& aCalc, double& bCalc, double& aAlpha) const; + + // Special functions not inherited from MixtureFugacityTP + + double daAlpha_dT() const; + double d2aAlpha_dT2() const; + + void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) const; + + //! Solve the cubic equation of state + /*! + * The P-R equation of state may be solved via the following formula: + * + * V**3 - V**2(RT/P - b) - V(2bRT/P - \alpha a/P + 3*b*b) - (a \alpha b/p - b*b RT/P - b*b*b) = 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. + * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354–359, https://www.jstor.org/stable/3619777) + */ + int NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, + double Vroot[3]) const; + +protected: + //! Form of the temperature parameterization + /*! + * 0 = There is no temperature parameterization of a or b + * 1 = The a_ij parameter is a linear function of the temperature + */ + int m_formTempParam; + + //! Value of b in the equation of state + /*! + * m_b_current is a function of the temperature and the mole fractions. + */ + double m_b_current; + + //! Value of a and alpha in the equation of state + /*! + * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. + */ + double m_a_current; + double m_aAlpha_current; + + // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk + vector_fp a_vec_Curr_; + vector_fp b_vec_Curr_; + vector_fp aAlpha_vec_Curr_; + vector_fp alpha_vec_Curr_; + vector_fp kappa_vec_; + mutable vector_fp dalphadT_vec_Curr_; + mutable vector_fp d2alphadT2_; + + Array2D a_coeff_vec; + Array2D aAlpha_coeff_vec; + + int NSolns_; + + double Vroot_[3]; + + //! 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; + + //! The derivative of the pressure with respect to the volume + /*! + * Calculated at the current conditions. temperature and mole number kept + * constant + */ + mutable double 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 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 dpdni_; + +public: + //! 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/interfaces/cython/cantera/ctml_writer.py b/interfaces/cython/cantera/ctml_writer.py index 5c65bd1c85..a112a08197 100644 --- a/interfaces/cython/cantera/ctml_writer.py +++ b/interfaces/cython/cantera/ctml_writer.py @@ -842,23 +842,27 @@ class pureFluidParameters(activityCoefficients): """ """ - def __init__(self, species = None, a_coeff = [], b_coeff = 0): + def __init__(self, species = None, a_coeff = [], b_coeff = 0, acentric_factor = None): """ """ self._species = species self._acoeff = a_coeff self._bcoeff = b_coeff + self._w_ac = acentric_factor 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 + if self._w_ac: + s = '%.10g\n' % self._w_ac + cc = f.addChild("acentric_factor", s) class crossFluidParameters(activityCoefficients): @@ -872,12 +876,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,6 +2401,47 @@ def build(self, p): k = ph.addChild("kinetics") k['model'] = self._kin +class PengRobinsonMFTP(phase): + """A multi-component fluid model for non-ideal gas fluids. """ + + def __init__(self, + name = '', + elements = '', + species = '', + note = '', + reactions = 'none', + kinetics = 'GasKinetics', + initial_state = None, + activity_coefficients = None, + transport = 'None', + options = []): + + phase.__init__(self, name, 3, elements, species, note, reactions, + initial_state, options) + self._pure = 0 + self._kin = kinetics + self._tr = transport + self._activityCoefficients = activity_coefficients + + def build(self, p): + ph = phase.build(self, p) + e = ph.child("thermo") + e['model'] = 'PengRobinsonMFTP' + if self._activityCoefficients: + a = e.addChild("activityCoefficients") + if isinstance(self._activityCoefficients, activityCoefficients): + self._activityCoefficients.build(a) + else: + na = len(self._activityCoefficients) + for n in range(na): + self._activityCoefficients[n].build(a) + + if self._tr: + t = ph.addChild('transport') + t['model'] = self._tr + if self._kin: + k = ph.addChild("kinetics") + k['model'] = self._kin class ideal_interface(phase): """A chemically-reacting ideal surface solution of multiple species.""" diff --git a/src/thermo/PengRobinsonMFTP.cpp b/src/thermo/PengRobinsonMFTP.cpp new file mode 100644 index 0000000000..012468e6f5 --- /dev/null +++ b/src/thermo/PengRobinsonMFTP.cpp @@ -0,0 +1,1300 @@ +//! @file PengRobinsonMFTP.cpp + +// This file is part of Cantera. See License.txt in the top-level directory or +// at http://www.cantera.org/license.txt for license and copyright information. + +#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/ThermoFactory.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 PengRobinsonMFTP::omega_a = 4.5723552892138218E-01; +const double PengRobinsonMFTP::omega_b = 7.77960739038885E-02; +const double PengRobinsonMFTP::omega_vc = 3.07401308698703833E-01; + +PengRobinsonMFTP::PengRobinsonMFTP() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); +} + +PengRobinsonMFTP::PengRobinsonMFTP(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + initThermoFile(infile, id_); +} + +PengRobinsonMFTP::PengRobinsonMFTP(XML_Node& phaseRefRoot, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + importPhase(phaseRefRoot, this); +} + +void PengRobinsonMFTP::calculateAlpha(const std::string& species, double a, double b, double w) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + + // Calculate value of kappa (independent of temperature) + // w is an acentric factor of species and must be specified in the CTI file + + if (w <= 0.491) { + kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; + } else { + kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; + } + + //Calculate alpha (temperature dependent interaction parameter) + double criTemp = speciesCritTemperature(a, b); // critical temperature of individual species + double sqt_T_r = sqrt(temperature() / criTemp); + double sqt_alpha = 1 + kappa_vec_[k] * (1 - sqt_T_r); + alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; +} + +void PengRobinsonMFTP::setSpeciesCoeffs(const std::string& species, + double a, double b, double w) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + size_t counter = k + m_kk * k; + a_coeff_vec(0, counter) = a; + // we store this locally because it is used below to calculate a_Alpha: + double aAlpha_k = a*alpha_vec_Curr_[k]; + aAlpha_coeff_vec(0, counter) = 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(a_coeff_vec(0, j + m_kk * j) * a); + double aAlpha_j = a*alpha_vec_Curr_[j]; + double a_Alpha = sqrt(aAlpha_j*aAlpha_k); + if (a_coeff_vec(0, j + m_kk * k) == 0) { + a_coeff_vec(0, j + m_kk * k) = a0kj; + aAlpha_coeff_vec(0, j + m_kk * k) = a_Alpha; + a_coeff_vec(0, k + m_kk * j) = a0kj; + aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; + } + } + a_coeff_vec.getRow(0, a_vec_Curr_.data()); + aAlpha_coeff_vec.getRow(0, aAlpha_vec_Curr_.data()); + b_vec_Curr_[k] = b; +} + +void PengRobinsonMFTP::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double alpha) +{ + size_t ki = speciesIndex(species_i); + if (ki == npos) { + throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_i); + } + size_t kj = speciesIndex(species_j); + if (kj == npos) { + throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_j); + } + + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; + aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; + a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; + aAlpha_vec_Curr_[counter1] = aAlpha_vec_Curr_[counter2] = a0*alpha; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +double PengRobinsonMFTP::enthalpy_mole() const +{ + _updateReferenceStateThermo(); + double h_ideal = RT() * mean_X(m_h0_RT); + double h_nonideal = hresid(); + return h_ideal + h_nonideal; +} + +double PengRobinsonMFTP::entropy_mole() const +{ + _updateReferenceStateThermo(); + double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() + - std::log(pressure()/refPressure())); + double sr_nonideal = sresid(); + return sr_ideal + sr_nonideal; +} + +double PengRobinsonMFTP::cp_mole() const +{ + _updateReferenceStateThermo(); + double TKelvin = temperature(); + double mv = molarVolume(); + double vpb = mv + (1 + M_SQRT2)*m_b_current; + double vmb = mv + (1 - M_SQRT2)*m_b_current; + pressureDerivatives(); + double cpref = GasConstant * mean_X(m_cp0_R); + double dHdT_V = cpref + mv * dpdT_ - GasConstant + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * TKelvin *d2aAlpha_dT2(); + return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; +} + +double PengRobinsonMFTP::cv_mole() const +{ + _updateReferenceStateThermo(); + double TKelvin = temperature(); + pressureDerivatives(); + return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); +} + +double PengRobinsonMFTP::pressure() const +{ + _updateReferenceStateThermo(); + // Get a copy of the private variables stored in the State object + double TKelvin = temperature(); + double mv = molarVolume(); + double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double pp = GasConstant * TKelvin / (mv - m_b_current) - m_aAlpha_current / denom; + return pp; +} + +void PengRobinsonMFTP::calcDensity() +{ + // Calculate the molarVolume of the solution (m**3 kmol-1) + const double* 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 PengRobinsonMFTP::setTemperature(const double temp) +{ + Phase::setTemperature(temp); + _updateReferenceStateThermo(); + updateAB(); +} + +void PengRobinsonMFTP::compositionChanged() +{ + MixtureFugacityTP::compositionChanged(); + updateAB(); +} + +void PengRobinsonMFTP::getActivityConcentrations(double* c) const +{ + getActivityCoefficients(c); + double p_RT = pressure() / RT(); + for (size_t k = 0; k < m_kk; k++) { + c[k] *= moleFraction(k)* p_RT; + } +} + +double PengRobinsonMFTP::standardConcentration(size_t k) const +{ + getStandardVolumes(m_tmpV.data()); + return 1.0 / m_tmpV[k]; +} + +void PengRobinsonMFTP::getActivityCoefficients(double* ac) const +{ + double mv = molarVolume(); + double T = temperature(); + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double vmb = mv - m_b_current; + 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) + + RTkelvin * b_vec_Curr_[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* b_vec_Curr_[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 PengRobinsonMFTP::getChemPotentials_RT(double* muRT) const +{ + getChemPotentials(muRT); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RTkelvin; + } +} + +void PengRobinsonMFTP::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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + double pres = pressure(); + double refP = refPressure(); + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + + mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + + RTkelvin * log(mv / vmb) + + RTkelvin * b_vec_Curr_[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* b_vec_Curr_[k] * mv/den2 + ); + } +} + +void PengRobinsonMFTP::getPartialMolarEnthalpies(double* hbar) const +{ + // First we get the reference state contributions + getEnthalpy_RT_ref(hbar); + scale(hbar, hbar+m_kk, hbar, RT()); + + // We calculate dpdni_ + double TKelvin = temperature(); + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double den2 = den*den; + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + dpdni_[k] = RTkelvin /vmb + RTkelvin * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + + 2 * vmb * m_aAlpha_current * b_vec_Curr_[k] / den2; + } + + double daAlphadT = daAlpha_dT(); + double fac = TKelvin * daAlphadT - m_aAlpha_current; + + pressureDerivatives(); + double fac2 = mv + TKelvin * dpdT_ / dpdV_; + double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac + + (mv * b_vec_Curr_[k]) /(m_b_current*den) * fac; + hbar[k] = hbar[k] + hE_v; + hbar[k] -= fac2 * dpdni_[k]; + } +} + +void PengRobinsonMFTP::getPartialMolarEntropies(double* sbar) const +{ + getEntropy_R_ref(sbar); + scale(sbar, sbar+m_kk, sbar, GasConstant); + double TKelvin = temperature(); + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double refP = refPressure(); + double daAlphadT = daAlpha_dT(); + double coeff1 = 0; + double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + + // 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(dalphadT_vec_Curr_[i] / alpha_vec_Curr_[i]); + } + m_pp[k] = m_pp[k] * dalphadT_vec_Curr_[k] / alpha_vec_Curr_[k]; + } + + + for (size_t k = 0; k < m_kk; k++) { + coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; + sbar[k] += GasConstant * log(GasConstant * TKelvin / (refP * mv)) + + GasConstant + + GasConstant * log(mv / vmb) + + GasConstant * b_vec_Curr_[k] / vmb + - coeff1* log(vpb2 / vmb2) / den1 + - b_vec_Curr_[k] * mv * daAlphadT / den2 / m_b_current; + } + pressureDerivatives(); + getPartialMolarVolumes(m_partialMolarVolumes.data()); + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -= m_partialMolarVolumes[k] * dpdT_; + } +} + +void PengRobinsonMFTP::getPartialMolarIntEnergies(double* ubar) const +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void PengRobinsonMFTP::getPartialMolarCp(double* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +void PengRobinsonMFTP::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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb = mv + m_b_current; + double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double fac2 = fac * fac; + double RTkelvin = RT(); + + for (size_t k = 0; k < m_kk; k++) { + double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * b_vec_Curr_[k] / vmb + + RTkelvin * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + - 2 * mv * m_pp[k] / fac + + 2 * mv * vmb * m_aAlpha_current * b_vec_Curr_[k] / fac2 + ); + double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) + + m_aAlpha_current/fac + - 2 * mv* vpb *m_aAlpha_current / fac2 + ); + vbar[k] = num / denom; + } +} + +double PengRobinsonMFTP::speciesCritTemperature(double a, double b) const +{ + double pc, tc, vc; + calcCriticalConditions(a, b, pc, tc, vc); + return tc; +} + +double PengRobinsonMFTP::critTemperature() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return tc; +} + +double PengRobinsonMFTP::critPressure() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc; +} + +double PengRobinsonMFTP::critVolume() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return vc; +} + +double PengRobinsonMFTP::critCompressibility() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc*vc/tc/GasConstant; +} + +double PengRobinsonMFTP::critDensity() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + double mmw = meanMolecularWeight(); + return mmw / vc; +} + +void PengRobinsonMFTP::setToEquilState(const double* mu_RT) +{ + double tmp, tmp2; + _updateReferenceStateThermo(); + getGibbs_RT_ref(m_tmpV.data()); + + // Within the method, we protect against inf results if the exponent is too + // high. + // + // If it is too low, we set the partial pressure to zero. This capability is + // needed by the elemental potential method. + double pres = 0.0; + double m_p0 = refPressure(); + for (size_t k = 0; k < m_kk; k++) { + tmp = -m_tmpV[k] + mu_RT[k]; + if (tmp < -600.) { + m_pp[k] = 0.0; + } else if (tmp > 500.0) { + tmp2 = tmp / 500.; + tmp2 *= tmp2; + m_pp[k] = m_p0 * exp(500.) * tmp2; + } else { + m_pp[k] = m_p0 * exp(tmp); + } + pres += m_pp[k]; + } + // set state + setState_PX(pres, &m_pp[0]); +} + +bool PengRobinsonMFTP::addSpecies(shared_ptr spec) +{ + bool added = MixtureFugacityTP::addSpecies(spec); + if (added) { + a_vec_Curr_.resize(m_kk * m_kk, 0.0); + b_vec_Curr_.push_back(0.0); + a_vec_Curr_.push_back(0.0); + aAlpha_vec_Curr_.resize(m_kk * m_kk, 0.0); + aAlpha_vec_Curr_.push_back(0.0); + kappa_vec_.push_back(0.0); + + alpha_vec_Curr_.push_back(0.0); + a_coeff_vec.resize(1, m_kk * m_kk, 0.0); + aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); + dalphadT_vec_Curr_.push_back(0.0); + d2alphadT2_.push_back(0.0); + + m_pp.push_back(0.0); + m_tmpV.push_back(0.0); + m_partialMolarVolumes.push_back(0.0); + dpdni_.push_back(0.0); + } + return added; +} + +vector PengRobinsonMFTP::getCoeff(const std::string& iName) +{ + vector_fp spCoeff{ 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, P_crit; + + 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("PengRobinsonMFTP::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("PengRobinsonMFTP::getCoeff", + "Critical Pressure must be positive "); + } + P_crit = vParams; + } + + //Assuming no temperature dependence + 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 + break; + } + } + return spCoeff; +} + +void PengRobinsonMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) +{ + if (phaseNode.hasChild("thermo")) { + XML_Node& thermoNode = phaseNode.child("thermo"); + std::string model = thermoNode["model"]; + if (model != "PengRobinson" && model != "PengRobinsonMFTP") { + throw CanteraError("PengRobinsonMFTP::initThermoXML", + "Unknown thermo model : " + model); + } + + // Go get all of the coefficients and factors in the + // activityCoefficients XML block + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + + // Count the number of species with parameters provided in the + // input file: + size_t nParams = 0; + + // Loop through the children and read out fluid parameters. Process + // all the pureFluidParameters, first: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { + readXMLPureFluid(xmlACChild); + nParams += 1; + } + } + + // If any species exist which have undefined pureFluidParameters, + // search the database in 'critProperties.xml' to find critical + // temperature and pressure to calculate a and b. + + // Loop through all species in the CTI file + size_t iSpecies = 0; + + for (size_t i = 0; i < m_kk; i++) { + string iName = speciesName(i); + + // Get the index of the species + iSpecies = speciesIndex(iName); + + // Check if a and b are already populated (only the diagonal elements of a). + size_t counter = iSpecies + m_kk * iSpecies; + + // If not, then search the database: + if (isnan(a_coeff_vec(0, counter))) { + + vector coeffArray; + + // Search the database for the species name and calculate + // coefficients a and b, from critical properties: + // coeffArray[0] = a0, coeffArray[1] = b, coeffArray[2] = w; + coeffArray = getCoeff(iName); + + // Check if species was found in the database of critical properties, + // and assign the results + if (!isnan(coeffArray[0])) { + //Assuming no temperature dependence (i,e a1 = 0) + setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); + } + } + } + + // Loop back through the "activityCoefficients" children and process the + // crossFluidParameters in the XML tree: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { + readXMLCrossFluid(xmlACChild); + } + } + } + } + + MixtureFugacityTP::initThermoXML(phaseNode, id); +} + +void PengRobinsonMFTP::readXMLPureFluid(XML_Node& pureFluidParam) +{ + string xname = pureFluidParam.name(); + if (xname != "pureFluidParameters") { + throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", + "Incorrect name for processing this routine: " + xname); + } + + double a0 = 0.0; + double a1 = 0.0; + double b = 0.0; + double w = 0.0; + for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { + XML_Node& xmlChild = pureFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + string iModel = toLowerCopy(xmlChild.attrib("model")); + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + + if (vParams.size() == 1) { + a0 = vParams[0]; + } else if (vParams.size() == 2) { + a0 = vParams[0]; + a1 = vParams[1]; + } else { + throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", + "unknown model or incorrect number of parameters"); + } + } else if (nodeName == "b_coeff") { + b = getFloatCurrent(xmlChild, "toSI"); + } else if (nodeName == "acentric_factor") { + w = getFloatCurrent(xmlChild); + } + } + calculateAlpha(pureFluidParam.attrib("species"), a0, b, w); + setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); +} + +void PengRobinsonMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) +{ + string xname = CrossFluidParam.name(); + if (xname != "crossFluidParameters") { + throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", + "Incorrect name for processing this routine: " + xname); + } + + string iName = CrossFluidParam.attrib("species1"); + string jName = CrossFluidParam.attrib("species2"); + + size_t num = CrossFluidParam.nChildren(); + for (size_t iChild = 0; iChild < num; iChild++) { + XML_Node& xmlChild = CrossFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + string iModel = toLowerCopy(xmlChild.attrib("model")); + if (iModel == "constant" && vParams.size() == 1) { + setBinaryCoeffs(iName, jName, vParams[0], 0.0); + } else if (iModel == "linear_a") { + setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); + } else { + throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", + "unknown model ({}) or wrong number of parameters ({})", + iModel, vParams.size()); + } + } + } +} + +void PengRobinsonMFTP::setParametersFromXML(const XML_Node& thermoNode) +{ + MixtureFugacityTP::setParametersFromXML(thermoNode); +} + +double PengRobinsonMFTP::sresid() const +{ + double molarV = molarVolume(); + double hh = m_b_current / molarV; + double zz = z(); + double alpha_1 = daAlpha_dT(); + double T = temperature(); + double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; + double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; + double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); + double sresid_mol_R = log(zz*(1.0 - hh)) + fac * log(vpb / vmb) / GasConstant; + return GasConstant * sresid_mol_R; +} + +double PengRobinsonMFTP::hresid() const +{ + double molarV = molarVolume(); + double zz = z(); + double aAlpha_1 = daAlpha_dT(); + double T = temperature(); + double vpb = molarV + (1 + M_SQRT2) *m_b_current; + double vmb = molarV + (1 - M_SQRT2) *m_b_current; + double fac = 1 / (2.0 * M_SQRT2 * m_b_current); + return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); +} + +double PengRobinsonMFTP::liquidVolEst(double TKelvin, double& presGuess) const +{ + double v = m_b_current * 1.1; + double atmp; + double btmp; + double aAlphatmp; + calculateAB(TKelvin, atmp, btmp, aAlphatmp); + double pres = std::max(psatEst(TKelvin), presGuess); + double Vroot[3]; + bool foundLiq = false; + int m = 0; + while (m < 100 && !foundLiq) { + int nsol = NicholsSolve(TKelvin, 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 PengRobinsonMFTP::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) +{ + // It's necessary to set the temperature so that m_aAlpha_current is set correctly. + setTemperature(TKelvin); + double tcrit = critTemperature(); + double mmw = meanMolecularWeight(); + if (rhoGuess == -1.0) { + if (phaseRequested != FLUID_GAS) { + if (TKelvin > tcrit) { + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } else { + if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } else 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 + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } + } + + double volGuess = mmw / rhoGuess; + NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + + double molarVolLast = Vroot_[0]; + if (NSolns_ >= 2) { + if (phaseRequested >= FLUID_LIQUID_0) { + molarVolLast = Vroot_[0]; + } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[2]; + } else { + if (volGuess > Vroot_[1]) { + molarVolLast = Vroot_[2]; + } else { + molarVolLast = Vroot_[0]; + } + } + } else if (NSolns_ == 1) { + if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else if (NSolns_ == -1) { + if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[0]; + } else if (TKelvin > tcrit) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else { + molarVolLast = Vroot_[0]; + return -1.0; + } + return mmw / molarVolLast; +} + +double PengRobinsonMFTP::densSpinodalLiquid() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, 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 PengRobinsonMFTP::densSpinodalGas() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, 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 PengRobinsonMFTP::pressureCalc(double TKelvin, double molarVol) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; + return pres; +} + +double PengRobinsonMFTP::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; + + double vpb = molarVol + m_b_current; + double vmb = molarVol - m_b_current; + double dpdv = -GasConstant * TKelvin / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); + return dpdv; +} + +void PengRobinsonMFTP::pressureDerivatives() const +{ + double TKelvin = temperature(); + double mv = molarVolume(); + double pres; + + dpdV_ = dpdVCalc(TKelvin, mv, pres); + double vmb = mv - m_b_current; + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); +} + +void PengRobinsonMFTP::updateMixingExpressions() +{ + updateAB(); +} + +void PengRobinsonMFTP::updateAB() +{ + double temp = temperature(); + //Update aAlpha_i + double sqt_alpha; + double criTemp = critTemperature(); + double sqt_T_reduced = sqrt(temp / criTemp); + + // Update indiviual alpha + for (size_t j = 0; j < m_kk; j++) { + sqt_alpha = 1 + kappa_vec_[j] * (1 - sqt_T_reduced); + alpha_vec_Curr_[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++) { + size_t counter = i * m_kk + j; + a_vec_Curr_[counter] = a_coeff_vec(0, counter); + aAlpha_vec_Curr_[counter] = sqrt(alpha_vec_Curr_[i] * alpha_vec_Curr_[j]) * a_coeff_vec(0, counter); + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + m_aAlpha_current = 0.0; + + for (size_t i = 0; i < m_kk; i++) { + m_b_current += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + } + } +} + +void PengRobinsonMFTP::calculateAB(double temp, 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] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + double a_vec_Curr = a_coeff_vec(0, counter); + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += aAlpha_vec_Curr_[counter] * moleFractions_[i] * moleFractions_[j]; + } + } +} + +double PengRobinsonMFTP::daAlpha_dT() const +{ + double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; + double coeff1, coeff2; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + // Calculate first derivative of alpha for individual species + Tc = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); + sqtTr = sqrt(temperature() / Tc); //we need species critical temperature + coeff1 = 1 / (Tc*sqtTr); + coeff2 = sqtTr - 1; + k = kappa_vec_[i]; + dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + } + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j * m_kk + j; + temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alpha_vec_Curr_[i] * alpha_vec_Curr_[j])); + daAlphadT += moleFractions_[i] * moleFractions_[j] * temp + * (dalphadT_vec_Curr_[j] * alpha_vec_Curr_[i] + dalphadT_vec_Curr_[i] * alpha_vec_Curr_[j]); + } + } + return daAlphadT; +} + +double PengRobinsonMFTP::d2aAlpha_dT2() const +{ + double daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; + double k; + double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature + double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); + double coeff2 = sqt_Tr - 1; + for (size_t i = 0; i < m_kk; i++) { + // Calculate first and second derivatives of alpha for individual species + size_t counter = i + m_kk * i; + k = kappa_vec_[i]; + dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); + } + + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + alphai = alpha_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j + m_kk * j; + alphaj = alpha_vec_Curr_[j]; + alphaij = alphai * alphaj; + temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); + num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); + fac1 = -(0.5 / alphaij)*num*num; + fac2 = alphaj * d2alphadT2_[counter1] + alphai *d2alphadT2_[counter2] + 2 * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); + } + } + return d2aAlphadT2; +} + +void PengRobinsonMFTP::calcCriticalConditions(double a, double b, + double& pc, double& tc, double& vc) const +{ + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + tc = a * omega_b / (b * omega_a * GasConstant); + pc = omega_b * GasConstant * tc / b; + vc = omega_vc * GasConstant * tc / pc; +} + +int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, + double Vroot[3]) const +{ + double tmp; + fill_n(Vroot, 3, 0.0); + if (TKelvin <= 0.0) { + throw CanteraError("PengRobinsonMFTP::NicholsSolve()", "negative temperature T = {}", TKelvin); + } + + // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. + double bsqr = b * b; + double RT_p = GasConstant * TKelvin / 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; + + // 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 * TKelvin); // B + if (fabs(ratio1) < 1.0E-7) { + double ratio3 = aAlpha / (GasConstant * TKelvin) * pres / (GasConstant * TKelvin); // 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 * TKelvin / 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("PengRobinsonMFTP::NicholsSolve()", "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; + } + + // 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}):" + " WARNING roots have merged: {}, {}\n", + TKelvin, 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}): " + "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); + writelogendl(); + } + } + + if (nSolnValues == 1) { + if (TKelvin > tc) { + if (Vroot[0] < vc) { + // Liquid phase root + nSolnValues = -1; + } + } else { + if (Vroot[0] < xN) { + nSolnValues = -1; + } + } + } else { + if (nSolnValues == 2 && delta > 1e-14) { + nSolnValues = -2; + } + } + return nSolnValues; +} + +} diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index f1a4c52a98..5425ae6bad 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -23,6 +23,8 @@ #include "cantera/thermo/IonsFromNeutralVPSSTP.h" #include "cantera/thermo/PureFluidPhase.h" #include "cantera/thermo/RedlichKwongMFTP.h" +#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/ConstDensityThermo.h" #include "cantera/thermo/SurfPhase.h" #include "cantera/thermo/EdgePhase.h" #include "cantera/thermo/MetalPhase.h" diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti new file mode 100644 index 0000000000..8692fef113 --- /dev/null +++ b/test/data/co2_PR_example.cti @@ -0,0 +1,168 @@ +# Transport data from file ../transport/gri30_tran.dat. + +units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") + + +PengRobinsonMFTP(name ="carbondioxide", + elements ="C O H N", + species ="""CO2 H2O H2 CO CH4 O2 N2""", + activity_coefficients = (pureFluidParameters(species="CO2", a_coeff = [3.958134E+11, 0], b_coeff = 26.6275, acentric_factor = 0.228), + pureFluidParameters(species="H2O", a_coeff = [5.998873E+11, 0], b_coeff = 18.9714, acentric_factor = 0.344), + pureFluidParameters(species="H2", a_coeff = [2.668423E+10, 0], b_coeff = 16.5478, acentric_factor = -0.22), + pureFluidParameters(species="CO", a_coeff = [1.607164E+11, 0], b_coeff = 24.6549, acentric_factor = 0.049), + pureFluidParameters(species="CH4", a_coeff = [2.496344E+11, 0], b_coeff = 26.8028, acentric_factor = 0.01), + pureFluidParameters(species="O2", a_coeff = [1.497732E+11, 0], b_coeff = 19.8281, acentric_factor = 0.022), + pureFluidParameters(species="N2", a_coeff = [1.485031E+11, 0], b_coeff = 28.0810, acentric_factor = 0.04)), + transport ="Multi", + reactions ="all", + initial_state = state(temperature = 300.0, + pressure = OneAtm, + mole_fractions = 'CO2:0.99, H2:0.01')) + +#------------------------------------------------------------------------------- +# Species data +#------------------------------------------------------------------------------- + +species(name ="H2", + atoms ="H:2", + thermo = ( + NASA([200.00, 1000.00], [2.344331120E+00, 7.980520750E-03, + -1.947815100E-05, 2.015720940E-08, -7.376117610E-12, + -9.179351730E+02, 6.830102380E-01]), + NASA([1000.00, 3500.00], [3.337279200E+00, -4.940247310E-05, + 4.994567780E-07, -1.795663940E-10, 2.002553760E-14, + -9.501589220E+02, -3.205023310E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 2.92, + well_depth = 38.00, + polar = 0.79, + rot_relax = 280.00), + note ="TPIS78" + ) + +species(name ="CO", + atoms ="C:1 O:1", + thermo = ( + NASA([200.00, 1000.00], [3.579533470E+00, -6.103536800E-04, + 1.016814330E-06, 9.070058840E-10, -9.044244990E-13, + -1.434408600E+04, 3.508409280E+00]), + NASA([1000.00, 3500.00], [2.715185610E+00, 2.062527430E-03, + -9.988257710E-07, 2.300530080E-10, -2.036477160E-14, + -1.415187240E+04, 7.818687720E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.65, + well_depth = 98.10, + polar = 1.95, + rot_relax = 1.80), + note ="TPIS79" + ) + + +species(name ="N2", + atoms ="N:2", + thermo = ( + NASA([300.00, 1000.00], [3.298677000E+00, 1.408240400E-03, + -3.963222000E-06, 5.641515000E-09, -2.444854000E-12, + -1.020899900E+03, 3.950372000E+00]), + NASA([1000.00, 5000.00], [2.926640000E+00, 1.487976800E-03, + -5.684760000E-07, 1.009703800E-10, -6.753351000E-15, + -9.227977000E+02, 5.980528000E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.62, + well_depth = 97.53, + polar = 1.76, + rot_relax = 4.00), + note ="121286" + ) + +species(name ="O2", + atoms ="O:2", + thermo = ( + NASA([200.00, 1000.00], [3.782456360E+00, -2.996734160E-03, + 9.847302010E-06, -9.681295090E-09, 3.243728370E-12, + -1.063943560E+03, 3.657675730E+00]), + NASA([1000.00, 3500.00], [3.282537840E+00, 1.483087540E-03, + -7.579666690E-07, 2.094705550E-10, -2.167177940E-14, + -1.088457720E+03, 5.453231290E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.46, + well_depth = 107.40, + polar = 1.60, + rot_relax = 3.80), + note ="TPIS89" + ) + + +species(name ="CO2", + atoms ="C:1 O:2", + thermo = ( + NASA([200.00, 1000.00], [2.356773520E+00, 8.984596770E-03, + -7.123562690E-06, 2.459190220E-09, -1.436995480E-13, + -4.837196970E+04, 9.901052220E+00]), + NASA([1000.00, 3500.00], [3.857460290E+00, 4.414370260E-03, + -2.214814040E-06, 5.234901880E-10, -4.720841640E-14, + -4.875916600E+04, 2.271638060E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.76, + well_depth = 244.00, + polar = 2.65, + rot_relax = 2.10), + note ="L 7/88" + ) + +species(name ="CH4", + atoms ="C:1 H:4", + thermo = ( + NASA([200.00, 1000.00], [5.149876130E+00, -1.367097880E-02, + 4.918005990E-05, -4.847430260E-08, 1.666939560E-11, + -1.024664760E+04, -4.641303760E+00]), + NASA([1000.00, 3500.00], [7.485149500E-02, 1.339094670E-02, + -5.732858090E-06, 1.222925350E-09, -1.018152300E-13, + -9.468344590E+03, 1.843731800E+01]) + ), + transport = gas_transport( + geom ="nonlinear", + diam = 3.75, + well_depth = 141.40, + polar = 2.60, + rot_relax = 13.00), + note ="L 8/88" + ) + + +species(name ="H2O", + atoms ="H:2 O:1", + thermo = ( + NASA([200.00, 1000.00], [4.198640560E+00, -2.036434100E-03, + 6.520402110E-06, -5.487970620E-09, 1.771978170E-12, + -3.029372670E+04, -8.490322080E-01]), + NASA([1000.00, 3500.00], [3.033992490E+00, 2.176918040E-03, + -1.640725180E-07, -9.704198700E-11, 1.682009920E-14, + -3.000429710E+04, 4.966770100E+00]) + ), + transport = gas_transport( + geom ="nonlinear", + diam = 2.60, + well_depth = 572.40, + dipole = 1.85, + rot_relax = 4.00), + note ="L 8/89" + ) + +#——————————————————————————————————————————— +# Reaction data +#——————————————————————————————————————————— + +# Reaction 1 +reaction("CO2 + H2 <=> CO + H2O", [1.2E+3, 0, 0]) + diff --git a/test/thermo/PengRobinsonMFTP_Test.cpp b/test/thermo/PengRobinsonMFTP_Test.cpp new file mode 100644 index 0000000000..9d8043cb5d --- /dev/null +++ b/test/thermo/PengRobinsonMFTP_Test.cpp @@ -0,0 +1,205 @@ +#include "gtest/gtest.h" +#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/ThermoFactory.h" + + +namespace Cantera +{ + +class PengRobinsonMFTP_Test : public testing::Test +{ +public: + PengRobinsonMFTP_Test() { + test_phase.reset(newPhase("../data/co2_PR_example.cti")); + } + + //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(PengRobinsonMFTP_Test, construct_from_cti) +{ + PengRobinsonMFTP* peng_robinson_phase = dynamic_cast(test_phase.get()); + EXPECT_TRUE(peng_robinson_phase != NULL); +} + +TEST_F(PengRobinsonMFTP_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] = { + -4.5736182681761962e+008, + -4.5733771904416579e+008, + -4.5732943831449223e+008, + -4.5732206687414169e+008, + -4.5731546826955432e+008, + -4.5730953161186475e+008, + -4.5730416590547645e+008, + -4.5729929581635743e+008, + -4.5729485847173005e+008 + }; + + 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(PengRobinsonMFTP_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(PengRobinsonMFTP_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(PengRobinsonMFTP_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(PengRobinsonMFTP_Test, setTP) +{ + // Check to make sure that the phase diagram is accurately reproduced for a few select isobars + + // All sub-cooled liquid: + const double p1[6] = { + 1.7474528924963985e+002, + 1.6800540828415956e+002, + 1.62278413743154e+002, + 1.5728963799103039e+002, + 1.5286573762819748e+002, + 1.4888956030449546e+002 + }; + // Phase change between temperatures 4 & 5: + const double p2[6] = { + 7.5565889855724288e+002, + 7.2577747673480337e+002, + 6.913183942651284e+002, + 6.494661249672663e+002, + 5.9240469307757724e+002, + 3.645826047440932e+002 + }; + // Supercritical; no discontinuity in rho values: + const double p3[6] = { + 8.047430802847415e+002, + 7.8291565113595595e+002, + 7.5958477920749681e+002, + 7.3445460137134626e+002, + 7.0712433093853724e+002, + 6.77034438769492e+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(),p1[i],1.e-8); + + test_phase->setState_TP(temp, 7389370.); + EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); + + test_phase->setState_TP(temp, 9236712.5); + EXPECT_NEAR(test_phase->density(),p3[i],1.e-8); + } +} + +TEST_F(PengRobinsonMFTP_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. + * 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.958134E+5; + double b_coeff = 26.6275/1000; + double acc_factor = 0.228; + double pres_theoretical, kappa, alpha, mv; + const double rho = 1.0737; + const double Tcrit = test_phase->critTemperature(); + + //Calculate kappa value + kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; + + for (int i = 0; i<10; i++) + { + const double temp = 296 + i * 2; + set_r(0.999); + test_phase->setState_TR(temp, 1.0737); + mv = 1 / rho * test_phase->meanMolecularWeight(); + //Calculate pressure using Peng-Robinson EoS + alpha = pow(1 + kappa*(1 - sqrt(temp / Tcrit)), 2); + 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, 2); + } +} +}; From f9145df1589c1170b0f4625a5a8475790e7e2d54 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 20 Dec 2019 09:00:05 -0700 Subject: [PATCH 002/110] Updating PengRobinsonMFTP_Test to clarify test composition. --- test/thermo/PengRobinsonMFTP_Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/thermo/PengRobinsonMFTP_Test.cpp b/test/thermo/PengRobinsonMFTP_Test.cpp index 9d8043cb5d..0eb24693b5 100644 --- a/test/thermo/PengRobinsonMFTP_Test.cpp +++ b/test/thermo/PengRobinsonMFTP_Test.cpp @@ -171,7 +171,7 @@ TEST_F(PengRobinsonMFTP_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. + /* This test uses CO2 as the only species (mole fraction 99.9%, balance H2). * 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 From 151a48675986c8565b35861ab692dae39a4c5224 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 20 Dec 2019 09:41:21 -0700 Subject: [PATCH 003/110] Removing MFTP suffix from PengRobinson class --- .../{PengRobinsonMFTP.h => PengRobinson.h} | 20 +-- interfaces/cython/cantera/ctml_writer.py | 4 +- ...{PengRobinsonMFTP.cpp => PengRobinson.cpp} | 152 +++++++++--------- src/thermo/ThermoFactory.cpp | 2 +- test/data/co2_PR_example.cti | 2 +- ...sonMFTP_Test.cpp => PengRobinson_Test.cpp} | 22 +-- 6 files changed, 101 insertions(+), 101 deletions(-) rename include/cantera/thermo/{PengRobinsonMFTP.h => PengRobinson.h} (96%) rename src/thermo/{PengRobinsonMFTP.cpp => PengRobinson.cpp} (89%) rename test/thermo/{PengRobinsonMFTP_Test.cpp => PengRobinson_Test.cpp} (91%) diff --git a/include/cantera/thermo/PengRobinsonMFTP.h b/include/cantera/thermo/PengRobinson.h similarity index 96% rename from include/cantera/thermo/PengRobinsonMFTP.h rename to include/cantera/thermo/PengRobinson.h index 2d47674264..0c14a2e24e 100644 --- a/include/cantera/thermo/PengRobinsonMFTP.h +++ b/include/cantera/thermo/PengRobinson.h @@ -1,10 +1,10 @@ -//! @file PengRobinsonMFTP.h +//! @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_PENGROBINSONMFTP_H -#define CT_PENGROBINSONMFTP_H +#ifndef CT_PENGROBINSON_H +#define CT_PENGROBINSON_H #include "MixtureFugacityTP.h" #include "cantera/base/Array.h" @@ -16,16 +16,16 @@ namespace Cantera * * @ingroup thermoprops */ -class PengRobinsonMFTP : public MixtureFugacityTP +class PengRobinson : public MixtureFugacityTP { public: //! @name Constructors and Duplicators //! @{ //! Base constructor. - PengRobinsonMFTP(); + PengRobinson(); - //! Construct and initialize a PengRobinsonMFTP object directly from an + //! Construct and initialize a PengRobinson object directly from an //! ASCII input file /*! * @param infile Name of the input file containing the phase XML data @@ -33,16 +33,16 @@ class PengRobinsonMFTP : public MixtureFugacityTP * @param id ID of the phase in the input file. Defaults to the empty * string. */ - PengRobinsonMFTP(const std::string& infile, const std::string& id=""); + PengRobinson(const std::string& infile, const std::string& id=""); - //! Construct and initialize a PengRobinsonMFTP object directly from an + //! Construct and initialize a PengRobinson object directly from an //! XML database /*! * @param phaseRef XML phase node containing the description of the phase * @param id id attribute containing the name of the phase. (default * is the empty string) */ - PengRobinsonMFTP(XML_Node& phaseRef, const std::string& id = ""); + PengRobinson(XML_Node& phaseRef, const std::string& id = ""); virtual std::string type() const { return "PengRobinson"; @@ -340,7 +340,7 @@ class PengRobinsonMFTP : public MixtureFugacityTP * 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. - * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354–359, https://www.jstor.org/stable/3619777) + * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354�359, https://www.jstor.org/stable/3619777) */ int NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, double Vroot[3]) const; diff --git a/interfaces/cython/cantera/ctml_writer.py b/interfaces/cython/cantera/ctml_writer.py index a112a08197..f91e2b3614 100644 --- a/interfaces/cython/cantera/ctml_writer.py +++ b/interfaces/cython/cantera/ctml_writer.py @@ -2401,7 +2401,7 @@ def build(self, p): k = ph.addChild("kinetics") k['model'] = self._kin -class PengRobinsonMFTP(phase): +class PengRobinson(phase): """A multi-component fluid model for non-ideal gas fluids. """ def __init__(self, @@ -2426,7 +2426,7 @@ def __init__(self, def build(self, p): ph = phase.build(self, p) e = ph.child("thermo") - e['model'] = 'PengRobinsonMFTP' + e['model'] = 'PengRobinson' if self._activityCoefficients: a = e.addChild("activityCoefficients") if isinstance(self._activityCoefficients, activityCoefficients): diff --git a/src/thermo/PengRobinsonMFTP.cpp b/src/thermo/PengRobinson.cpp similarity index 89% rename from src/thermo/PengRobinsonMFTP.cpp rename to src/thermo/PengRobinson.cpp index 012468e6f5..7c713eb208 100644 --- a/src/thermo/PengRobinsonMFTP.cpp +++ b/src/thermo/PengRobinson.cpp @@ -1,9 +1,9 @@ -//! @file PengRobinsonMFTP.cpp +//! @file PengRobinson.cpp // This file is part of Cantera. See License.txt in the top-level directory or // at http://www.cantera.org/license.txt for license and copyright information. -#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/PengRobinson.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/base/stringUtils.h" #include "cantera/base/ctml.h" @@ -19,11 +19,11 @@ namespace bmt = boost::math::tools; namespace Cantera { -const double PengRobinsonMFTP::omega_a = 4.5723552892138218E-01; -const double PengRobinsonMFTP::omega_b = 7.77960739038885E-02; -const double PengRobinsonMFTP::omega_vc = 3.07401308698703833E-01; +const double PengRobinson::omega_a = 4.5723552892138218E-01; +const double PengRobinson::omega_b = 7.77960739038885E-02; +const double PengRobinson::omega_vc = 3.07401308698703833E-01; -PengRobinsonMFTP::PengRobinsonMFTP() : +PengRobinson::PengRobinson() : m_formTempParam(0), m_b_current(0.0), m_a_current(0.0), @@ -35,7 +35,7 @@ PengRobinsonMFTP::PengRobinsonMFTP() : fill_n(Vroot_, 3, 0.0); } -PengRobinsonMFTP::PengRobinsonMFTP(const std::string& infile, const std::string& id_) : +PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : m_formTempParam(0), m_b_current(0.0), m_a_current(0.0), @@ -48,7 +48,7 @@ PengRobinsonMFTP::PengRobinsonMFTP(const std::string& infile, const std::string& initThermoFile(infile, id_); } -PengRobinsonMFTP::PengRobinsonMFTP(XML_Node& phaseRefRoot, const std::string& id_) : +PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : m_formTempParam(0), m_b_current(0.0), m_a_current(0.0), @@ -61,11 +61,11 @@ PengRobinsonMFTP::PengRobinsonMFTP(XML_Node& phaseRefRoot, const std::string& id importPhase(phaseRefRoot, this); } -void PengRobinsonMFTP::calculateAlpha(const std::string& species, double a, double b, double w) +void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); if (k == npos) { - throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", + throw CanteraError("PengRobinson::setSpeciesCoeffs", "Unknown species '{}'.", species); } @@ -85,12 +85,12 @@ void PengRobinsonMFTP::calculateAlpha(const std::string& species, double a, doub alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; } -void PengRobinsonMFTP::setSpeciesCoeffs(const std::string& species, +void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); if (k == npos) { - throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", + throw CanteraError("PengRobinson::setSpeciesCoeffs", "Unknown species '{}'.", species); } size_t counter = k + m_kk * k; @@ -119,17 +119,17 @@ void PengRobinsonMFTP::setSpeciesCoeffs(const std::string& species, b_vec_Curr_[k] = b; } -void PengRobinsonMFTP::setBinaryCoeffs(const std::string& species_i, +void PengRobinson::setBinaryCoeffs(const std::string& species_i, const std::string& species_j, double a0, double alpha) { size_t ki = speciesIndex(species_i); if (ki == npos) { - throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", + throw CanteraError("PengRobinson::setBinaryCoeffs", "Unknown species '{}'.", species_i); } size_t kj = speciesIndex(species_j); if (kj == npos) { - throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", + throw CanteraError("PengRobinson::setBinaryCoeffs", "Unknown species '{}'.", species_j); } @@ -143,7 +143,7 @@ void PengRobinsonMFTP::setBinaryCoeffs(const std::string& species_i, // ------------Molar Thermodynamic Properties ------------------------- -double PengRobinsonMFTP::enthalpy_mole() const +double PengRobinson::enthalpy_mole() const { _updateReferenceStateThermo(); double h_ideal = RT() * mean_X(m_h0_RT); @@ -151,7 +151,7 @@ double PengRobinsonMFTP::enthalpy_mole() const return h_ideal + h_nonideal; } -double PengRobinsonMFTP::entropy_mole() const +double PengRobinson::entropy_mole() const { _updateReferenceStateThermo(); double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() @@ -160,7 +160,7 @@ double PengRobinsonMFTP::entropy_mole() const return sr_ideal + sr_nonideal; } -double PengRobinsonMFTP::cp_mole() const +double PengRobinson::cp_mole() const { _updateReferenceStateThermo(); double TKelvin = temperature(); @@ -174,7 +174,7 @@ double PengRobinsonMFTP::cp_mole() const return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; } -double PengRobinsonMFTP::cv_mole() const +double PengRobinson::cv_mole() const { _updateReferenceStateThermo(); double TKelvin = temperature(); @@ -182,7 +182,7 @@ double PengRobinsonMFTP::cv_mole() const return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); } -double PengRobinsonMFTP::pressure() const +double PengRobinson::pressure() const { _updateReferenceStateThermo(); // Get a copy of the private variables stored in the State object @@ -193,7 +193,7 @@ double PengRobinsonMFTP::pressure() const return pp; } -void PengRobinsonMFTP::calcDensity() +void PengRobinson::calcDensity() { // Calculate the molarVolume of the solution (m**3 kmol-1) const double* const dtmp = moleFractdivMMW(); @@ -205,20 +205,20 @@ void PengRobinsonMFTP::calcDensity() Phase::setDensity(1.0/invDens); } -void PengRobinsonMFTP::setTemperature(const double temp) +void PengRobinson::setTemperature(const double temp) { Phase::setTemperature(temp); _updateReferenceStateThermo(); updateAB(); } -void PengRobinsonMFTP::compositionChanged() +void PengRobinson::compositionChanged() { MixtureFugacityTP::compositionChanged(); updateAB(); } -void PengRobinsonMFTP::getActivityConcentrations(double* c) const +void PengRobinson::getActivityConcentrations(double* c) const { getActivityCoefficients(c); double p_RT = pressure() / RT(); @@ -227,13 +227,13 @@ void PengRobinsonMFTP::getActivityConcentrations(double* c) const } } -double PengRobinsonMFTP::standardConcentration(size_t k) const +double PengRobinson::standardConcentration(size_t k) const { getStandardVolumes(m_tmpV.data()); return 1.0 / m_tmpV[k]; } -void PengRobinsonMFTP::getActivityCoefficients(double* ac) const +void PengRobinson::getActivityCoefficients(double* ac) const { double mv = molarVolume(); double T = temperature(); @@ -268,7 +268,7 @@ void PengRobinsonMFTP::getActivityCoefficients(double* ac) const // ---- Partial Molar Properties of the Solution ----------------- -void PengRobinsonMFTP::getChemPotentials_RT(double* muRT) const +void PengRobinson::getChemPotentials_RT(double* muRT) const { getChemPotentials(muRT); double RTkelvin = RT(); @@ -277,7 +277,7 @@ void PengRobinsonMFTP::getChemPotentials_RT(double* muRT) const } } -void PengRobinsonMFTP::getChemPotentials(double* mu) const +void PengRobinson::getChemPotentials(double* mu) const { getGibbs_ref(mu); double RTkelvin = RT(); @@ -316,7 +316,7 @@ void PengRobinsonMFTP::getChemPotentials(double* mu) const } } -void PengRobinsonMFTP::getPartialMolarEnthalpies(double* hbar) const +void PengRobinson::getPartialMolarEnthalpies(double* hbar) const { // First we get the reference state contributions getEnthalpy_RT_ref(hbar); @@ -359,7 +359,7 @@ void PengRobinsonMFTP::getPartialMolarEnthalpies(double* hbar) const } } -void PengRobinsonMFTP::getPartialMolarEntropies(double* sbar) const +void PengRobinson::getPartialMolarEntropies(double* sbar) const { getEntropy_R_ref(sbar); scale(sbar, sbar+m_kk, sbar, GasConstant); @@ -404,19 +404,19 @@ void PengRobinsonMFTP::getPartialMolarEntropies(double* sbar) const } } -void PengRobinsonMFTP::getPartialMolarIntEnergies(double* ubar) const +void PengRobinson::getPartialMolarIntEnergies(double* ubar) const { getIntEnergy_RT(ubar); scale(ubar, ubar+m_kk, ubar, RT()); } -void PengRobinsonMFTP::getPartialMolarCp(double* cpbar) const +void PengRobinson::getPartialMolarCp(double* cpbar) const { getCp_R(cpbar); scale(cpbar, cpbar+m_kk, cpbar, GasConstant); } -void PengRobinsonMFTP::getPartialMolarVolumes(double* vbar) const +void PengRobinson::getPartialMolarVolumes(double* vbar) const { for (size_t k = 0; k < m_kk; k++) { m_pp[k] = 0.0; @@ -447,42 +447,42 @@ void PengRobinsonMFTP::getPartialMolarVolumes(double* vbar) const } } -double PengRobinsonMFTP::speciesCritTemperature(double a, double b) const +double PengRobinson::speciesCritTemperature(double a, double b) const { double pc, tc, vc; calcCriticalConditions(a, b, pc, tc, vc); return tc; } -double PengRobinsonMFTP::critTemperature() const +double PengRobinson::critTemperature() const { double pc, tc, vc; calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); return tc; } -double PengRobinsonMFTP::critPressure() const +double PengRobinson::critPressure() const { double pc, tc, vc; calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); return pc; } -double PengRobinsonMFTP::critVolume() const +double PengRobinson::critVolume() const { double pc, tc, vc; calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); return vc; } -double PengRobinsonMFTP::critCompressibility() const +double PengRobinson::critCompressibility() const { double pc, tc, vc; calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); return pc*vc/tc/GasConstant; } -double PengRobinsonMFTP::critDensity() const +double PengRobinson::critDensity() const { double pc, tc, vc; calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); @@ -490,7 +490,7 @@ double PengRobinsonMFTP::critDensity() const return mmw / vc; } -void PengRobinsonMFTP::setToEquilState(const double* mu_RT) +void PengRobinson::setToEquilState(const double* mu_RT) { double tmp, tmp2; _updateReferenceStateThermo(); @@ -520,7 +520,7 @@ void PengRobinsonMFTP::setToEquilState(const double* mu_RT) setState_PX(pres, &m_pp[0]); } -bool PengRobinsonMFTP::addSpecies(shared_ptr spec) +bool PengRobinson::addSpecies(shared_ptr spec) { bool added = MixtureFugacityTP::addSpecies(spec); if (added) { @@ -545,7 +545,7 @@ bool PengRobinsonMFTP::addSpecies(shared_ptr spec) return added; } -vector PengRobinsonMFTP::getCoeff(const std::string& iName) +vector PengRobinson::getCoeff(const std::string& iName) { vector_fp spCoeff{ NAN, NAN }; @@ -577,7 +577,7 @@ vector PengRobinsonMFTP::getCoeff(const std::string& iName) vParams = strSItoDbl(critTemp); } if (vParams <= 0.0) { //Assuming that Pc and Tc are non zero. - throw CanteraError("PengRobinsonMFTP::getCoeff", + throw CanteraError("PengRobinson::getCoeff", "Critical Temperature must be positive "); } T_crit = vParams; @@ -590,7 +590,7 @@ vector PengRobinsonMFTP::getCoeff(const std::string& iName) vParams = strSItoDbl(critPressure); } if (vParams <= 0.0) { //Assuming that Pc and Tc are non zero. - throw CanteraError("PengRobinsonMFTP::getCoeff", + throw CanteraError("PengRobinson::getCoeff", "Critical Pressure must be positive "); } P_crit = vParams; @@ -605,13 +605,13 @@ vector PengRobinsonMFTP::getCoeff(const std::string& iName) return spCoeff; } -void PengRobinsonMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) +void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) { if (phaseNode.hasChild("thermo")) { XML_Node& thermoNode = phaseNode.child("thermo"); std::string model = thermoNode["model"]; - if (model != "PengRobinson" && model != "PengRobinsonMFTP") { - throw CanteraError("PengRobinsonMFTP::initThermoXML", + if (model != "PengRobinson" && model != "PengRobinson") { + throw CanteraError("PengRobinson::initThermoXML", "Unknown thermo model : " + model); } @@ -683,11 +683,11 @@ void PengRobinsonMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) MixtureFugacityTP::initThermoXML(phaseNode, id); } -void PengRobinsonMFTP::readXMLPureFluid(XML_Node& pureFluidParam) +void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) { string xname = pureFluidParam.name(); if (xname != "pureFluidParameters") { - throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", + throw CanteraError("PengRobinson::readXMLPureFluid", "Incorrect name for processing this routine: " + xname); } @@ -710,7 +710,7 @@ void PengRobinsonMFTP::readXMLPureFluid(XML_Node& pureFluidParam) a0 = vParams[0]; a1 = vParams[1]; } else { - throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", + throw CanteraError("PengRobinson::readXMLPureFluid", "unknown model or incorrect number of parameters"); } } else if (nodeName == "b_coeff") { @@ -723,11 +723,11 @@ void PengRobinsonMFTP::readXMLPureFluid(XML_Node& pureFluidParam) setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); } -void PengRobinsonMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) +void PengRobinson::readXMLCrossFluid(XML_Node& CrossFluidParam) { string xname = CrossFluidParam.name(); if (xname != "crossFluidParameters") { - throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", + throw CanteraError("PengRobinson::readXMLCrossFluid", "Incorrect name for processing this routine: " + xname); } @@ -748,7 +748,7 @@ void PengRobinsonMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) } else if (iModel == "linear_a") { setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); } else { - throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", + throw CanteraError("PengRobinson::readXMLCrossFluid", "unknown model ({}) or wrong number of parameters ({})", iModel, vParams.size()); } @@ -756,12 +756,12 @@ void PengRobinsonMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) } } -void PengRobinsonMFTP::setParametersFromXML(const XML_Node& thermoNode) +void PengRobinson::setParametersFromXML(const XML_Node& thermoNode) { MixtureFugacityTP::setParametersFromXML(thermoNode); } -double PengRobinsonMFTP::sresid() const +double PengRobinson::sresid() const { double molarV = molarVolume(); double hh = m_b_current / molarV; @@ -775,7 +775,7 @@ double PengRobinsonMFTP::sresid() const return GasConstant * sresid_mol_R; } -double PengRobinsonMFTP::hresid() const +double PengRobinson::hresid() const { double molarV = molarVolume(); double zz = z(); @@ -787,7 +787,7 @@ double PengRobinsonMFTP::hresid() const return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); } -double PengRobinsonMFTP::liquidVolEst(double TKelvin, double& presGuess) const +double PengRobinson::liquidVolEst(double TKelvin, double& presGuess) const { double v = m_b_current * 1.1; double atmp; @@ -820,7 +820,7 @@ double PengRobinsonMFTP::liquidVolEst(double TKelvin, double& presGuess) const return v; } -double PengRobinsonMFTP::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) +double PengRobinson::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) { // It's necessary to set the temperature so that m_aAlpha_current is set correctly. setTemperature(TKelvin); @@ -881,7 +881,7 @@ double PengRobinsonMFTP::densityCalc(double TKelvin, double presPa, int phaseReq return mmw / molarVolLast; } -double PengRobinsonMFTP::densSpinodalLiquid() const +double PengRobinson::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); @@ -903,7 +903,7 @@ double PengRobinsonMFTP::densSpinodalLiquid() const return mmw / (0.5 * (vv.first + vv.second)); } -double PengRobinsonMFTP::densSpinodalGas() const +double PengRobinson::densSpinodalGas() const { double Vroot[3]; double T = temperature(); @@ -925,14 +925,14 @@ double PengRobinsonMFTP::densSpinodalGas() const return mmw / (0.5 * (vv.first + vv.second)); } -double PengRobinsonMFTP::pressureCalc(double TKelvin, double molarVol) const +double PengRobinson::pressureCalc(double TKelvin, double molarVol) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; return pres; } -double PengRobinsonMFTP::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const +double PengRobinson::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; @@ -943,7 +943,7 @@ double PengRobinsonMFTP::dpdVCalc(double TKelvin, double molarVol, double& presC return dpdv; } -void PengRobinsonMFTP::pressureDerivatives() const +void PengRobinson::pressureDerivatives() const { double TKelvin = temperature(); double mv = molarVolume(); @@ -955,12 +955,12 @@ void PengRobinsonMFTP::pressureDerivatives() const dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); } -void PengRobinsonMFTP::updateMixingExpressions() +void PengRobinson::updateMixingExpressions() { updateAB(); } -void PengRobinsonMFTP::updateAB() +void PengRobinson::updateAB() { double temp = temperature(); //Update aAlpha_i @@ -996,7 +996,7 @@ void PengRobinsonMFTP::updateAB() } } -void PengRobinsonMFTP::calculateAB(double temp, double& aCalc, double& bCalc, double& aAlphaCalc) const +void PengRobinson::calculateAB(double temp, double& aCalc, double& bCalc, double& aAlphaCalc) const { bCalc = 0.0; aCalc = 0.0; @@ -1012,7 +1012,7 @@ void PengRobinsonMFTP::calculateAB(double temp, double& aCalc, double& bCalc, do } } -double PengRobinsonMFTP::daAlpha_dT() const +double PengRobinson::daAlpha_dT() const { double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; double coeff1, coeff2; @@ -1039,7 +1039,7 @@ double PengRobinsonMFTP::daAlpha_dT() const return daAlphadT; } -double PengRobinsonMFTP::d2aAlpha_dT2() const +double PengRobinson::d2aAlpha_dT2() const { double daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; double k; @@ -1072,7 +1072,7 @@ double PengRobinsonMFTP::d2aAlpha_dT2() const return d2aAlphadT2; } -void PengRobinsonMFTP::calcCriticalConditions(double a, double b, +void PengRobinson::calcCriticalConditions(double a, double b, double& pc, double& tc, double& vc) const { if (b <= 0.0) { @@ -1092,13 +1092,13 @@ void PengRobinsonMFTP::calcCriticalConditions(double a, double b, vc = omega_vc * GasConstant * tc / pc; } -int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, +int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, double Vroot[3]) const { double tmp; fill_n(Vroot, 3, 0.0); if (TKelvin <= 0.0) { - throw CanteraError("PengRobinsonMFTP::NicholsSolve()", "negative temperature T = {}", TKelvin); + throw CanteraError("PengRobinson::NicholsSolve()", "negative temperature T = {}", TKelvin); } // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. @@ -1157,7 +1157,7 @@ int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double //check if y = h if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) { if (disc > 1e-10) { - throw CanteraError("PengRobinsonMFTP::NicholsSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); + throw CanteraError("PengRobinson::NicholsSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); } disc = 0.0; } @@ -1212,7 +1212,7 @@ int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}):" + writelog("PengRobinson::NicholsSolve(T = {}, p = {}):" " WARNING roots have merged: {}, {}\n", TKelvin, pres, Vroot[i], Vroot[j]); } @@ -1232,7 +1232,7 @@ int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad conditioned."); + throw CanteraError("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -1240,7 +1240,7 @@ int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad conditioned."); + throw CanteraError("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad conditioned."); } delta = -delta; Vroot[0] = xN + delta; @@ -1272,7 +1272,7 @@ int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double } } if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) { - writelog("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}): " + writelog("PengRobinson::NicholsSolve(T = {}, p = {}): " "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); writelogendl(); } diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 5425ae6bad..a134efb793 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -23,7 +23,7 @@ #include "cantera/thermo/IonsFromNeutralVPSSTP.h" #include "cantera/thermo/PureFluidPhase.h" #include "cantera/thermo/RedlichKwongMFTP.h" -#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/PengRobinson.h" #include "cantera/thermo/ConstDensityThermo.h" #include "cantera/thermo/SurfPhase.h" #include "cantera/thermo/EdgePhase.h" diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti index 8692fef113..147816cd9d 100644 --- a/test/data/co2_PR_example.cti +++ b/test/data/co2_PR_example.cti @@ -3,7 +3,7 @@ units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") -PengRobinsonMFTP(name ="carbondioxide", +PengRobinson(name ="carbondioxide", elements ="C O H N", species ="""CO2 H2O H2 CO CH4 O2 N2""", activity_coefficients = (pureFluidParameters(species="CO2", a_coeff = [3.958134E+11, 0], b_coeff = 26.6275, acentric_factor = 0.228), diff --git a/test/thermo/PengRobinsonMFTP_Test.cpp b/test/thermo/PengRobinson_Test.cpp similarity index 91% rename from test/thermo/PengRobinsonMFTP_Test.cpp rename to test/thermo/PengRobinson_Test.cpp index 0eb24693b5..201d197ae3 100644 --- a/test/thermo/PengRobinsonMFTP_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -1,15 +1,15 @@ #include "gtest/gtest.h" -#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/PengRobinson.h" #include "cantera/thermo/ThermoFactory.h" namespace Cantera { -class PengRobinsonMFTP_Test : public testing::Test +class PengRobinson_Test : public testing::Test { public: - PengRobinsonMFTP_Test() { + PengRobinson_Test() { test_phase.reset(newPhase("../data/co2_PR_example.cti")); } @@ -24,13 +24,13 @@ class PengRobinsonMFTP_Test : public testing::Test std::unique_ptr test_phase; }; -TEST_F(PengRobinsonMFTP_Test, construct_from_cti) +TEST_F(PengRobinson_Test, construct_from_cti) { - PengRobinsonMFTP* peng_robinson_phase = dynamic_cast(test_phase.get()); + PengRobinson* peng_robinson_phase = dynamic_cast(test_phase.get()); EXPECT_TRUE(peng_robinson_phase != NULL); } -TEST_F(PengRobinsonMFTP_Test, chem_potentials) +TEST_F(PengRobinson_Test, chem_potentials) { test_phase->setState_TP(298.15, 101325.); /* Chemical potential should increase with increasing co2 mole fraction: @@ -63,7 +63,7 @@ TEST_F(PengRobinsonMFTP_Test, chem_potentials) } } -TEST_F(PengRobinsonMFTP_Test, activityCoeffs) +TEST_F(PengRobinson_Test, activityCoeffs) { test_phase->setState_TP(298., 1.); @@ -89,13 +89,13 @@ TEST_F(PengRobinsonMFTP_Test, activityCoeffs) } } -TEST_F(PengRobinsonMFTP_Test, standardConcentrations) +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(PengRobinsonMFTP_Test, activityConcentrations) +TEST_F(PengRobinson_Test, activityConcentrations) { // Check to make sure activityConcentration_i == standardConcentration_i * gamma_i * X_i vector_fp standardConcs(7); @@ -120,7 +120,7 @@ TEST_F(PengRobinsonMFTP_Test, activityConcentrations) } } -TEST_F(PengRobinsonMFTP_Test, setTP) +TEST_F(PengRobinson_Test, setTP) { // Check to make sure that the phase diagram is accurately reproduced for a few select isobars @@ -167,7 +167,7 @@ TEST_F(PengRobinsonMFTP_Test, setTP) } } -TEST_F(PengRobinsonMFTP_Test, getPressure) +TEST_F(PengRobinson_Test, getPressure) { // Check to make sure that the P-R equation is accurately reproduced for a few selected values From 3fbb73f8141dde78cbc0ea3a5399155f7359ca23 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 20 Dec 2019 11:04:47 -0700 Subject: [PATCH 004/110] Renaming TKelvin and cleaning up unused variables in PengRobinson.cpp --- src/thermo/PengRobinson.cpp | 92 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 7c713eb208..3fc9671e89 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -163,33 +163,33 @@ double PengRobinson::entropy_mole() const double PengRobinson::cp_mole() const { _updateReferenceStateThermo(); - double TKelvin = temperature(); + double T = temperature(); double mv = molarVolume(); double vpb = mv + (1 + M_SQRT2)*m_b_current; double vmb = mv + (1 - M_SQRT2)*m_b_current; pressureDerivatives(); double cpref = GasConstant * mean_X(m_cp0_R); double dHdT_V = cpref + mv * dpdT_ - GasConstant - + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * TKelvin *d2aAlpha_dT2(); - return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); + return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_; } double PengRobinson::cv_mole() const { _updateReferenceStateThermo(); - double TKelvin = temperature(); + double T = temperature(); pressureDerivatives(); - return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); + return (cp_mole() + T* dpdT_* dpdT_ / dpdV_); } double PengRobinson::pressure() const { _updateReferenceStateThermo(); // Get a copy of the private variables stored in the State object - double TKelvin = temperature(); + double T = temperature(); double mv = molarVolume(); double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double pp = GasConstant * TKelvin / (mv - m_b_current) - m_aAlpha_current / denom; + double pp = GasConstant * T / (mv - m_b_current) - m_aAlpha_current / denom; return pp; } @@ -236,7 +236,7 @@ double PengRobinson::standardConcentration(size_t k) const void PengRobinson::getActivityCoefficients(double* ac) const { double mv = molarVolume(); - double T = temperature(); + //double T = temperature(); double vpb2 = mv + (1 + M_SQRT2)*m_b_current; double vmb2 = mv + (1 - M_SQRT2)*m_b_current; double vmb = mv - m_b_current; @@ -323,7 +323,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const scale(hbar, hbar+m_kk, hbar, RT()); // We calculate dpdni_ - double TKelvin = temperature(); + double T = temperature(); double mv = molarVolume(); double vmb = mv - m_b_current; double vpb2 = mv + (1 + M_SQRT2)*m_b_current; @@ -346,10 +346,10 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const } double daAlphadT = daAlpha_dT(); - double fac = TKelvin * daAlphadT - m_aAlpha_current; + double fac = T * daAlphadT - m_aAlpha_current; pressureDerivatives(); - double fac2 = mv + TKelvin * dpdT_ / dpdV_; + double fac2 = mv + T * dpdT_ / dpdV_; double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; for (size_t k = 0; k < m_kk; k++) { double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac @@ -363,7 +363,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const { getEntropy_R_ref(sbar); scale(sbar, sbar+m_kk, sbar, GasConstant); - double TKelvin = temperature(); + double T = temperature(); double mv = molarVolume(); double vmb = mv - m_b_current; double vpb2 = mv + (1 + M_SQRT2)*m_b_current; @@ -390,7 +390,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t k = 0; k < m_kk; k++) { coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; - sbar[k] += GasConstant * log(GasConstant * TKelvin / (refP * mv)) + sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) + GasConstant + GasConstant * log(mv / vmb) + GasConstant * b_vec_Curr_[k] / vmb @@ -567,7 +567,7 @@ vector PengRobinson::getCoeff(const std::string& iName) if (iNameLower == dbName) { // Read from database and calculate a and b coefficients double vParams; - double T_crit, P_crit; + double T_crit, P_crit = 0.0; if (acNodeDoc.hasChild("Tc")) { vParams = 0.0; @@ -767,7 +767,6 @@ double PengRobinson::sresid() const double hh = m_b_current / molarV; double zz = z(); double alpha_1 = daAlpha_dT(); - double T = temperature(); double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); @@ -787,19 +786,19 @@ double PengRobinson::hresid() const return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); } -double PengRobinson::liquidVolEst(double TKelvin, double& presGuess) const +double PengRobinson::liquidVolEst(double T, double& presGuess) const { double v = m_b_current * 1.1; double atmp; double btmp; double aAlphatmp; - calculateAB(TKelvin, atmp, btmp, aAlphatmp); - double pres = std::max(psatEst(TKelvin), presGuess); + calculateAB(T, 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 = NicholsSolve(TKelvin, pres, atmp, btmp, aAlphatmp, Vroot); + int nsol = NicholsSolve(T, pres, atmp, btmp, aAlphatmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -820,32 +819,32 @@ double PengRobinson::liquidVolEst(double TKelvin, double& presGuess) const return v; } -double PengRobinson::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) +double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, double rhoGuess) { // It's necessary to set the temperature so that m_aAlpha_current is set correctly. - setTemperature(TKelvin); + setTemperature(T); double tcrit = critTemperature(); double mmw = meanMolecularWeight(); if (rhoGuess == -1.0) { if (phaseRequested != FLUID_GAS) { - if (TKelvin > tcrit) { - rhoGuess = presPa * mmw / (GasConstant * TKelvin); + if (T > tcrit) { + rhoGuess = presPa * mmw / (GasConstant * T); } else { if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - rhoGuess = presPa * mmw / (GasConstant * TKelvin); + rhoGuess = presPa * mmw / (GasConstant * T); } else if (phaseRequested >= FLUID_LIQUID_0) { - double lqvol = liquidVolEst(TKelvin, presPa); + 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 * TKelvin); + rhoGuess = presPa * mmw / (GasConstant * T); } } double volGuess = mmw / rhoGuess; - NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + NSolns_ = NicholsSolve(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); double molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -869,7 +868,7 @@ double PengRobinson::densityCalc(double TKelvin, double presPa, int phaseRequest } else if (NSolns_ == -1) { if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { molarVolLast = Vroot_[0]; - } else if (TKelvin > tcrit) { + } else if (T > tcrit) { molarVolLast = Vroot_[0]; } else { return -2.0; @@ -925,31 +924,31 @@ double PengRobinson::densSpinodalGas() const return mmw / (0.5 * (vv.first + vv.second)); } -double PengRobinson::pressureCalc(double TKelvin, double molarVol) const +double PengRobinson::pressureCalc(double T, double molarVol) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; + double pres = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; return pres; } -double PengRobinson::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const +double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; + presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current/ den; double vpb = molarVol + m_b_current; double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * TKelvin / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); + double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); return dpdv; } void PengRobinson::pressureDerivatives() const { - double TKelvin = temperature(); + double T = temperature(); double mv = molarVolume(); double pres; - dpdV_ = dpdVCalc(TKelvin, mv, pres); + dpdV_ = dpdVCalc(T, mv, pres); double vmb = mv - m_b_current; double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); @@ -1041,14 +1040,13 @@ double PengRobinson::daAlpha_dT() const double PengRobinson::d2aAlpha_dT2() const { - double daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; + double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; double k; double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); double coeff2 = sqt_Tr - 1; for (size_t i = 0; i < m_kk; i++) { // Calculate first and second derivatives of alpha for individual species - size_t counter = i + m_kk * i; k = kappa_vec_[i]; dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); @@ -1092,18 +1090,18 @@ void PengRobinson::calcCriticalConditions(double a, double b, vc = omega_vc * GasConstant * tc / pc; } -int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, +int PengRobinson::NicholsSolve(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const { double tmp; fill_n(Vroot, 3, 0.0); - if (TKelvin <= 0.0) { - throw CanteraError("PengRobinson::NicholsSolve()", "negative temperature T = {}", TKelvin); + if (T <= 0.0) { + throw CanteraError("PengRobinson::NicholsSolve()", "negative temperature T = {}", T); } // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. double bsqr = b * b; - double RT_p = GasConstant * TKelvin / pres; + double RT_p = GasConstant * T / pres; double aAlpha_p = aAlpha / pres; double an = 1.0; double bn = (b - RT_p); @@ -1124,9 +1122,9 @@ int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, // 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 * TKelvin); // B + double ratio2 = pres * b / (GasConstant * T); // B if (fabs(ratio1) < 1.0E-7) { - double ratio3 = aAlpha / (GasConstant * TKelvin) * pres / (GasConstant * TKelvin); // A + 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; @@ -1138,7 +1136,7 @@ int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, break; } } - double v = zz * GasConstant * TKelvin / pres; + double v = zz * GasConstant * T / pres; Vroot[0] = v; return 1; } @@ -1214,7 +1212,7 @@ int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, if (j != i && fabs(Vroot[i] - Vroot[j]) < 1.0E-4 * (fabs(Vroot[i]) + fabs(Vroot[j]))) { writelog("PengRobinson::NicholsSolve(T = {}, p = {}):" " WARNING roots have merged: {}, {}\n", - TKelvin, pres, Vroot[i], Vroot[j]); + T, pres, Vroot[i], Vroot[j]); } } } @@ -1273,13 +1271,13 @@ int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, } if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) { writelog("PengRobinson::NicholsSolve(T = {}, p = {}): " - "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); + "WARNING root didn't converge V = {}", T, pres, Vroot[i]); writelogendl(); } } if (nSolnValues == 1) { - if (TKelvin > tc) { + if (T > tc) { if (Vroot[0] < vc) { // Liquid phase root nSolnValues = -1; From b0d1333cc1f137c89e126a04cd990c8b177e606b Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 20 Dec 2019 23:25:06 -0700 Subject: [PATCH 005/110] Abstracting thermo calcs from PengRobinson and RedlichKwong up to MixtureFugacityTP --- include/cantera/thermo/MixtureFugacityTP.h | 12 +++++++ include/cantera/thermo/PengRobinson.h | 22 ------------- include/cantera/thermo/RedlichKwongMFTP.h | 24 +------------- src/thermo/MixtureFugacityTP.cpp | 37 ++++++++++++++++++++- src/thermo/PengRobinson.cpp | 38 ---------------------- src/thermo/RedlichKwongMFTP.cpp | 37 --------------------- 6 files changed, 49 insertions(+), 121 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 1658cac337..2e60855793 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 doublereal entropy_mole() const; + //@} /// @name Partial Molar Properties of the Solution //@{ @@ -318,6 +325,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 @@ -509,6 +519,8 @@ 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 diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 0c14a2e24e..53c3552465 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -51,8 +51,6 @@ class PengRobinson : public MixtureFugacityTP //! @name Molar Thermodynamic properties //! @{ - virtual double enthalpy_mole() const; - virtual double entropy_mole() const; virtual double cp_mole() const; virtual double cv_mole() const; @@ -105,31 +103,11 @@ class PengRobinson : 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 double temp); virtual void compositionChanged(); public: - virtual void getActivityConcentrations(double* c) const; //! Returns the standard concentration \f$ C^0_k \f$, which is used to //! normalize the generalized concentration. diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index d6317e33d9..857b934948 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -47,9 +47,7 @@ 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; @@ -72,31 +70,11 @@ 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. diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index b16d3e8357..73f210000e 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -44,6 +44,25 @@ int MixtureFugacityTP::reportSolnBranchActual() const return iState_; } +// ---- Molar Thermodynamic Properties --------------------------- +double MixtureFugacityTP::enthalpy_mole() const +{ + _updateReferenceStateThermo(); + double h_ideal = RT() * mean_X(m_h0_RT); + double h_nonideal = hresid(); + return h_ideal + h_nonideal; +} + + +double MixtureFugacityTP::entropy_mole() const +{ + _updateReferenceStateThermo(); + double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() + - std::log(pressure()/refPressure())); + double sr_nonideal = sresid(); + return sr_ideal + sr_nonideal; +} + // ---- Partial Molar Properties of the Solution ----------------- void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const @@ -237,6 +256,15 @@ void MixtureFugacityTP::compositionChanged() getMoleFractions(moleFractions_.data()); } +void MixtureFugacityTP::getActivityConcentrations(doublereal* c) const +{ + getActivityCoefficients(c); + double p_RT = pressure() / RT(); + for (size_t k = 0; k < m_kk; k++) { + c[k] *= moleFraction(k)*p_RT; + } +} + void MixtureFugacityTP::setMoleFractions_NoState(const doublereal* const x) { Phase::setMoleFractions(x); @@ -246,7 +274,14 @@ void MixtureFugacityTP::setMoleFractions_NoState(const doublereal* const x) void MixtureFugacityTP::calcDensity() { - throw NotImplementedError("MixtureFugacityTP::calcDensity"); + // Calculate the molarVolume of the solution (m**3 kmol-1) + const double* 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 MixtureFugacityTP::setState_TP(doublereal t, doublereal pres) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 3fc9671e89..a3844ff8c2 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -143,23 +143,6 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, // ------------Molar Thermodynamic Properties ------------------------- -double PengRobinson::enthalpy_mole() const -{ - _updateReferenceStateThermo(); - double h_ideal = RT() * mean_X(m_h0_RT); - double h_nonideal = hresid(); - return h_ideal + h_nonideal; -} - -double PengRobinson::entropy_mole() const -{ - _updateReferenceStateThermo(); - double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - - std::log(pressure()/refPressure())); - double sr_nonideal = sresid(); - return sr_ideal + sr_nonideal; -} - double PengRobinson::cp_mole() const { _updateReferenceStateThermo(); @@ -193,18 +176,6 @@ double PengRobinson::pressure() const return pp; } -void PengRobinson::calcDensity() -{ - // Calculate the molarVolume of the solution (m**3 kmol-1) - const double* 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 PengRobinson::setTemperature(const double temp) { Phase::setTemperature(temp); @@ -218,15 +189,6 @@ void PengRobinson::compositionChanged() updateAB(); } -void PengRobinson::getActivityConcentrations(double* c) const -{ - getActivityCoefficients(c); - double p_RT = pressure() / RT(); - for (size_t k = 0; k < m_kk; k++) { - c[k] *= moleFraction(k)* p_RT; - } -} - double PengRobinson::standardConcentration(size_t k) const { getStandardVolumes(m_tmpV.data()); diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 50ca199855..96dda3ede7 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,18 +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); @@ -198,14 +169,6 @@ void RedlichKwongMFTP::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()); From 1bee6cf8a5a8c0f3a6d424c23769c173083b4d19 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 30 Dec 2019 10:25:43 -0700 Subject: [PATCH 006/110] Abstracting cubic solver to MixtureFugacityTP thermo class. --- include/cantera/thermo/MixtureFugacityTP.h | 26 +++ include/cantera/thermo/PengRobinson.h | 15 +- include/cantera/thermo/RedlichKwongMFTP.h | 16 +- src/thermo/MixtureFugacityTP.cpp | 200 +++++++++++++++++++ src/thermo/PengRobinson.cpp | 209 +------------------ src/thermo/RedlichKwongMFTP.cpp | 222 ++------------------- 6 files changed, 254 insertions(+), 434 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 2e60855793..c430ddcf8f 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -545,6 +545,19 @@ class MixtureFugacityTP : public ThermoPhase virtual void updateMixingExpressions(); + + //! Solve the cubic equation of state + /*! + * + * 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. + * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354�359, https://www.jstor.org/stable/3619777) + */ + int NicholsSolve(double T, double pres, double a, double b, + double aAlpha, double Vroot[3], double an, + double bn, double cn, double dn, double tc) const; + //@} protected: @@ -583,6 +596,19 @@ class MixtureFugacityTP : public ThermoPhase //! Temporary storage for dimensionless reference state entropies mutable vector_fp m_s0_R; + +public: + //! 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; }; } diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 53c3552465..f94e9671c1 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -309,20 +309,9 @@ class PengRobinson : public MixtureFugacityTP void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) const; - //! Solve the cubic equation of state - /*! - * The P-R equation of state may be solved via the following formula: - * - * V**3 - V**2(RT/P - b) - V(2bRT/P - \alpha a/P + 3*b*b) - (a \alpha b/p - b*b RT/P - b*b*b) = 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. - * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354�359, https://www.jstor.org/stable/3619777) - */ - int NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, + //! Prepare variables and call the function to solve the cubic equation of state + int NicholsCall(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const; - protected: //! Form of the temperature parameterization /*! diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index 857b934948..ca6d5d4059 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -223,7 +223,7 @@ 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; @@ -264,18 +264,8 @@ class RedlichKwongMFTP : public MixtureFugacityTP void calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, 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 NicholsCall(double T, double pres, double a, double b, double Vroot[3]) const; protected: //! Form of the temperature parameterization diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 73f210000e..6275d50c46 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -17,6 +17,10 @@ using namespace std; namespace Cantera { +const double MixtureFugacityTP::omega_a = 0.; +const double MixtureFugacityTP::omega_b = 0.; +const double MixtureFugacityTP::omega_vc = 0.; + MixtureFugacityTP::MixtureFugacityTP() : iState_(FLUID_GAS), forcedState_(FLUID_UNDEFINED), @@ -851,4 +855,200 @@ void MixtureFugacityTP::invalidateCache() m_Tlast_ref += 0.001234; } +int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, + double aAlpha, double Vroot[3], double an, + double bn, double cn, double dn, double tc) const +{ + double tmp; + fill_n(Vroot, 3, 0.0); + if (T <= 0.0) { + throw CanteraError("MixtureFugacityTP::NicholsSolve()", "negative temperature T = {}", T); + } + + double pc = omega_b * GasConstant * tc / b; + double vc = omega_vc * GasConstant * tc / pc; + + // 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("PengRobinson::NicholsSolve()", "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; + } + + // 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("PengRobinson::NicholsSolve(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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve(T = {}, p = {}): " + "WARNING root didn't converge V = {}", T, pres, Vroot[i]); + writelogendl(); + } + } + + if (nSolnValues == 1) { + if (T > tc) { + if (Vroot[0] < vc) { + // Liquid phase root + nSolnValues = -1; + } + } else { + if (Vroot[0] < xN) { + nSolnValues = -1; + } + } + } else { + if (nSolnValues == 2 && delta > 1e-14) { + nSolnValues = -2; + } + } + return nSolnValues; +} + } diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index a3844ff8c2..1127c0af84 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -760,7 +760,7 @@ double PengRobinson::liquidVolEst(double T, double& presGuess) const bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { - int nsol = NicholsSolve(T, pres, atmp, btmp, aAlphatmp, Vroot); + int nsol = NicholsCall(T, pres, atmp, btmp, aAlphatmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -788,25 +788,17 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do double tcrit = critTemperature(); double mmw = meanMolecularWeight(); if (rhoGuess == -1.0) { - if (phaseRequested != FLUID_GAS) { - if (T > tcrit) { - rhoGuess = presPa * mmw / (GasConstant * T); - } else { - if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - rhoGuess = presPa * mmw / (GasConstant * T); - } else if (phaseRequested >= FLUID_LIQUID_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; - NSolns_ = NicholsSolve(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + NSolns_ = NicholsCall(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); double molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -846,7 +838,7 @@ double PengRobinson::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); - int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); + int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -868,7 +860,7 @@ double PengRobinson::densSpinodalGas() const { double Vroot[3]; double T = temperature(); - int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); + int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -1052,15 +1044,8 @@ void PengRobinson::calcCriticalConditions(double a, double b, vc = omega_vc * GasConstant * tc / pc; } -int PengRobinson::NicholsSolve(double T, double pres, double a, double b, double aAlpha, - double Vroot[3]) const +int PengRobinson::NicholsCall(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const { - double tmp; - fill_n(Vroot, 3, 0.0); - if (T <= 0.0) { - throw CanteraError("PengRobinson::NicholsSolve()", "negative temperature T = {}", T); - } - // 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; @@ -1071,189 +1056,9 @@ int PengRobinson::NicholsSolve(double T, double pres, double a, double b, double 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; - - // 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("PengRobinson::NicholsSolve()", "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; - } - - // 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("PengRobinson::NicholsSolve(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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve(T = {}, p = {}): " - "WARNING root didn't converge V = {}", T, pres, Vroot[i]); - writelogendl(); - } - } + int nSolnValues = NicholsSolve(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); - if (nSolnValues == 1) { - if (T > tc) { - if (Vroot[0] < vc) { - // Liquid phase root - nSolnValues = -1; - } - } else { - if (Vroot[0] < xN) { - nSolnValues = -1; - } - } - } else { - if (nSolnValues == 2 && delta > 1e-14) { - nSolnValues = -2; - } - } return nSolnValues; } diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 96dda3ede7..34bf91a923 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -805,7 +805,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 = NicholsCall(TKelvin, pres, atmp, btmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -831,28 +831,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_ = NicholsCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); doublereal molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -892,7 +884,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 = NicholsCall(T, pressure(), m_a_current, m_b_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -914,7 +906,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 = NicholsCall(T, pressure(), m_a_current, m_b_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -1091,204 +1083,22 @@ 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::NicholsCall(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); double pp = 2./3.; 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 = NicholsSolve(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc); + return nSolnValues; } From 0ffbe6b77f06d0910d236b999389877093691b72 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 9 Jan 2020 21:30:18 -0700 Subject: [PATCH 007/110] Add capability to load Peng Robinson via YAML. --- include/cantera/thermo/PengRobinson.h | 3 +- src/thermo/MixtureFugacityTP.cpp | 2 +- src/thermo/PengRobinson.cpp | 77 +++++++++++++-- test/data/co2_PR_example.yaml | 129 ++++++++++++++++++++++++++ test/thermo/PengRobinson_Test.cpp | 2 +- 5 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 test/data/co2_PR_example.yaml diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index f94e9671c1..e2d8a7fe67 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -195,6 +195,7 @@ class PengRobinson : public MixtureFugacityTP virtual void setParametersFromXML(const XML_Node& thermoNode); virtual void setToEquilState(const double* lambda_RT); virtual void initThermoXML(XML_Node& phaseNode, const std::string& id); + virtual void initThermo(); //! Retrieve a and b coefficients by looking up tabulated critical parameters /*! @@ -225,7 +226,7 @@ class PengRobinson : public MixtureFugacityTP * @param alpha dimensionless function of T_r and \omega * @param omega acentric factor */ - void setSpeciesCoeffs(const std::string& species, double a, double b, + void setSpeciesCoeffs(const std::string& species, double a0, double b, double w); //! Set values for the interaction parameter between two species diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 6275d50c46..5f19587460 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -911,7 +911,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, //check if y = h if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) { if (disc > 1e-10) { - throw CanteraError("PengRobinson::NicholsSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); + throw CanteraError("MixtureFugacityTP::NicholsSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); } disc = 0.0; } diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 1127c0af84..7e93e32857 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -65,22 +65,24 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b { size_t k = speciesIndex(species); if (k == npos) { - throw CanteraError("PengRobinson::setSpeciesCoeffs", + throw CanteraError("PengRobinson::calculateAlpha", "Unknown species '{}'.", species); } // Calculate value of kappa (independent of temperature) // w is an acentric factor of species and must be specified in the CTI file - - if (w <= 0.491) { + if (isnan(w)){ + throw CanteraError("PengRobinson::calculateALpha", + "No acentric factor loaded."); + } else if (w <= 0.491) { kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; } //Calculate alpha (temperature dependent interaction parameter) - double criTemp = speciesCritTemperature(a, b); // critical temperature of individual species - double sqt_T_r = sqrt(temperature() / criTemp); + double critTemp = speciesCritTemperature(a, b); // critical temperature of individual species + double sqt_T_r = sqrt(temperature() / critTemp); double sqt_alpha = 1 + kappa_vec_[k] * (1 - sqt_T_r); alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; } @@ -645,6 +647,67 @@ void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) MixtureFugacityTP::initThermoXML(phaseNode, id); } +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"].as(); + if (eos.getString("model", "") != "Peng-Robinson") { + throw InputFileError("PengRobinson::initThermo", eos, + "Expected species equation of state to be 'Peng-Robinson', " + "but got '{}' instead", eos.getString("model", "")); + } + double a0 = 0, a1 = 0; + if (eos["a"].isScalar()) { + a0 = eos.convert("a", "Pa*m^6/kmol^2"); + } else { + auto avec = eos["a"].asVector(2); + a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); + a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K"); + } + double b = eos.convert("b", "m^3/kmol"); + // unitless acentric factor: + double w = eos.getDouble("w_ac",NAN); + + calculateAlpha(item.first, a0, b, w); + 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, a1 = 0; + if (item2.second.isScalar()) { + a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); + } else { + auto avec = item2.second.asVector(2); + a0 = units.convert(avec[0], "Pa*m^6/kmol^2"); + a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K"); + } + setBinaryCoeffs(item.first, item2.first, a0, a1); + } + } + } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { + // coeffs[0] = a0, coeffs[1] = b; + vector coeffs = getCoeff(item.first); + + // Check if species was found in the database of critical + // properties, and assign the results + if (!isnan(coeffs[0])) { + // Assuming no temperature dependence (i.e. a1 = 0) + setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); + } + } + } + } +} + void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) { string xname = pureFluidParam.name(); @@ -918,8 +981,8 @@ void PengRobinson::updateAB() double temp = temperature(); //Update aAlpha_i double sqt_alpha; - double criTemp = critTemperature(); - double sqt_T_reduced = sqrt(temp / criTemp); + double critTemp = critTemperature(); + double sqt_T_reduced = sqrt(temp / critTemp); // Update indiviual alpha for (size_t j = 0; j < m_kk; j++) { diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml new file mode 100644 index 0000000000..2e04a3dac1 --- /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, 0] + b: 26.6275 + w_ac: 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, 0] + b: 18.9714 + w_ac: 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, 0] + b: 16.5478 + w_ac: -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, 0] + b: 24.6549 + w_ac: 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, 0] + b: 26.8028 + w_ac: 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, 0] + b: 19.8281 + w_ac: 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, 0] + b: 28.0810 + w_ac: 0.04 + + +reactions: +- equation: CO2 + H2 <=> CO + H2O # Reaction 1 + rate-constant: {A: 1.2E+3, b: 0, Ea: 0} \ No newline at end of file diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 201d197ae3..fd75902ae4 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -10,7 +10,7 @@ class PengRobinson_Test : public testing::Test { public: PengRobinson_Test() { - test_phase.reset(newPhase("../data/co2_PR_example.cti")); + test_phase.reset(newPhase("../data/co2_PR_example.yaml")); } //vary the composition of a co2-h2 mixture: From fa3c77654c921039388250969f0e8ce64509ea15 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 10 Jan 2020 14:17:05 -0700 Subject: [PATCH 008/110] Removing xml and cti capabilities from thermo::PengRobinson. --- include/cantera/thermo/PengRobinson.h | 28 +--------------- interfaces/cython/cantera/ctml_writer.py | 42 ------------------------ src/thermo/PengRobinson.cpp | 12 +++---- 3 files changed, 7 insertions(+), 75 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index e2d8a7fe67..17b5a598fd 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -28,22 +28,13 @@ class PengRobinson : public MixtureFugacityTP //! Construct and initialize a PengRobinson object directly from an //! ASCII input file /*! - * @param infile Name of the input file containing the phase XML data + * @param infile Name of the input file containing the phase YAML data * to set up the object * @param id ID of the phase in the input file. Defaults to the empty * string. */ PengRobinson(const std::string& infile, const std::string& id=""); - //! Construct and initialize a PengRobinson object directly from an - //! XML database - /*! - * @param phaseRef XML phase node containing the description of the phase - * @param id id attribute containing the name of the phase. (default - * is the empty string) - */ - PengRobinson(XML_Node& phaseRef, const std::string& id = ""); - virtual std::string type() const { return "PengRobinson"; } @@ -192,9 +183,7 @@ class PengRobinson : public MixtureFugacityTP //@{ virtual bool addSpecies(shared_ptr spec); - virtual void setParametersFromXML(const XML_Node& thermoNode); virtual void setToEquilState(const double* lambda_RT); - virtual void initThermoXML(XML_Node& phaseNode, const std::string& id); virtual void initThermo(); //! Retrieve a and b coefficients by looking up tabulated critical parameters @@ -247,21 +236,6 @@ class PengRobinson : public MixtureFugacityTP void setBinaryCoeffs(const std::string& species_i, const std::string& species_j, double a0, double a1); -private: - //! Read the pure species PengRobinson input parameters - /*! - * @param pureFluidParam XML_Node for the pure fluid parameters - */ - void readXMLPureFluid(XML_Node& pureFluidParam); - - //! Read the cross species PengRobinson input parameters - /*! - * @param crossFluidParam XML_Node for the cross fluid parameters - */ - void readXMLCrossFluid(XML_Node& crossFluidParam); - - // @} - protected: // Special functions inherited from MixtureFugacityTP virtual double sresid() const; diff --git a/interfaces/cython/cantera/ctml_writer.py b/interfaces/cython/cantera/ctml_writer.py index f91e2b3614..7873218d50 100644 --- a/interfaces/cython/cantera/ctml_writer.py +++ b/interfaces/cython/cantera/ctml_writer.py @@ -2401,48 +2401,6 @@ def build(self, p): k = ph.addChild("kinetics") k['model'] = self._kin -class PengRobinson(phase): - """A multi-component fluid model for non-ideal gas fluids. """ - - def __init__(self, - name = '', - elements = '', - species = '', - note = '', - reactions = 'none', - kinetics = 'GasKinetics', - initial_state = None, - activity_coefficients = None, - transport = 'None', - options = []): - - phase.__init__(self, name, 3, elements, species, note, reactions, - initial_state, options) - self._pure = 0 - self._kin = kinetics - self._tr = transport - self._activityCoefficients = activity_coefficients - - def build(self, p): - ph = phase.build(self, p) - e = ph.child("thermo") - e['model'] = 'PengRobinson' - if self._activityCoefficients: - a = e.addChild("activityCoefficients") - if isinstance(self._activityCoefficients, activityCoefficients): - self._activityCoefficients.build(a) - else: - na = len(self._activityCoefficients) - for n in range(na): - self._activityCoefficients[n].build(a) - - if self._tr: - t = ph.addChild('transport') - t['model'] = self._tr - if self._kin: - 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/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 7e93e32857..aff2aaeb4b 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -48,7 +48,7 @@ PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : initThermoFile(infile, id_); } -PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : +/*PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : m_formTempParam(0), m_b_current(0.0), m_a_current(0.0), @@ -60,7 +60,7 @@ PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : fill_n(Vroot_, 3, 0.0); importPhase(phaseRefRoot, this); } - +*/ void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); @@ -569,7 +569,7 @@ vector PengRobinson::getCoeff(const std::string& iName) return spCoeff; } -void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) +/* void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) { if (phaseNode.hasChild("thermo")) { XML_Node& thermoNode = phaseNode.child("thermo"); @@ -646,7 +646,7 @@ void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) MixtureFugacityTP::initThermoXML(phaseNode, id); } - +*/ void PengRobinson::initThermo() { for (auto& item : m_species) { @@ -708,7 +708,7 @@ void PengRobinson::initThermo() } } -void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) +/*void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) { string xname = pureFluidParam.name(); if (xname != "pureFluidParameters") { @@ -785,7 +785,7 @@ void PengRobinson::setParametersFromXML(const XML_Node& thermoNode) { MixtureFugacityTP::setParametersFromXML(thermoNode); } - +*/ double PengRobinson::sresid() const { double molarV = molarVolume(); From bc06b6d2f2748c12347f8bf435b8c5ac8135e4b7 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 00:51:34 -0700 Subject: [PATCH 009/110] Fix species coefficient updates in PengRobinson. -Species coefficients were using phase critical termperature, rather than species critical temepratures. -Fixing this required updates to regression tests. -Removed commented code from previous commit (removing XML capabilities) --- src/thermo/PengRobinson.cpp | 170 ++---------------------------- test/thermo/PengRobinson_Test.cpp | 56 +++++----- 2 files changed, 36 insertions(+), 190 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index aff2aaeb4b..f759ed7685 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -569,84 +569,6 @@ vector PengRobinson::getCoeff(const std::string& iName) return spCoeff; } -/* void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) -{ - if (phaseNode.hasChild("thermo")) { - XML_Node& thermoNode = phaseNode.child("thermo"); - std::string model = thermoNode["model"]; - if (model != "PengRobinson" && model != "PengRobinson") { - throw CanteraError("PengRobinson::initThermoXML", - "Unknown thermo model : " + model); - } - - // Go get all of the coefficients and factors in the - // activityCoefficients XML block - if (thermoNode.hasChild("activityCoefficients")) { - XML_Node& acNode = thermoNode.child("activityCoefficients"); - - // Count the number of species with parameters provided in the - // input file: - size_t nParams = 0; - - // Loop through the children and read out fluid parameters. Process - // all the pureFluidParameters, first: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { - readXMLPureFluid(xmlACChild); - nParams += 1; - } - } - - // If any species exist which have undefined pureFluidParameters, - // search the database in 'critProperties.xml' to find critical - // temperature and pressure to calculate a and b. - - // Loop through all species in the CTI file - size_t iSpecies = 0; - - for (size_t i = 0; i < m_kk; i++) { - string iName = speciesName(i); - - // Get the index of the species - iSpecies = speciesIndex(iName); - - // Check if a and b are already populated (only the diagonal elements of a). - size_t counter = iSpecies + m_kk * iSpecies; - - // If not, then search the database: - if (isnan(a_coeff_vec(0, counter))) { - - vector coeffArray; - - // Search the database for the species name and calculate - // coefficients a and b, from critical properties: - // coeffArray[0] = a0, coeffArray[1] = b, coeffArray[2] = w; - coeffArray = getCoeff(iName); - - // Check if species was found in the database of critical properties, - // and assign the results - if (!isnan(coeffArray[0])) { - //Assuming no temperature dependence (i,e a1 = 0) - setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); - } - } - } - - // Loop back through the "activityCoefficients" children and process the - // crossFluidParameters in the XML tree: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { - readXMLCrossFluid(xmlACChild); - } - } - } - } - - MixtureFugacityTP::initThermoXML(phaseNode, id); -} -*/ void PengRobinson::initThermo() { for (auto& item : m_species) { @@ -708,84 +630,6 @@ void PengRobinson::initThermo() } } -/*void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) -{ - string xname = pureFluidParam.name(); - if (xname != "pureFluidParameters") { - throw CanteraError("PengRobinson::readXMLPureFluid", - "Incorrect name for processing this routine: " + xname); - } - - double a0 = 0.0; - double a1 = 0.0; - double b = 0.0; - double w = 0.0; - for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { - XML_Node& xmlChild = pureFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - string iModel = toLowerCopy(xmlChild.attrib("model")); - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - - if (vParams.size() == 1) { - a0 = vParams[0]; - } else if (vParams.size() == 2) { - a0 = vParams[0]; - a1 = vParams[1]; - } else { - throw CanteraError("PengRobinson::readXMLPureFluid", - "unknown model or incorrect number of parameters"); - } - } else if (nodeName == "b_coeff") { - b = getFloatCurrent(xmlChild, "toSI"); - } else if (nodeName == "acentric_factor") { - w = getFloatCurrent(xmlChild); - } - } - calculateAlpha(pureFluidParam.attrib("species"), a0, b, w); - setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); -} - -void PengRobinson::readXMLCrossFluid(XML_Node& CrossFluidParam) -{ - string xname = CrossFluidParam.name(); - if (xname != "crossFluidParameters") { - throw CanteraError("PengRobinson::readXMLCrossFluid", - "Incorrect name for processing this routine: " + xname); - } - - string iName = CrossFluidParam.attrib("species1"); - string jName = CrossFluidParam.attrib("species2"); - - size_t num = CrossFluidParam.nChildren(); - for (size_t iChild = 0; iChild < num; iChild++) { - XML_Node& xmlChild = CrossFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - string iModel = toLowerCopy(xmlChild.attrib("model")); - if (iModel == "constant" && vParams.size() == 1) { - setBinaryCoeffs(iName, jName, vParams[0], 0.0); - } else if (iModel == "linear_a") { - setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); - } else { - throw CanteraError("PengRobinson::readXMLCrossFluid", - "unknown model ({}) or wrong number of parameters ({})", - iModel, vParams.size()); - } - } - } -} - -void PengRobinson::setParametersFromXML(const XML_Node& thermoNode) -{ - MixtureFugacityTP::setParametersFromXML(thermoNode); -} -*/ double PengRobinson::sresid() const { double molarV = molarVolume(); @@ -981,12 +825,12 @@ void PengRobinson::updateAB() double temp = temperature(); //Update aAlpha_i double sqt_alpha; - double critTemp = critTemperature(); - double sqt_T_reduced = sqrt(temp / critTemp); // Update indiviual alpha for (size_t j = 0; j < m_kk; j++) { - sqt_alpha = 1 + kappa_vec_[j] * (1 - sqt_T_reduced); + size_t counter = j * m_kk + j; + double critTemp_j = speciesCritTemperature(a_vec_Curr_[counter],b_vec_Curr_[j]); + sqt_alpha = 1 + kappa_vec_[j] * (1 - sqrt(temp / critTemp_j)); alpha_vec_Curr_[j] = sqt_alpha*sqt_alpha; } @@ -1059,10 +903,12 @@ double PengRobinson::d2aAlpha_dT2() const { double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; double k; - double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature - double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); - double coeff2 = sqt_Tr - 1; for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + double Tcrit_i = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[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 k = kappa_vec_[i]; dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index fd75902ae4..0dd5f551d1 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -39,15 +39,15 @@ TEST_F(PengRobinson_Test, chem_potentials) * calculated using the model. */ const double expected_result[9] = { - -4.5736182681761962e+008, - -4.5733771904416579e+008, - -4.5732943831449223e+008, - -4.5732206687414169e+008, - -4.5731546826955432e+008, - -4.5730953161186475e+008, - -4.5730416590547645e+008, - -4.5729929581635743e+008, - -4.5729485847173005e+008 + -4.5736172836328608e+008, + -4.5735067964144629e+008, + -4.5734081696030951e+008, + -4.5733195900446898e+008, + -4.5732395963640761e+008, + -4.5731669975736719e+008, + -4.5731008132866347e+008, + -4.5730402291258186e+008, + -4.5729845630046815e+008 }; double xmin = 0.6; @@ -126,30 +126,30 @@ TEST_F(PengRobinson_Test, setTP) // All sub-cooled liquid: const double p1[6] = { - 1.7474528924963985e+002, - 1.6800540828415956e+002, - 1.62278413743154e+002, - 1.5728963799103039e+002, - 1.5286573762819748e+002, - 1.4888956030449546e+002 + 1.7504058222993714e+002, + 1.6824762614489907e+002, + 1.6248354709581241e+002, + 1.5746729362032696e+002, + 1.5302217175386241e+002, + 1.4902908974486667e+002 }; // Phase change between temperatures 4 & 5: const double p2[6] = { - 7.5565889855724288e+002, - 7.2577747673480337e+002, - 6.913183942651284e+002, - 6.494661249672663e+002, - 5.9240469307757724e+002, - 3.645826047440932e+002 + 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 p3[6] = { - 8.047430802847415e+002, - 7.8291565113595595e+002, - 7.5958477920749681e+002, - 7.3445460137134626e+002, - 7.0712433093853724e+002, - 6.77034438769492e+002 + 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) @@ -159,7 +159,7 @@ TEST_F(PengRobinson_Test, setTP) test_phase->setState_TP(temp, 5542027.5); EXPECT_NEAR(test_phase->density(),p1[i],1.e-8); - test_phase->setState_TP(temp, 7389370.); + test_phase->setState_TP(temp, 7388370.); EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); test_phase->setState_TP(temp, 9236712.5); From bf5102a1fe184de0bb91093132517bf52e882d87 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 10:00:18 -0700 Subject: [PATCH 010/110] Deleting test/data/co2_PR_example.cti --- test/data/co2_PR_example.cti | 168 ----------------------------------- 1 file changed, 168 deletions(-) delete mode 100644 test/data/co2_PR_example.cti diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti deleted file mode 100644 index 147816cd9d..0000000000 --- a/test/data/co2_PR_example.cti +++ /dev/null @@ -1,168 +0,0 @@ -# Transport data from file ../transport/gri30_tran.dat. - -units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") - - -PengRobinson(name ="carbondioxide", - elements ="C O H N", - species ="""CO2 H2O H2 CO CH4 O2 N2""", - activity_coefficients = (pureFluidParameters(species="CO2", a_coeff = [3.958134E+11, 0], b_coeff = 26.6275, acentric_factor = 0.228), - pureFluidParameters(species="H2O", a_coeff = [5.998873E+11, 0], b_coeff = 18.9714, acentric_factor = 0.344), - pureFluidParameters(species="H2", a_coeff = [2.668423E+10, 0], b_coeff = 16.5478, acentric_factor = -0.22), - pureFluidParameters(species="CO", a_coeff = [1.607164E+11, 0], b_coeff = 24.6549, acentric_factor = 0.049), - pureFluidParameters(species="CH4", a_coeff = [2.496344E+11, 0], b_coeff = 26.8028, acentric_factor = 0.01), - pureFluidParameters(species="O2", a_coeff = [1.497732E+11, 0], b_coeff = 19.8281, acentric_factor = 0.022), - pureFluidParameters(species="N2", a_coeff = [1.485031E+11, 0], b_coeff = 28.0810, acentric_factor = 0.04)), - transport ="Multi", - reactions ="all", - initial_state = state(temperature = 300.0, - pressure = OneAtm, - mole_fractions = 'CO2:0.99, H2:0.01')) - -#------------------------------------------------------------------------------- -# Species data -#------------------------------------------------------------------------------- - -species(name ="H2", - atoms ="H:2", - thermo = ( - NASA([200.00, 1000.00], [2.344331120E+00, 7.980520750E-03, - -1.947815100E-05, 2.015720940E-08, -7.376117610E-12, - -9.179351730E+02, 6.830102380E-01]), - NASA([1000.00, 3500.00], [3.337279200E+00, -4.940247310E-05, - 4.994567780E-07, -1.795663940E-10, 2.002553760E-14, - -9.501589220E+02, -3.205023310E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 2.92, - well_depth = 38.00, - polar = 0.79, - rot_relax = 280.00), - note ="TPIS78" - ) - -species(name ="CO", - atoms ="C:1 O:1", - thermo = ( - NASA([200.00, 1000.00], [3.579533470E+00, -6.103536800E-04, - 1.016814330E-06, 9.070058840E-10, -9.044244990E-13, - -1.434408600E+04, 3.508409280E+00]), - NASA([1000.00, 3500.00], [2.715185610E+00, 2.062527430E-03, - -9.988257710E-07, 2.300530080E-10, -2.036477160E-14, - -1.415187240E+04, 7.818687720E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.65, - well_depth = 98.10, - polar = 1.95, - rot_relax = 1.80), - note ="TPIS79" - ) - - -species(name ="N2", - atoms ="N:2", - thermo = ( - NASA([300.00, 1000.00], [3.298677000E+00, 1.408240400E-03, - -3.963222000E-06, 5.641515000E-09, -2.444854000E-12, - -1.020899900E+03, 3.950372000E+00]), - NASA([1000.00, 5000.00], [2.926640000E+00, 1.487976800E-03, - -5.684760000E-07, 1.009703800E-10, -6.753351000E-15, - -9.227977000E+02, 5.980528000E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.62, - well_depth = 97.53, - polar = 1.76, - rot_relax = 4.00), - note ="121286" - ) - -species(name ="O2", - atoms ="O:2", - thermo = ( - NASA([200.00, 1000.00], [3.782456360E+00, -2.996734160E-03, - 9.847302010E-06, -9.681295090E-09, 3.243728370E-12, - -1.063943560E+03, 3.657675730E+00]), - NASA([1000.00, 3500.00], [3.282537840E+00, 1.483087540E-03, - -7.579666690E-07, 2.094705550E-10, -2.167177940E-14, - -1.088457720E+03, 5.453231290E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.46, - well_depth = 107.40, - polar = 1.60, - rot_relax = 3.80), - note ="TPIS89" - ) - - -species(name ="CO2", - atoms ="C:1 O:2", - thermo = ( - NASA([200.00, 1000.00], [2.356773520E+00, 8.984596770E-03, - -7.123562690E-06, 2.459190220E-09, -1.436995480E-13, - -4.837196970E+04, 9.901052220E+00]), - NASA([1000.00, 3500.00], [3.857460290E+00, 4.414370260E-03, - -2.214814040E-06, 5.234901880E-10, -4.720841640E-14, - -4.875916600E+04, 2.271638060E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.76, - well_depth = 244.00, - polar = 2.65, - rot_relax = 2.10), - note ="L 7/88" - ) - -species(name ="CH4", - atoms ="C:1 H:4", - thermo = ( - NASA([200.00, 1000.00], [5.149876130E+00, -1.367097880E-02, - 4.918005990E-05, -4.847430260E-08, 1.666939560E-11, - -1.024664760E+04, -4.641303760E+00]), - NASA([1000.00, 3500.00], [7.485149500E-02, 1.339094670E-02, - -5.732858090E-06, 1.222925350E-09, -1.018152300E-13, - -9.468344590E+03, 1.843731800E+01]) - ), - transport = gas_transport( - geom ="nonlinear", - diam = 3.75, - well_depth = 141.40, - polar = 2.60, - rot_relax = 13.00), - note ="L 8/88" - ) - - -species(name ="H2O", - atoms ="H:2 O:1", - thermo = ( - NASA([200.00, 1000.00], [4.198640560E+00, -2.036434100E-03, - 6.520402110E-06, -5.487970620E-09, 1.771978170E-12, - -3.029372670E+04, -8.490322080E-01]), - NASA([1000.00, 3500.00], [3.033992490E+00, 2.176918040E-03, - -1.640725180E-07, -9.704198700E-11, 1.682009920E-14, - -3.000429710E+04, 4.966770100E+00]) - ), - transport = gas_transport( - geom ="nonlinear", - diam = 2.60, - well_depth = 572.40, - dipole = 1.85, - rot_relax = 4.00), - note ="L 8/89" - ) - -#——————————————————————————————————————————— -# Reaction data -#——————————————————————————————————————————— - -# Reaction 1 -reaction("CO2 + H2 <=> CO + H2O", [1.2E+3, 0, 0]) - From 46808864a353a28e2f7be571c0fac55f195001fe Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 10:08:53 -0700 Subject: [PATCH 011/110] Fixing location of critProperties.xml referenced in thermo/PengRobinson.h --- include/cantera/thermo/PengRobinson.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 17b5a598fd..2c488da38c 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -189,7 +189,7 @@ class PengRobinson : public MixtureFugacityTP //! 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 build/data/thermo/critProperties.xml. + * 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] * From 9f592438e4df10bdb1292054b80f91e5a91e0a22 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 14:49:01 -0700 Subject: [PATCH 012/110] Deleting commented code in thermo/PengRobinson.cpp --- src/thermo/PengRobinson.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index f759ed7685..0a20057994 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -48,19 +48,6 @@ PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : initThermoFile(infile, id_); } -/*PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - importPhase(phaseRefRoot, this); -} -*/ void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); From c40bd53dbd931b02114eaeeb3f2698c46803a14c Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 15:26:26 -0700 Subject: [PATCH 013/110] Adding ThermoFromYAML:PengRobinson_CO2 test - Creates CO2-PR phase in test/data/thermo-models.yaml - Creates `PengRobinson_CO2` test in test/thermo/thermoFromYAML.cpp - Fixes minor capitalization typo in PengRobinson:calculateAlpha --- src/thermo/PengRobinson.cpp | 2 +- test/data/thermo-models.yaml | 59 ++++++++++++++++++++++++++++++++++ test/thermo/thermoFromYaml.cpp | 14 ++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 0a20057994..ddbdae81b8 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -59,7 +59,7 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b // Calculate value of kappa (independent of temperature) // w is an acentric factor of species and must be specified in the CTI file if (isnan(w)){ - throw CanteraError("PengRobinson::calculateALpha", + throw CanteraError("PengRobinson::calculateAlpha", "No acentric factor loaded."); } else if (w <= 0.491) { kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; diff --git a/test/data/thermo-models.yaml b/test/data/thermo-models.yaml index 7c1f998ceb..2e341b7444 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, 0] + b: 16.5478 + w_ac: -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, 0] + b: 18.9714 + w_ac: 0.344 + binary-a: + H2: [4 bar*cm^6/mol^2, 40 bar*cm^6/mol^2*K^-1] + 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, 0] + b: 26.6275 + w_ac: 0.228 + + HMW-species: - name: H2O(L) composition: {H: 2, O: 1} diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index abdda8a3b4..a166221451 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -332,6 +332,20 @@ 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.2135196672034, 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(), 3065.022259252295, 1e-8); +} + TEST(ThermoFromYaml, PureFluid_nitrogen) { auto thermo = newThermo("thermo-models.yaml", "nitrogen"); From 8ee1372700fb96e2f43b5062521e6f21813f6e71 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Thu, 16 Jan 2020 17:20:38 -0500 Subject: [PATCH 014/110] Remove unnecessary overrides in MixtureFugacityTP and children --- include/cantera/thermo/MixtureFugacityTP.h | 34 ------ include/cantera/thermo/PengRobinson.h | 9 +- include/cantera/thermo/RedlichKwongMFTP.h | 9 +- src/thermo/MixtureFugacityTP.cpp | 115 ++++++--------------- src/thermo/PengRobinson.cpp | 18 ---- src/thermo/RedlichKwongMFTP.cpp | 20 +--- 6 files changed, 35 insertions(+), 170 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index c430ddcf8f..7fc8ff0053 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -279,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. /*! @@ -561,8 +533,6 @@ class MixtureFugacityTP : public ThermoPhase //@} protected: - virtual void invalidateCache(); - //! Storage for the current values of the mole fractions of the species /*! * This vector is kept up-to-date when some the setState functions are called. @@ -581,10 +551,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; diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 2c488da38c..dbba37fb7c 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -93,11 +93,6 @@ class PengRobinson : public MixtureFugacityTP // @} -protected: - - virtual void setTemperature(const double temp); - virtual void compositionChanged(); - public: //! Returns the standard concentration \f$ C^0_k \f$, which is used to @@ -256,15 +251,13 @@ class PengRobinson : 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 /*! diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index ca6d5d4059..09ee4db89b 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -69,11 +69,6 @@ class RedlichKwongMFTP : public MixtureFugacityTP // @} -protected: - - virtual void setTemperature(const doublereal temp); - virtual void compositionChanged(); - public: //! Returns the standard concentration \f$ C^0_k \f$, which is used to @@ -236,15 +231,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 /*! diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 5f19587460..025b5e7644 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -23,8 +23,7 @@ const double MixtureFugacityTP::omega_vc = 0.; MixtureFugacityTP::MixtureFugacityTP() : iState_(FLUID_GAS), - forcedState_(FLUID_UNDEFINED), - m_Tlast_ref(-1.0) + forcedState_(FLUID_UNDEFINED) { } @@ -221,7 +220,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); } } @@ -245,129 +244,85 @@ 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::getActivityConcentrations(doublereal* c) const -{ - getActivityCoefficients(c); - double p_RT = pressure() / RT(); - for (size_t k = 0; k < m_kk; k++) { - c[k] *= moleFraction(k)*p_RT; - } -} - -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() -{ - // Calculate the molarVolume of the solution (m**3 kmol-1) - const double* 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 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 @@ -834,9 +789,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++) { @@ -849,12 +804,6 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const } } -void MixtureFugacityTP::invalidateCache() -{ - ThermoPhase::invalidateCache(); - m_Tlast_ref += 0.001234; -} - int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, double aAlpha, double Vroot[3], double an, double bn, double cn, double dn, double tc) const diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index ddbdae81b8..c528f28eb2 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -165,19 +165,6 @@ double PengRobinson::pressure() const return pp; } -void PengRobinson::setTemperature(const double temp) -{ - Phase::setTemperature(temp); - _updateReferenceStateThermo(); - updateAB(); -} - -void PengRobinson::compositionChanged() -{ - MixtureFugacityTP::compositionChanged(); - updateAB(); -} - double PengRobinson::standardConcentration(size_t k) const { getStandardVolumes(m_tmpV.data()); @@ -803,11 +790,6 @@ void PengRobinson::pressureDerivatives() const } void PengRobinson::updateMixingExpressions() -{ - updateAB(); -} - -void PengRobinson::updateAB() { double temp = temperature(); //Update aAlpha_i diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 34bf91a923..7341d539d8 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -156,19 +156,6 @@ doublereal RedlichKwongMFTP::pressure() const return pp; } -void RedlichKwongMFTP::setTemperature(const doublereal temp) -{ - Phase::setTemperature(temp); - _updateReferenceStateThermo(); - updateAB(); -} - -void RedlichKwongMFTP::compositionChanged() -{ - MixtureFugacityTP::compositionChanged(); - updateAB(); -} - doublereal RedlichKwongMFTP::standardConcentration(size_t k) const { getStandardVolumes(m_tmpV.data()); @@ -961,11 +948,6 @@ void RedlichKwongMFTP::pressureDerivatives() const } void RedlichKwongMFTP::updateMixingExpressions() -{ - updateAB(); -} - -void RedlichKwongMFTP::updateAB() { double temp = temperature(); if (m_formTempParam == 1) { @@ -997,7 +979,7 @@ void RedlichKwongMFTP::updateAB() } } } - throw CanteraError("RedlichKwongMFTP::updateAB", + throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); } } From 09d520af962617e4777290fdaadf10d35b1a8973 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 22:25:26 -0700 Subject: [PATCH 015/110] Creating python test for PengRobinson thermo class. --- interfaces/cython/cantera/test/test_thermo.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index 05d74c1852..0d2193ad78 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -913,6 +913,9 @@ def test_nondimensional(self): self.assertNear(np.dot(g.standard_gibbs_RT, g.X) - Smix_R, g.gibbs_mole / (R*g.T)) +class TestPengRobinsonPhase(utilities.CanteraTest): + def setup(self): + self.gas = ct.Solution('co2_PR_exampl.yaml','CO2-PR') class TestInterfacePhase(utilities.CanteraTest): def setUp(self): From 759e73847b3f5c3186533135681c0cc7788ed026 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 16 Jan 2020 22:32:37 -0700 Subject: [PATCH 016/110] Fixing indent error in PengRobinson::updateMixingExpressions --- src/thermo/PengRobinson.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index c528f28eb2..d066ad2eff 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -820,7 +820,7 @@ void PengRobinson::updateMixingExpressions() m_b_current += moleFractions_[i] * b_vec_Curr_[i]; for (size_t j = 0; j < m_kk; j++) { m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; } } } From ffee0f7cce5ef0aea7719c24570d9a70b492eaae Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 17 Jan 2020 01:07:14 -0700 Subject: [PATCH 017/110] Testing critical property lookup for PengRobinson. Deletes CO2 interaction parameters from co2_PR_example.yaml, such that phase construction in PengRobinson_Test.cpp covers the cirtical property lookup capabilities in PengRobinson::getCoeff. Note that this required changing some of the regression test expected values, as the critical properties don't correspond perfectly with the interaction parameters previously stored in co2_PR_exmple.yaml. --- data/inputs/critProperties.xml | 2 +- src/thermo/PengRobinson.cpp | 20 +++++-- test/data/co2_PR_example.yaml | 5 -- test/thermo/PengRobinson_Test.cpp | 94 +++++++++++++++++++------------ test/thermo/thermoFromYaml.cpp | 1 + 5 files changed, 77 insertions(+), 45 deletions(-) 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/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index d066ad2eff..a02710aa22 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -485,7 +485,7 @@ bool PengRobinson::addSpecies(shared_ptr spec) vector PengRobinson::getCoeff(const std::string& iName) { - vector_fp spCoeff{ NAN, NAN }; + vector_fp spCoeff{ NAN, NAN, NAN }; // Get number of species in the database // open xml file critProperties.xml @@ -505,7 +505,7 @@ vector PengRobinson::getCoeff(const std::string& iName) if (iNameLower == dbName) { // Read from database and calculate a and b coefficients double vParams; - double T_crit, P_crit = 0.0; + double T_crit = 0.0, P_crit = 0.0, w_ac = 0.0; if (acNodeDoc.hasChild("Tc")) { vParams = 0.0; @@ -533,10 +533,21 @@ vector PengRobinson::getCoeff(const std::string& iName) } 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; + + } //Assuming no temperature dependence 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; } } @@ -589,7 +600,7 @@ void PengRobinson::initThermo() // 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { + if (a_coeff_vec(0, k + m_kk * k) == 0.0) { // coeffs[0] = a0, coeffs[1] = b; vector coeffs = getCoeff(item.first); @@ -597,7 +608,8 @@ void PengRobinson::initThermo() // properties, and assign the results if (!isnan(coeffs[0])) { // Assuming no temperature dependence (i.e. a1 = 0) - setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); + calculateAlpha(item.first, coeffs[0], coeffs[1], coeffs[2]); + setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); } } } diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml index 2e04a3dac1..ee159f4f5e 100644 --- a/test/data/co2_PR_example.yaml +++ b/test/data/co2_PR_example.yaml @@ -21,11 +21,6 @@ species: - [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, 0] - b: 26.6275 - w_ac: 0.228 - name: H2O composition: {H: 2, O: 1} thermo: diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 0dd5f551d1..8ea6a8c676 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -24,7 +24,7 @@ class PengRobinson_Test : public testing::Test std::unique_ptr test_phase; }; -TEST_F(PengRobinson_Test, construct_from_cti) +TEST_F(PengRobinson_Test, construct_from_yaml) { PengRobinson* peng_robinson_phase = dynamic_cast(test_phase.get()); EXPECT_TRUE(peng_robinson_phase != NULL); @@ -39,15 +39,15 @@ TEST_F(PengRobinson_Test, chem_potentials) * calculated using the model. */ const double expected_result[9] = { - -4.5736172836328608e+008, - -4.5735067964144629e+008, - -4.5734081696030951e+008, - -4.5733195900446898e+008, - -4.5732395963640761e+008, - -4.5731669975736719e+008, - -4.5731008132866347e+008, - -4.5730402291258186e+008, - -4.5729845630046815e+008 + -457361607.71983075, + -457350560.54839599, + -457340699.25698096, + -457331842.5539279, + -457323844.32100844, + -457316585.4752928, + -457309967.99120748, + -457303910.44199038, + -457298344.62820804 }; double xmin = 0.6; @@ -63,6 +63,30 @@ TEST_F(PengRobinson_Test, chem_potentials) } } +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.); @@ -126,30 +150,30 @@ TEST_F(PengRobinson_Test, setTP) // All sub-cooled liquid: const double p1[6] = { - 1.7504058222993714e+002, - 1.6824762614489907e+002, - 1.6248354709581241e+002, - 1.5746729362032696e+002, - 1.5302217175386241e+002, - 1.4902908974486667e+002 + 1.7084253549322079e+002, + 1.6543121742659784e+002, + 1.6066148681014121e+002, + 1.5639259178086871e+002, + 1.525268502259365e+002, + 1.4899341020317422e+002 }; // Phase change between temperatures 4 & 5: const double p2[6] = { - 7.5732259810273172e+002, - 7.2766981078381912e+002, - 6.935475475396446e+002, - 6.5227027102964917e+002, - 5.9657442842753153e+002, - 3.9966973266966875e+002 + 7.3025772038910179e+002, + 7.0307777665949902e+002, + 6.7179381832541878e+002, + 6.3389192023192868e+002, + 5.8250166044528487e+002, + 3.8226318921022073e+002 }; // Supercritical; no discontinuity in rho values: const double p3[6] = { - 8.0601205067780199e+002, - 7.8427655940884574e+002, - 7.6105347579146576e+002, - 7.3605202492828505e+002, - 7.0887891410210011e+002, - 6.7898591969734434e+002 + 7.8626284773748239e+002, + 7.6743023689871097e+002, + 7.4747463917603955e+002, + 7.2620055080831412e+002, + 7.0335270498118734e+002, + 6.7859003092723128e+002 }; for(int i=0; i<6; ++i) @@ -180,12 +204,11 @@ TEST_F(PengRobinson_Test, getPressure) * kappa is a function calulated based on the accentric factor. */ - double a_coeff = 3.958134E+5; - double b_coeff = 26.6275/1000; + 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; - const double Tcrit = test_phase->critTemperature(); //Calculate kappa value kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; @@ -193,13 +216,14 @@ TEST_F(PengRobinson_Test, getPressure) for (int i = 0; i<10; i++) { const double temp = 296 + i * 2; - set_r(0.999); - test_phase->setState_TR(temp, 1.0737); + 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); + 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, 2); + EXPECT_NEAR(test_phase->pressure(), pres_theoretical, 3); } } }; diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index a166221451..b6e137ae55 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -344,6 +344,7 @@ TEST(ThermoFromYaml, PengRobinson_CO2) EXPECT_NEAR(thermo->density(), 606.92307568968181, 1e-8); EXPECT_NEAR(thermo->enthalpy_mass(), -9067591.6182085164, 1e-6); EXPECT_NEAR(thermo->cp_mass(), 3065.022259252295, 1e-8); + EXPECT_NEAR(thermo->cv_mole(), 32221.352497580105, 1e-8); } TEST(ThermoFromYaml, PureFluid_nitrogen) From defe2df8e4330cf924b51dc216800c7ff9f769ad Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 20 Jan 2020 23:06:52 -0700 Subject: [PATCH 018/110] Adding cubic solver test --- test/thermo/cubicSolver_Test.cpp | 81 ++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 test/thermo/cubicSolver_Test.cpp diff --git a/test/thermo/cubicSolver_Test.cpp b/test/thermo/cubicSolver_Test.cpp new file mode 100644 index 0000000000..6314356326 --- /dev/null +++ b/test/thermo/cubicSolver_Test.cpp @@ -0,0 +1,81 @@ +#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")); + } + + 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); + + 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; + int nSolnValues; + double Vroot[3]; + + const double expected_result[3] = { + 24.810673386441, + 0.0658516543054, + 0.0728171459395 + }; + + //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->NicholsCall(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); + + //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->NicholsCall(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); + + //Near critical point -> nSolnValues = -1 + + //calculate alpha + alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2); + //Find cubic roots + nSolnValues = peng_robinson_phase->NicholsCall(Tcrit, pCrit, a_coeff, b_coeff, alpha * a_coeff, Vroot); + EXPECT_NEAR(expected_result[2], Vroot[0], 1.e-6); + EXPECT_NEAR(nSolnValues, -1, 1.e-6); +} +}; From 835198eddf01dc3b28efd8e6ddea3ef09ae6cd0f Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Tue, 12 Nov 2019 11:22:47 -0500 Subject: [PATCH 019/110] Add functions for factory alias management This allows for error checking at the time of adding the alias, as well as providing a public interface for adding aliases. --- src/thermo/ThermoFactory.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index a134efb793..9883e9bd91 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -23,7 +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/PengRobinsonMFTP.h" #include "cantera/thermo/ConstDensityThermo.h" #include "cantera/thermo/SurfPhase.h" #include "cantera/thermo/EdgePhase.h" @@ -90,6 +90,12 @@ ThermoFactory::ThermoFactory() addAlias("liquid-water-IAPWS95", "PureLiquidWater"); reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); +<<<<<<< HEAD +======= + reg("PengRobinson", []() { return new PengRobinsonMFTP(); }); + addAlias["PengRobinsonMFTP"] = "PengRobinson"; + addAlias["Peng-Robinson"] = "PengRobinson"; +>>>>>>> Add functions for factory alias management } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) From 8edf26179326df4ba2b02a5924943a49f7976f22 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 16 May 2019 11:09:46 -0600 Subject: [PATCH 020/110] Implementation of Peng-Robinson equation of state. Implemetation of Peng-Robinson equation of state added factories for FlowDevice and Wall objects Updating `thermo/PengRobinsonMFPT` class addition. -Adds `test/data/co2_PR_exmample.cti` for testing. -Corrects typo in `src/thermo/PengRobinsonMFTP::setSpeciesCoeffs` -Minor formatting changes (indentations) -Change how `Vroot_` is initialized. Updating ctml_writer.py to include acentric_factor Updating a typo in PengRobinsonMFTP.cpp Updating ctml_writer.py to add units for b_coeff Updating ctml_writer.py to add units for b_coeff with correct indentation Correcting a mistake in the getActivityCoefficient subroutine Updating PengRobinson_Test.cpp with accurate hard-coded values Adding a test getPressure in PengRobinsonMFTP_Test.cpp file Fixing tabs and spaces Replacing 'doublereal' with 'double' Fixing tabs and spaces in the CTI file Adding comments and renaming few parameter names Fixing tabs and spaces Following modifications in the code are performed: 1. Fixing typos 2. Adding more detailed documentation 3. Deleting redundant variables Fixing typos and comments in the cubic solver Fixing typos in PengRobinsonMFTP.h Replacing (molecularWt/rho) calculations by molarVolume() function Modifying tests with tighter tolerances Modifying comments and reference for cubic solver Replacing sqrt(2) with math constant M_SQRT2 Modification in cubic solver with additional comments and removal of redundant code --- include/cantera/thermo/PengRobinsonMFTP.h | 430 +++++++ src/thermo/PengRobinsonMFTP.cpp | 1300 +++++++++++++++++++++ test/data/co2_PR_example.cti | 168 +++ test/thermo/PengRobinsonMFTP_Test.cpp | 205 ++++ 4 files changed, 2103 insertions(+) create mode 100644 include/cantera/thermo/PengRobinsonMFTP.h create mode 100644 src/thermo/PengRobinsonMFTP.cpp create mode 100644 test/data/co2_PR_example.cti create mode 100644 test/thermo/PengRobinsonMFTP_Test.cpp diff --git a/include/cantera/thermo/PengRobinsonMFTP.h b/include/cantera/thermo/PengRobinsonMFTP.h new file mode 100644 index 0000000000..2d47674264 --- /dev/null +++ b/include/cantera/thermo/PengRobinsonMFTP.h @@ -0,0 +1,430 @@ +//! @file PengRobinsonMFTP.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_PENGROBINSONMFTP_H +#define CT_PENGROBINSONMFTP_H + +#include "MixtureFugacityTP.h" +#include "cantera/base/Array.h" + +namespace Cantera +{ +/** + * Implementation of a multi-species Peng-Robinson equation of state + * + * @ingroup thermoprops + */ +class PengRobinsonMFTP : public MixtureFugacityTP +{ +public: + //! @name Constructors and Duplicators + //! @{ + + //! Base constructor. + PengRobinsonMFTP(); + + //! Construct and initialize a PengRobinsonMFTP object directly from an + //! ASCII input file + /*! + * @param infile Name of the input file containing the phase XML data + * to set up the object + * @param id ID of the phase in the input file. Defaults to the empty + * string. + */ + PengRobinsonMFTP(const std::string& infile, const std::string& id=""); + + //! Construct and initialize a PengRobinsonMFTP object directly from an + //! XML database + /*! + * @param phaseRef XML phase node containing the description of the phase + * @param id id attribute containing the name of the phase. (default + * is the empty string) + */ + PengRobinsonMFTP(XML_Node& phaseRef, const std::string& id = ""); + + virtual std::string type() const { + return "PengRobinson"; + } + + //! @name Molar Thermodynamic properties + //! @{ + + virtual double enthalpy_mole() const; + virtual double entropy_mole() const; + 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) for omega <= 0.491 + * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 + * \f] + * + *Coefficients a_mix, b_mix and (a \alpha)_{mix} are caclulated 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; + + // @} + +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 double temp); + virtual void compositionChanged(); + +public: + virtual void getActivityConcentrations(double* c) 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. In many cases, this + * quantity will be the same for all species in a phase. + * 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 m3 kmol-1. + */ + 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 + //@{ + + //! Get the array of non-dimensional species chemical potentials. + //! These are partial molar Gibbs free energies. + /*! + * \f$ \mu_k / \hat R T \f$. + * Units: unitless + * + * We close the loop on this function here calling getChemPotentials() and + * then dividing by RT. No need for child classes to handle. + * + * @param mu Output vector of non-dimensional species chemical potentials + * Length: m_kk. + */ + virtual void getChemPotentials_RT(double* mu) const; + + 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 the temperature dependent interaction parameter alpha needed for P-R EoS + /* + * 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. + * Units: unitless + */ + virtual void calculateAlpha(const std::string& species, double a, double b, double w); + //@} + /// @name Critical State Properties. + //@{ + + virtual double critTemperature() const; + virtual double critPressure() const; + virtual double critVolume() const; + virtual double critCompressibility() const; + virtual double critDensity() const; + virtual double speciesCritTemperature(double a, double b) const; + +public: + //@} + //! @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 setParametersFromXML(const XML_Node& thermoNode); + virtual void setToEquilState(const double* lambda_RT); + virtual void initThermoXML(XML_Node& phaseNode, const std::string& id); + + //! 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 build/data/thermo/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 std::vector getCoeff(const std::string& iName); + + //! Set the pure fluid interaction parameters for a species + /*! + * The "a" parameter for species *i* in the Peng-Robinson model is assumed + * to be a linear function of temperature: + * \f[ a = a_0 + a_1 T \f] + * + * @param species Name of the species + * @param a0 constant term in the expression for the "a" parameter + * of the specified species [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the expression for the + * "a" parameter of the specified species [Pa-m^6/kmol^2/K] + * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] + * @param alpha dimensionless function of T_r and \omega + * @param omega 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] + * + * This function overrides the defaults with the specified parameters: + * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] + * + * @param species_i Name of one species + * @param species_j Name of the other species + * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the "a" expression + * [Pa-m^6/kmol^2/K] + */ + void setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1); + +private: + //! Read the pure species PengRobinson input parameters + /*! + * @param pureFluidParam XML_Node for the pure fluid parameters + */ + void readXMLPureFluid(XML_Node& pureFluidParam); + + //! Read the cross species PengRobinson input parameters + /*! + * @param crossFluidParam XML_Node for the cross fluid parameters + */ + void readXMLCrossFluid(XML_Node& crossFluidParam); + + // @} + +protected: + // Special functions inherited from MixtureFugacityTP + virtual double sresid() const; + virtual double hresid() const; + +public: + 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 pressureCalc(double TKelvin, double molarVol) const; + virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + + //! Calculate dpdV and dpdT at the current conditions + /*! + * These are stored internally. + */ + 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(); + + //! Calculate the a and the b 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 temp Temperature (TKelvin) + * @param aCalc (output) Returns the a value + * @param bCalc (output) Returns the b value. + */ + void calculateAB(double temp, double& aCalc, double& bCalc, double& aAlpha) const; + + // Special functions not inherited from MixtureFugacityTP + + double daAlpha_dT() const; + double d2aAlpha_dT2() const; + + void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) const; + + //! Solve the cubic equation of state + /*! + * The P-R equation of state may be solved via the following formula: + * + * V**3 - V**2(RT/P - b) - V(2bRT/P - \alpha a/P + 3*b*b) - (a \alpha b/p - b*b RT/P - b*b*b) = 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. + * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354–359, https://www.jstor.org/stable/3619777) + */ + int NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, + double Vroot[3]) const; + +protected: + //! Form of the temperature parameterization + /*! + * 0 = There is no temperature parameterization of a or b + * 1 = The a_ij parameter is a linear function of the temperature + */ + int m_formTempParam; + + //! Value of b in the equation of state + /*! + * m_b_current is a function of the temperature and the mole fractions. + */ + double m_b_current; + + //! Value of a and alpha in the equation of state + /*! + * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. + */ + double m_a_current; + double m_aAlpha_current; + + // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk + vector_fp a_vec_Curr_; + vector_fp b_vec_Curr_; + vector_fp aAlpha_vec_Curr_; + vector_fp alpha_vec_Curr_; + vector_fp kappa_vec_; + mutable vector_fp dalphadT_vec_Curr_; + mutable vector_fp d2alphadT2_; + + Array2D a_coeff_vec; + Array2D aAlpha_coeff_vec; + + int NSolns_; + + double Vroot_[3]; + + //! 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; + + //! The derivative of the pressure with respect to the volume + /*! + * Calculated at the current conditions. temperature and mole number kept + * constant + */ + mutable double 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 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 dpdni_; + +public: + //! 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/src/thermo/PengRobinsonMFTP.cpp b/src/thermo/PengRobinsonMFTP.cpp new file mode 100644 index 0000000000..012468e6f5 --- /dev/null +++ b/src/thermo/PengRobinsonMFTP.cpp @@ -0,0 +1,1300 @@ +//! @file PengRobinsonMFTP.cpp + +// This file is part of Cantera. See License.txt in the top-level directory or +// at http://www.cantera.org/license.txt for license and copyright information. + +#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/ThermoFactory.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 PengRobinsonMFTP::omega_a = 4.5723552892138218E-01; +const double PengRobinsonMFTP::omega_b = 7.77960739038885E-02; +const double PengRobinsonMFTP::omega_vc = 3.07401308698703833E-01; + +PengRobinsonMFTP::PengRobinsonMFTP() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); +} + +PengRobinsonMFTP::PengRobinsonMFTP(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + initThermoFile(infile, id_); +} + +PengRobinsonMFTP::PengRobinsonMFTP(XML_Node& phaseRefRoot, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + importPhase(phaseRefRoot, this); +} + +void PengRobinsonMFTP::calculateAlpha(const std::string& species, double a, double b, double w) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + + // Calculate value of kappa (independent of temperature) + // w is an acentric factor of species and must be specified in the CTI file + + if (w <= 0.491) { + kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; + } else { + kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; + } + + //Calculate alpha (temperature dependent interaction parameter) + double criTemp = speciesCritTemperature(a, b); // critical temperature of individual species + double sqt_T_r = sqrt(temperature() / criTemp); + double sqt_alpha = 1 + kappa_vec_[k] * (1 - sqt_T_r); + alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; +} + +void PengRobinsonMFTP::setSpeciesCoeffs(const std::string& species, + double a, double b, double w) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + size_t counter = k + m_kk * k; + a_coeff_vec(0, counter) = a; + // we store this locally because it is used below to calculate a_Alpha: + double aAlpha_k = a*alpha_vec_Curr_[k]; + aAlpha_coeff_vec(0, counter) = 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(a_coeff_vec(0, j + m_kk * j) * a); + double aAlpha_j = a*alpha_vec_Curr_[j]; + double a_Alpha = sqrt(aAlpha_j*aAlpha_k); + if (a_coeff_vec(0, j + m_kk * k) == 0) { + a_coeff_vec(0, j + m_kk * k) = a0kj; + aAlpha_coeff_vec(0, j + m_kk * k) = a_Alpha; + a_coeff_vec(0, k + m_kk * j) = a0kj; + aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; + } + } + a_coeff_vec.getRow(0, a_vec_Curr_.data()); + aAlpha_coeff_vec.getRow(0, aAlpha_vec_Curr_.data()); + b_vec_Curr_[k] = b; +} + +void PengRobinsonMFTP::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double alpha) +{ + size_t ki = speciesIndex(species_i); + if (ki == npos) { + throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_i); + } + size_t kj = speciesIndex(species_j); + if (kj == npos) { + throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_j); + } + + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; + aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; + a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; + aAlpha_vec_Curr_[counter1] = aAlpha_vec_Curr_[counter2] = a0*alpha; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +double PengRobinsonMFTP::enthalpy_mole() const +{ + _updateReferenceStateThermo(); + double h_ideal = RT() * mean_X(m_h0_RT); + double h_nonideal = hresid(); + return h_ideal + h_nonideal; +} + +double PengRobinsonMFTP::entropy_mole() const +{ + _updateReferenceStateThermo(); + double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() + - std::log(pressure()/refPressure())); + double sr_nonideal = sresid(); + return sr_ideal + sr_nonideal; +} + +double PengRobinsonMFTP::cp_mole() const +{ + _updateReferenceStateThermo(); + double TKelvin = temperature(); + double mv = molarVolume(); + double vpb = mv + (1 + M_SQRT2)*m_b_current; + double vmb = mv + (1 - M_SQRT2)*m_b_current; + pressureDerivatives(); + double cpref = GasConstant * mean_X(m_cp0_R); + double dHdT_V = cpref + mv * dpdT_ - GasConstant + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * TKelvin *d2aAlpha_dT2(); + return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; +} + +double PengRobinsonMFTP::cv_mole() const +{ + _updateReferenceStateThermo(); + double TKelvin = temperature(); + pressureDerivatives(); + return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); +} + +double PengRobinsonMFTP::pressure() const +{ + _updateReferenceStateThermo(); + // Get a copy of the private variables stored in the State object + double TKelvin = temperature(); + double mv = molarVolume(); + double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double pp = GasConstant * TKelvin / (mv - m_b_current) - m_aAlpha_current / denom; + return pp; +} + +void PengRobinsonMFTP::calcDensity() +{ + // Calculate the molarVolume of the solution (m**3 kmol-1) + const double* 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 PengRobinsonMFTP::setTemperature(const double temp) +{ + Phase::setTemperature(temp); + _updateReferenceStateThermo(); + updateAB(); +} + +void PengRobinsonMFTP::compositionChanged() +{ + MixtureFugacityTP::compositionChanged(); + updateAB(); +} + +void PengRobinsonMFTP::getActivityConcentrations(double* c) const +{ + getActivityCoefficients(c); + double p_RT = pressure() / RT(); + for (size_t k = 0; k < m_kk; k++) { + c[k] *= moleFraction(k)* p_RT; + } +} + +double PengRobinsonMFTP::standardConcentration(size_t k) const +{ + getStandardVolumes(m_tmpV.data()); + return 1.0 / m_tmpV[k]; +} + +void PengRobinsonMFTP::getActivityCoefficients(double* ac) const +{ + double mv = molarVolume(); + double T = temperature(); + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double vmb = mv - m_b_current; + 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) + + RTkelvin * b_vec_Curr_[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* b_vec_Curr_[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 PengRobinsonMFTP::getChemPotentials_RT(double* muRT) const +{ + getChemPotentials(muRT); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RTkelvin; + } +} + +void PengRobinsonMFTP::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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + double pres = pressure(); + double refP = refPressure(); + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + + mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + + RTkelvin * log(mv / vmb) + + RTkelvin * b_vec_Curr_[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* b_vec_Curr_[k] * mv/den2 + ); + } +} + +void PengRobinsonMFTP::getPartialMolarEnthalpies(double* hbar) const +{ + // First we get the reference state contributions + getEnthalpy_RT_ref(hbar); + scale(hbar, hbar+m_kk, hbar, RT()); + + // We calculate dpdni_ + double TKelvin = temperature(); + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double den2 = den*den; + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + dpdni_[k] = RTkelvin /vmb + RTkelvin * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + + 2 * vmb * m_aAlpha_current * b_vec_Curr_[k] / den2; + } + + double daAlphadT = daAlpha_dT(); + double fac = TKelvin * daAlphadT - m_aAlpha_current; + + pressureDerivatives(); + double fac2 = mv + TKelvin * dpdT_ / dpdV_; + double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac + + (mv * b_vec_Curr_[k]) /(m_b_current*den) * fac; + hbar[k] = hbar[k] + hE_v; + hbar[k] -= fac2 * dpdni_[k]; + } +} + +void PengRobinsonMFTP::getPartialMolarEntropies(double* sbar) const +{ + getEntropy_R_ref(sbar); + scale(sbar, sbar+m_kk, sbar, GasConstant); + double TKelvin = temperature(); + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double refP = refPressure(); + double daAlphadT = daAlpha_dT(); + double coeff1 = 0; + double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + + // 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(dalphadT_vec_Curr_[i] / alpha_vec_Curr_[i]); + } + m_pp[k] = m_pp[k] * dalphadT_vec_Curr_[k] / alpha_vec_Curr_[k]; + } + + + for (size_t k = 0; k < m_kk; k++) { + coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; + sbar[k] += GasConstant * log(GasConstant * TKelvin / (refP * mv)) + + GasConstant + + GasConstant * log(mv / vmb) + + GasConstant * b_vec_Curr_[k] / vmb + - coeff1* log(vpb2 / vmb2) / den1 + - b_vec_Curr_[k] * mv * daAlphadT / den2 / m_b_current; + } + pressureDerivatives(); + getPartialMolarVolumes(m_partialMolarVolumes.data()); + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -= m_partialMolarVolumes[k] * dpdT_; + } +} + +void PengRobinsonMFTP::getPartialMolarIntEnergies(double* ubar) const +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void PengRobinsonMFTP::getPartialMolarCp(double* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +void PengRobinsonMFTP::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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb = mv + m_b_current; + double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double fac2 = fac * fac; + double RTkelvin = RT(); + + for (size_t k = 0; k < m_kk; k++) { + double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * b_vec_Curr_[k] / vmb + + RTkelvin * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + - 2 * mv * m_pp[k] / fac + + 2 * mv * vmb * m_aAlpha_current * b_vec_Curr_[k] / fac2 + ); + double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) + + m_aAlpha_current/fac + - 2 * mv* vpb *m_aAlpha_current / fac2 + ); + vbar[k] = num / denom; + } +} + +double PengRobinsonMFTP::speciesCritTemperature(double a, double b) const +{ + double pc, tc, vc; + calcCriticalConditions(a, b, pc, tc, vc); + return tc; +} + +double PengRobinsonMFTP::critTemperature() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return tc; +} + +double PengRobinsonMFTP::critPressure() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc; +} + +double PengRobinsonMFTP::critVolume() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return vc; +} + +double PengRobinsonMFTP::critCompressibility() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc*vc/tc/GasConstant; +} + +double PengRobinsonMFTP::critDensity() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + double mmw = meanMolecularWeight(); + return mmw / vc; +} + +void PengRobinsonMFTP::setToEquilState(const double* mu_RT) +{ + double tmp, tmp2; + _updateReferenceStateThermo(); + getGibbs_RT_ref(m_tmpV.data()); + + // Within the method, we protect against inf results if the exponent is too + // high. + // + // If it is too low, we set the partial pressure to zero. This capability is + // needed by the elemental potential method. + double pres = 0.0; + double m_p0 = refPressure(); + for (size_t k = 0; k < m_kk; k++) { + tmp = -m_tmpV[k] + mu_RT[k]; + if (tmp < -600.) { + m_pp[k] = 0.0; + } else if (tmp > 500.0) { + tmp2 = tmp / 500.; + tmp2 *= tmp2; + m_pp[k] = m_p0 * exp(500.) * tmp2; + } else { + m_pp[k] = m_p0 * exp(tmp); + } + pres += m_pp[k]; + } + // set state + setState_PX(pres, &m_pp[0]); +} + +bool PengRobinsonMFTP::addSpecies(shared_ptr spec) +{ + bool added = MixtureFugacityTP::addSpecies(spec); + if (added) { + a_vec_Curr_.resize(m_kk * m_kk, 0.0); + b_vec_Curr_.push_back(0.0); + a_vec_Curr_.push_back(0.0); + aAlpha_vec_Curr_.resize(m_kk * m_kk, 0.0); + aAlpha_vec_Curr_.push_back(0.0); + kappa_vec_.push_back(0.0); + + alpha_vec_Curr_.push_back(0.0); + a_coeff_vec.resize(1, m_kk * m_kk, 0.0); + aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); + dalphadT_vec_Curr_.push_back(0.0); + d2alphadT2_.push_back(0.0); + + m_pp.push_back(0.0); + m_tmpV.push_back(0.0); + m_partialMolarVolumes.push_back(0.0); + dpdni_.push_back(0.0); + } + return added; +} + +vector PengRobinsonMFTP::getCoeff(const std::string& iName) +{ + vector_fp spCoeff{ 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, P_crit; + + 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("PengRobinsonMFTP::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("PengRobinsonMFTP::getCoeff", + "Critical Pressure must be positive "); + } + P_crit = vParams; + } + + //Assuming no temperature dependence + 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 + break; + } + } + return spCoeff; +} + +void PengRobinsonMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) +{ + if (phaseNode.hasChild("thermo")) { + XML_Node& thermoNode = phaseNode.child("thermo"); + std::string model = thermoNode["model"]; + if (model != "PengRobinson" && model != "PengRobinsonMFTP") { + throw CanteraError("PengRobinsonMFTP::initThermoXML", + "Unknown thermo model : " + model); + } + + // Go get all of the coefficients and factors in the + // activityCoefficients XML block + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + + // Count the number of species with parameters provided in the + // input file: + size_t nParams = 0; + + // Loop through the children and read out fluid parameters. Process + // all the pureFluidParameters, first: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { + readXMLPureFluid(xmlACChild); + nParams += 1; + } + } + + // If any species exist which have undefined pureFluidParameters, + // search the database in 'critProperties.xml' to find critical + // temperature and pressure to calculate a and b. + + // Loop through all species in the CTI file + size_t iSpecies = 0; + + for (size_t i = 0; i < m_kk; i++) { + string iName = speciesName(i); + + // Get the index of the species + iSpecies = speciesIndex(iName); + + // Check if a and b are already populated (only the diagonal elements of a). + size_t counter = iSpecies + m_kk * iSpecies; + + // If not, then search the database: + if (isnan(a_coeff_vec(0, counter))) { + + vector coeffArray; + + // Search the database for the species name and calculate + // coefficients a and b, from critical properties: + // coeffArray[0] = a0, coeffArray[1] = b, coeffArray[2] = w; + coeffArray = getCoeff(iName); + + // Check if species was found in the database of critical properties, + // and assign the results + if (!isnan(coeffArray[0])) { + //Assuming no temperature dependence (i,e a1 = 0) + setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); + } + } + } + + // Loop back through the "activityCoefficients" children and process the + // crossFluidParameters in the XML tree: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { + readXMLCrossFluid(xmlACChild); + } + } + } + } + + MixtureFugacityTP::initThermoXML(phaseNode, id); +} + +void PengRobinsonMFTP::readXMLPureFluid(XML_Node& pureFluidParam) +{ + string xname = pureFluidParam.name(); + if (xname != "pureFluidParameters") { + throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", + "Incorrect name for processing this routine: " + xname); + } + + double a0 = 0.0; + double a1 = 0.0; + double b = 0.0; + double w = 0.0; + for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { + XML_Node& xmlChild = pureFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + string iModel = toLowerCopy(xmlChild.attrib("model")); + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + + if (vParams.size() == 1) { + a0 = vParams[0]; + } else if (vParams.size() == 2) { + a0 = vParams[0]; + a1 = vParams[1]; + } else { + throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", + "unknown model or incorrect number of parameters"); + } + } else if (nodeName == "b_coeff") { + b = getFloatCurrent(xmlChild, "toSI"); + } else if (nodeName == "acentric_factor") { + w = getFloatCurrent(xmlChild); + } + } + calculateAlpha(pureFluidParam.attrib("species"), a0, b, w); + setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); +} + +void PengRobinsonMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) +{ + string xname = CrossFluidParam.name(); + if (xname != "crossFluidParameters") { + throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", + "Incorrect name for processing this routine: " + xname); + } + + string iName = CrossFluidParam.attrib("species1"); + string jName = CrossFluidParam.attrib("species2"); + + size_t num = CrossFluidParam.nChildren(); + for (size_t iChild = 0; iChild < num; iChild++) { + XML_Node& xmlChild = CrossFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + string iModel = toLowerCopy(xmlChild.attrib("model")); + if (iModel == "constant" && vParams.size() == 1) { + setBinaryCoeffs(iName, jName, vParams[0], 0.0); + } else if (iModel == "linear_a") { + setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); + } else { + throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", + "unknown model ({}) or wrong number of parameters ({})", + iModel, vParams.size()); + } + } + } +} + +void PengRobinsonMFTP::setParametersFromXML(const XML_Node& thermoNode) +{ + MixtureFugacityTP::setParametersFromXML(thermoNode); +} + +double PengRobinsonMFTP::sresid() const +{ + double molarV = molarVolume(); + double hh = m_b_current / molarV; + double zz = z(); + double alpha_1 = daAlpha_dT(); + double T = temperature(); + double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; + double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; + double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); + double sresid_mol_R = log(zz*(1.0 - hh)) + fac * log(vpb / vmb) / GasConstant; + return GasConstant * sresid_mol_R; +} + +double PengRobinsonMFTP::hresid() const +{ + double molarV = molarVolume(); + double zz = z(); + double aAlpha_1 = daAlpha_dT(); + double T = temperature(); + double vpb = molarV + (1 + M_SQRT2) *m_b_current; + double vmb = molarV + (1 - M_SQRT2) *m_b_current; + double fac = 1 / (2.0 * M_SQRT2 * m_b_current); + return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); +} + +double PengRobinsonMFTP::liquidVolEst(double TKelvin, double& presGuess) const +{ + double v = m_b_current * 1.1; + double atmp; + double btmp; + double aAlphatmp; + calculateAB(TKelvin, atmp, btmp, aAlphatmp); + double pres = std::max(psatEst(TKelvin), presGuess); + double Vroot[3]; + bool foundLiq = false; + int m = 0; + while (m < 100 && !foundLiq) { + int nsol = NicholsSolve(TKelvin, 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 PengRobinsonMFTP::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) +{ + // It's necessary to set the temperature so that m_aAlpha_current is set correctly. + setTemperature(TKelvin); + double tcrit = critTemperature(); + double mmw = meanMolecularWeight(); + if (rhoGuess == -1.0) { + if (phaseRequested != FLUID_GAS) { + if (TKelvin > tcrit) { + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } else { + if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } else 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 + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } + } + + double volGuess = mmw / rhoGuess; + NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + + double molarVolLast = Vroot_[0]; + if (NSolns_ >= 2) { + if (phaseRequested >= FLUID_LIQUID_0) { + molarVolLast = Vroot_[0]; + } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[2]; + } else { + if (volGuess > Vroot_[1]) { + molarVolLast = Vroot_[2]; + } else { + molarVolLast = Vroot_[0]; + } + } + } else if (NSolns_ == 1) { + if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else if (NSolns_ == -1) { + if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[0]; + } else if (TKelvin > tcrit) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else { + molarVolLast = Vroot_[0]; + return -1.0; + } + return mmw / molarVolLast; +} + +double PengRobinsonMFTP::densSpinodalLiquid() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, 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 PengRobinsonMFTP::densSpinodalGas() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, 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 PengRobinsonMFTP::pressureCalc(double TKelvin, double molarVol) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; + return pres; +} + +double PengRobinsonMFTP::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; + + double vpb = molarVol + m_b_current; + double vmb = molarVol - m_b_current; + double dpdv = -GasConstant * TKelvin / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); + return dpdv; +} + +void PengRobinsonMFTP::pressureDerivatives() const +{ + double TKelvin = temperature(); + double mv = molarVolume(); + double pres; + + dpdV_ = dpdVCalc(TKelvin, mv, pres); + double vmb = mv - m_b_current; + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); +} + +void PengRobinsonMFTP::updateMixingExpressions() +{ + updateAB(); +} + +void PengRobinsonMFTP::updateAB() +{ + double temp = temperature(); + //Update aAlpha_i + double sqt_alpha; + double criTemp = critTemperature(); + double sqt_T_reduced = sqrt(temp / criTemp); + + // Update indiviual alpha + for (size_t j = 0; j < m_kk; j++) { + sqt_alpha = 1 + kappa_vec_[j] * (1 - sqt_T_reduced); + alpha_vec_Curr_[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++) { + size_t counter = i * m_kk + j; + a_vec_Curr_[counter] = a_coeff_vec(0, counter); + aAlpha_vec_Curr_[counter] = sqrt(alpha_vec_Curr_[i] * alpha_vec_Curr_[j]) * a_coeff_vec(0, counter); + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + m_aAlpha_current = 0.0; + + for (size_t i = 0; i < m_kk; i++) { + m_b_current += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + } + } +} + +void PengRobinsonMFTP::calculateAB(double temp, 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] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + double a_vec_Curr = a_coeff_vec(0, counter); + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += aAlpha_vec_Curr_[counter] * moleFractions_[i] * moleFractions_[j]; + } + } +} + +double PengRobinsonMFTP::daAlpha_dT() const +{ + double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; + double coeff1, coeff2; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + // Calculate first derivative of alpha for individual species + Tc = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); + sqtTr = sqrt(temperature() / Tc); //we need species critical temperature + coeff1 = 1 / (Tc*sqtTr); + coeff2 = sqtTr - 1; + k = kappa_vec_[i]; + dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + } + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j * m_kk + j; + temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alpha_vec_Curr_[i] * alpha_vec_Curr_[j])); + daAlphadT += moleFractions_[i] * moleFractions_[j] * temp + * (dalphadT_vec_Curr_[j] * alpha_vec_Curr_[i] + dalphadT_vec_Curr_[i] * alpha_vec_Curr_[j]); + } + } + return daAlphadT; +} + +double PengRobinsonMFTP::d2aAlpha_dT2() const +{ + double daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; + double k; + double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature + double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); + double coeff2 = sqt_Tr - 1; + for (size_t i = 0; i < m_kk; i++) { + // Calculate first and second derivatives of alpha for individual species + size_t counter = i + m_kk * i; + k = kappa_vec_[i]; + dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); + } + + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + alphai = alpha_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j + m_kk * j; + alphaj = alpha_vec_Curr_[j]; + alphaij = alphai * alphaj; + temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); + num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); + fac1 = -(0.5 / alphaij)*num*num; + fac2 = alphaj * d2alphadT2_[counter1] + alphai *d2alphadT2_[counter2] + 2 * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); + } + } + return d2aAlphadT2; +} + +void PengRobinsonMFTP::calcCriticalConditions(double a, double b, + double& pc, double& tc, double& vc) const +{ + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + tc = a * omega_b / (b * omega_a * GasConstant); + pc = omega_b * GasConstant * tc / b; + vc = omega_vc * GasConstant * tc / pc; +} + +int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, + double Vroot[3]) const +{ + double tmp; + fill_n(Vroot, 3, 0.0); + if (TKelvin <= 0.0) { + throw CanteraError("PengRobinsonMFTP::NicholsSolve()", "negative temperature T = {}", TKelvin); + } + + // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. + double bsqr = b * b; + double RT_p = GasConstant * TKelvin / 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; + + // 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 * TKelvin); // B + if (fabs(ratio1) < 1.0E-7) { + double ratio3 = aAlpha / (GasConstant * TKelvin) * pres / (GasConstant * TKelvin); // 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 * TKelvin / 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("PengRobinsonMFTP::NicholsSolve()", "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; + } + + // 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}):" + " WARNING roots have merged: {}, {}\n", + TKelvin, 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}): " + "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); + writelogendl(); + } + } + + if (nSolnValues == 1) { + if (TKelvin > tc) { + if (Vroot[0] < vc) { + // Liquid phase root + nSolnValues = -1; + } + } else { + if (Vroot[0] < xN) { + nSolnValues = -1; + } + } + } else { + if (nSolnValues == 2 && delta > 1e-14) { + nSolnValues = -2; + } + } + return nSolnValues; +} + +} diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti new file mode 100644 index 0000000000..8692fef113 --- /dev/null +++ b/test/data/co2_PR_example.cti @@ -0,0 +1,168 @@ +# Transport data from file ../transport/gri30_tran.dat. + +units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") + + +PengRobinsonMFTP(name ="carbondioxide", + elements ="C O H N", + species ="""CO2 H2O H2 CO CH4 O2 N2""", + activity_coefficients = (pureFluidParameters(species="CO2", a_coeff = [3.958134E+11, 0], b_coeff = 26.6275, acentric_factor = 0.228), + pureFluidParameters(species="H2O", a_coeff = [5.998873E+11, 0], b_coeff = 18.9714, acentric_factor = 0.344), + pureFluidParameters(species="H2", a_coeff = [2.668423E+10, 0], b_coeff = 16.5478, acentric_factor = -0.22), + pureFluidParameters(species="CO", a_coeff = [1.607164E+11, 0], b_coeff = 24.6549, acentric_factor = 0.049), + pureFluidParameters(species="CH4", a_coeff = [2.496344E+11, 0], b_coeff = 26.8028, acentric_factor = 0.01), + pureFluidParameters(species="O2", a_coeff = [1.497732E+11, 0], b_coeff = 19.8281, acentric_factor = 0.022), + pureFluidParameters(species="N2", a_coeff = [1.485031E+11, 0], b_coeff = 28.0810, acentric_factor = 0.04)), + transport ="Multi", + reactions ="all", + initial_state = state(temperature = 300.0, + pressure = OneAtm, + mole_fractions = 'CO2:0.99, H2:0.01')) + +#------------------------------------------------------------------------------- +# Species data +#------------------------------------------------------------------------------- + +species(name ="H2", + atoms ="H:2", + thermo = ( + NASA([200.00, 1000.00], [2.344331120E+00, 7.980520750E-03, + -1.947815100E-05, 2.015720940E-08, -7.376117610E-12, + -9.179351730E+02, 6.830102380E-01]), + NASA([1000.00, 3500.00], [3.337279200E+00, -4.940247310E-05, + 4.994567780E-07, -1.795663940E-10, 2.002553760E-14, + -9.501589220E+02, -3.205023310E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 2.92, + well_depth = 38.00, + polar = 0.79, + rot_relax = 280.00), + note ="TPIS78" + ) + +species(name ="CO", + atoms ="C:1 O:1", + thermo = ( + NASA([200.00, 1000.00], [3.579533470E+00, -6.103536800E-04, + 1.016814330E-06, 9.070058840E-10, -9.044244990E-13, + -1.434408600E+04, 3.508409280E+00]), + NASA([1000.00, 3500.00], [2.715185610E+00, 2.062527430E-03, + -9.988257710E-07, 2.300530080E-10, -2.036477160E-14, + -1.415187240E+04, 7.818687720E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.65, + well_depth = 98.10, + polar = 1.95, + rot_relax = 1.80), + note ="TPIS79" + ) + + +species(name ="N2", + atoms ="N:2", + thermo = ( + NASA([300.00, 1000.00], [3.298677000E+00, 1.408240400E-03, + -3.963222000E-06, 5.641515000E-09, -2.444854000E-12, + -1.020899900E+03, 3.950372000E+00]), + NASA([1000.00, 5000.00], [2.926640000E+00, 1.487976800E-03, + -5.684760000E-07, 1.009703800E-10, -6.753351000E-15, + -9.227977000E+02, 5.980528000E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.62, + well_depth = 97.53, + polar = 1.76, + rot_relax = 4.00), + note ="121286" + ) + +species(name ="O2", + atoms ="O:2", + thermo = ( + NASA([200.00, 1000.00], [3.782456360E+00, -2.996734160E-03, + 9.847302010E-06, -9.681295090E-09, 3.243728370E-12, + -1.063943560E+03, 3.657675730E+00]), + NASA([1000.00, 3500.00], [3.282537840E+00, 1.483087540E-03, + -7.579666690E-07, 2.094705550E-10, -2.167177940E-14, + -1.088457720E+03, 5.453231290E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.46, + well_depth = 107.40, + polar = 1.60, + rot_relax = 3.80), + note ="TPIS89" + ) + + +species(name ="CO2", + atoms ="C:1 O:2", + thermo = ( + NASA([200.00, 1000.00], [2.356773520E+00, 8.984596770E-03, + -7.123562690E-06, 2.459190220E-09, -1.436995480E-13, + -4.837196970E+04, 9.901052220E+00]), + NASA([1000.00, 3500.00], [3.857460290E+00, 4.414370260E-03, + -2.214814040E-06, 5.234901880E-10, -4.720841640E-14, + -4.875916600E+04, 2.271638060E+00]) + ), + transport = gas_transport( + geom ="linear", + diam = 3.76, + well_depth = 244.00, + polar = 2.65, + rot_relax = 2.10), + note ="L 7/88" + ) + +species(name ="CH4", + atoms ="C:1 H:4", + thermo = ( + NASA([200.00, 1000.00], [5.149876130E+00, -1.367097880E-02, + 4.918005990E-05, -4.847430260E-08, 1.666939560E-11, + -1.024664760E+04, -4.641303760E+00]), + NASA([1000.00, 3500.00], [7.485149500E-02, 1.339094670E-02, + -5.732858090E-06, 1.222925350E-09, -1.018152300E-13, + -9.468344590E+03, 1.843731800E+01]) + ), + transport = gas_transport( + geom ="nonlinear", + diam = 3.75, + well_depth = 141.40, + polar = 2.60, + rot_relax = 13.00), + note ="L 8/88" + ) + + +species(name ="H2O", + atoms ="H:2 O:1", + thermo = ( + NASA([200.00, 1000.00], [4.198640560E+00, -2.036434100E-03, + 6.520402110E-06, -5.487970620E-09, 1.771978170E-12, + -3.029372670E+04, -8.490322080E-01]), + NASA([1000.00, 3500.00], [3.033992490E+00, 2.176918040E-03, + -1.640725180E-07, -9.704198700E-11, 1.682009920E-14, + -3.000429710E+04, 4.966770100E+00]) + ), + transport = gas_transport( + geom ="nonlinear", + diam = 2.60, + well_depth = 572.40, + dipole = 1.85, + rot_relax = 4.00), + note ="L 8/89" + ) + +#——————————————————————————————————————————— +# Reaction data +#——————————————————————————————————————————— + +# Reaction 1 +reaction("CO2 + H2 <=> CO + H2O", [1.2E+3, 0, 0]) + diff --git a/test/thermo/PengRobinsonMFTP_Test.cpp b/test/thermo/PengRobinsonMFTP_Test.cpp new file mode 100644 index 0000000000..9d8043cb5d --- /dev/null +++ b/test/thermo/PengRobinsonMFTP_Test.cpp @@ -0,0 +1,205 @@ +#include "gtest/gtest.h" +#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/ThermoFactory.h" + + +namespace Cantera +{ + +class PengRobinsonMFTP_Test : public testing::Test +{ +public: + PengRobinsonMFTP_Test() { + test_phase.reset(newPhase("../data/co2_PR_example.cti")); + } + + //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(PengRobinsonMFTP_Test, construct_from_cti) +{ + PengRobinsonMFTP* peng_robinson_phase = dynamic_cast(test_phase.get()); + EXPECT_TRUE(peng_robinson_phase != NULL); +} + +TEST_F(PengRobinsonMFTP_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] = { + -4.5736182681761962e+008, + -4.5733771904416579e+008, + -4.5732943831449223e+008, + -4.5732206687414169e+008, + -4.5731546826955432e+008, + -4.5730953161186475e+008, + -4.5730416590547645e+008, + -4.5729929581635743e+008, + -4.5729485847173005e+008 + }; + + 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(PengRobinsonMFTP_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(PengRobinsonMFTP_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(PengRobinsonMFTP_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(PengRobinsonMFTP_Test, setTP) +{ + // Check to make sure that the phase diagram is accurately reproduced for a few select isobars + + // All sub-cooled liquid: + const double p1[6] = { + 1.7474528924963985e+002, + 1.6800540828415956e+002, + 1.62278413743154e+002, + 1.5728963799103039e+002, + 1.5286573762819748e+002, + 1.4888956030449546e+002 + }; + // Phase change between temperatures 4 & 5: + const double p2[6] = { + 7.5565889855724288e+002, + 7.2577747673480337e+002, + 6.913183942651284e+002, + 6.494661249672663e+002, + 5.9240469307757724e+002, + 3.645826047440932e+002 + }; + // Supercritical; no discontinuity in rho values: + const double p3[6] = { + 8.047430802847415e+002, + 7.8291565113595595e+002, + 7.5958477920749681e+002, + 7.3445460137134626e+002, + 7.0712433093853724e+002, + 6.77034438769492e+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(),p1[i],1.e-8); + + test_phase->setState_TP(temp, 7389370.); + EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); + + test_phase->setState_TP(temp, 9236712.5); + EXPECT_NEAR(test_phase->density(),p3[i],1.e-8); + } +} + +TEST_F(PengRobinsonMFTP_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. + * 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.958134E+5; + double b_coeff = 26.6275/1000; + double acc_factor = 0.228; + double pres_theoretical, kappa, alpha, mv; + const double rho = 1.0737; + const double Tcrit = test_phase->critTemperature(); + + //Calculate kappa value + kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; + + for (int i = 0; i<10; i++) + { + const double temp = 296 + i * 2; + set_r(0.999); + test_phase->setState_TR(temp, 1.0737); + mv = 1 / rho * test_phase->meanMolecularWeight(); + //Calculate pressure using Peng-Robinson EoS + alpha = pow(1 + kappa*(1 - sqrt(temp / Tcrit)), 2); + 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, 2); + } +} +}; From fc650ad979573383b3b93780f26dc132d7461561 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Fri, 20 Dec 2019 09:41:21 -0700 Subject: [PATCH 021/110] Removing MFTP suffix from PengRobinson class --- include/cantera/thermo/PengRobinsonMFTP.h | 430 ------- src/thermo/PengRobinson.cpp | 616 ++++++++++ src/thermo/PengRobinsonMFTP.cpp | 1300 --------------------- src/thermo/ThermoFactory.cpp | 2 +- test/data/co2_PR_example.cti | 2 +- test/thermo/PengRobinsonMFTP_Test.cpp | 205 ---- test/thermo/PengRobinson_Test.cpp | 77 ++ 7 files changed, 695 insertions(+), 1937 deletions(-) delete mode 100644 include/cantera/thermo/PengRobinsonMFTP.h delete mode 100644 src/thermo/PengRobinsonMFTP.cpp delete mode 100644 test/thermo/PengRobinsonMFTP_Test.cpp diff --git a/include/cantera/thermo/PengRobinsonMFTP.h b/include/cantera/thermo/PengRobinsonMFTP.h deleted file mode 100644 index 2d47674264..0000000000 --- a/include/cantera/thermo/PengRobinsonMFTP.h +++ /dev/null @@ -1,430 +0,0 @@ -//! @file PengRobinsonMFTP.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_PENGROBINSONMFTP_H -#define CT_PENGROBINSONMFTP_H - -#include "MixtureFugacityTP.h" -#include "cantera/base/Array.h" - -namespace Cantera -{ -/** - * Implementation of a multi-species Peng-Robinson equation of state - * - * @ingroup thermoprops - */ -class PengRobinsonMFTP : public MixtureFugacityTP -{ -public: - //! @name Constructors and Duplicators - //! @{ - - //! Base constructor. - PengRobinsonMFTP(); - - //! Construct and initialize a PengRobinsonMFTP object directly from an - //! ASCII input file - /*! - * @param infile Name of the input file containing the phase XML data - * to set up the object - * @param id ID of the phase in the input file. Defaults to the empty - * string. - */ - PengRobinsonMFTP(const std::string& infile, const std::string& id=""); - - //! Construct and initialize a PengRobinsonMFTP object directly from an - //! XML database - /*! - * @param phaseRef XML phase node containing the description of the phase - * @param id id attribute containing the name of the phase. (default - * is the empty string) - */ - PengRobinsonMFTP(XML_Node& phaseRef, const std::string& id = ""); - - virtual std::string type() const { - return "PengRobinson"; - } - - //! @name Molar Thermodynamic properties - //! @{ - - virtual double enthalpy_mole() const; - virtual double entropy_mole() const; - 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) for omega <= 0.491 - * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 - * \f] - * - *Coefficients a_mix, b_mix and (a \alpha)_{mix} are caclulated 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; - - // @} - -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 double temp); - virtual void compositionChanged(); - -public: - virtual void getActivityConcentrations(double* c) 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. In many cases, this - * quantity will be the same for all species in a phase. - * 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 m3 kmol-1. - */ - 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 - //@{ - - //! Get the array of non-dimensional species chemical potentials. - //! These are partial molar Gibbs free energies. - /*! - * \f$ \mu_k / \hat R T \f$. - * Units: unitless - * - * We close the loop on this function here calling getChemPotentials() and - * then dividing by RT. No need for child classes to handle. - * - * @param mu Output vector of non-dimensional species chemical potentials - * Length: m_kk. - */ - virtual void getChemPotentials_RT(double* mu) const; - - 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 the temperature dependent interaction parameter alpha needed for P-R EoS - /* - * 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. - * Units: unitless - */ - virtual void calculateAlpha(const std::string& species, double a, double b, double w); - //@} - /// @name Critical State Properties. - //@{ - - virtual double critTemperature() const; - virtual double critPressure() const; - virtual double critVolume() const; - virtual double critCompressibility() const; - virtual double critDensity() const; - virtual double speciesCritTemperature(double a, double b) const; - -public: - //@} - //! @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 setParametersFromXML(const XML_Node& thermoNode); - virtual void setToEquilState(const double* lambda_RT); - virtual void initThermoXML(XML_Node& phaseNode, const std::string& id); - - //! 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 build/data/thermo/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 std::vector getCoeff(const std::string& iName); - - //! Set the pure fluid interaction parameters for a species - /*! - * The "a" parameter for species *i* in the Peng-Robinson model is assumed - * to be a linear function of temperature: - * \f[ a = a_0 + a_1 T \f] - * - * @param species Name of the species - * @param a0 constant term in the expression for the "a" parameter - * of the specified species [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the expression for the - * "a" parameter of the specified species [Pa-m^6/kmol^2/K] - * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] - * @param alpha dimensionless function of T_r and \omega - * @param omega 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] - * - * This function overrides the defaults with the specified parameters: - * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] - * - * @param species_i Name of one species - * @param species_j Name of the other species - * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the "a" expression - * [Pa-m^6/kmol^2/K] - */ - void setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1); - -private: - //! Read the pure species PengRobinson input parameters - /*! - * @param pureFluidParam XML_Node for the pure fluid parameters - */ - void readXMLPureFluid(XML_Node& pureFluidParam); - - //! Read the cross species PengRobinson input parameters - /*! - * @param crossFluidParam XML_Node for the cross fluid parameters - */ - void readXMLCrossFluid(XML_Node& crossFluidParam); - - // @} - -protected: - // Special functions inherited from MixtureFugacityTP - virtual double sresid() const; - virtual double hresid() const; - -public: - 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 pressureCalc(double TKelvin, double molarVol) const; - virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; - - //! Calculate dpdV and dpdT at the current conditions - /*! - * These are stored internally. - */ - 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(); - - //! Calculate the a and the b 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 temp Temperature (TKelvin) - * @param aCalc (output) Returns the a value - * @param bCalc (output) Returns the b value. - */ - void calculateAB(double temp, double& aCalc, double& bCalc, double& aAlpha) const; - - // Special functions not inherited from MixtureFugacityTP - - double daAlpha_dT() const; - double d2aAlpha_dT2() const; - - void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) const; - - //! Solve the cubic equation of state - /*! - * The P-R equation of state may be solved via the following formula: - * - * V**3 - V**2(RT/P - b) - V(2bRT/P - \alpha a/P + 3*b*b) - (a \alpha b/p - b*b RT/P - b*b*b) = 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. - * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354–359, https://www.jstor.org/stable/3619777) - */ - int NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, - double Vroot[3]) const; - -protected: - //! Form of the temperature parameterization - /*! - * 0 = There is no temperature parameterization of a or b - * 1 = The a_ij parameter is a linear function of the temperature - */ - int m_formTempParam; - - //! Value of b in the equation of state - /*! - * m_b_current is a function of the temperature and the mole fractions. - */ - double m_b_current; - - //! Value of a and alpha in the equation of state - /*! - * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. - */ - double m_a_current; - double m_aAlpha_current; - - // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk - vector_fp a_vec_Curr_; - vector_fp b_vec_Curr_; - vector_fp aAlpha_vec_Curr_; - vector_fp alpha_vec_Curr_; - vector_fp kappa_vec_; - mutable vector_fp dalphadT_vec_Curr_; - mutable vector_fp d2alphadT2_; - - Array2D a_coeff_vec; - Array2D aAlpha_coeff_vec; - - int NSolns_; - - double Vroot_[3]; - - //! 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; - - //! The derivative of the pressure with respect to the volume - /*! - * Calculated at the current conditions. temperature and mole number kept - * constant - */ - mutable double 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 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 dpdni_; - -public: - //! 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/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index a02710aa22..aa6747a2d5 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -48,28 +48,58 @@ PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : initThermoFile(infile, id_); } +<<<<<<< HEAD +======= +PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + importPhase(phaseRefRoot, this); +} + +>>>>>>> Removing MFTP suffix from PengRobinson class void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); if (k == npos) { +<<<<<<< HEAD throw CanteraError("PengRobinson::calculateAlpha", +======= + throw CanteraError("PengRobinson::setSpeciesCoeffs", +>>>>>>> Removing MFTP suffix from PengRobinson class "Unknown species '{}'.", species); } // Calculate value of kappa (independent of temperature) // w is an acentric factor of species and must be specified in the CTI file +<<<<<<< HEAD if (isnan(w)){ throw CanteraError("PengRobinson::calculateAlpha", "No acentric factor loaded."); } else if (w <= 0.491) { +======= + + if (w <= 0.491) { +>>>>>>> Removing MFTP suffix from PengRobinson class kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; } //Calculate alpha (temperature dependent interaction parameter) +<<<<<<< HEAD double critTemp = speciesCritTemperature(a, b); // critical temperature of individual species double sqt_T_r = sqrt(temperature() / critTemp); +======= + double criTemp = speciesCritTemperature(a, b); // critical temperature of individual species + double sqt_T_r = sqrt(temperature() / criTemp); +>>>>>>> Removing MFTP suffix from PengRobinson class double sqt_alpha = 1 + kappa_vec_[k] * (1 - sqt_T_r); alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; } @@ -132,32 +162,68 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, // ------------Molar Thermodynamic Properties ------------------------- +<<<<<<< HEAD double PengRobinson::cp_mole() const { _updateReferenceStateThermo(); double T = temperature(); +======= +double PengRobinson::enthalpy_mole() const +{ + _updateReferenceStateThermo(); + double h_ideal = RT() * mean_X(m_h0_RT); + double h_nonideal = hresid(); + return h_ideal + h_nonideal; +} + +double PengRobinson::entropy_mole() const +{ + _updateReferenceStateThermo(); + double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() + - std::log(pressure()/refPressure())); + double sr_nonideal = sresid(); + return sr_ideal + sr_nonideal; +} + +double PengRobinson::cp_mole() const +{ + _updateReferenceStateThermo(); + double TKelvin = temperature(); +>>>>>>> Removing MFTP suffix from PengRobinson class double mv = molarVolume(); double vpb = mv + (1 + M_SQRT2)*m_b_current; double vmb = mv + (1 - M_SQRT2)*m_b_current; pressureDerivatives(); double cpref = GasConstant * mean_X(m_cp0_R); double dHdT_V = cpref + mv * dpdT_ - GasConstant +<<<<<<< HEAD + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_; +======= + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * TKelvin *d2aAlpha_dT2(); + return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; +>>>>>>> Removing MFTP suffix from PengRobinson class } double PengRobinson::cv_mole() const { _updateReferenceStateThermo(); +<<<<<<< HEAD double T = temperature(); pressureDerivatives(); return (cp_mole() + T* dpdT_* dpdT_ / dpdV_); +======= + double TKelvin = temperature(); + pressureDerivatives(); + return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); +>>>>>>> Removing MFTP suffix from PengRobinson class } double PengRobinson::pressure() const { _updateReferenceStateThermo(); // Get a copy of the private variables stored in the State object +<<<<<<< HEAD double T = temperature(); double mv = molarVolume(); double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; @@ -165,6 +231,49 @@ double PengRobinson::pressure() const return pp; } +======= + double TKelvin = temperature(); + double mv = molarVolume(); + double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double pp = GasConstant * TKelvin / (mv - m_b_current) - m_aAlpha_current / denom; + return pp; +} + +void PengRobinson::calcDensity() +{ + // Calculate the molarVolume of the solution (m**3 kmol-1) + const double* 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 PengRobinson::setTemperature(const double temp) +{ + Phase::setTemperature(temp); + _updateReferenceStateThermo(); + updateAB(); +} + +void PengRobinson::compositionChanged() +{ + MixtureFugacityTP::compositionChanged(); + updateAB(); +} + +void PengRobinson::getActivityConcentrations(double* c) const +{ + getActivityCoefficients(c); + double p_RT = pressure() / RT(); + for (size_t k = 0; k < m_kk; k++) { + c[k] *= moleFraction(k)* p_RT; + } +} + +>>>>>>> Removing MFTP suffix from PengRobinson class double PengRobinson::standardConcentration(size_t k) const { getStandardVolumes(m_tmpV.data()); @@ -174,7 +283,11 @@ double PengRobinson::standardConcentration(size_t k) const void PengRobinson::getActivityCoefficients(double* ac) const { double mv = molarVolume(); +<<<<<<< HEAD //double T = temperature(); +======= + double T = temperature(); +>>>>>>> Removing MFTP suffix from PengRobinson class double vpb2 = mv + (1 + M_SQRT2)*m_b_current; double vmb2 = mv + (1 - M_SQRT2)*m_b_current; double vmb = mv - m_b_current; @@ -261,7 +374,11 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const scale(hbar, hbar+m_kk, hbar, RT()); // We calculate dpdni_ +<<<<<<< HEAD double T = temperature(); +======= + double TKelvin = temperature(); +>>>>>>> Removing MFTP suffix from PengRobinson class double mv = molarVolume(); double vmb = mv - m_b_current; double vpb2 = mv + (1 + M_SQRT2)*m_b_current; @@ -284,10 +401,17 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const } double daAlphadT = daAlpha_dT(); +<<<<<<< HEAD double fac = T * daAlphadT - m_aAlpha_current; pressureDerivatives(); double fac2 = mv + T * dpdT_ / dpdV_; +======= + double fac = TKelvin * daAlphadT - m_aAlpha_current; + + pressureDerivatives(); + double fac2 = mv + TKelvin * dpdT_ / dpdV_; +>>>>>>> Removing MFTP suffix from PengRobinson class double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; for (size_t k = 0; k < m_kk; k++) { double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac @@ -328,7 +452,11 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t k = 0; k < m_kk; k++) { coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; +<<<<<<< HEAD sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) +======= + sbar[k] += GasConstant * log(GasConstant * TKelvin / (refP * mv)) +>>>>>>> Removing MFTP suffix from PengRobinson class + GasConstant + GasConstant * log(mv / vmb) + GasConstant * b_vec_Curr_[k] / vmb @@ -485,7 +613,11 @@ bool PengRobinson::addSpecies(shared_ptr spec) vector PengRobinson::getCoeff(const std::string& iName) { +<<<<<<< HEAD vector_fp spCoeff{ NAN, NAN, NAN }; +======= + vector_fp spCoeff{ NAN, NAN }; +>>>>>>> Removing MFTP suffix from PengRobinson class // Get number of species in the database // open xml file critProperties.xml @@ -505,7 +637,11 @@ vector PengRobinson::getCoeff(const std::string& iName) if (iNameLower == dbName) { // Read from database and calculate a and b coefficients double vParams; +<<<<<<< HEAD double T_crit = 0.0, P_crit = 0.0, w_ac = 0.0; +======= + double T_crit, P_crit; +>>>>>>> Removing MFTP suffix from PengRobinson class if (acNodeDoc.hasChild("Tc")) { vParams = 0.0; @@ -533,6 +669,7 @@ vector PengRobinson::getCoeff(const std::string& iName) } P_crit = vParams; } +<<<<<<< HEAD if (acNodeDoc.hasChild("omega")) { vParams = 0.0; XML_Node& xmlChildCoeff = acNodeDoc.child("omega"); @@ -543,17 +680,23 @@ vector PengRobinson::getCoeff(const std::string& iName) w_ac = vParams; } +======= +>>>>>>> Removing MFTP suffix from PengRobinson class //Assuming no temperature dependence 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 +<<<<<<< HEAD spCoeff[2] = w_ac; // acentric factor +======= +>>>>>>> Removing MFTP suffix from PengRobinson class break; } } return spCoeff; } +<<<<<<< HEAD void PengRobinson::initThermo() { for (auto& item : m_species) { @@ -614,6 +757,162 @@ void PengRobinson::initThermo() } } } +======= +void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) +{ + if (phaseNode.hasChild("thermo")) { + XML_Node& thermoNode = phaseNode.child("thermo"); + std::string model = thermoNode["model"]; + if (model != "PengRobinson" && model != "PengRobinson") { + throw CanteraError("PengRobinson::initThermoXML", + "Unknown thermo model : " + model); + } + + // Go get all of the coefficients and factors in the + // activityCoefficients XML block + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + + // Count the number of species with parameters provided in the + // input file: + size_t nParams = 0; + + // Loop through the children and read out fluid parameters. Process + // all the pureFluidParameters, first: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { + readXMLPureFluid(xmlACChild); + nParams += 1; + } + } + + // If any species exist which have undefined pureFluidParameters, + // search the database in 'critProperties.xml' to find critical + // temperature and pressure to calculate a and b. + + // Loop through all species in the CTI file + size_t iSpecies = 0; + + for (size_t i = 0; i < m_kk; i++) { + string iName = speciesName(i); + + // Get the index of the species + iSpecies = speciesIndex(iName); + + // Check if a and b are already populated (only the diagonal elements of a). + size_t counter = iSpecies + m_kk * iSpecies; + + // If not, then search the database: + if (isnan(a_coeff_vec(0, counter))) { + + vector coeffArray; + + // Search the database for the species name and calculate + // coefficients a and b, from critical properties: + // coeffArray[0] = a0, coeffArray[1] = b, coeffArray[2] = w; + coeffArray = getCoeff(iName); + + // Check if species was found in the database of critical properties, + // and assign the results + if (!isnan(coeffArray[0])) { + //Assuming no temperature dependence (i,e a1 = 0) + setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); + } + } + } + + // Loop back through the "activityCoefficients" children and process the + // crossFluidParameters in the XML tree: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { + readXMLCrossFluid(xmlACChild); + } + } + } + } + + MixtureFugacityTP::initThermoXML(phaseNode, id); +} + +void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) +{ + string xname = pureFluidParam.name(); + if (xname != "pureFluidParameters") { + throw CanteraError("PengRobinson::readXMLPureFluid", + "Incorrect name for processing this routine: " + xname); + } + + double a0 = 0.0; + double a1 = 0.0; + double b = 0.0; + double w = 0.0; + for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { + XML_Node& xmlChild = pureFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + string iModel = toLowerCopy(xmlChild.attrib("model")); + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + + if (vParams.size() == 1) { + a0 = vParams[0]; + } else if (vParams.size() == 2) { + a0 = vParams[0]; + a1 = vParams[1]; + } else { + throw CanteraError("PengRobinson::readXMLPureFluid", + "unknown model or incorrect number of parameters"); + } + } else if (nodeName == "b_coeff") { + b = getFloatCurrent(xmlChild, "toSI"); + } else if (nodeName == "acentric_factor") { + w = getFloatCurrent(xmlChild); + } + } + calculateAlpha(pureFluidParam.attrib("species"), a0, b, w); + setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); +} + +void PengRobinson::readXMLCrossFluid(XML_Node& CrossFluidParam) +{ + string xname = CrossFluidParam.name(); + if (xname != "crossFluidParameters") { + throw CanteraError("PengRobinson::readXMLCrossFluid", + "Incorrect name for processing this routine: " + xname); + } + + string iName = CrossFluidParam.attrib("species1"); + string jName = CrossFluidParam.attrib("species2"); + + size_t num = CrossFluidParam.nChildren(); + for (size_t iChild = 0; iChild < num; iChild++) { + XML_Node& xmlChild = CrossFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + string iModel = toLowerCopy(xmlChild.attrib("model")); + if (iModel == "constant" && vParams.size() == 1) { + setBinaryCoeffs(iName, jName, vParams[0], 0.0); + } else if (iModel == "linear_a") { + setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); + } else { + throw CanteraError("PengRobinson::readXMLCrossFluid", + "unknown model ({}) or wrong number of parameters ({})", + iModel, vParams.size()); + } + } + } +} + +void PengRobinson::setParametersFromXML(const XML_Node& thermoNode) +{ + MixtureFugacityTP::setParametersFromXML(thermoNode); +>>>>>>> Removing MFTP suffix from PengRobinson class } double PengRobinson::sresid() const @@ -622,6 +921,10 @@ double PengRobinson::sresid() const double hh = m_b_current / molarV; double zz = z(); double alpha_1 = daAlpha_dT(); +<<<<<<< HEAD +======= + double T = temperature(); +>>>>>>> Removing MFTP suffix from PengRobinson class double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); @@ -641,19 +944,32 @@ double PengRobinson::hresid() const return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); } +<<<<<<< HEAD double PengRobinson::liquidVolEst(double T, double& presGuess) const +======= +double PengRobinson::liquidVolEst(double TKelvin, double& presGuess) const +>>>>>>> Removing MFTP suffix from PengRobinson class { double v = m_b_current * 1.1; double atmp; double btmp; double aAlphatmp; +<<<<<<< HEAD calculateAB(T, atmp, btmp, aAlphatmp); double pres = std::max(psatEst(T), presGuess); +======= + calculateAB(TKelvin, atmp, btmp, aAlphatmp); + double pres = std::max(psatEst(TKelvin), presGuess); +>>>>>>> Removing MFTP suffix from PengRobinson class double Vroot[3]; bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { +<<<<<<< HEAD int nsol = NicholsCall(T, pres, atmp, btmp, aAlphatmp, Vroot); +======= + int nsol = NicholsSolve(TKelvin, pres, atmp, btmp, aAlphatmp, Vroot); +>>>>>>> Removing MFTP suffix from PengRobinson class if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -674,6 +990,7 @@ double PengRobinson::liquidVolEst(double T, double& presGuess) const return v; } +<<<<<<< HEAD double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, double rhoGuess) { // It's necessary to set the temperature so that m_aAlpha_current is set correctly. @@ -692,6 +1009,34 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do double volGuess = mmw / rhoGuess; NSolns_ = NicholsCall(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); +======= +double PengRobinson::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) +{ + // It's necessary to set the temperature so that m_aAlpha_current is set correctly. + setTemperature(TKelvin); + double tcrit = critTemperature(); + double mmw = meanMolecularWeight(); + if (rhoGuess == -1.0) { + if (phaseRequested != FLUID_GAS) { + if (TKelvin > tcrit) { + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } else { + if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } else 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 + rhoGuess = presPa * mmw / (GasConstant * TKelvin); + } + } + + double volGuess = mmw / rhoGuess; + NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); +>>>>>>> Removing MFTP suffix from PengRobinson class double molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -715,7 +1060,11 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do } else if (NSolns_ == -1) { if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { molarVolLast = Vroot_[0]; +<<<<<<< HEAD } else if (T > tcrit) { +======= + } else if (TKelvin > tcrit) { +>>>>>>> Removing MFTP suffix from PengRobinson class molarVolLast = Vroot_[0]; } else { return -2.0; @@ -731,7 +1080,11 @@ double PengRobinson::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); +<<<<<<< HEAD int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); +======= + int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); +>>>>>>> Removing MFTP suffix from PengRobinson class if (nsol != 3) { return critDensity(); } @@ -753,7 +1106,11 @@ double PengRobinson::densSpinodalGas() const { double Vroot[3]; double T = temperature(); +<<<<<<< HEAD int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); +======= + int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); +>>>>>>> Removing MFTP suffix from PengRobinson class if (nsol != 3) { return critDensity(); } @@ -771,6 +1128,7 @@ double PengRobinson::densSpinodalGas() const return mmw / (0.5 * (vv.first + vv.second)); } +<<<<<<< HEAD double PengRobinson::pressureCalc(double T, double molarVol) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; @@ -786,16 +1144,41 @@ double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const double vpb = molarVol + m_b_current; double vmb = molarVol - m_b_current; double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); +======= +double PengRobinson::pressureCalc(double TKelvin, double molarVol) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; + return pres; +} + +double PengRobinson::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; + + double vpb = molarVol + m_b_current; + double vmb = molarVol - m_b_current; + double dpdv = -GasConstant * TKelvin / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); +>>>>>>> Removing MFTP suffix from PengRobinson class return dpdv; } void PengRobinson::pressureDerivatives() const { +<<<<<<< HEAD double T = temperature(); double mv = molarVolume(); double pres; dpdV_ = dpdVCalc(T, mv, pres); +======= + double TKelvin = temperature(); + double mv = molarVolume(); + double pres; + + dpdV_ = dpdVCalc(TKelvin, mv, pres); +>>>>>>> Removing MFTP suffix from PengRobinson class double vmb = mv - m_b_current; double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); @@ -803,6 +1186,7 @@ void PengRobinson::pressureDerivatives() const void PengRobinson::updateMixingExpressions() { +<<<<<<< HEAD double temp = temperature(); //Update aAlpha_i double sqt_alpha; @@ -812,6 +1196,22 @@ void PengRobinson::updateMixingExpressions() size_t counter = j * m_kk + j; double critTemp_j = speciesCritTemperature(a_vec_Curr_[counter],b_vec_Curr_[j]); sqt_alpha = 1 + kappa_vec_[j] * (1 - sqrt(temp / critTemp_j)); +======= + updateAB(); +} + +void PengRobinson::updateAB() +{ + double temp = temperature(); + //Update aAlpha_i + double sqt_alpha; + double criTemp = critTemperature(); + double sqt_T_reduced = sqrt(temp / criTemp); + + // Update indiviual alpha + for (size_t j = 0; j < m_kk; j++) { + sqt_alpha = 1 + kappa_vec_[j] * (1 - sqt_T_reduced); +>>>>>>> Removing MFTP suffix from PengRobinson class alpha_vec_Curr_[j] = sqt_alpha*sqt_alpha; } @@ -832,7 +1232,11 @@ void PengRobinson::updateMixingExpressions() m_b_current += moleFractions_[i] * b_vec_Curr_[i]; for (size_t j = 0; j < m_kk; j++) { m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; +<<<<<<< HEAD m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; +======= + m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; +>>>>>>> Removing MFTP suffix from PengRobinson class } } } @@ -882,6 +1286,7 @@ double PengRobinson::daAlpha_dT() const double PengRobinson::d2aAlpha_dT2() const { +<<<<<<< HEAD double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; double k; for (size_t i = 0; i < m_kk; i++) { @@ -891,6 +1296,16 @@ double PengRobinson::d2aAlpha_dT2() const 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 daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; + double k; + double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature + double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); + double coeff2 = sqt_Tr - 1; + for (size_t i = 0; i < m_kk; i++) { + // Calculate first and second derivatives of alpha for individual species + size_t counter = i + m_kk * i; +>>>>>>> Removing MFTP suffix from PengRobinson class k = kappa_vec_[i]; dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); @@ -934,11 +1349,26 @@ void PengRobinson::calcCriticalConditions(double a, double b, vc = omega_vc * GasConstant * tc / pc; } +<<<<<<< HEAD int PengRobinson::NicholsCall(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; +======= +int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, + double Vroot[3]) const +{ + double tmp; + fill_n(Vroot, 3, 0.0); + if (TKelvin <= 0.0) { + throw CanteraError("PengRobinson::NicholsSolve()", "negative temperature T = {}", TKelvin); + } + + // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. + double bsqr = b * b; + double RT_p = GasConstant * TKelvin / pres; +>>>>>>> Removing MFTP suffix from PengRobinson class double aAlpha_p = aAlpha / pres; double an = 1.0; double bn = (b - RT_p); @@ -946,9 +1376,195 @@ int PengRobinson::NicholsCall(double T, double pres, double a, double b, double double dn = (bsqr * RT_p + bsqr * b - aAlpha_p * b); double tc = a * omega_b / (b * omega_a * GasConstant); +<<<<<<< HEAD int nSolnValues = NicholsSolve(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); +======= + double pc = omega_b * GasConstant * tc / b; + double vc = omega_vc * GasConstant * tc / pc; + + // 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 * TKelvin); // B + if (fabs(ratio1) < 1.0E-7) { + double ratio3 = aAlpha / (GasConstant * TKelvin) * pres / (GasConstant * TKelvin); // 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 * TKelvin / 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("PengRobinson::NicholsSolve()", "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; + } + + // 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("PengRobinson::NicholsSolve(T = {}, p = {}):" + " WARNING roots have merged: {}, {}\n", + TKelvin, 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve(T = {}, p = {}): " + "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); + writelogendl(); + } + } + + if (nSolnValues == 1) { + if (TKelvin > tc) { + if (Vroot[0] < vc) { + // Liquid phase root + nSolnValues = -1; + } + } else { + if (Vroot[0] < xN) { + nSolnValues = -1; + } + } + } else { + if (nSolnValues == 2 && delta > 1e-14) { + nSolnValues = -2; + } + } +>>>>>>> Removing MFTP suffix from PengRobinson class return nSolnValues; } diff --git a/src/thermo/PengRobinsonMFTP.cpp b/src/thermo/PengRobinsonMFTP.cpp deleted file mode 100644 index 012468e6f5..0000000000 --- a/src/thermo/PengRobinsonMFTP.cpp +++ /dev/null @@ -1,1300 +0,0 @@ -//! @file PengRobinsonMFTP.cpp - -// This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. - -#include "cantera/thermo/PengRobinsonMFTP.h" -#include "cantera/thermo/ThermoFactory.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 PengRobinsonMFTP::omega_a = 4.5723552892138218E-01; -const double PengRobinsonMFTP::omega_b = 7.77960739038885E-02; -const double PengRobinsonMFTP::omega_vc = 3.07401308698703833E-01; - -PengRobinsonMFTP::PengRobinsonMFTP() : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); -} - -PengRobinsonMFTP::PengRobinsonMFTP(const std::string& infile, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - initThermoFile(infile, id_); -} - -PengRobinsonMFTP::PengRobinsonMFTP(XML_Node& phaseRefRoot, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - importPhase(phaseRefRoot, this); -} - -void PengRobinsonMFTP::calculateAlpha(const std::string& species, double a, double b, double w) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } - - // Calculate value of kappa (independent of temperature) - // w is an acentric factor of species and must be specified in the CTI file - - if (w <= 0.491) { - kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; - } else { - kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; - } - - //Calculate alpha (temperature dependent interaction parameter) - double criTemp = speciesCritTemperature(a, b); // critical temperature of individual species - double sqt_T_r = sqrt(temperature() / criTemp); - double sqt_alpha = 1 + kappa_vec_[k] * (1 - sqt_T_r); - alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; -} - -void PengRobinsonMFTP::setSpeciesCoeffs(const std::string& species, - double a, double b, double w) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("PengRobinsonMFTP::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } - size_t counter = k + m_kk * k; - a_coeff_vec(0, counter) = a; - // we store this locally because it is used below to calculate a_Alpha: - double aAlpha_k = a*alpha_vec_Curr_[k]; - aAlpha_coeff_vec(0, counter) = 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(a_coeff_vec(0, j + m_kk * j) * a); - double aAlpha_j = a*alpha_vec_Curr_[j]; - double a_Alpha = sqrt(aAlpha_j*aAlpha_k); - if (a_coeff_vec(0, j + m_kk * k) == 0) { - a_coeff_vec(0, j + m_kk * k) = a0kj; - aAlpha_coeff_vec(0, j + m_kk * k) = a_Alpha; - a_coeff_vec(0, k + m_kk * j) = a0kj; - aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; - } - } - a_coeff_vec.getRow(0, a_vec_Curr_.data()); - aAlpha_coeff_vec.getRow(0, aAlpha_vec_Curr_.data()); - b_vec_Curr_[k] = b; -} - -void PengRobinsonMFTP::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double alpha) -{ - size_t ki = speciesIndex(species_i); - if (ki == npos) { - throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_i); - } - size_t kj = speciesIndex(species_j); - if (kj == npos) { - throw CanteraError("PengRobinsonMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_j); - } - - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; - a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; - aAlpha_vec_Curr_[counter1] = aAlpha_vec_Curr_[counter2] = a0*alpha; -} - -// ------------Molar Thermodynamic Properties ------------------------- - -double PengRobinsonMFTP::enthalpy_mole() const -{ - _updateReferenceStateThermo(); - double h_ideal = RT() * mean_X(m_h0_RT); - double h_nonideal = hresid(); - return h_ideal + h_nonideal; -} - -double PengRobinsonMFTP::entropy_mole() const -{ - _updateReferenceStateThermo(); - double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - - std::log(pressure()/refPressure())); - double sr_nonideal = sresid(); - return sr_ideal + sr_nonideal; -} - -double PengRobinsonMFTP::cp_mole() const -{ - _updateReferenceStateThermo(); - double TKelvin = temperature(); - double mv = molarVolume(); - double vpb = mv + (1 + M_SQRT2)*m_b_current; - double vmb = mv + (1 - M_SQRT2)*m_b_current; - pressureDerivatives(); - double cpref = GasConstant * mean_X(m_cp0_R); - double dHdT_V = cpref + mv * dpdT_ - GasConstant - + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * TKelvin *d2aAlpha_dT2(); - return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; -} - -double PengRobinsonMFTP::cv_mole() const -{ - _updateReferenceStateThermo(); - double TKelvin = temperature(); - pressureDerivatives(); - return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); -} - -double PengRobinsonMFTP::pressure() const -{ - _updateReferenceStateThermo(); - // Get a copy of the private variables stored in the State object - double TKelvin = temperature(); - double mv = molarVolume(); - double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double pp = GasConstant * TKelvin / (mv - m_b_current) - m_aAlpha_current / denom; - return pp; -} - -void PengRobinsonMFTP::calcDensity() -{ - // Calculate the molarVolume of the solution (m**3 kmol-1) - const double* 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 PengRobinsonMFTP::setTemperature(const double temp) -{ - Phase::setTemperature(temp); - _updateReferenceStateThermo(); - updateAB(); -} - -void PengRobinsonMFTP::compositionChanged() -{ - MixtureFugacityTP::compositionChanged(); - updateAB(); -} - -void PengRobinsonMFTP::getActivityConcentrations(double* c) const -{ - getActivityCoefficients(c); - double p_RT = pressure() / RT(); - for (size_t k = 0; k < m_kk; k++) { - c[k] *= moleFraction(k)* p_RT; - } -} - -double PengRobinsonMFTP::standardConcentration(size_t k) const -{ - getStandardVolumes(m_tmpV.data()); - return 1.0 / m_tmpV[k]; -} - -void PengRobinsonMFTP::getActivityCoefficients(double* ac) const -{ - double mv = molarVolume(); - double T = temperature(); - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double vmb = mv - m_b_current; - 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; - ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) - + RTkelvin * b_vec_Curr_[k] / vmb - - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* b_vec_Curr_[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 PengRobinsonMFTP::getChemPotentials_RT(double* muRT) const -{ - getChemPotentials(muRT); - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RTkelvin; - } -} - -void PengRobinsonMFTP::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_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - double pres = pressure(); - double refP = refPressure(); - double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); - - for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; - - mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) - + RTkelvin * log(mv / vmb) - + RTkelvin * b_vec_Curr_[k] / vmb - - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* b_vec_Curr_[k] * mv/den2 - ); - } -} - -void PengRobinsonMFTP::getPartialMolarEnthalpies(double* hbar) const -{ - // First we get the reference state contributions - getEnthalpy_RT_ref(hbar); - scale(hbar, hbar+m_kk, hbar, RT()); - - // We calculate dpdni_ - double TKelvin = temperature(); - double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double den2 = den*den; - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - dpdni_[k] = RTkelvin /vmb + RTkelvin * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / den - + 2 * vmb * m_aAlpha_current * b_vec_Curr_[k] / den2; - } - - double daAlphadT = daAlpha_dT(); - double fac = TKelvin * daAlphadT - m_aAlpha_current; - - pressureDerivatives(); - double fac2 = mv + TKelvin * dpdT_ / dpdV_; - double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; - for (size_t k = 0; k < m_kk; k++) { - double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac - + (mv * b_vec_Curr_[k]) /(m_b_current*den) * fac; - hbar[k] = hbar[k] + hE_v; - hbar[k] -= fac2 * dpdni_[k]; - } -} - -void PengRobinsonMFTP::getPartialMolarEntropies(double* sbar) const -{ - getEntropy_R_ref(sbar); - scale(sbar, sbar+m_kk, sbar, GasConstant); - double TKelvin = temperature(); - double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double refP = refPressure(); - double daAlphadT = daAlpha_dT(); - double coeff1 = 0; - double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - - // 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(dalphadT_vec_Curr_[i] / alpha_vec_Curr_[i]); - } - m_pp[k] = m_pp[k] * dalphadT_vec_Curr_[k] / alpha_vec_Curr_[k]; - } - - - for (size_t k = 0; k < m_kk; k++) { - coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; - sbar[k] += GasConstant * log(GasConstant * TKelvin / (refP * mv)) - + GasConstant - + GasConstant * log(mv / vmb) - + GasConstant * b_vec_Curr_[k] / vmb - - coeff1* log(vpb2 / vmb2) / den1 - - b_vec_Curr_[k] * mv * daAlphadT / den2 / m_b_current; - } - pressureDerivatives(); - getPartialMolarVolumes(m_partialMolarVolumes.data()); - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -= m_partialMolarVolumes[k] * dpdT_; - } -} - -void PengRobinsonMFTP::getPartialMolarIntEnergies(double* ubar) const -{ - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); -} - -void PengRobinsonMFTP::getPartialMolarCp(double* cpbar) const -{ - getCp_R(cpbar); - scale(cpbar, cpbar+m_kk, cpbar, GasConstant); -} - -void PengRobinsonMFTP::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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - - double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb = mv + m_b_current; - double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double fac2 = fac * fac; - double RTkelvin = RT(); - - for (size_t k = 0; k < m_kk; k++) { - double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * b_vec_Curr_[k] / vmb - + RTkelvin * m_b_current * b_vec_Curr_[k] /(vmb * vmb) - - 2 * mv * m_pp[k] / fac - + 2 * mv * vmb * m_aAlpha_current * b_vec_Curr_[k] / fac2 - ); - double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) - + m_aAlpha_current/fac - - 2 * mv* vpb *m_aAlpha_current / fac2 - ); - vbar[k] = num / denom; - } -} - -double PengRobinsonMFTP::speciesCritTemperature(double a, double b) const -{ - double pc, tc, vc; - calcCriticalConditions(a, b, pc, tc, vc); - return tc; -} - -double PengRobinsonMFTP::critTemperature() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return tc; -} - -double PengRobinsonMFTP::critPressure() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc; -} - -double PengRobinsonMFTP::critVolume() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return vc; -} - -double PengRobinsonMFTP::critCompressibility() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc*vc/tc/GasConstant; -} - -double PengRobinsonMFTP::critDensity() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - double mmw = meanMolecularWeight(); - return mmw / vc; -} - -void PengRobinsonMFTP::setToEquilState(const double* mu_RT) -{ - double tmp, tmp2; - _updateReferenceStateThermo(); - getGibbs_RT_ref(m_tmpV.data()); - - // Within the method, we protect against inf results if the exponent is too - // high. - // - // If it is too low, we set the partial pressure to zero. This capability is - // needed by the elemental potential method. - double pres = 0.0; - double m_p0 = refPressure(); - for (size_t k = 0; k < m_kk; k++) { - tmp = -m_tmpV[k] + mu_RT[k]; - if (tmp < -600.) { - m_pp[k] = 0.0; - } else if (tmp > 500.0) { - tmp2 = tmp / 500.; - tmp2 *= tmp2; - m_pp[k] = m_p0 * exp(500.) * tmp2; - } else { - m_pp[k] = m_p0 * exp(tmp); - } - pres += m_pp[k]; - } - // set state - setState_PX(pres, &m_pp[0]); -} - -bool PengRobinsonMFTP::addSpecies(shared_ptr spec) -{ - bool added = MixtureFugacityTP::addSpecies(spec); - if (added) { - a_vec_Curr_.resize(m_kk * m_kk, 0.0); - b_vec_Curr_.push_back(0.0); - a_vec_Curr_.push_back(0.0); - aAlpha_vec_Curr_.resize(m_kk * m_kk, 0.0); - aAlpha_vec_Curr_.push_back(0.0); - kappa_vec_.push_back(0.0); - - alpha_vec_Curr_.push_back(0.0); - a_coeff_vec.resize(1, m_kk * m_kk, 0.0); - aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); - dalphadT_vec_Curr_.push_back(0.0); - d2alphadT2_.push_back(0.0); - - m_pp.push_back(0.0); - m_tmpV.push_back(0.0); - m_partialMolarVolumes.push_back(0.0); - dpdni_.push_back(0.0); - } - return added; -} - -vector PengRobinsonMFTP::getCoeff(const std::string& iName) -{ - vector_fp spCoeff{ 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, P_crit; - - 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("PengRobinsonMFTP::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("PengRobinsonMFTP::getCoeff", - "Critical Pressure must be positive "); - } - P_crit = vParams; - } - - //Assuming no temperature dependence - 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 - break; - } - } - return spCoeff; -} - -void PengRobinsonMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) -{ - if (phaseNode.hasChild("thermo")) { - XML_Node& thermoNode = phaseNode.child("thermo"); - std::string model = thermoNode["model"]; - if (model != "PengRobinson" && model != "PengRobinsonMFTP") { - throw CanteraError("PengRobinsonMFTP::initThermoXML", - "Unknown thermo model : " + model); - } - - // Go get all of the coefficients and factors in the - // activityCoefficients XML block - if (thermoNode.hasChild("activityCoefficients")) { - XML_Node& acNode = thermoNode.child("activityCoefficients"); - - // Count the number of species with parameters provided in the - // input file: - size_t nParams = 0; - - // Loop through the children and read out fluid parameters. Process - // all the pureFluidParameters, first: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { - readXMLPureFluid(xmlACChild); - nParams += 1; - } - } - - // If any species exist which have undefined pureFluidParameters, - // search the database in 'critProperties.xml' to find critical - // temperature and pressure to calculate a and b. - - // Loop through all species in the CTI file - size_t iSpecies = 0; - - for (size_t i = 0; i < m_kk; i++) { - string iName = speciesName(i); - - // Get the index of the species - iSpecies = speciesIndex(iName); - - // Check if a and b are already populated (only the diagonal elements of a). - size_t counter = iSpecies + m_kk * iSpecies; - - // If not, then search the database: - if (isnan(a_coeff_vec(0, counter))) { - - vector coeffArray; - - // Search the database for the species name and calculate - // coefficients a and b, from critical properties: - // coeffArray[0] = a0, coeffArray[1] = b, coeffArray[2] = w; - coeffArray = getCoeff(iName); - - // Check if species was found in the database of critical properties, - // and assign the results - if (!isnan(coeffArray[0])) { - //Assuming no temperature dependence (i,e a1 = 0) - setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); - } - } - } - - // Loop back through the "activityCoefficients" children and process the - // crossFluidParameters in the XML tree: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { - readXMLCrossFluid(xmlACChild); - } - } - } - } - - MixtureFugacityTP::initThermoXML(phaseNode, id); -} - -void PengRobinsonMFTP::readXMLPureFluid(XML_Node& pureFluidParam) -{ - string xname = pureFluidParam.name(); - if (xname != "pureFluidParameters") { - throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", - "Incorrect name for processing this routine: " + xname); - } - - double a0 = 0.0; - double a1 = 0.0; - double b = 0.0; - double w = 0.0; - for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { - XML_Node& xmlChild = pureFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - string iModel = toLowerCopy(xmlChild.attrib("model")); - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - - if (vParams.size() == 1) { - a0 = vParams[0]; - } else if (vParams.size() == 2) { - a0 = vParams[0]; - a1 = vParams[1]; - } else { - throw CanteraError("PengRobinsonMFTP::readXMLPureFluid", - "unknown model or incorrect number of parameters"); - } - } else if (nodeName == "b_coeff") { - b = getFloatCurrent(xmlChild, "toSI"); - } else if (nodeName == "acentric_factor") { - w = getFloatCurrent(xmlChild); - } - } - calculateAlpha(pureFluidParam.attrib("species"), a0, b, w); - setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); -} - -void PengRobinsonMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) -{ - string xname = CrossFluidParam.name(); - if (xname != "crossFluidParameters") { - throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", - "Incorrect name for processing this routine: " + xname); - } - - string iName = CrossFluidParam.attrib("species1"); - string jName = CrossFluidParam.attrib("species2"); - - size_t num = CrossFluidParam.nChildren(); - for (size_t iChild = 0; iChild < num; iChild++) { - XML_Node& xmlChild = CrossFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - string iModel = toLowerCopy(xmlChild.attrib("model")); - if (iModel == "constant" && vParams.size() == 1) { - setBinaryCoeffs(iName, jName, vParams[0], 0.0); - } else if (iModel == "linear_a") { - setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); - } else { - throw CanteraError("PengRobinsonMFTP::readXMLCrossFluid", - "unknown model ({}) or wrong number of parameters ({})", - iModel, vParams.size()); - } - } - } -} - -void PengRobinsonMFTP::setParametersFromXML(const XML_Node& thermoNode) -{ - MixtureFugacityTP::setParametersFromXML(thermoNode); -} - -double PengRobinsonMFTP::sresid() const -{ - double molarV = molarVolume(); - double hh = m_b_current / molarV; - double zz = z(); - double alpha_1 = daAlpha_dT(); - double T = temperature(); - double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; - double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; - double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); - double sresid_mol_R = log(zz*(1.0 - hh)) + fac * log(vpb / vmb) / GasConstant; - return GasConstant * sresid_mol_R; -} - -double PengRobinsonMFTP::hresid() const -{ - double molarV = molarVolume(); - double zz = z(); - double aAlpha_1 = daAlpha_dT(); - double T = temperature(); - double vpb = molarV + (1 + M_SQRT2) *m_b_current; - double vmb = molarV + (1 - M_SQRT2) *m_b_current; - double fac = 1 / (2.0 * M_SQRT2 * m_b_current); - return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); -} - -double PengRobinsonMFTP::liquidVolEst(double TKelvin, double& presGuess) const -{ - double v = m_b_current * 1.1; - double atmp; - double btmp; - double aAlphatmp; - calculateAB(TKelvin, atmp, btmp, aAlphatmp); - double pres = std::max(psatEst(TKelvin), presGuess); - double Vroot[3]; - bool foundLiq = false; - int m = 0; - while (m < 100 && !foundLiq) { - int nsol = NicholsSolve(TKelvin, 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 PengRobinsonMFTP::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) -{ - // It's necessary to set the temperature so that m_aAlpha_current is set correctly. - setTemperature(TKelvin); - double tcrit = critTemperature(); - double mmw = meanMolecularWeight(); - if (rhoGuess == -1.0) { - if (phaseRequested != FLUID_GAS) { - if (TKelvin > tcrit) { - rhoGuess = presPa * mmw / (GasConstant * TKelvin); - } else { - if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - rhoGuess = presPa * mmw / (GasConstant * TKelvin); - } else 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 - rhoGuess = presPa * mmw / (GasConstant * TKelvin); - } - } - - double volGuess = mmw / rhoGuess; - NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); - - double molarVolLast = Vroot_[0]; - if (NSolns_ >= 2) { - if (phaseRequested >= FLUID_LIQUID_0) { - molarVolLast = Vroot_[0]; - } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[2]; - } else { - if (volGuess > Vroot_[1]) { - molarVolLast = Vroot_[2]; - } else { - molarVolLast = Vroot_[0]; - } - } - } else if (NSolns_ == 1) { - if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else if (NSolns_ == -1) { - if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[0]; - } else if (TKelvin > tcrit) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else { - molarVolLast = Vroot_[0]; - return -1.0; - } - return mmw / molarVolLast; -} - -double PengRobinsonMFTP::densSpinodalLiquid() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, 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 PengRobinsonMFTP::densSpinodalGas() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, 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 PengRobinsonMFTP::pressureCalc(double TKelvin, double molarVol) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; - return pres; -} - -double PengRobinsonMFTP::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; - - double vpb = molarVol + m_b_current; - double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * TKelvin / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); - return dpdv; -} - -void PengRobinsonMFTP::pressureDerivatives() const -{ - double TKelvin = temperature(); - double mv = molarVolume(); - double pres; - - dpdV_ = dpdVCalc(TKelvin, mv, pres); - double vmb = mv - m_b_current; - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); -} - -void PengRobinsonMFTP::updateMixingExpressions() -{ - updateAB(); -} - -void PengRobinsonMFTP::updateAB() -{ - double temp = temperature(); - //Update aAlpha_i - double sqt_alpha; - double criTemp = critTemperature(); - double sqt_T_reduced = sqrt(temp / criTemp); - - // Update indiviual alpha - for (size_t j = 0; j < m_kk; j++) { - sqt_alpha = 1 + kappa_vec_[j] * (1 - sqt_T_reduced); - alpha_vec_Curr_[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++) { - size_t counter = i * m_kk + j; - a_vec_Curr_[counter] = a_coeff_vec(0, counter); - aAlpha_vec_Curr_[counter] = sqrt(alpha_vec_Curr_[i] * alpha_vec_Curr_[j]) * a_coeff_vec(0, counter); - } - } - - m_b_current = 0.0; - m_a_current = 0.0; - m_aAlpha_current = 0.0; - - for (size_t i = 0; i < m_kk; i++) { - m_b_current += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - } - } -} - -void PengRobinsonMFTP::calculateAB(double temp, 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] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - double a_vec_Curr = a_coeff_vec(0, counter); - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - aAlphaCalc += aAlpha_vec_Curr_[counter] * moleFractions_[i] * moleFractions_[j]; - } - } -} - -double PengRobinsonMFTP::daAlpha_dT() const -{ - double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; - double coeff1, coeff2; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; - // Calculate first derivative of alpha for individual species - Tc = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); - sqtTr = sqrt(temperature() / Tc); //we need species critical temperature - coeff1 = 1 / (Tc*sqtTr); - coeff2 = sqtTr - 1; - k = kappa_vec_[i]; - dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); - } - //Calculate mixture derivative - for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; - for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j * m_kk + j; - temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alpha_vec_Curr_[i] * alpha_vec_Curr_[j])); - daAlphadT += moleFractions_[i] * moleFractions_[j] * temp - * (dalphadT_vec_Curr_[j] * alpha_vec_Curr_[i] + dalphadT_vec_Curr_[i] * alpha_vec_Curr_[j]); - } - } - return daAlphadT; -} - -double PengRobinsonMFTP::d2aAlpha_dT2() const -{ - double daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; - double k; - double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature - double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); - double coeff2 = sqt_Tr - 1; - for (size_t i = 0; i < m_kk; i++) { - // Calculate first and second derivatives of alpha for individual species - size_t counter = i + m_kk * i; - k = kappa_vec_[i]; - dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); - d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); - } - - //Calculate mixture derivative - for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; - alphai = alpha_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j + m_kk * j; - alphaj = alpha_vec_Curr_[j]; - alphaij = alphai * alphaj; - temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); - num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); - fac1 = -(0.5 / alphaij)*num*num; - fac2 = alphaj * d2alphadT2_[counter1] + alphai *d2alphadT2_[counter2] + 2 * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); - } - } - return d2aAlphadT2; -} - -void PengRobinsonMFTP::calcCriticalConditions(double a, double b, - double& pc, double& tc, double& vc) const -{ - if (b <= 0.0) { - tc = 1000000.; - pc = 1.0E13; - vc = omega_vc * GasConstant * tc / pc; - return; - } - if (a <= 0.0) { - tc = 0.0; - pc = 0.0; - vc = 2.0 * b; - return; - } - tc = a * omega_b / (b * omega_a * GasConstant); - pc = omega_b * GasConstant * tc / b; - vc = omega_vc * GasConstant * tc / pc; -} - -int PengRobinsonMFTP::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, - double Vroot[3]) const -{ - double tmp; - fill_n(Vroot, 3, 0.0); - if (TKelvin <= 0.0) { - throw CanteraError("PengRobinsonMFTP::NicholsSolve()", "negative temperature T = {}", TKelvin); - } - - // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. - double bsqr = b * b; - double RT_p = GasConstant * TKelvin / 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; - - // 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 * TKelvin); // B - if (fabs(ratio1) < 1.0E-7) { - double ratio3 = aAlpha / (GasConstant * TKelvin) * pres / (GasConstant * TKelvin); // 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 * TKelvin / 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("PengRobinsonMFTP::NicholsSolve()", "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; - } - - // 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}):" - " WARNING roots have merged: {}, {}\n", - TKelvin, 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinsonMFTP::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinsonMFTP::NicholsSolve(T = {}, p = {}): " - "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); - writelogendl(); - } - } - - if (nSolnValues == 1) { - if (TKelvin > tc) { - if (Vroot[0] < vc) { - // Liquid phase root - nSolnValues = -1; - } - } else { - if (Vroot[0] < xN) { - nSolnValues = -1; - } - } - } else { - if (nSolnValues == 2 && delta > 1e-14) { - nSolnValues = -2; - } - } - return nSolnValues; -} - -} diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 9883e9bd91..7ba0f7b5fa 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -23,7 +23,7 @@ #include "cantera/thermo/IonsFromNeutralVPSSTP.h" #include "cantera/thermo/PureFluidPhase.h" #include "cantera/thermo/RedlichKwongMFTP.h" -#include "cantera/thermo/PengRobinsonMFTP.h" +#include "cantera/thermo/PengRobinson.h" #include "cantera/thermo/ConstDensityThermo.h" #include "cantera/thermo/SurfPhase.h" #include "cantera/thermo/EdgePhase.h" diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti index 8692fef113..147816cd9d 100644 --- a/test/data/co2_PR_example.cti +++ b/test/data/co2_PR_example.cti @@ -3,7 +3,7 @@ units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") -PengRobinsonMFTP(name ="carbondioxide", +PengRobinson(name ="carbondioxide", elements ="C O H N", species ="""CO2 H2O H2 CO CH4 O2 N2""", activity_coefficients = (pureFluidParameters(species="CO2", a_coeff = [3.958134E+11, 0], b_coeff = 26.6275, acentric_factor = 0.228), diff --git a/test/thermo/PengRobinsonMFTP_Test.cpp b/test/thermo/PengRobinsonMFTP_Test.cpp deleted file mode 100644 index 9d8043cb5d..0000000000 --- a/test/thermo/PengRobinsonMFTP_Test.cpp +++ /dev/null @@ -1,205 +0,0 @@ -#include "gtest/gtest.h" -#include "cantera/thermo/PengRobinsonMFTP.h" -#include "cantera/thermo/ThermoFactory.h" - - -namespace Cantera -{ - -class PengRobinsonMFTP_Test : public testing::Test -{ -public: - PengRobinsonMFTP_Test() { - test_phase.reset(newPhase("../data/co2_PR_example.cti")); - } - - //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(PengRobinsonMFTP_Test, construct_from_cti) -{ - PengRobinsonMFTP* peng_robinson_phase = dynamic_cast(test_phase.get()); - EXPECT_TRUE(peng_robinson_phase != NULL); -} - -TEST_F(PengRobinsonMFTP_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] = { - -4.5736182681761962e+008, - -4.5733771904416579e+008, - -4.5732943831449223e+008, - -4.5732206687414169e+008, - -4.5731546826955432e+008, - -4.5730953161186475e+008, - -4.5730416590547645e+008, - -4.5729929581635743e+008, - -4.5729485847173005e+008 - }; - - 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(PengRobinsonMFTP_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(PengRobinsonMFTP_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(PengRobinsonMFTP_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(PengRobinsonMFTP_Test, setTP) -{ - // Check to make sure that the phase diagram is accurately reproduced for a few select isobars - - // All sub-cooled liquid: - const double p1[6] = { - 1.7474528924963985e+002, - 1.6800540828415956e+002, - 1.62278413743154e+002, - 1.5728963799103039e+002, - 1.5286573762819748e+002, - 1.4888956030449546e+002 - }; - // Phase change between temperatures 4 & 5: - const double p2[6] = { - 7.5565889855724288e+002, - 7.2577747673480337e+002, - 6.913183942651284e+002, - 6.494661249672663e+002, - 5.9240469307757724e+002, - 3.645826047440932e+002 - }; - // Supercritical; no discontinuity in rho values: - const double p3[6] = { - 8.047430802847415e+002, - 7.8291565113595595e+002, - 7.5958477920749681e+002, - 7.3445460137134626e+002, - 7.0712433093853724e+002, - 6.77034438769492e+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(),p1[i],1.e-8); - - test_phase->setState_TP(temp, 7389370.); - EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); - - test_phase->setState_TP(temp, 9236712.5); - EXPECT_NEAR(test_phase->density(),p3[i],1.e-8); - } -} - -TEST_F(PengRobinsonMFTP_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. - * 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.958134E+5; - double b_coeff = 26.6275/1000; - double acc_factor = 0.228; - double pres_theoretical, kappa, alpha, mv; - const double rho = 1.0737; - const double Tcrit = test_phase->critTemperature(); - - //Calculate kappa value - kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; - - for (int i = 0; i<10; i++) - { - const double temp = 296 + i * 2; - set_r(0.999); - test_phase->setState_TR(temp, 1.0737); - mv = 1 / rho * test_phase->meanMolecularWeight(); - //Calculate pressure using Peng-Robinson EoS - alpha = pow(1 + kappa*(1 - sqrt(temp / Tcrit)), 2); - 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, 2); - } -} -}; diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 8ea6a8c676..5af6043436 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -10,7 +10,11 @@ class PengRobinson_Test : public testing::Test { public: PengRobinson_Test() { +<<<<<<< HEAD test_phase.reset(newPhase("../data/co2_PR_example.yaml")); +======= + test_phase.reset(newPhase("../data/co2_PR_example.cti")); +>>>>>>> Removing MFTP suffix from PengRobinson class } //vary the composition of a co2-h2 mixture: @@ -24,7 +28,11 @@ class PengRobinson_Test : public testing::Test std::unique_ptr test_phase; }; +<<<<<<< HEAD TEST_F(PengRobinson_Test, construct_from_yaml) +======= +TEST_F(PengRobinson_Test, construct_from_cti) +>>>>>>> Removing MFTP suffix from PengRobinson class { PengRobinson* peng_robinson_phase = dynamic_cast(test_phase.get()); EXPECT_TRUE(peng_robinson_phase != NULL); @@ -39,6 +47,7 @@ TEST_F(PengRobinson_Test, chem_potentials) * calculated using the model. */ const double expected_result[9] = { +<<<<<<< HEAD -457361607.71983075, -457350560.54839599, -457340699.25698096, @@ -48,6 +57,17 @@ TEST_F(PengRobinson_Test, chem_potentials) -457309967.99120748, -457303910.44199038, -457298344.62820804 +======= + -4.5736182681761962e+008, + -4.5733771904416579e+008, + -4.5732943831449223e+008, + -4.5732206687414169e+008, + -4.5731546826955432e+008, + -4.5730953161186475e+008, + -4.5730416590547645e+008, + -4.5729929581635743e+008, + -4.5729485847173005e+008 +>>>>>>> Removing MFTP suffix from PengRobinson class }; double xmin = 0.6; @@ -63,6 +83,7 @@ TEST_F(PengRobinson_Test, chem_potentials) } } +<<<<<<< HEAD TEST_F(PengRobinson_Test, chemPotentials_RT) { test_phase->setState_TP(298., 1.); @@ -87,6 +108,8 @@ TEST_F(PengRobinson_Test, chemPotentials_RT) } } +======= +>>>>>>> Removing MFTP suffix from PengRobinson class TEST_F(PengRobinson_Test, activityCoeffs) { test_phase->setState_TP(298., 1.); @@ -150,6 +173,7 @@ TEST_F(PengRobinson_Test, setTP) // All sub-cooled liquid: const double p1[6] = { +<<<<<<< HEAD 1.7084253549322079e+002, 1.6543121742659784e+002, 1.6066148681014121e+002, @@ -174,6 +198,32 @@ TEST_F(PengRobinson_Test, setTP) 7.2620055080831412e+002, 7.0335270498118734e+002, 6.7859003092723128e+002 +======= + 1.7474528924963985e+002, + 1.6800540828415956e+002, + 1.62278413743154e+002, + 1.5728963799103039e+002, + 1.5286573762819748e+002, + 1.4888956030449546e+002 + }; + // Phase change between temperatures 4 & 5: + const double p2[6] = { + 7.5565889855724288e+002, + 7.2577747673480337e+002, + 6.913183942651284e+002, + 6.494661249672663e+002, + 5.9240469307757724e+002, + 3.645826047440932e+002 + }; + // Supercritical; no discontinuity in rho values: + const double p3[6] = { + 8.047430802847415e+002, + 7.8291565113595595e+002, + 7.5958477920749681e+002, + 7.3445460137134626e+002, + 7.0712433093853724e+002, + 6.77034438769492e+002 +>>>>>>> Removing MFTP suffix from PengRobinson class }; for(int i=0; i<6; ++i) @@ -183,7 +233,11 @@ TEST_F(PengRobinson_Test, setTP) test_phase->setState_TP(temp, 5542027.5); EXPECT_NEAR(test_phase->density(),p1[i],1.e-8); +<<<<<<< HEAD test_phase->setState_TP(temp, 7388370.); +======= + test_phase->setState_TP(temp, 7389370.); +>>>>>>> Removing MFTP suffix from PengRobinson class EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); test_phase->setState_TP(temp, 9236712.5); @@ -195,7 +249,11 @@ TEST_F(PengRobinson_Test, getPressure) { // Check to make sure that the P-R equation is accurately reproduced for a few selected values +<<<<<<< HEAD /* This test uses CO2 as the only species (mole fraction 99.9%, balance H2). +======= + /* This test uses CO2 as the only species. +>>>>>>> Removing MFTP suffix from PengRobinson class * 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 @@ -204,11 +262,20 @@ TEST_F(PengRobinson_Test, getPressure) * kappa is a function calulated based on the accentric factor. */ +<<<<<<< HEAD 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; +======= + double a_coeff = 3.958134E+5; + double b_coeff = 26.6275/1000; + double acc_factor = 0.228; + double pres_theoretical, kappa, alpha, mv; + const double rho = 1.0737; + const double Tcrit = test_phase->critTemperature(); +>>>>>>> Removing MFTP suffix from PengRobinson class //Calculate kappa value kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; @@ -216,6 +283,7 @@ TEST_F(PengRobinson_Test, getPressure) for (int i = 0; i<10; i++) { const double temp = 296 + i * 2; +<<<<<<< HEAD set_r(1.0); test_phase->setState_TR(temp, rho); const double Tcrit = test_phase->critTemperature(); @@ -224,6 +292,15 @@ TEST_F(PengRobinson_Test, getPressure) 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); +======= + set_r(0.999); + test_phase->setState_TR(temp, 1.0737); + mv = 1 / rho * test_phase->meanMolecularWeight(); + //Calculate pressure using Peng-Robinson EoS + alpha = pow(1 + kappa*(1 - sqrt(temp / Tcrit)), 2); + 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, 2); +>>>>>>> Removing MFTP suffix from PengRobinson class } } }; From 6ecfec35f31e4d39a75bc926b015da220a423258 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Wed, 22 Jan 2020 14:38:27 -0700 Subject: [PATCH 022/110] Changing 'bad' to 'poorly' in MixtureFugacity TP error message --- src/thermo/MixtureFugacityTP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 025b5e7644..00369fb5d6 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -935,7 +935,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad conditioned."); + throw CanteraError("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -943,7 +943,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad conditioned."); + throw CanteraError("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); } delta = -delta; Vroot[0] = xN + delta; From d2ec33799f3d2e0768af4c0409abf1f3efba9db6 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 27 Jan 2020 07:09:42 -0700 Subject: [PATCH 023/110] Fixing typo in previous commit. --- src/thermo/ThermoFactory.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 7ba0f7b5fa..37107383a9 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -90,12 +90,9 @@ ThermoFactory::ThermoFactory() addAlias("liquid-water-IAPWS95", "PureLiquidWater"); reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); -<<<<<<< HEAD -======= reg("PengRobinson", []() { return new PengRobinsonMFTP(); }); addAlias["PengRobinsonMFTP"] = "PengRobinson"; addAlias["Peng-Robinson"] = "PengRobinson"; ->>>>>>> Add functions for factory alias management } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) From 8828ef975f8261e95b1a2b9a54562ec1c746c8a7 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 27 Jan 2020 07:40:48 -0700 Subject: [PATCH 024/110] Fixing up merge conflicts in PengRobinson_Test.cpp --- test/thermo/PengRobinson_Test.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 5af6043436..c4beac3f16 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -283,7 +283,6 @@ TEST_F(PengRobinson_Test, getPressure) for (int i = 0; i<10; i++) { const double temp = 296 + i * 2; -<<<<<<< HEAD set_r(1.0); test_phase->setState_TR(temp, rho); const double Tcrit = test_phase->critTemperature(); @@ -292,15 +291,6 @@ TEST_F(PengRobinson_Test, getPressure) 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); -======= - set_r(0.999); - test_phase->setState_TR(temp, 1.0737); - mv = 1 / rho * test_phase->meanMolecularWeight(); - //Calculate pressure using Peng-Robinson EoS - alpha = pow(1 + kappa*(1 - sqrt(temp / Tcrit)), 2); - 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, 2); ->>>>>>> Removing MFTP suffix from PengRobinson class } } }; From ef8b92c498b85fb9defc9a873acc43fd90dce8a7 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 27 Jan 2020 11:43:30 -0700 Subject: [PATCH 025/110] Cleaning up typos and merge conflicts. Cleaning up merge conflicts in src/thermo/PengRobinson.cpp Cleaning up `addAlias` typos in src/thermo/ThermoFactory.cpp --- src/thermo/PengRobinson.cpp | 616 ----------------------------------- src/thermo/ThermoFactory.cpp | 4 +- 2 files changed, 2 insertions(+), 618 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index aa6747a2d5..a02710aa22 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -48,58 +48,28 @@ PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : initThermoFile(infile, id_); } -<<<<<<< HEAD -======= -PengRobinson::PengRobinson(XML_Node& phaseRefRoot, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - importPhase(phaseRefRoot, this); -} - ->>>>>>> Removing MFTP suffix from PengRobinson class void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); if (k == npos) { -<<<<<<< HEAD throw CanteraError("PengRobinson::calculateAlpha", -======= - throw CanteraError("PengRobinson::setSpeciesCoeffs", ->>>>>>> Removing MFTP suffix from PengRobinson class "Unknown species '{}'.", species); } // Calculate value of kappa (independent of temperature) // w is an acentric factor of species and must be specified in the CTI file -<<<<<<< HEAD if (isnan(w)){ throw CanteraError("PengRobinson::calculateAlpha", "No acentric factor loaded."); } else if (w <= 0.491) { -======= - - if (w <= 0.491) { ->>>>>>> Removing MFTP suffix from PengRobinson class kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; } //Calculate alpha (temperature dependent interaction parameter) -<<<<<<< HEAD double critTemp = speciesCritTemperature(a, b); // critical temperature of individual species double sqt_T_r = sqrt(temperature() / critTemp); -======= - double criTemp = speciesCritTemperature(a, b); // critical temperature of individual species - double sqt_T_r = sqrt(temperature() / criTemp); ->>>>>>> Removing MFTP suffix from PengRobinson class double sqt_alpha = 1 + kappa_vec_[k] * (1 - sqt_T_r); alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; } @@ -162,68 +132,32 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, // ------------Molar Thermodynamic Properties ------------------------- -<<<<<<< HEAD double PengRobinson::cp_mole() const { _updateReferenceStateThermo(); double T = temperature(); -======= -double PengRobinson::enthalpy_mole() const -{ - _updateReferenceStateThermo(); - double h_ideal = RT() * mean_X(m_h0_RT); - double h_nonideal = hresid(); - return h_ideal + h_nonideal; -} - -double PengRobinson::entropy_mole() const -{ - _updateReferenceStateThermo(); - double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - - std::log(pressure()/refPressure())); - double sr_nonideal = sresid(); - return sr_ideal + sr_nonideal; -} - -double PengRobinson::cp_mole() const -{ - _updateReferenceStateThermo(); - double TKelvin = temperature(); ->>>>>>> Removing MFTP suffix from PengRobinson class double mv = molarVolume(); double vpb = mv + (1 + M_SQRT2)*m_b_current; double vmb = mv + (1 - M_SQRT2)*m_b_current; pressureDerivatives(); double cpref = GasConstant * mean_X(m_cp0_R); double dHdT_V = cpref + mv * dpdT_ - GasConstant -<<<<<<< HEAD + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_; -======= - + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * TKelvin *d2aAlpha_dT2(); - return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; ->>>>>>> Removing MFTP suffix from PengRobinson class } double PengRobinson::cv_mole() const { _updateReferenceStateThermo(); -<<<<<<< HEAD double T = temperature(); pressureDerivatives(); return (cp_mole() + T* dpdT_* dpdT_ / dpdV_); -======= - double TKelvin = temperature(); - pressureDerivatives(); - return (cp_mole() + TKelvin* dpdT_* dpdT_ / dpdV_); ->>>>>>> Removing MFTP suffix from PengRobinson class } double PengRobinson::pressure() const { _updateReferenceStateThermo(); // Get a copy of the private variables stored in the State object -<<<<<<< HEAD double T = temperature(); double mv = molarVolume(); double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; @@ -231,49 +165,6 @@ double PengRobinson::pressure() const return pp; } -======= - double TKelvin = temperature(); - double mv = molarVolume(); - double denom = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double pp = GasConstant * TKelvin / (mv - m_b_current) - m_aAlpha_current / denom; - return pp; -} - -void PengRobinson::calcDensity() -{ - // Calculate the molarVolume of the solution (m**3 kmol-1) - const double* 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 PengRobinson::setTemperature(const double temp) -{ - Phase::setTemperature(temp); - _updateReferenceStateThermo(); - updateAB(); -} - -void PengRobinson::compositionChanged() -{ - MixtureFugacityTP::compositionChanged(); - updateAB(); -} - -void PengRobinson::getActivityConcentrations(double* c) const -{ - getActivityCoefficients(c); - double p_RT = pressure() / RT(); - for (size_t k = 0; k < m_kk; k++) { - c[k] *= moleFraction(k)* p_RT; - } -} - ->>>>>>> Removing MFTP suffix from PengRobinson class double PengRobinson::standardConcentration(size_t k) const { getStandardVolumes(m_tmpV.data()); @@ -283,11 +174,7 @@ double PengRobinson::standardConcentration(size_t k) const void PengRobinson::getActivityCoefficients(double* ac) const { double mv = molarVolume(); -<<<<<<< HEAD //double T = temperature(); -======= - double T = temperature(); ->>>>>>> Removing MFTP suffix from PengRobinson class double vpb2 = mv + (1 + M_SQRT2)*m_b_current; double vmb2 = mv + (1 - M_SQRT2)*m_b_current; double vmb = mv - m_b_current; @@ -374,11 +261,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const scale(hbar, hbar+m_kk, hbar, RT()); // We calculate dpdni_ -<<<<<<< HEAD double T = temperature(); -======= - double TKelvin = temperature(); ->>>>>>> Removing MFTP suffix from PengRobinson class double mv = molarVolume(); double vmb = mv - m_b_current; double vpb2 = mv + (1 + M_SQRT2)*m_b_current; @@ -401,17 +284,10 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const } double daAlphadT = daAlpha_dT(); -<<<<<<< HEAD double fac = T * daAlphadT - m_aAlpha_current; pressureDerivatives(); double fac2 = mv + T * dpdT_ / dpdV_; -======= - double fac = TKelvin * daAlphadT - m_aAlpha_current; - - pressureDerivatives(); - double fac2 = mv + TKelvin * dpdT_ / dpdV_; ->>>>>>> Removing MFTP suffix from PengRobinson class double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; for (size_t k = 0; k < m_kk; k++) { double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac @@ -452,11 +328,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t k = 0; k < m_kk; k++) { coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; -<<<<<<< HEAD sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) -======= - sbar[k] += GasConstant * log(GasConstant * TKelvin / (refP * mv)) ->>>>>>> Removing MFTP suffix from PengRobinson class + GasConstant + GasConstant * log(mv / vmb) + GasConstant * b_vec_Curr_[k] / vmb @@ -613,11 +485,7 @@ bool PengRobinson::addSpecies(shared_ptr spec) vector PengRobinson::getCoeff(const std::string& iName) { -<<<<<<< HEAD vector_fp spCoeff{ NAN, NAN, NAN }; -======= - vector_fp spCoeff{ NAN, NAN }; ->>>>>>> Removing MFTP suffix from PengRobinson class // Get number of species in the database // open xml file critProperties.xml @@ -637,11 +505,7 @@ vector PengRobinson::getCoeff(const std::string& iName) if (iNameLower == dbName) { // Read from database and calculate a and b coefficients double vParams; -<<<<<<< HEAD double T_crit = 0.0, P_crit = 0.0, w_ac = 0.0; -======= - double T_crit, P_crit; ->>>>>>> Removing MFTP suffix from PengRobinson class if (acNodeDoc.hasChild("Tc")) { vParams = 0.0; @@ -669,7 +533,6 @@ vector PengRobinson::getCoeff(const std::string& iName) } P_crit = vParams; } -<<<<<<< HEAD if (acNodeDoc.hasChild("omega")) { vParams = 0.0; XML_Node& xmlChildCoeff = acNodeDoc.child("omega"); @@ -680,23 +543,17 @@ vector PengRobinson::getCoeff(const std::string& iName) w_ac = vParams; } -======= ->>>>>>> Removing MFTP suffix from PengRobinson class //Assuming no temperature dependence 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 -<<<<<<< HEAD spCoeff[2] = w_ac; // acentric factor -======= ->>>>>>> Removing MFTP suffix from PengRobinson class break; } } return spCoeff; } -<<<<<<< HEAD void PengRobinson::initThermo() { for (auto& item : m_species) { @@ -757,162 +614,6 @@ void PengRobinson::initThermo() } } } -======= -void PengRobinson::initThermoXML(XML_Node& phaseNode, const std::string& id) -{ - if (phaseNode.hasChild("thermo")) { - XML_Node& thermoNode = phaseNode.child("thermo"); - std::string model = thermoNode["model"]; - if (model != "PengRobinson" && model != "PengRobinson") { - throw CanteraError("PengRobinson::initThermoXML", - "Unknown thermo model : " + model); - } - - // Go get all of the coefficients and factors in the - // activityCoefficients XML block - if (thermoNode.hasChild("activityCoefficients")) { - XML_Node& acNode = thermoNode.child("activityCoefficients"); - - // Count the number of species with parameters provided in the - // input file: - size_t nParams = 0; - - // Loop through the children and read out fluid parameters. Process - // all the pureFluidParameters, first: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { - readXMLPureFluid(xmlACChild); - nParams += 1; - } - } - - // If any species exist which have undefined pureFluidParameters, - // search the database in 'critProperties.xml' to find critical - // temperature and pressure to calculate a and b. - - // Loop through all species in the CTI file - size_t iSpecies = 0; - - for (size_t i = 0; i < m_kk; i++) { - string iName = speciesName(i); - - // Get the index of the species - iSpecies = speciesIndex(iName); - - // Check if a and b are already populated (only the diagonal elements of a). - size_t counter = iSpecies + m_kk * iSpecies; - - // If not, then search the database: - if (isnan(a_coeff_vec(0, counter))) { - - vector coeffArray; - - // Search the database for the species name and calculate - // coefficients a and b, from critical properties: - // coeffArray[0] = a0, coeffArray[1] = b, coeffArray[2] = w; - coeffArray = getCoeff(iName); - - // Check if species was found in the database of critical properties, - // and assign the results - if (!isnan(coeffArray[0])) { - //Assuming no temperature dependence (i,e a1 = 0) - setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); - } - } - } - - // Loop back through the "activityCoefficients" children and process the - // crossFluidParameters in the XML tree: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { - readXMLCrossFluid(xmlACChild); - } - } - } - } - - MixtureFugacityTP::initThermoXML(phaseNode, id); -} - -void PengRobinson::readXMLPureFluid(XML_Node& pureFluidParam) -{ - string xname = pureFluidParam.name(); - if (xname != "pureFluidParameters") { - throw CanteraError("PengRobinson::readXMLPureFluid", - "Incorrect name for processing this routine: " + xname); - } - - double a0 = 0.0; - double a1 = 0.0; - double b = 0.0; - double w = 0.0; - for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { - XML_Node& xmlChild = pureFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - string iModel = toLowerCopy(xmlChild.attrib("model")); - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - - if (vParams.size() == 1) { - a0 = vParams[0]; - } else if (vParams.size() == 2) { - a0 = vParams[0]; - a1 = vParams[1]; - } else { - throw CanteraError("PengRobinson::readXMLPureFluid", - "unknown model or incorrect number of parameters"); - } - } else if (nodeName == "b_coeff") { - b = getFloatCurrent(xmlChild, "toSI"); - } else if (nodeName == "acentric_factor") { - w = getFloatCurrent(xmlChild); - } - } - calculateAlpha(pureFluidParam.attrib("species"), a0, b, w); - setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, b, w); -} - -void PengRobinson::readXMLCrossFluid(XML_Node& CrossFluidParam) -{ - string xname = CrossFluidParam.name(); - if (xname != "crossFluidParameters") { - throw CanteraError("PengRobinson::readXMLCrossFluid", - "Incorrect name for processing this routine: " + xname); - } - - string iName = CrossFluidParam.attrib("species1"); - string jName = CrossFluidParam.attrib("species2"); - - size_t num = CrossFluidParam.nChildren(); - for (size_t iChild = 0; iChild < num; iChild++) { - XML_Node& xmlChild = CrossFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - string iModel = toLowerCopy(xmlChild.attrib("model")); - if (iModel == "constant" && vParams.size() == 1) { - setBinaryCoeffs(iName, jName, vParams[0], 0.0); - } else if (iModel == "linear_a") { - setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); - } else { - throw CanteraError("PengRobinson::readXMLCrossFluid", - "unknown model ({}) or wrong number of parameters ({})", - iModel, vParams.size()); - } - } - } -} - -void PengRobinson::setParametersFromXML(const XML_Node& thermoNode) -{ - MixtureFugacityTP::setParametersFromXML(thermoNode); ->>>>>>> Removing MFTP suffix from PengRobinson class } double PengRobinson::sresid() const @@ -921,10 +622,6 @@ double PengRobinson::sresid() const double hh = m_b_current / molarV; double zz = z(); double alpha_1 = daAlpha_dT(); -<<<<<<< HEAD -======= - double T = temperature(); ->>>>>>> Removing MFTP suffix from PengRobinson class double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); @@ -944,32 +641,19 @@ double PengRobinson::hresid() const return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); } -<<<<<<< HEAD double PengRobinson::liquidVolEst(double T, double& presGuess) const -======= -double PengRobinson::liquidVolEst(double TKelvin, double& presGuess) const ->>>>>>> Removing MFTP suffix from PengRobinson class { double v = m_b_current * 1.1; double atmp; double btmp; double aAlphatmp; -<<<<<<< HEAD calculateAB(T, atmp, btmp, aAlphatmp); double pres = std::max(psatEst(T), presGuess); -======= - calculateAB(TKelvin, atmp, btmp, aAlphatmp); - double pres = std::max(psatEst(TKelvin), presGuess); ->>>>>>> Removing MFTP suffix from PengRobinson class double Vroot[3]; bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { -<<<<<<< HEAD int nsol = NicholsCall(T, pres, atmp, btmp, aAlphatmp, Vroot); -======= - int nsol = NicholsSolve(TKelvin, pres, atmp, btmp, aAlphatmp, Vroot); ->>>>>>> Removing MFTP suffix from PengRobinson class if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -990,7 +674,6 @@ double PengRobinson::liquidVolEst(double TKelvin, double& presGuess) const return v; } -<<<<<<< HEAD double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, double rhoGuess) { // It's necessary to set the temperature so that m_aAlpha_current is set correctly. @@ -1009,34 +692,6 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do double volGuess = mmw / rhoGuess; NSolns_ = NicholsCall(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); -======= -double PengRobinson::densityCalc(double TKelvin, double presPa, int phaseRequested, double rhoGuess) -{ - // It's necessary to set the temperature so that m_aAlpha_current is set correctly. - setTemperature(TKelvin); - double tcrit = critTemperature(); - double mmw = meanMolecularWeight(); - if (rhoGuess == -1.0) { - if (phaseRequested != FLUID_GAS) { - if (TKelvin > tcrit) { - rhoGuess = presPa * mmw / (GasConstant * TKelvin); - } else { - if (phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - rhoGuess = presPa * mmw / (GasConstant * TKelvin); - } else 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 - rhoGuess = presPa * mmw / (GasConstant * TKelvin); - } - } - - double volGuess = mmw / rhoGuess; - NSolns_ = NicholsSolve(TKelvin, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); ->>>>>>> Removing MFTP suffix from PengRobinson class double molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -1060,11 +715,7 @@ double PengRobinson::densityCalc(double TKelvin, double presPa, int phaseRequest } else if (NSolns_ == -1) { if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { molarVolLast = Vroot_[0]; -<<<<<<< HEAD } else if (T > tcrit) { -======= - } else if (TKelvin > tcrit) { ->>>>>>> Removing MFTP suffix from PengRobinson class molarVolLast = Vroot_[0]; } else { return -2.0; @@ -1080,11 +731,7 @@ double PengRobinson::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); -<<<<<<< HEAD int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); -======= - int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); ->>>>>>> Removing MFTP suffix from PengRobinson class if (nsol != 3) { return critDensity(); } @@ -1106,11 +753,7 @@ double PengRobinson::densSpinodalGas() const { double Vroot[3]; double T = temperature(); -<<<<<<< HEAD int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); -======= - int nsol = NicholsSolve(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); ->>>>>>> Removing MFTP suffix from PengRobinson class if (nsol != 3) { return critDensity(); } @@ -1128,7 +771,6 @@ double PengRobinson::densSpinodalGas() const return mmw / (0.5 * (vv.first + vv.second)); } -<<<<<<< HEAD double PengRobinson::pressureCalc(double T, double molarVol) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; @@ -1144,41 +786,16 @@ double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const double vpb = molarVol + m_b_current; double vmb = molarVol - m_b_current; double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); -======= -double PengRobinson::pressureCalc(double TKelvin, double molarVol) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - double pres = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current / den; - return pres; -} - -double PengRobinson::dpdVCalc(double TKelvin, double molarVol, double& presCalc) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - m_aAlpha_current/ den; - - double vpb = molarVol + m_b_current; - double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * TKelvin / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); ->>>>>>> Removing MFTP suffix from PengRobinson class return dpdv; } void PengRobinson::pressureDerivatives() const { -<<<<<<< HEAD double T = temperature(); double mv = molarVolume(); double pres; dpdV_ = dpdVCalc(T, mv, pres); -======= - double TKelvin = temperature(); - double mv = molarVolume(); - double pres; - - dpdV_ = dpdVCalc(TKelvin, mv, pres); ->>>>>>> Removing MFTP suffix from PengRobinson class double vmb = mv - m_b_current; double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); @@ -1186,7 +803,6 @@ void PengRobinson::pressureDerivatives() const void PengRobinson::updateMixingExpressions() { -<<<<<<< HEAD double temp = temperature(); //Update aAlpha_i double sqt_alpha; @@ -1196,22 +812,6 @@ void PengRobinson::updateMixingExpressions() size_t counter = j * m_kk + j; double critTemp_j = speciesCritTemperature(a_vec_Curr_[counter],b_vec_Curr_[j]); sqt_alpha = 1 + kappa_vec_[j] * (1 - sqrt(temp / critTemp_j)); -======= - updateAB(); -} - -void PengRobinson::updateAB() -{ - double temp = temperature(); - //Update aAlpha_i - double sqt_alpha; - double criTemp = critTemperature(); - double sqt_T_reduced = sqrt(temp / criTemp); - - // Update indiviual alpha - for (size_t j = 0; j < m_kk; j++) { - sqt_alpha = 1 + kappa_vec_[j] * (1 - sqt_T_reduced); ->>>>>>> Removing MFTP suffix from PengRobinson class alpha_vec_Curr_[j] = sqt_alpha*sqt_alpha; } @@ -1232,11 +832,7 @@ void PengRobinson::updateAB() m_b_current += moleFractions_[i] * b_vec_Curr_[i]; for (size_t j = 0; j < m_kk; j++) { m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; -<<<<<<< HEAD m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; -======= - m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; ->>>>>>> Removing MFTP suffix from PengRobinson class } } } @@ -1286,7 +882,6 @@ double PengRobinson::daAlpha_dT() const double PengRobinson::d2aAlpha_dT2() const { -<<<<<<< HEAD double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; double k; for (size_t i = 0; i < m_kk; i++) { @@ -1296,16 +891,6 @@ double PengRobinson::d2aAlpha_dT2() const 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 daAlphadT = 0.0, temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; - double k; - double sqt_Tr = sqrt(temperature() / critTemperature()); //we need species critical temperature - double coeff1 = 1 / (critTemperature()*critTemperature()*sqt_Tr); - double coeff2 = sqt_Tr - 1; - for (size_t i = 0; i < m_kk; i++) { - // Calculate first and second derivatives of alpha for individual species - size_t counter = i + m_kk * i; ->>>>>>> Removing MFTP suffix from PengRobinson class k = kappa_vec_[i]; dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); @@ -1349,26 +934,11 @@ void PengRobinson::calcCriticalConditions(double a, double b, vc = omega_vc * GasConstant * tc / pc; } -<<<<<<< HEAD int PengRobinson::NicholsCall(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; -======= -int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, double aAlpha, - double Vroot[3]) const -{ - double tmp; - fill_n(Vroot, 3, 0.0); - if (TKelvin <= 0.0) { - throw CanteraError("PengRobinson::NicholsSolve()", "negative temperature T = {}", TKelvin); - } - - // Derive the coefficients of the cubic polynomial (in terms of molar volume v) to solve. - double bsqr = b * b; - double RT_p = GasConstant * TKelvin / pres; ->>>>>>> Removing MFTP suffix from PengRobinson class double aAlpha_p = aAlpha / pres; double an = 1.0; double bn = (b - RT_p); @@ -1376,195 +946,9 @@ int PengRobinson::NicholsSolve(double TKelvin, double pres, double a, double b, double dn = (bsqr * RT_p + bsqr * b - aAlpha_p * b); double tc = a * omega_b / (b * omega_a * GasConstant); -<<<<<<< HEAD int nSolnValues = NicholsSolve(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); -======= - double pc = omega_b * GasConstant * tc / b; - double vc = omega_vc * GasConstant * tc / pc; - - // 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 * TKelvin); // B - if (fabs(ratio1) < 1.0E-7) { - double ratio3 = aAlpha / (GasConstant * TKelvin) * pres / (GasConstant * TKelvin); // 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 * TKelvin / 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("PengRobinson::NicholsSolve()", "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; - } - - // 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("PengRobinson::NicholsSolve(T = {}, p = {}):" - " WARNING roots have merged: {}, {}\n", - TKelvin, 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is bad 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("PengRobinson::NicholsSolve(T = {}, p = {}): " - "WARNING root didn't converge V = {}", TKelvin, pres, Vroot[i]); - writelogendl(); - } - } - - if (nSolnValues == 1) { - if (TKelvin > tc) { - if (Vroot[0] < vc) { - // Liquid phase root - nSolnValues = -1; - } - } else { - if (Vroot[0] < xN) { - nSolnValues = -1; - } - } - } else { - if (nSolnValues == 2 && delta > 1e-14) { - nSolnValues = -2; - } - } ->>>>>>> Removing MFTP suffix from PengRobinson class return nSolnValues; } diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 37107383a9..18062746a6 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -91,8 +91,8 @@ ThermoFactory::ThermoFactory() reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); reg("PengRobinson", []() { return new PengRobinsonMFTP(); }); - addAlias["PengRobinsonMFTP"] = "PengRobinson"; - addAlias["Peng-Robinson"] = "PengRobinson"; + addAlias("PengRobinsonMFTP", "PengRobinson"); + addAlias("Peng-Robinson", "PengRobinson"); } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) From b8ff389167dcf07275634c2cd9cfc47c07195df6 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 27 Jan 2020 13:48:39 -0700 Subject: [PATCH 026/110] Fixing Peng-Robinson aliasing in ThermoFactory. --- src/thermo/ThermoFactory.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 18062746a6..d7fd639590 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -90,9 +90,9 @@ ThermoFactory::ThermoFactory() addAlias("liquid-water-IAPWS95", "PureLiquidWater"); reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); - reg("PengRobinson", []() { return new PengRobinsonMFTP(); }); - addAlias("PengRobinsonMFTP", "PengRobinson"); - addAlias("Peng-Robinson", "PengRobinson"); + reg("PengRobinson", []() { return new PengRobinson(); }); + addAlias("PengRobinson", "PengRobinsonMFTP"); + addAlias("PengRobinson", "Peng-Robinson"); } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) From dfdaa82551b3793c5c1a8daa482ff32a673a6e61 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 27 Jan 2020 14:09:49 -0700 Subject: [PATCH 027/110] Fixing merge conflicts in PengRobinson_Test --- test/thermo/PengRobinson_Test.cpp | 67 ------------------------------- 1 file changed, 67 deletions(-) diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index c4beac3f16..8ea6a8c676 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -10,11 +10,7 @@ class PengRobinson_Test : public testing::Test { public: PengRobinson_Test() { -<<<<<<< HEAD test_phase.reset(newPhase("../data/co2_PR_example.yaml")); -======= - test_phase.reset(newPhase("../data/co2_PR_example.cti")); ->>>>>>> Removing MFTP suffix from PengRobinson class } //vary the composition of a co2-h2 mixture: @@ -28,11 +24,7 @@ class PengRobinson_Test : public testing::Test std::unique_ptr test_phase; }; -<<<<<<< HEAD TEST_F(PengRobinson_Test, construct_from_yaml) -======= -TEST_F(PengRobinson_Test, construct_from_cti) ->>>>>>> Removing MFTP suffix from PengRobinson class { PengRobinson* peng_robinson_phase = dynamic_cast(test_phase.get()); EXPECT_TRUE(peng_robinson_phase != NULL); @@ -47,7 +39,6 @@ TEST_F(PengRobinson_Test, chem_potentials) * calculated using the model. */ const double expected_result[9] = { -<<<<<<< HEAD -457361607.71983075, -457350560.54839599, -457340699.25698096, @@ -57,17 +48,6 @@ TEST_F(PengRobinson_Test, chem_potentials) -457309967.99120748, -457303910.44199038, -457298344.62820804 -======= - -4.5736182681761962e+008, - -4.5733771904416579e+008, - -4.5732943831449223e+008, - -4.5732206687414169e+008, - -4.5731546826955432e+008, - -4.5730953161186475e+008, - -4.5730416590547645e+008, - -4.5729929581635743e+008, - -4.5729485847173005e+008 ->>>>>>> Removing MFTP suffix from PengRobinson class }; double xmin = 0.6; @@ -83,7 +63,6 @@ TEST_F(PengRobinson_Test, chem_potentials) } } -<<<<<<< HEAD TEST_F(PengRobinson_Test, chemPotentials_RT) { test_phase->setState_TP(298., 1.); @@ -108,8 +87,6 @@ TEST_F(PengRobinson_Test, chemPotentials_RT) } } -======= ->>>>>>> Removing MFTP suffix from PengRobinson class TEST_F(PengRobinson_Test, activityCoeffs) { test_phase->setState_TP(298., 1.); @@ -173,7 +150,6 @@ TEST_F(PengRobinson_Test, setTP) // All sub-cooled liquid: const double p1[6] = { -<<<<<<< HEAD 1.7084253549322079e+002, 1.6543121742659784e+002, 1.6066148681014121e+002, @@ -198,32 +174,6 @@ TEST_F(PengRobinson_Test, setTP) 7.2620055080831412e+002, 7.0335270498118734e+002, 6.7859003092723128e+002 -======= - 1.7474528924963985e+002, - 1.6800540828415956e+002, - 1.62278413743154e+002, - 1.5728963799103039e+002, - 1.5286573762819748e+002, - 1.4888956030449546e+002 - }; - // Phase change between temperatures 4 & 5: - const double p2[6] = { - 7.5565889855724288e+002, - 7.2577747673480337e+002, - 6.913183942651284e+002, - 6.494661249672663e+002, - 5.9240469307757724e+002, - 3.645826047440932e+002 - }; - // Supercritical; no discontinuity in rho values: - const double p3[6] = { - 8.047430802847415e+002, - 7.8291565113595595e+002, - 7.5958477920749681e+002, - 7.3445460137134626e+002, - 7.0712433093853724e+002, - 6.77034438769492e+002 ->>>>>>> Removing MFTP suffix from PengRobinson class }; for(int i=0; i<6; ++i) @@ -233,11 +183,7 @@ TEST_F(PengRobinson_Test, setTP) test_phase->setState_TP(temp, 5542027.5); EXPECT_NEAR(test_phase->density(),p1[i],1.e-8); -<<<<<<< HEAD test_phase->setState_TP(temp, 7388370.); -======= - test_phase->setState_TP(temp, 7389370.); ->>>>>>> Removing MFTP suffix from PengRobinson class EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); test_phase->setState_TP(temp, 9236712.5); @@ -249,11 +195,7 @@ TEST_F(PengRobinson_Test, getPressure) { // Check to make sure that the P-R equation is accurately reproduced for a few selected values -<<<<<<< HEAD /* This test uses CO2 as the only species (mole fraction 99.9%, balance H2). -======= - /* This test uses CO2 as the only species. ->>>>>>> Removing MFTP suffix from PengRobinson class * 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 @@ -262,20 +204,11 @@ TEST_F(PengRobinson_Test, getPressure) * kappa is a function calulated based on the accentric factor. */ -<<<<<<< HEAD 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; -======= - double a_coeff = 3.958134E+5; - double b_coeff = 26.6275/1000; - double acc_factor = 0.228; - double pres_theoretical, kappa, alpha, mv; - const double rho = 1.0737; - const double Tcrit = test_phase->critTemperature(); ->>>>>>> Removing MFTP suffix from PengRobinson class //Calculate kappa value kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; From 32af524efeb6cf3b9225b94f44ef6ca235fa7ded Mon Sep 17 00:00:00 2001 From: gkogekar Date: Mon, 27 Jan 2020 14:34:55 -0700 Subject: [PATCH 028/110] Adding following tests to PengRobinson_Test.cpp to verify that 1. g = h- T*s 2. cp = dH/dT at constant pressure using Finite difference method 3. molar enthalpy = \sum x_k h_k --- test/thermo/PengRobinson_Test.cpp | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 8ea6a8c676..826dffe5dd 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -226,4 +226,88 @@ TEST_F(PengRobinson_Test, getPressure) 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; + const double RT = GasConstant * 298; + 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); + } + } +} + }; From 1eca522f5432f0499e36fe8408c5c22f3cbe8859 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Thu, 30 Jan 2020 20:56:52 -0700 Subject: [PATCH 029/110] Fixing indexing error in PengRobinson:d2aAlpha_dT2 --- src/thermo/PengRobinson.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index a02710aa22..08418d8a83 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -883,7 +883,6 @@ double PengRobinson::daAlpha_dT() const double PengRobinson::d2aAlpha_dT2() const { double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; - double k; for (size_t i = 0; i < m_kk; i++) { size_t counter = i + m_kk * i; double Tcrit_i = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); @@ -891,7 +890,7 @@ double PengRobinson::d2aAlpha_dT2() const double coeff1 = 1 / (Tcrit_i*Tcrit_i*sqt_Tr); double coeff2 = sqt_Tr - 1; // Calculate first and second derivatives of alpha for individual species - k = kappa_vec_[i]; + double k = kappa_vec_[i]; dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); } @@ -907,8 +906,8 @@ double PengRobinson::d2aAlpha_dT2() const temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); fac1 = -(0.5 / alphaij)*num*num; - fac2 = alphaj * d2alphadT2_[counter1] + alphai *d2alphadT2_[counter2] + 2 * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); + fac2 = alphaj * d2alphadT2_[i] + alphai *d2alphadT2_[j] + 2. * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); //d2alphadT2_[counter1]; // } } return d2aAlphadT2; From 26149ca451d8cd7078776ebda556f9a256624692 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Tue, 4 Feb 2020 10:43:17 -0700 Subject: [PATCH 030/110] Cleaning up thermo/PengRobinson -Temperature input not needed in PengRobinson:calculateAB -Unused variable RT in PengRobinson_Test/totalEnthalpy -Replacing duplicative code with call to calcualteAB --- include/cantera/thermo/PengRobinson.h | 2 +- src/thermo/PengRobinson.cpp | 12 +++--------- test/thermo/PengRobinson_Test.cpp | 1 - 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index dbba37fb7c..c4a568f9f6 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -268,7 +268,7 @@ class PengRobinson : public MixtureFugacityTP * @param aCalc (output) Returns the a value * @param bCalc (output) Returns the b value. */ - void calculateAB(double temp, double& aCalc, double& bCalc, double& aAlpha) const; + void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; // Special functions not inherited from MixtureFugacityTP diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 08418d8a83..629799b1f1 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -647,7 +647,7 @@ double PengRobinson::liquidVolEst(double T, double& presGuess) const double atmp; double btmp; double aAlphatmp; - calculateAB(T, atmp, btmp, aAlphatmp); + calculateAB(atmp, btmp, aAlphatmp); double pres = std::max(psatEst(T), presGuess); double Vroot[3]; bool foundLiq = false; @@ -828,16 +828,10 @@ void PengRobinson::updateMixingExpressions() m_a_current = 0.0; m_aAlpha_current = 0.0; - for (size_t i = 0; i < m_kk; i++) { - m_b_current += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - m_aAlpha_current += aAlpha_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - } - } + calculateAB(m_a_current,m_b_current,m_aAlpha_current); } -void PengRobinson::calculateAB(double temp, double& aCalc, double& bCalc, double& aAlphaCalc) const +void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) const { bCalc = 0.0; aCalc = 0.0; diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 826dffe5dd..7814f426f9 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -251,7 +251,6 @@ TEST_F(PengRobinson_Test, totalEnthalpy) { // Test that hbar = \sum (h_k*x_k) double hbar, sum = 0.0; - const double RT = GasConstant * 298; vector_fp partialEnthalpies(7); vector_fp moleFractions(7); double xmin = 0.6; From 7402961ce60c289fc04fdafd97c224e027ab29b1 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Tue, 4 Feb 2020 12:31:46 -0700 Subject: [PATCH 031/110] Rename Nichols cubic solve methods as `cubicSolve` --- include/cantera/thermo/MixtureFugacityTP.h | 2 +- include/cantera/thermo/PengRobinson.h | 2 +- include/cantera/thermo/RedlichKwongMFTP.h | 2 +- src/thermo/MixtureFugacityTP.cpp | 14 +++++++------- src/thermo/PengRobinson.cpp | 12 ++++++------ src/thermo/RedlichKwongMFTP.cpp | 12 ++++++------ test/thermo/cubicSolver_Test.cpp | 6 +++--- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 7fc8ff0053..d8a90a28b9 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -526,7 +526,7 @@ class MixtureFugacityTP : public ThermoPhase * 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) */ - int NicholsSolve(double T, double pres, double a, double b, + 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) const; diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index c4a568f9f6..3af1f70a17 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -278,7 +278,7 @@ class PengRobinson : public MixtureFugacityTP void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) const; //! Prepare variables and call the function to solve the cubic equation of state - int NicholsCall(double T, double pres, double a, double b, double aAlpha, + int solveCubic(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const; protected: //! Form of the temperature parameterization diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index 09ee4db89b..768786d138 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -258,7 +258,7 @@ class RedlichKwongMFTP : public MixtureFugacityTP doublereal& pc, doublereal& tc, doublereal& vc) const; //! Prepare variables and call the function to solve the cubic equation of state - int NicholsCall(double T, double pres, double a, double b, double Vroot[3]) const; + int CubicCall(double T, double pres, double a, double b, double Vroot[3]) const; protected: //! Form of the temperature parameterization diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 00369fb5d6..c36a88c2b7 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -804,14 +804,14 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const } } -int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, +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) const { double tmp; fill_n(Vroot, 3, 0.0); if (T <= 0.0) { - throw CanteraError("MixtureFugacityTP::NicholsSolve()", "negative temperature T = {}", T); + throw CanteraError("MixtureFugacityTP::CubicSolve()", "negative temperature T = {}", T); } double pc = omega_b * GasConstant * tc / b; @@ -860,7 +860,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, //check if y = h if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) { if (disc > 1e-10) { - throw CanteraError("MixtureFugacityTP::NicholsSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); + throw CanteraError("MixtureFugacityTP::CubicSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); } disc = 0.0; } @@ -915,7 +915,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, 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("PengRobinson::NicholsSolve(T = {}, p = {}):" + writelog("PengRobinson::CubicSolve(T = {}, p = {}):" " WARNING roots have merged: {}, {}\n", T, pres, Vroot[i], Vroot[j]); } @@ -935,7 +935,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -943,7 +943,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, 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("PengRobinson::NicholsSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); } delta = -delta; Vroot[0] = xN + delta; @@ -975,7 +975,7 @@ int MixtureFugacityTP::NicholsSolve(double T, double pres, double a, double b, } } if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) { - writelog("PengRobinson::NicholsSolve(T = {}, p = {}): " + writelog("PengRobinson::CubicSolve(T = {}, p = {}): " "WARNING root didn't converge V = {}", T, pres, Vroot[i]); writelogendl(); } diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 629799b1f1..073861174b 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -653,7 +653,7 @@ double PengRobinson::liquidVolEst(double T, double& presGuess) const bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { - int nsol = NicholsCall(T, pres, atmp, btmp, aAlphatmp, Vroot); + int nsol = solveCubic(T, pres, atmp, btmp, aAlphatmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -691,7 +691,7 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do } double volGuess = mmw / rhoGuess; - NSolns_ = NicholsCall(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + NSolns_ = solveCubic(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); double molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -731,7 +731,7 @@ double PengRobinson::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); - int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); + int nsol = solveCubic(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -753,7 +753,7 @@ double PengRobinson::densSpinodalGas() const { double Vroot[3]; double T = temperature(); - int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); + int nsol = solveCubic(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -927,7 +927,7 @@ void PengRobinson::calcCriticalConditions(double a, double b, vc = omega_vc * GasConstant * tc / pc; } -int PengRobinson::NicholsCall(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const +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; @@ -940,7 +940,7 @@ int PengRobinson::NicholsCall(double T, double pres, double a, double b, double double tc = a * omega_b / (b * omega_a * GasConstant); - int nSolnValues = NicholsSolve(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); + int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); return nSolnValues; } diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 7341d539d8..455c1e067c 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -792,7 +792,7 @@ doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGu bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { - int nsol = NicholsCall(TKelvin, pres, atmp, btmp, Vroot); + int nsol = CubicCall(TKelvin, pres, atmp, btmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -831,7 +831,7 @@ doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, doublereal volguess = mmw / rhoguess; - NSolns_ = NicholsCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); + NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); doublereal molarVolLast = Vroot_[0]; if (NSolns_ >= 2) { @@ -871,7 +871,7 @@ doublereal RedlichKwongMFTP::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); - int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, Vroot); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -893,7 +893,7 @@ doublereal RedlichKwongMFTP::densSpinodalGas() const { double Vroot[3]; double T = temperature(); - int nsol = NicholsCall(T, pressure(), m_a_current, m_b_current, Vroot); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, Vroot); if (nsol != 3) { return critDensity(); } @@ -1065,7 +1065,7 @@ void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, double vc = omega_vc * GasConstant * tc / pc; } -int RedlichKwongMFTP::NicholsCall(double T, double pres, double a, double b, double Vroot[3]) const +int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const { // Derive the coefficients of the cubic polynomial to solve. @@ -1079,7 +1079,7 @@ int RedlichKwongMFTP::NicholsCall(double T, double pres, double a, double b, dou double pp = 2./3.; double tc = pow(tmp, pp); - int nSolnValues = NicholsSolve(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc); + int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc); return nSolnValues; } diff --git a/test/thermo/cubicSolver_Test.cpp b/test/thermo/cubicSolver_Test.cpp index 6314356326..bd86ec4894 100644 --- a/test/thermo/cubicSolver_Test.cpp +++ b/test/thermo/cubicSolver_Test.cpp @@ -55,7 +55,7 @@ TEST_F(cubicSolver_Test, solve_cubic) //calculate alpha alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2); //Find cubic roots - nSolnValues = peng_robinson_phase->NicholsCall(temp, pres, a_coeff, b_coeff, alpha * a_coeff, Vroot); + 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); @@ -65,7 +65,7 @@ TEST_F(cubicSolver_Test, solve_cubic) //calculate alpha alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2); //Find cubic roots - nSolnValues = peng_robinson_phase->NicholsCall(temp, pres, a_coeff, b_coeff, alpha * a_coeff, Vroot); + 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); @@ -74,7 +74,7 @@ TEST_F(cubicSolver_Test, solve_cubic) //calculate alpha alpha = pow(1 + kappa * (1 - sqrt(temp / Tcrit)), 2); //Find cubic roots - nSolnValues = peng_robinson_phase->NicholsCall(Tcrit, pCrit, a_coeff, b_coeff, alpha * a_coeff, Vroot); + 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, -1, 1.e-6); } From bb97af2fb963f1ea5f2f92c6ba1ec64e6e37be3e Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 5 Feb 2020 11:36:44 -0700 Subject: [PATCH 032/110] Adding detailed comments for cubic solver --- src/thermo/MixtureFugacityTP.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index c36a88c2b7..0847868362 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -982,18 +982,24 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, } 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) { - // Liquid phase root + // 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; } } From 5032994faa0c31b28b58b5cc5594c7bf3d8a9990 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 7 Apr 2020 18:30:56 -0600 Subject: [PATCH 033/110] Modifying pengRobinson test from thermoFromYAML --- test/thermo/thermoFromYaml.cpp | 492 +++++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index b6e137ae55..951bd95cda 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -1,3 +1,4 @@ +<<<<<<< HEAD #include "gtest/gtest.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/thermo/Elements.h" @@ -447,3 +448,494 @@ TEST(ThermoFromYaml, BinarySolutionTabulatedThermo) thermo->setMoleFractionsByName("Li[anode]: 0.55, V[anode]: 0.45"); EXPECT_NEAR(thermo->gibbs_mass(), -87066.246182649265, 1e-9); } +======= +#include "gtest/gtest.h" +#include "cantera/thermo/ThermoFactory.h" +#include "cantera/thermo/Elements.h" +#include "cantera/thermo/MolalityVPSSTP.h" +#include "cantera/thermo/IdealGasPhase.h" +#include "cantera/thermo/SurfPhase.h" +#include + +using namespace Cantera; + +namespace { + +shared_ptr newThermo(const std::string& fileName, + const std::string& phaseName) +{ + return shared_ptr(newPhase(fileName, phaseName)); +} + +} // namespace + +TEST(ThermoFromYaml, simpleIdealGas) +{ + IdealGasPhase thermo("ideal-gas.yaml", "simple"); + EXPECT_EQ(thermo.nSpecies(), (size_t) 3); + EXPECT_DOUBLE_EQ(thermo.density(), 7.0318220966379288); + EXPECT_DOUBLE_EQ(thermo.cp_mass(), 1037.7546065787594); +} + +TEST(ThermoFromYaml, failDuplicateSpecies) +{ + EXPECT_THROW(newThermo("ideal-gas.yaml", "duplicate-species"), CanteraError); +} + +TEST(ThermoFromYaml, elementOverride) +{ + auto thermo = newThermo("ideal-gas.yaml", "element-override"); + EXPECT_EQ(thermo->nElements(), (size_t) 3); + EXPECT_DOUBLE_EQ(thermo->atomicWeight(0), getElementWeight("N")); + EXPECT_DOUBLE_EQ(thermo->atomicWeight(1), getElementWeight("O")); + EXPECT_DOUBLE_EQ(thermo->atomicWeight(2), 36); +} + +TEST(ThermoFromYaml, elementFromDifferentFile) +{ + auto thermo = newThermo("ideal-gas.yaml", "element-remote"); + EXPECT_EQ(thermo->nElements(), (size_t) 3); + EXPECT_DOUBLE_EQ(thermo->atomicWeight(0), getElementWeight("N")); + EXPECT_DOUBLE_EQ(thermo->atomicWeight(1), getElementWeight("O")); + EXPECT_DOUBLE_EQ(thermo->atomicWeight(2), 38); +} + +TEST(ThermoFromYaml, speciesFromDifferentFile) +{ + IdealGasPhase thermo("ideal-gas.yaml", "species-remote"); + EXPECT_EQ(thermo.nElements(), (size_t) 2); + EXPECT_EQ(thermo.nSpecies(), (size_t) 4); + EXPECT_EQ(thermo.species(0)->composition["O"], 2); + EXPECT_EQ(thermo.species(3)->composition["O"], 1); + EXPECT_EQ(thermo.species(2)->name, "NO2"); + EXPECT_DOUBLE_EQ(thermo.moleFraction(3), 0.3); +} + +TEST(ThermoFromYaml, speciesAll) +{ + auto thermo = newThermo("ideal-gas.yaml", "species-all"); + EXPECT_EQ(thermo->nElements(), (size_t) 3); + EXPECT_EQ(thermo->nSpecies(), (size_t) 6); + EXPECT_EQ(thermo->species(1)->name, "NO"); + EXPECT_EQ(thermo->species(2)->name, "N2"); +} + +TEST(ThermoFromYaml, StoichSubstance1) +{ + auto thermo = newThermo("thermo-models.yaml", "NaCl(s)"); + EXPECT_EQ(thermo->type(), "StoichSubstance"); + EXPECT_EQ(thermo->nSpecies(), (size_t) 1); + EXPECT_EQ(thermo->nElements(), (size_t) 2); + EXPECT_DOUBLE_EQ(thermo->density(), 2165.0); + EXPECT_DOUBLE_EQ(thermo->cp_mass(), 864.88371960557095); // Regression test based on XML +} + +TEST(ThermoFromYaml, StoichSubstance2) +{ + auto thermo = newThermo("thermo-models.yaml", "KCl(s)"); + EXPECT_EQ(thermo->type(), "StoichSubstance"); + EXPECT_EQ(thermo->nSpecies(), (size_t) 1); + EXPECT_EQ(thermo->nElements(), (size_t) 2); + EXPECT_NEAR(thermo->density(), 1980, 0.1); +} + +TEST(ThermoFromYaml, SurfPhase) +{ + auto thermo = newThermo("surface-phases.yaml", "Pt-surf"); + EXPECT_EQ(thermo->type(), "Surf"); + EXPECT_EQ(thermo->nSpecies(), (size_t) 3); + auto surf = std::dynamic_pointer_cast(thermo); + EXPECT_DOUBLE_EQ(surf->siteDensity(), 2.7063e-8); + vector_fp cov(surf->nSpecies()); + surf->getCoverages(cov.data()); + EXPECT_DOUBLE_EQ(cov[surf->speciesIndex("Pt(s)")], 0.5); + EXPECT_DOUBLE_EQ(cov[surf->speciesIndex("H(s)")], 0.4); +} + +TEST(ThermoFromYaml, EdgePhase) +{ + auto thermo = newThermo("surface-phases.yaml", "TPB"); + EXPECT_EQ(thermo->type(), "Edge"); + EXPECT_EQ(thermo->nSpecies(), (size_t) 1); + auto edge = std::dynamic_pointer_cast(thermo); + EXPECT_DOUBLE_EQ(edge->siteDensity(), 5e-18); +} + +TEST(ThermoFromYaml, WaterSSTP) +{ + auto thermo = newThermo("thermo-models.yaml", "liquid-water"); + EXPECT_EQ(thermo->nSpecies(), (size_t) 1); + thermo->setState_TP(350, 2*OneAtm); + // Regression tests based on XML + EXPECT_NEAR(thermo->density(), 973.7736331, 1e-6); + EXPECT_NEAR(thermo->enthalpy_mass(), -15649685.52296013, 1e-6); +} + +//! @todo Remove after Cantera 2.5 - class FixedChemPotSSTP is deprecated +TEST(ThermoFromYaml, FixedChemPot) +{ + suppress_deprecation_warnings(); + auto thermo = newThermo("thermo-models.yaml", "Li-fixed"); + EXPECT_EQ(thermo->nSpecies(), (size_t) 1); + double mu; + thermo->getChemPotentials(&mu); + EXPECT_DOUBLE_EQ(mu, -2.3e7); + make_deprecation_warnings_fatal(); +} + +TEST(ThermoFromYaml, Margules) +{ + auto thermo = newThermo("thermo-models.yaml", "molten-salt-Margules"); + EXPECT_EQ(thermo->type(), "Margules"); + + // Regression test based on LiKCl_liquid.xml + EXPECT_NEAR(thermo->density(), 2041.9831422315351, 1e-9); + EXPECT_NEAR(thermo->gibbs_mass(), -9683614.0890585743, 1e-5); + EXPECT_NEAR(thermo->cp_mole(), 67478.48085733457, 1e-8); +} + +TEST(ThermoFromYaml, IdealMolalSoln) +{ + auto thermo = newThermo("thermo-models.yaml", "ideal-molal-aqueous"); + EXPECT_EQ(thermo->type(), "IdealMolalSoln"); + + EXPECT_NEAR(thermo->enthalpy_mole(), 0.013282, 1e-6); + EXPECT_NEAR(thermo->gibbs_mole(), -3.8986e7, 1e3); + EXPECT_NEAR(thermo->density(), 12.058, 1e-3); +} + +TEST(ThermoFromYaml, DebyeHuckel_bdot_ak) +{ + auto thermo = newThermo("thermo-models.yaml", "debye-huckel-B-dot-ak"); + + // Regression test based on XML input file + EXPECT_EQ(thermo->type(), "DebyeHuckel"); + EXPECT_NEAR(thermo->density(), 60.296, 1e-2); + EXPECT_NEAR(thermo->cp_mass(), 1.58216e5, 1e0); + EXPECT_NEAR(thermo->entropy_mass(), 4.04233e3, 1e-2); + + vector_fp actcoeff(thermo->nSpecies()); + vector_fp mu_ss(thermo->nSpecies()); + auto& molphase = dynamic_cast(*thermo); + molphase.getMolalityActivityCoefficients(actcoeff.data()); + thermo->getStandardChemPotentials(mu_ss.data()); + double act_ref[] = {0.849231, 1.18392, 0.990068, 1.69245, 1.09349, 1.0}; + double mu_ss_ref[] = {-3.06816e+08, -2.57956e+08, -1.84117e+08, 0.0, + -2.26855e+08, -4.3292e+08}; + for (size_t k = 0; k < thermo->nSpecies(); k++) { + EXPECT_NEAR(actcoeff[k], act_ref[k], 1e-5); + EXPECT_NEAR(mu_ss[k], mu_ss_ref[k], 1e3); + } +} + +TEST(ThermoFromYaml, DebyeHuckel_beta_ij) +{ + auto thermo = newThermo("thermo-models.yaml", "debye-huckel-beta_ij"); + + // Regression test based on XML input file + EXPECT_EQ(thermo->type(), "DebyeHuckel"); + EXPECT_NEAR(thermo->density(), 122.262, 1e-3); + EXPECT_NEAR(thermo->cp_mass(), 81263.5, 1e-1); + EXPECT_NEAR(thermo->entropy_mass(), 4022.35, 1e-2); + + vector_fp actcoeff(thermo->nSpecies()); + vector_fp mu_ss(thermo->nSpecies()); + auto& molphase = dynamic_cast(*thermo); + molphase.getMolalityActivityCoefficients(actcoeff.data()); + thermo->getStandardChemPotentials(mu_ss.data()); + double act_ref[] = {0.959912, 1.16955, 1.16955, 2.40275, 0.681552, 1.0}; + double mu_ss_ref[] = {-3.06816e+08, -2.57956e+08, -1.84117e+08, 0, + -2.26855e+08, -4.3292e+08}; + for (size_t k = 0; k < thermo->nSpecies(); k++) { + EXPECT_NEAR(actcoeff[k], act_ref[k], 1e-5); + EXPECT_NEAR(mu_ss[k], mu_ss_ref[k], 1e3); + } +} + +TEST(ThermoFromYaml, IonsFromNeutral) +{ + auto thermo = newThermo("thermo-models.yaml", "ions-from-neutral-molecule"); + ASSERT_EQ((int) thermo->nSpecies(), 2); + vector_fp mu(thermo->nSpecies()); + thermo->getChemPotentials(mu.data()); + + // Values for regression testing only -- same as "fromScratch" test + EXPECT_NEAR(thermo->density(), 1984.2507319669949, 1e-6); + EXPECT_NEAR(thermo->enthalpy_mass(), -14738312.44316336, 1e-6); + EXPECT_NEAR(mu[0], -4.66404010e+08, 1e1); + EXPECT_NEAR(mu[1], -2.88157316e+06, 1e-1); +} + +TEST(ThermoFromYaml, IonsFromNeutral_fromString) +{ + // A little different because we can't re-read the input file to get the + // phase definition for the neutral phase + std::ifstream infile("../data/thermo-models.yaml"); + std::stringstream buffer; + buffer << infile.rdbuf(); + AnyMap input = AnyMap::fromYamlString(buffer.str()); + auto thermo = newPhase( + input["phases"].getMapWhere("name", "ions-from-neutral-molecule"), + input); + ASSERT_EQ((int) thermo->nSpecies(), 2); + vector_fp mu(thermo->nSpecies()); + thermo->getChemPotentials(mu.data()); + + // Values for regression testing only -- same as "fromScratch" test + EXPECT_NEAR(thermo->density(), 1984.2507319669949, 1e-6); + EXPECT_NEAR(thermo->enthalpy_mass(), -14738312.44316336, 1e-6); + EXPECT_NEAR(mu[0], -4.66404010e+08, 1e1); + EXPECT_NEAR(mu[1], -2.88157316e+06, 1e-1); +} + +//! @todo Remove after Cantera 2.5 - "gas" mode of IdealSolnGasVPSS is +//! deprecated +TEST(ThermoFromYaml, IdealSolnGas_gas) +{ + suppress_deprecation_warnings(); + auto thermo = newThermo("thermo-models.yaml", "IdealSolnGas-gas"); + make_deprecation_warnings_fatal(); + thermo->equilibrate("HP"); + EXPECT_NEAR(thermo->temperature(), 479.929, 1e-3); // based on h2o2.cti + EXPECT_NEAR(thermo->moleFraction("H2O"), 0.01, 1e-4); + EXPECT_NEAR(thermo->moleFraction("H2"), 0.0, 1e-4); +} + +TEST(ThermoFromYaml, IdealSolnGas_liquid) +{ + auto thermo = newThermo("thermo-models.yaml", "IdealSolnGas-liquid"); + thermo->setState_TP(300, OneAtm); + EXPECT_NEAR(thermo->density(), 505.42393940, 2e-8); + EXPECT_NEAR(thermo->gibbs_mole(), -7801634.1184443515, 2e-8); + thermo->setState_TP(400, 2*OneAtm); + EXPECT_NEAR(thermo->density(), 495.06986080, 2e-8); + EXPECT_NEAR(thermo->molarVolume(), 0.014018223587243668, 2e-12); + thermo->setState_TP(500, 2*OneAtm); + EXPECT_NEAR(thermo->density(), 484.66590, 2e-8); + EXPECT_NEAR(thermo->enthalpy_mass(), 1236701.0904197122, 2e-8); + EXPECT_NEAR(thermo->entropy_mole(), 49848.488477407751, 2e-8); +} + +TEST(ThermoFromYaml, RedlichKister) +{ + auto thermo = newThermo("thermo-models.yaml", "Redlich-Kister-LiC6"); + vector_fp chemPotentials(2); + vector_fp dlnActCoeffdx(2); + thermo->setState_TP(298.15, OneAtm); + thermo->setMoleFractionsByName("Li(C6): 0.6375, V(C6): 0.3625"); + thermo->getChemPotentials(chemPotentials.data()); + thermo->getdlnActCoeffdlnX_diag(dlnActCoeffdx.data()); + EXPECT_NEAR(chemPotentials[0], -1.2618554573674981e+007, 1e-6); + EXPECT_NEAR(dlnActCoeffdx[0], 0.200612, 1e-6); + + thermo->setMoleFractionsByName("Li(C6): 0.8625, V(C6): 0.1375"); + thermo->getChemPotentials(chemPotentials.data()); + thermo->getdlnActCoeffdlnX_diag(dlnActCoeffdx.data()); + EXPECT_NEAR(chemPotentials[0], -1.179299486233677e+07, 1e-6); + EXPECT_NEAR(dlnActCoeffdx[0], -0.309379, 1e-6); +} + +TEST(ThermoFromYaml, MaskellSolidSoln) +{ + auto thermo = newThermo("thermo-models.yaml", "MaskellSolidSoln"); + vector_fp chemPotentials(2); + thermo->getChemPotentials(chemPotentials.data()); + EXPECT_NEAR(chemPotentials[0], -4.989677789060059e6, 1e-6); + EXPECT_NEAR(chemPotentials[1], 4.989677789060059e6 + 1000, 1e-6); +} + +TEST(ThermoFromYaml, HMWSoln) +{ + auto thermo = newThermo("thermo-models.yaml", "HMW-NaCl-electrolyte"); + size_t N = thermo->nSpecies(); + auto HMW = dynamic_cast(thermo.get()); + vector_fp acMol(N), mf(N), activities(N), moll(N), mu0(N); + thermo->getMoleFractions(mf.data()); + HMW->getMolalities(moll.data()); + HMW->getMolalityActivityCoefficients(acMol.data()); + thermo->getActivities(activities.data()); + thermo->getStandardChemPotentials(mu0.data()); + + double acMolRef[] = {0.9341, 1.0191, 3.9637, 1.0191, 0.4660}; + double mfRef[] = {0.8198, 0.0901, 0.0000, 0.0901, 0.0000}; + double activitiesRef[] = {0.7658, 6.2164, 0.0000, 6.2164, 0.0000}; + double mollRef[] = {55.5093, 6.0997, 0.0000, 6.0997, 0.0000}; + double mu0Ref[] = {-317.175792, -186.014569, 0.0017225, -441.615456, -322.000432}; // kJ/gmol + + for (size_t k = 0 ; k < N; k++) { + EXPECT_NEAR(acMol[k], acMolRef[k], 2e-4); + EXPECT_NEAR(mf[k], mfRef[k], 2e-4); + EXPECT_NEAR(activities[k], activitiesRef[k], 2e-4); + EXPECT_NEAR(moll[k], mollRef[k], 2e-4); + EXPECT_NEAR(mu0[k]/1e6, mu0Ref[k], 2e-6); + } + EXPECT_EQ("liquid", HMW->phaseOfMatter()); +} + +TEST(ThermoFromYaml, HMWSoln_HKFT) +{ + auto thermo = newThermo("thermo-models.yaml", "HMW-NaCl-HKFT"); + double mvRef[] = {0.01815224, 0.00157182, 0.01954605, 0.00173137, -0.0020266}; + double hRef[] = {-2.84097587e+08, -2.38159643e+08, -1.68846908e+08, + 3.59728865e+06, -2.29291570e+08}; + double acoeffRef[] = {0.922403480, 1.21859875, 1.21859855, 5.08171133, + 0.5983205}; + + // Regression test based on HMWSoln.fromScratch_HKFT + size_t N = thermo->nSpecies(); + vector_fp mv(N), h(N), acoeff(N); + thermo->getPartialMolarVolumes(mv.data()); + thermo->getPartialMolarEnthalpies(h.data()); + thermo->getActivityCoefficients(acoeff.data()); + for (size_t k = 0; k < N; k++) { + EXPECT_NEAR(mv[k], mvRef[k], 2e-8); + EXPECT_NEAR(h[k], hRef[k], 2e0); + EXPECT_NEAR(acoeff[k], acoeffRef[k], 2e-8); + } +} + +TEST(ThermoFromYaml, RedlichKwong_CO2) +{ + auto thermo = newThermo("thermo-models.yaml", "CO2-RK"); + EXPECT_NEAR(thermo->density(), 892.404657616, 1e-8); + EXPECT_NEAR(thermo->enthalpy_mass(), -9199911.5290408, 1e-6); + EXPECT_NEAR(thermo->cp_mass(), 2219.940330064, 1e-8); + + thermo->setState_TPX(350, 180*OneAtm, "CO2:0.6, H2O:0.02, H2:0.38"); + EXPECT_NEAR(thermo->density(), 181.564971902, 1e-8); + EXPECT_NEAR(thermo->enthalpy_mass(), -8873033.2793978, 1e-6); + 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"); + thermo->setState_TP(70, 2*OneAtm); + EXPECT_NEAR(thermo->density(), 841.0420151, 1e-6); + EXPECT_NEAR(thermo->gibbs_mole(), -17654454.0912211, 1e-6); +} + +TEST(ThermoFromYaml, PureFluid_CO2) +{ + auto thermo = newThermo("thermo-models.yaml", "CO2-purefluid"); + EXPECT_NEAR(thermo->vaporFraction(), 0.1, 1e-6); + EXPECT_NEAR(thermo->density(), 513.27928388, 1e-6); +} + +TEST(ThermoFromYaml, PureFluid_Unknown) +{ + AnyMap root = AnyMap::fromYamlString( + "phases:\n" + "- name: unknown-purefluid\n" + " species: [N2]\n" + " thermo: pure-fluid\n" + " pure-fluid-name: unknown-purefluid\n" + ); + AnyMap& phase = root["phases"].getMapWhere("name", "unknown-purefluid"); + EXPECT_THROW(newPhase(phase, root), CanteraError); +} + +TEST(ThermoFromYaml, ConstDensityThermo) +{ + suppress_deprecation_warnings(); + auto thermo = newThermo("thermo-models.yaml", "const-density"); + EXPECT_DOUBLE_EQ(thermo->density(), 700.0); + make_deprecation_warnings_fatal(); +} + +TEST(ThermoFromYaml, IdealSolidSolnPhase) +{ + auto thermo = newThermo("thermo-models.yaml", "IdealSolidSolnPhase"); + + // Regression test following IdealSolidSolnPhase.fromScratch + EXPECT_NEAR(thermo->density(), 10.1787080, 1e-6); + EXPECT_NEAR(thermo->enthalpy_mass(), -15642788.8547624, 1e-4); + EXPECT_NEAR(thermo->gibbs_mole(), -313642312.7114608, 1e-4); +} + +TEST(ThermoFromYaml, Lattice) +{ + auto thermo = newThermo("thermo-models.yaml", "Li7Si3_and_interstitials"); + + // Regression test based on modified version of Li7Si3_ls.xml + EXPECT_NEAR(thermo->enthalpy_mass(), -2077955.0584538165, 1e-6); + double mu_ref[] = {-4.62717474e+08, -4.64248485e+07, 1.16370186e+05}; + double vol_ref[] = {0.095564748201438871, 0.2, 0.09557086}; + vector_fp mu(thermo->nSpecies()); + vector_fp vol(thermo->nSpecies()); + thermo->getChemPotentials(mu.data()); + thermo->getPartialMolarVolumes(vol.data()); + + for (size_t k = 0; k < thermo->nSpecies(); k++) { + EXPECT_NEAR(mu[k], mu_ref[k], 1e-7*fabs(mu_ref[k])); + EXPECT_NEAR(vol[k], vol_ref[k], 1e-7); + } +} + +TEST(ThermoFromYaml, Lattice_fromString) +{ + // A little different because we can't re-read the input file to get the + // phase definition for the neutral phase + std::ifstream infile("../data/thermo-models.yaml"); + std::stringstream buffer; + buffer << infile.rdbuf(); + AnyMap input = AnyMap::fromYamlString(buffer.str()); + auto thermo = newPhase( + input["phases"].getMapWhere("name", "Li7Si3_and_interstitials"), + input); + + // Regression test based on modified version of Li7Si3_ls.xml + EXPECT_NEAR(thermo->enthalpy_mass(), -2077955.0584538165, 1e-6); + double mu_ref[] = {-4.62717474e+08, -4.64248485e+07, 1.16370186e+05}; + double vol_ref[] = {0.095564748201438871, 0.2, 0.09557086}; + vector_fp mu(thermo->nSpecies()); + vector_fp vol(thermo->nSpecies()); + thermo->getChemPotentials(mu.data()); + thermo->getPartialMolarVolumes(vol.data()); + + for (size_t k = 0; k < thermo->nSpecies(); k++) { + EXPECT_NEAR(mu[k], mu_ref[k], 1e-7*fabs(mu_ref[k])); + EXPECT_NEAR(vol[k], vol_ref[k], 1e-7); + } +} + +TEST(ThermoFromYaml, Metal) +{ + auto thermo = newThermo("thermo-models.yaml", "Metal"); + EXPECT_DOUBLE_EQ(thermo->density(), 9.0); + EXPECT_DOUBLE_EQ(thermo->gibbs_mass(), 0.0); +} + +TEST(ThermoFromYaml, BinarySolutionTabulatedThermo) +{ + auto thermo = newThermo("thermo-models.yaml", "graphite-anode"); + EXPECT_NEAR(thermo->density(), 5031.7, 1e-5); + EXPECT_NEAR(thermo->enthalpy_mass(), -32501.245047302145, 1e-9); + EXPECT_NEAR(thermo->entropy_mass(), 90.443481807823474, 1e-12); + thermo->setMoleFractionsByName("Li[anode]: 0.55, V[anode]: 0.45"); + EXPECT_NEAR(thermo->gibbs_mass(), -87066.246182649265, 1e-9); +} + +TEST(ThermoFromYaml, DeprecatedPhase) +{ + // The deprecation warning in this file is turned into an + // error by make_deprecation_warnings_fatal() called in main() + // for the test suite. + EXPECT_THROW(newThermo("gri30.yaml", "gri30_mix"), CanteraError); +} +>>>>>>> ea38f153e... Modifying pengRobinson test from thermoFromYAML From 423154f2cbf83d9fa59a85f5ebef63c59ed24908 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 10:37:07 -0600 Subject: [PATCH 034/110] Moved setToEquil function to the parent class, 'MixtureFugacityTP". --- include/cantera/thermo/MixtureFugacityTP.h | 1165 +++++------ include/cantera/thermo/PengRobinson.h | 729 ++++--- src/thermo/MixtureFugacityTP.cpp | 2050 ++++++++++---------- src/thermo/PengRobinson.cpp | 1866 +++++++++--------- src/thermo/RedlichKwongMFTP.cpp | 1099 +++++++++++ test/thermo/PengRobinson_Test.cpp | 624 +++--- 6 files changed, 4318 insertions(+), 3215 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index d8a90a28b9..1037e5f8de 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -1,581 +1,584 @@ -/** - * @file MixtureFugacityTP.h - * Header file for a derived class of ThermoPhase that handles - * non-ideal mixtures based on the fugacity models (see \ref thermoprops and - * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). - */ - -// 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_MIXTUREFUGACITYTP_H -#define CT_MIXTUREFUGACITYTP_H - -#include "ThermoPhase.h" -#include "cantera/numerics/ResidEval.h" - -namespace Cantera -{ -//! Various states of the Fugacity object. In general there can be multiple liquid -//! objects for a single phase identified with each species. - -#define FLUID_UNSTABLE -4 -#define FLUID_UNDEFINED -3 -#define FLUID_SUPERCRIT -2 -#define FLUID_GAS -1 -#define FLUID_LIQUID_0 0 -#define FLUID_LIQUID_1 1 -#define FLUID_LIQUID_2 2 -#define FLUID_LIQUID_3 3 -#define FLUID_LIQUID_4 4 -#define FLUID_LIQUID_5 5 -#define FLUID_LIQUID_6 6 -#define FLUID_LIQUID_7 7 -#define FLUID_LIQUID_8 8 -#define FLUID_LIQUID_9 9 - -/** - * @ingroup thermoprops - * - * This is a filter class for ThermoPhase that implements some preparatory steps - * for efficiently handling mixture of gases that whose standard states are - * defined as ideal gases, but which describe also non-ideal solutions. In - * addition a multicomponent liquid phase below the critical temperature of the - * mixture is also allowed. The main subclass is currently a mixture Redlich- - * Kwong class. - * - * @attention This class currently does not have any test cases or examples. Its - * implementation may be incomplete, and future changes to Cantera may - * unexpectedly cause this class to stop working. If you use this class, - * please consider contributing examples or test cases. In the absence of - * new tests or examples, this class may be deprecated and removed in a - * future version of Cantera. See - * https://github.com/Cantera/cantera/issues/267 for additional information. - * - * Several concepts are introduced. The first concept is there are temporary - * variables for holding the species standard state values of Cp, H, S, G, and V - * at the last temperature and pressure called. These functions are not - * recalculated if a new call is made using the previous temperature and - * pressure. - * - * The other concept is that the current state of the mixture is tracked. The - * state variable is either GAS, LIQUID, or SUPERCRIT fluid. Additionally, the - * variable LiquidContent is used and may vary between 0 and 1. - * - * Typically, only one liquid phase is allowed to be formed within these - * classes. Additionally, there is an inherent contradiction between three phase - * models and the ThermoPhase class. The ThermoPhase class is really only meant - * to represent a single instantiation of a phase. The three phase models may be - * in equilibrium with multiple phases of the fluid in equilibrium with each - * other. This has yet to be resolved. - * - * This class is usually used for non-ideal gases. - */ -class MixtureFugacityTP : public ThermoPhase -{ -public: - //! @name Constructors and Duplicators for MixtureFugacityTP - //! @{ - - //! Constructor. - MixtureFugacityTP(); - - //! @} - //! @name Utilities - //! @{ - - virtual std::string type() const { - return "MixtureFugacity"; - } - - virtual int standardStateConvention() const; - - //! Set the solution branch to force the ThermoPhase to exist on one branch - //! or another - /*! - * @param solnBranch Branch that the solution is restricted to. the value - * -1 means gas. The value -2 means unrestricted. Values of zero or - * greater refer to species dominated condensed phases. - */ - virtual void setForcedSolutionBranch(int solnBranch); - - //! Report the solution branch which the solution is restricted to - /*! - * @return Branch that the solution is restricted to. the value -1 means - * gas. The value -2 means unrestricted. Values of zero or greater - * refer to species dominated condensed phases. - */ - virtual int forcedSolutionBranch() const; - - //! Report the solution branch which the solution is actually on - /*! - * @return Branch that the solution is restricted to. the value -1 means - * gas. The value -2 means superfluid.. Values of zero or greater refer - * to species dominated condensed phases. - */ - virtual int reportSolnBranchActual() const; - - virtual void getdlnActCoeffdlnN_diag(doublereal* dlnActCoeffdlnN_diag) const { - throw NotImplementedError("MixtureFugacityTP::getdlnActCoeffdlnN_diag"); - } - - - //! @name Molar Thermodynamic properties - //! @{ - - virtual double enthalpy_mole() const; - virtual doublereal entropy_mole() const; - - //@} - /// @name Partial Molar Properties of the Solution - //@{ - - //! Get the array of non-dimensional species chemical potentials - //! These are partial molar Gibbs free energies. - /*! - * \f$ \mu_k / \hat R T \f$. - * Units: unitless - * - * We close the loop on this function, here, calling getChemPotentials() and - * then dividing by RT. No need for child classes to handle. - * - * @param mu Output vector of non-dimensional species chemical potentials - * Length: m_kk. - */ - virtual void getChemPotentials_RT(doublereal* mu) const; - - //@} - /*! - * @name Properties of the Standard State of the Species in the Solution - * - * Within MixtureFugacityTP, these properties are calculated via a common - * routine, _updateStandardStateThermo(), which must be overloaded in - * inherited objects. The values are cached within this object, and are not - * recalculated unless the temperature or pressure changes. - */ - //@{ - - //! Get the array of chemical potentials at unit activity. - /*! - * These are the standard state chemical potentials \f$ \mu^0_k(T,P) - * \f$. The values are evaluated at the current temperature and pressure. - * - * 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. - * - * @param mu Output vector of standard state chemical potentials. - * length = m_kk. units are J / kmol. - */ - virtual void getStandardChemPotentials(doublereal* mu) const; - - //! Get the nondimensional Enthalpy functions for the species at their - //! standard states at the current *T* and *P* of the solution. - /*! - * 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. - * - * @param hrt Output vector of standard state enthalpies. - * length = m_kk. units are unitless. - */ - virtual void getEnthalpy_RT(doublereal* hrt) const; - - //! Get the array of nondimensional Enthalpy functions for the standard - //! state species at the current *T* and *P* of the solution. - /*! - * 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. - * - * @param sr Output vector of nondimensional standard state entropies. - * length = m_kk. - */ - virtual void getEntropy_R(doublereal* sr) const; - - //! Get the nondimensional Gibbs functions for the species at their standard - //! states of solution at the current T and P of the solution. - /*! - * 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. - * - * @param grt Output vector of nondimensional standard state Gibbs free - * energies. length = m_kk. - */ - virtual void getGibbs_RT(doublereal* grt) const; - - //! Get the pure Gibbs free energies of each species. Species are assumed to - //! be in their standard states. - /*! - * This is the same as getStandardChemPotentials(). - * - * @param[out] gpure Array of standard state Gibbs free energies. length = - * m_kk. units are J/kmol. - */ - virtual void getPureGibbs(doublereal* gpure) const; - - //! Returns the vector of nondimensional internal Energies of the standard - //! state at the current temperature and pressure of the solution for each - //! species. - /*! - * 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. - * - * \f[ - * u^{ss}_k(T,P) = h^{ss}_k(T) - P * V^{ss}_k - * \f] - * - * @param urt Output vector of nondimensional standard state internal - * energies. length = m_kk. - */ - virtual void getIntEnergy_RT(doublereal* urt) const; - - //! Get the nondimensional Heat Capacities at constant pressure for the - //! standard state of the species at the current T and P. - /*! - * 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. - * - * @param cpr Output vector containing the the nondimensional Heat - * Capacities at constant pressure for the standard state of - * the species. Length: m_kk. - */ - virtual void getCp_R(doublereal* cpr) const; - - //! Get the molar volumes of each species in their standard states at the - //! current *T* and *P* of the solution. - /*! - * 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. - * - * units = m^3 / kmol - * - * @param vol Output vector of species volumes. length = m_kk. - * units = m^3 / kmol - */ - virtual void getStandardVolumes(doublereal* vol) const; - // @} - - //! Set the temperature of the phase - /*! - * Currently this passes down to setState_TP(). It does not make sense to - * calculate the standard state without first setting T and P. - * - * @param temp Temperature (kelvin) - */ - virtual void setTemperature(const doublereal temp); - - //! Set the internally stored pressure (Pa) at constant temperature and - //! composition - /*! - * Currently this passes down to setState_TP(). It does not make sense to - * calculate the standard state without first setting T and P. - * - * @param p input Pressure (Pa) - */ - virtual void setPressure(doublereal p); - -protected: - virtual void compositionChanged(); - - //! Updates the reference state thermodynamic functions at the current T of - //! the solution. - /*! - * This function must be called for every call to functions in this class. - * It checks to see whether the temperature has changed and thus the ss - * thermodynamics functions for all of the species must be recalculated. - * - * This function is responsible for updating the following internal members: - * - * - m_h0_RT; - * - m_cp0_R; - * - m_g0_RT; - * - 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 - /*! - * There are also temporary variables for holding the species reference- - * state values of Cp, H, S, and V at the last temperature and reference - * pressure called. These functions are not recalculated if a new call is - * made using the previous temperature. All calculations are done within the - * routine _updateRefStateThermo(). - */ - //@{ - - virtual void getEnthalpy_RT_ref(doublereal* hrt) const; - virtual void getGibbs_RT_ref(doublereal* grt) const; - -protected: - //! Returns the vector of nondimensional Gibbs free energies of the - //! reference state at the current temperature of the solution and the - //! reference pressure for the species. - /*! - * @return Output vector contains the nondimensional Gibbs free energies - * of the reference state of the species - * length = m_kk, units = dimensionless. - */ - const vector_fp& gibbs_RT_ref() const; - -public: - virtual void getGibbs_ref(doublereal* g) const; - virtual void getEntropy_R_ref(doublereal* er) const; - virtual void getCp_R_ref(doublereal* cprt) const; - virtual void getStandardVolumes_ref(doublereal* vol) 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 setStateFromXML(const XML_Node& state); - -protected: - //! @name Special Functions for fugacity classes - //! @{ - - //! Calculate the value of z - /*! - * \f[ - * z = \frac{P v}{R T} - * \f] - * - * returns the value of z - */ - doublereal z() const; - - //! Calculate the deviation terms for the total entropy of the mixture from - //! the ideal gas mixture - /* - * Here we use the current state conditions - * - * @returns the change in entropy in units of J kmol-1 K-1. - */ - virtual doublereal sresid() const; - - //! Calculate the deviation terms for the total enthalpy of the mixture from - //! the ideal gas mixture - /* - * Here we use the current state conditions - * - * @returns the change in entropy in units of J kmol-1. - */ - virtual doublereal hresid() const; - - //! Estimate for the saturation pressure - /*! - * Note: this is only used as a starting guess for later routines that - * actually calculate an accurate value for the saturation pressure. - * - * @param TKelvin temperature in kelvin - * @return the estimated saturation pressure at the given temperature - */ - virtual doublereal psatEst(doublereal TKelvin) const; - -public: - //! Estimate for the molar volume of the liquid - /*! - * Note: this is only used as a starting guess for later routines that - * actually calculate an accurate value for the liquid molar volume. This - * routine doesn't change the state of the system. - * - * @param TKelvin temperature in kelvin - * @param pres Pressure in Pa. This is used as an initial guess. If the - * routine needs to change the pressure to find a stable - * liquid state, the new pressure is returned in this - * variable. - * @returns the estimate of the liquid volume. If the liquid can't be - * found, this routine returns -1. - */ - virtual doublereal liquidVolEst(doublereal TKelvin, doublereal& pres) const; - - //! Calculates the density given the temperature and the pressure and a - //! guess at the density. - /*! - * Note, below T_c, this is a multivalued function. We do not cross the - * vapor dome in this. This is protected because it is called during - * 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) - * @param phaseRequested int representing the phase whose density we are - * requesting. If we put a gas or liquid phase here, we will attempt to - * find a volume in that part of the volume space, only, in this - * routine. A value of FLUID_UNDEFINED means that we will accept - * anything. - * @param rhoguess Guessed density of the fluid. A value of -1.0 indicates - * that there is no guessed density - * @return We return the density of the fluid at the requested phase. If - * we have not found any acceptable density we return a -1. If we - * have found an acceptable density at a different phase, we - * return a -2. - */ - virtual doublereal densityCalc(doublereal TKelvin, doublereal pressure, int phaseRequested, - doublereal rhoguess); - -protected: - //! Utility routine in the calculation of the saturation pressure - /*! - * @param TKelvin temperature (kelvin) - * @param pres pressure (Pascal) - * @param[out] densLiq density of liquid - * @param[out] densGas density of gas - * @param[out] liqGRT deltaG/RT of liquid - * @param[out] gasGRT deltaG/RT of gas - */ - int corr0(doublereal TKelvin, doublereal pres, doublereal& densLiq, - doublereal& densGas, doublereal& liqGRT, doublereal& gasGRT); - -public: - //! Returns the Phase State flag for the current state of the object - /*! - * @param checkState If true, this function does a complete check to see - * where in parameters space we are - * - * There are three values: - * - WATER_GAS below the critical temperature but below the critical density - * - WATER_LIQUID below the critical temperature but above the critical density - * - WATER_SUPERCRIT above the critical temperature - */ - int phaseState(bool checkState = false) const; - - //! Return the value of the density at the liquid spinodal point (on the - //! liquid side) for the current temperature. - /*! - * @returns the density with units of kg m-3 - */ - virtual doublereal densSpinodalLiquid() const; - - //! Return the value of the density at the gas spinodal point (on the gas - //! side) for the current temperature. - /*! - * @returns the density with units of kg m-3 - */ - virtual doublereal densSpinodalGas() const; - -public: - //! Calculate the saturation pressure at the current mixture content for the - //! given temperature - /*! - * @param TKelvin (input) Temperature (Kelvin) - * @param molarVolGas (return) Molar volume of the gas - * @param molarVolLiquid (return) Molar volume of the liquid - * @returns the saturation pressure at the given temperature - */ - doublereal calculatePsat(doublereal TKelvin, doublereal& molarVolGas, - doublereal& molarVolLiquid); - -public: - //! Calculate the saturation pressure at the current mixture content for the - //! given temperature - /*! - * @param TKelvin Temperature (Kelvin) - * @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 - /*! - * @param TKelvin temperature in kelvin - * @param molarVol molar volume ( m3/kmol) - * @returns the pressure. - */ - virtual doublereal pressureCalc(doublereal TKelvin, doublereal molarVol) const; - - //! Calculate the pressure and the pressure derivative given the temperature - //! and the molar volume - /*! - * Temperature and mole number are held constant - * - * @param TKelvin temperature in kelvin - * @param molarVol molar volume ( m3/kmol) - * @param presCalc Returns the pressure. - * @returns the derivative of the pressure wrt the molar volume - */ - virtual doublereal dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const; - - virtual void updateMixingExpressions(); - - - //! Solve the cubic equation of state - /*! - * - * 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. - * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354�359, https://www.jstor.org/stable/3619777) - */ - 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) const; - - //@} - -protected: - //! Storage for the current values of the mole fractions of the species - /*! - * This vector is kept up-to-date when some the setState functions are called. - */ - vector_fp moleFractions_; - - //! Current state of the fluid - /*! - * There are three possible states of the fluid: - * - FLUID_GAS - * - FLUID_LIQUID - * - FLUID_SUPERCRIT - */ - int iState_; - - //! Force the system to be on a particular side of the spinodal curve - int forcedState_; - - //! Temporary storage for dimensionless reference state enthalpies - mutable vector_fp m_h0_RT; - - //! Temporary storage for dimensionless reference state heat capacities - mutable vector_fp m_cp0_R; - - //! Temporary storage for dimensionless reference state Gibbs energies - mutable vector_fp m_g0_RT; - - //! Temporary storage for dimensionless reference state entropies - mutable vector_fp m_s0_R; - -public: - //! 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 +/** + * @file MixtureFugacityTP.h + * Header file for a derived class of ThermoPhase that handles + * non-ideal mixtures based on the fugacity models (see \ref thermoprops and + * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). + */ + +// 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_MIXTUREFUGACITYTP_H +#define CT_MIXTUREFUGACITYTP_H + +#include "ThermoPhase.h" +#include "cantera/numerics/ResidEval.h" + +namespace Cantera +{ +//! Various states of the Fugacity object. In general there can be multiple liquid +//! objects for a single phase identified with each species. + +#define FLUID_UNSTABLE -4 +#define FLUID_UNDEFINED -3 +#define FLUID_SUPERCRIT -2 +#define FLUID_GAS -1 +#define FLUID_LIQUID_0 0 +#define FLUID_LIQUID_1 1 +#define FLUID_LIQUID_2 2 +#define FLUID_LIQUID_3 3 +#define FLUID_LIQUID_4 4 +#define FLUID_LIQUID_5 5 +#define FLUID_LIQUID_6 6 +#define FLUID_LIQUID_7 7 +#define FLUID_LIQUID_8 8 +#define FLUID_LIQUID_9 9 + +/** + * @ingroup thermoprops + * + * This is a filter class for ThermoPhase that implements some preparatory steps + * for efficiently handling mixture of gases that whose standard states are + * defined as ideal gases, but which describe also non-ideal solutions. In + * addition a multicomponent liquid phase below the critical temperature of the + * mixture is also allowed. The main subclass is currently a mixture Redlich- + * Kwong class. + * + * @attention This class currently does not have any test cases or examples. Its + * implementation may be incomplete, and future changes to Cantera may + * unexpectedly cause this class to stop working. If you use this class, + * please consider contributing examples or test cases. In the absence of + * new tests or examples, this class may be deprecated and removed in a + * future version of Cantera. See + * https://github.com/Cantera/cantera/issues/267 for additional information. + * + * Several concepts are introduced. The first concept is there are temporary + * variables for holding the species standard state values of Cp, H, S, G, and V + * at the last temperature and pressure called. These functions are not + * recalculated if a new call is made using the previous temperature and + * pressure. + * + * The other concept is that the current state of the mixture is tracked. The + * state variable is either GAS, LIQUID, or SUPERCRIT fluid. Additionally, the + * variable LiquidContent is used and may vary between 0 and 1. + * + * Typically, only one liquid phase is allowed to be formed within these + * classes. Additionally, there is an inherent contradiction between three phase + * models and the ThermoPhase class. The ThermoPhase class is really only meant + * to represent a single instantiation of a phase. The three phase models may be + * in equilibrium with multiple phases of the fluid in equilibrium with each + * other. This has yet to be resolved. + * + * This class is usually used for non-ideal gases. + */ +class MixtureFugacityTP : public ThermoPhase +{ +public: + //! @name Constructors and Duplicators for MixtureFugacityTP + //! @{ + + //! Constructor. + MixtureFugacityTP(); + + //! @} + //! @name Utilities + //! @{ + + virtual std::string type() const { + return "MixtureFugacity"; + } + + virtual int standardStateConvention() const; + + //! Set the solution branch to force the ThermoPhase to exist on one branch + //! or another + /*! + * @param solnBranch Branch that the solution is restricted to. the value + * -1 means gas. The value -2 means unrestricted. Values of zero or + * greater refer to species dominated condensed phases. + */ + virtual void setForcedSolutionBranch(int solnBranch); + + //! Report the solution branch which the solution is restricted to + /*! + * @return Branch that the solution is restricted to. the value -1 means + * gas. The value -2 means unrestricted. Values of zero or greater + * refer to species dominated condensed phases. + */ + virtual int forcedSolutionBranch() const; + + //! Report the solution branch which the solution is actually on + /*! + * @return Branch that the solution is restricted to. the value -1 means + * gas. The value -2 means superfluid.. Values of zero or greater refer + * to species dominated condensed phases. + */ + virtual int reportSolnBranchActual() const; + + virtual void getdlnActCoeffdlnN_diag(doublereal* dlnActCoeffdlnN_diag) const { + throw NotImplementedError("MixtureFugacityTP::getdlnActCoeffdlnN_diag"); + } + + + //! @name Molar Thermodynamic properties + //! @{ + + virtual double enthalpy_mole() const; + virtual doublereal entropy_mole() const; + + //@} + /// @name Partial Molar Properties of the Solution + //@{ + + //! Get the array of non-dimensional species chemical potentials + //! These are partial molar Gibbs free energies. + /*! + * \f$ \mu_k / \hat R T \f$. + * Units: unitless + * + * We close the loop on this function, here, calling getChemPotentials() and + * then dividing by RT. No need for child classes to handle. + * + * @param mu Output vector of non-dimensional species chemical potentials + * Length: m_kk. + */ + virtual void getChemPotentials_RT(doublereal* mu) const; + + //@} + /*! + * @name Properties of the Standard State of the Species in the Solution + * + * Within MixtureFugacityTP, these properties are calculated via a common + * routine, _updateStandardStateThermo(), which must be overloaded in + * inherited objects. The values are cached within this object, and are not + * recalculated unless the temperature or pressure changes. + */ + //@{ + + //! Get the array of chemical potentials at unit activity. + /*! + * These are the standard state chemical potentials \f$ \mu^0_k(T,P) + * \f$. The values are evaluated at the current temperature and pressure. + * + * 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. + * + * @param mu Output vector of standard state chemical potentials. + * length = m_kk. units are J / kmol. + */ + virtual void getStandardChemPotentials(doublereal* mu) const; + + //! Get the nondimensional Enthalpy functions for the species at their + //! standard states at the current *T* and *P* of the solution. + /*! + * 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. + * + * @param hrt Output vector of standard state enthalpies. + * length = m_kk. units are unitless. + */ + virtual void getEnthalpy_RT(doublereal* hrt) const; + + //! Get the array of nondimensional Enthalpy functions for the standard + //! state species at the current *T* and *P* of the solution. + /*! + * 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. + * + * @param sr Output vector of nondimensional standard state entropies. + * length = m_kk. + */ + virtual void getEntropy_R(doublereal* sr) const; + + //! Get the nondimensional Gibbs functions for the species at their standard + //! states of solution at the current T and P of the solution. + /*! + * 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. + * + * @param grt Output vector of nondimensional standard state Gibbs free + * energies. length = m_kk. + */ + virtual void getGibbs_RT(doublereal* grt) const; + + //! Get the pure Gibbs free energies of each species. Species are assumed to + //! be in their standard states. + /*! + * This is the same as getStandardChemPotentials(). + * + * @param[out] gpure Array of standard state Gibbs free energies. length = + * m_kk. units are J/kmol. + */ + virtual void getPureGibbs(doublereal* gpure) const; + + //! Returns the vector of nondimensional internal Energies of the standard + //! state at the current temperature and pressure of the solution for each + //! species. + /*! + * 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. + * + * \f[ + * u^{ss}_k(T,P) = h^{ss}_k(T) - P * V^{ss}_k + * \f] + * + * @param urt Output vector of nondimensional standard state internal + * energies. length = m_kk. + */ + virtual void getIntEnergy_RT(doublereal* urt) const; + + //! Get the nondimensional Heat Capacities at constant pressure for the + //! standard state of the species at the current T and P. + /*! + * 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. + * + * @param cpr Output vector containing the the nondimensional Heat + * Capacities at constant pressure for the standard state of + * the species. Length: m_kk. + */ + virtual void getCp_R(doublereal* cpr) const; + + //! Get the molar volumes of each species in their standard states at the + //! current *T* and *P* of the solution. + /*! + * 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. + * + * units = m^3 / kmol + * + * @param vol Output vector of species volumes. length = m_kk. + * units = m^3 / kmol + */ + virtual void getStandardVolumes(doublereal* vol) const; + // @} + + //! Set the temperature of the phase + /*! + * Currently this passes down to setState_TP(). It does not make sense to + * calculate the standard state without first setting T and P. + * + * @param temp Temperature (kelvin) + */ + virtual void setTemperature(const doublereal temp); + + //! Set the internally stored pressure (Pa) at constant temperature and + //! composition + /*! + * Currently this passes down to setState_TP(). It does not make sense to + * calculate the standard state without first setting T and P. + * + * @param p input Pressure (Pa) + */ + virtual void setPressure(doublereal p); + +protected: + virtual void compositionChanged(); + + //! Updates the reference state thermodynamic functions at the current T of + //! the solution. + /*! + * This function must be called for every call to functions in this class. + * It checks to see whether the temperature has changed and thus the ss + * thermodynamics functions for all of the species must be recalculated. + * + * This function is responsible for updating the following internal members: + * + * - m_h0_RT; + * - m_cp0_R; + * - m_g0_RT; + * - 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 + /*! + * There are also temporary variables for holding the species reference- + * state values of Cp, H, S, and V at the last temperature and reference + * pressure called. These functions are not recalculated if a new call is + * made using the previous temperature. All calculations are done within the + * routine _updateRefStateThermo(). + */ + //@{ + + virtual void getEnthalpy_RT_ref(doublereal* hrt) const; + virtual void getGibbs_RT_ref(doublereal* grt) const; + +protected: + //! Returns the vector of nondimensional Gibbs free energies of the + //! reference state at the current temperature of the solution and the + //! reference pressure for the species. + /*! + * @return Output vector contains the nondimensional Gibbs free energies + * of the reference state of the species + * length = m_kk, units = dimensionless. + */ + const vector_fp& gibbs_RT_ref() const; + +public: + virtual void getGibbs_ref(doublereal* g) const; + virtual void getEntropy_R_ref(doublereal* er) const; + virtual void getCp_R_ref(doublereal* cprt) const; + virtual void getStandardVolumes_ref(doublereal* vol) 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 setStateFromXML(const XML_Node& state); + +protected: + //! @name Special Functions for fugacity classes + //! @{ + + //! Calculate the value of z + /*! + * \f[ + * z = \frac{P v}{R T} + * \f] + * + * returns the value of z + */ + doublereal z() const; + + //! Calculate the deviation terms for the total entropy of the mixture from + //! the ideal gas mixture + /* + * Here we use the current state conditions + * + * @returns the change in entropy in units of J kmol-1 K-1. + */ + virtual doublereal sresid() const; + + //! Calculate the deviation terms for the total enthalpy of the mixture from + //! the ideal gas mixture + /* + * Here we use the current state conditions + * + * @returns the change in entropy in units of J kmol-1. + */ + virtual doublereal hresid() const; + + //! Estimate for the saturation pressure + /*! + * Note: this is only used as a starting guess for later routines that + * actually calculate an accurate value for the saturation pressure. + * + * @param TKelvin temperature in kelvin + * @return the estimated saturation pressure at the given temperature + */ + virtual doublereal psatEst(doublereal TKelvin) const; + +public: + //! Estimate for the molar volume of the liquid + /*! + * Note: this is only used as a starting guess for later routines that + * actually calculate an accurate value for the liquid molar volume. This + * routine doesn't change the state of the system. + * + * @param TKelvin temperature in kelvin + * @param pres Pressure in Pa. This is used as an initial guess. If the + * routine needs to change the pressure to find a stable + * liquid state, the new pressure is returned in this + * variable. + * @returns the estimate of the liquid volume. If the liquid can't be + * found, this routine returns -1. + */ + virtual doublereal liquidVolEst(doublereal TKelvin, doublereal& pres) const; + + //! Calculates the density given the temperature and the pressure and a + //! guess at the density. + /*! + * Note, below T_c, this is a multivalued function. We do not cross the + * vapor dome in this. This is protected because it is called during + * 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) + * @param phaseRequested int representing the phase whose density we are + * requesting. If we put a gas or liquid phase here, we will attempt to + * find a volume in that part of the volume space, only, in this + * routine. A value of FLUID_UNDEFINED means that we will accept + * anything. + * @param rhoguess Guessed density of the fluid. A value of -1.0 indicates + * that there is no guessed density + * @return We return the density of the fluid at the requested phase. If + * we have not found any acceptable density we return a -1. If we + * have found an acceptable density at a different phase, we + * return a -2. + */ + virtual doublereal densityCalc(doublereal TKelvin, doublereal pressure, int phaseRequested, + doublereal rhoguess); + +protected: + //! Utility routine in the calculation of the saturation pressure + /*! + * @param TKelvin temperature (kelvin) + * @param pres pressure (Pascal) + * @param[out] densLiq density of liquid + * @param[out] densGas density of gas + * @param[out] liqGRT deltaG/RT of liquid + * @param[out] gasGRT deltaG/RT of gas + */ + int corr0(doublereal TKelvin, doublereal pres, doublereal& densLiq, + doublereal& densGas, doublereal& liqGRT, doublereal& gasGRT); + +public: + //! Returns the Phase State flag for the current state of the object + /*! + * @param checkState If true, this function does a complete check to see + * where in parameters space we are + * + * There are three values: + * - WATER_GAS below the critical temperature but below the critical density + * - WATER_LIQUID below the critical temperature but above the critical density + * - WATER_SUPERCRIT above the critical temperature + */ + int phaseState(bool checkState = false) const; + + //! Return the value of the density at the liquid spinodal point (on the + //! liquid side) for the current temperature. + /*! + * @returns the density with units of kg m-3 + */ + virtual doublereal densSpinodalLiquid() const; + + //! Return the value of the density at the gas spinodal point (on the gas + //! side) for the current temperature. + /*! + * @returns the density with units of kg m-3 + */ + virtual doublereal densSpinodalGas() const; + +public: + //! Calculate the saturation pressure at the current mixture content for the + //! given temperature + /*! + * @param TKelvin (input) Temperature (Kelvin) + * @param molarVolGas (return) Molar volume of the gas + * @param molarVolLiquid (return) Molar volume of the liquid + * @returns the saturation pressure at the given temperature + */ + doublereal calculatePsat(doublereal TKelvin, doublereal& molarVolGas, + doublereal& molarVolLiquid); + +public: + //! Calculate the saturation pressure at the current mixture content for the + //! given temperature + /*! + * @param TKelvin Temperature (Kelvin) + * @return The saturation pressure at the given temperature + */ + virtual doublereal satPressure(doublereal TKelvin); + virtual void setToEquilState(const doublereal* lambda_RT); + virtual void getActivityConcentrations(double* c) const; + +protected: + //! Calculate the pressure given the temperature and the molar volume + /*! + * @param TKelvin temperature in kelvin + * @param molarVol molar volume ( m3/kmol) + * @returns the pressure. + */ + virtual doublereal pressureCalc(doublereal TKelvin, doublereal molarVol) const; + + //! Calculate the pressure and the pressure derivative given the temperature + //! and the molar volume + /*! + * Temperature and mole number are held constant + * + * @param TKelvin temperature in kelvin + * @param molarVol molar volume ( m3/kmol) + * @param presCalc Returns the pressure. + * @returns the derivative of the pressure wrt the molar volume + */ + virtual doublereal dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const; + + virtual void updateMixingExpressions(); + + + //! Solve the cubic equation of state + /*! + * + * 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. + * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354�359, https://www.jstor.org/stable/3619777) + */ + 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) const; + + //@} + +protected: + //! Storage for the current values of the mole fractions of the species + /*! + * This vector is kept up-to-date when some the setState functions are called. + */ + vector_fp moleFractions_; + + //! Current state of the fluid + /*! + * There are three possible states of the fluid: + * - FLUID_GAS + * - FLUID_LIQUID + * - FLUID_SUPERCRIT + */ + int iState_; + + //! Force the system to be on a particular side of the spinodal curve + int forcedState_; + + //! Temporary storage for dimensionless reference state enthalpies + mutable vector_fp m_h0_RT; + + //! Temporary storage for dimensionless reference state heat capacities + mutable vector_fp m_cp0_R; + + //! Temporary storage for dimensionless reference state Gibbs energies + mutable vector_fp m_g0_RT; + + //! Temporary storage for dimensionless reference state entropies + mutable vector_fp m_s0_R; + +public: + //! 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; + + //! Temporary storage - length = m_kk. + mutable vector_fp m_pp; +}; +} + +#endif diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 3af1f70a17..1e17036650 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -1,365 +1,364 @@ -//! @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: - //! @name Constructors and Duplicators - //! @{ - - //! Base constructor. - PengRobinson(); - - //! Construct and initialize a PengRobinson object directly from an - //! ASCII input file - /*! - * @param infile Name of the input file containing the phase YAML data - * to set up the object - * @param id ID of the phase in the input file. Defaults to the empty - * string. - */ - 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) for omega <= 0.491 - * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 - * \f] - * - *Coefficients a_mix, b_mix and (a \alpha)_{mix} are caclulated 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; - - // @} - -public: - - //! 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. In many cases, this - * quantity will be the same for all species in a phase. - * 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 m3 kmol-1. - */ - 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 - //@{ - - //! Get the array of non-dimensional species chemical potentials. - //! These are partial molar Gibbs free energies. - /*! - * \f$ \mu_k / \hat R T \f$. - * Units: unitless - * - * We close the loop on this function here calling getChemPotentials() and - * then dividing by RT. No need for child classes to handle. - * - * @param mu Output vector of non-dimensional species chemical potentials - * Length: m_kk. - */ - virtual void getChemPotentials_RT(double* mu) const; - - 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 the temperature dependent interaction parameter alpha needed for P-R EoS - /* - * 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. - * Units: unitless - */ - virtual void calculateAlpha(const std::string& species, double a, double b, double w); - //@} - /// @name Critical State Properties. - //@{ - - virtual double critTemperature() const; - virtual double critPressure() const; - virtual double critVolume() const; - virtual double critCompressibility() const; - virtual double critDensity() const; - virtual double speciesCritTemperature(double a, double b) const; - -public: - //@} - //! @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 setToEquilState(const double* lambda_RT); - 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 std::vector getCoeff(const std::string& iName); - - //! Set the pure fluid interaction parameters for a species - /*! - * The "a" parameter for species *i* in the Peng-Robinson model is assumed - * to be a linear function of temperature: - * \f[ a = a_0 + a_1 T \f] - * - * @param species Name of the species - * @param a0 constant term in the expression for the "a" parameter - * of the specified species [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the expression for the - * "a" parameter of the specified species [Pa-m^6/kmol^2/K] - * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] - * @param alpha dimensionless function of T_r and \omega - * @param omega acentric factor - */ - void setSpeciesCoeffs(const std::string& species, double a0, 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] - * - * This function overrides the defaults with the specified parameters: - * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] - * - * @param species_i Name of one species - * @param species_j Name of the other species - * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the "a" expression - * [Pa-m^6/kmol^2/K] - */ - void setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1); - -protected: - // Special functions inherited from MixtureFugacityTP - virtual double sresid() const; - virtual double hresid() const; - -public: - 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 pressureCalc(double TKelvin, double molarVol) const; - virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; - - //! Calculate dpdV and dpdT at the current conditions - /*! - * These are stored internally. - */ - void pressureDerivatives() const; - - //! 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. - */ - virtual void updateMixingExpressions(); - - //! Calculate the a and the b 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 temp Temperature (TKelvin) - * @param aCalc (output) Returns the a value - * @param bCalc (output) Returns the b value. - */ - void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; - - // Special functions not inherited from MixtureFugacityTP - - double daAlpha_dT() const; - double d2aAlpha_dT2() const; - - void calcCriticalConditions(double a, double b,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: - //! Form of the temperature parameterization - /*! - * 0 = There is no temperature parameterization of a or b - * 1 = The a_ij parameter is a linear function of the temperature - */ - int m_formTempParam; - - //! Value of b in the equation of state - /*! - * m_b_current is a function of the temperature and the mole fractions. - */ - double m_b_current; - - //! Value of a and alpha in the equation of state - /*! - * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. - */ - double m_a_current; - double m_aAlpha_current; - - // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk - vector_fp a_vec_Curr_; - vector_fp b_vec_Curr_; - vector_fp aAlpha_vec_Curr_; - vector_fp alpha_vec_Curr_; - vector_fp kappa_vec_; - mutable vector_fp dalphadT_vec_Curr_; - mutable vector_fp d2alphadT2_; - - Array2D a_coeff_vec; - Array2D aAlpha_coeff_vec; - - int NSolns_; - - double Vroot_[3]; - - //! 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; - - //! The derivative of the pressure with respect to the volume - /*! - * Calculated at the current conditions. temperature and mole number kept - * constant - */ - mutable double 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 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 dpdni_; - -public: - //! 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 +//! @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: + //! @name Constructors and Duplicators + //! @{ + + //! Base constructor. + PengRobinson(); + + //! Construct and initialize a PengRobinson object directly from an + //! ASCII input file + /*! + * @param infile Name of the input file containing the phase YAML data + * to set up the object + * @param id ID of the phase in the input file. Defaults to the empty + * string. + */ + 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) for omega <= 0.491 + * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 + * \f] + * + *Coefficients a_mix, b_mix and (a \alpha)_{mix} are caclulated 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; + + // @} + +public: + + //! 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. In many cases, this + * quantity will be the same for all species in a phase. + * 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 m3 kmol-1. + */ + 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 + //@{ + + //! Get the array of non-dimensional species chemical potentials. + //! These are partial molar Gibbs free energies. + /*! + * \f$ \mu_k / \hat R T \f$. + * Units: unitless + * + * We close the loop on this function here calling getChemPotentials() and + * then dividing by RT. No need for child classes to handle. + * + * @param mu Output vector of non-dimensional species chemical potentials + * Length: m_kk. + */ + virtual void getChemPotentials_RT(double* mu) const; + + 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 the temperature dependent interaction parameter alpha needed for P-R EoS + /* + * 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. + * Units: unitless + */ + virtual void calculateAlpha(const std::string& species, double a, double b, double w); + //@} + /// @name Critical State Properties. + //@{ + + virtual double critTemperature() const; + virtual double critPressure() const; + virtual double critVolume() const; + virtual double critCompressibility() const; + virtual double critDensity() const; + virtual double speciesCritTemperature(double a, double b) const; + +public: + //@} + //! @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 std::vector getCoeff(const std::string& iName); + + //! Set the pure fluid interaction parameters for a species + /*! + * The "a" parameter for species *i* in the Peng-Robinson model is assumed + * to be a linear function of temperature: + * \f[ a = a_0 + a_1 T \f] + * + * @param species Name of the species + * @param a0 constant term in the expression for the "a" parameter + * of the specified species [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the expression for the + * "a" parameter of the specified species [Pa-m^6/kmol^2/K] + * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] + * @param alpha dimensionless function of T_r and \omega + * @param omega acentric factor + */ + void setSpeciesCoeffs(const std::string& species, double a0, 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] + * + * This function overrides the defaults with the specified parameters: + * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] + * + * @param species_i Name of one species + * @param species_j Name of the other species + * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the "a" expression + * [Pa-m^6/kmol^2/K] + */ + void setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1); + +protected: + // Special functions inherited from MixtureFugacityTP + virtual double sresid() const; + virtual double hresid() const; + +public: + 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 pressureCalc(double TKelvin, double molarVol) const; + virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + + //! Calculate dpdV and dpdT at the current conditions + /*! + * These are stored internally. + */ + void pressureDerivatives() const; + + //! 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. + */ + virtual void updateMixingExpressions(); + + //! Calculate the a and the b 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 temp Temperature (TKelvin) + * @param aCalc (output) Returns the a value + * @param bCalc (output) Returns the b value. + */ + void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; + + // Special functions not inherited from MixtureFugacityTP + + double daAlpha_dT() const; + double d2aAlpha_dT2() const; + + void calcCriticalConditions(double a, double b,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: + //! Form of the temperature parameterization + /*! + * 0 = There is no temperature parameterization of a or b + * 1 = The a_ij parameter is a linear function of the temperature + */ + int m_formTempParam; + + //! Value of b in the equation of state + /*! + * m_b_current is a function of the temperature and the mole fractions. + */ + double m_b_current; + + //! Value of a and alpha in the equation of state + /*! + * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. + */ + double m_a_current; + double m_aAlpha_current; + + // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk + vector_fp a_vec_Curr_; + vector_fp b_vec_Curr_; + vector_fp aAlpha_vec_Curr_; + vector_fp alpha_vec_Curr_; + vector_fp kappa_vec_; + mutable vector_fp dalphadT_vec_Curr_; + mutable vector_fp d2alphadT2_; + + Array2D a_coeff_vec; + Array2D aAlpha_coeff_vec; + + int NSolns_; + + double Vroot_[3]; + + //! 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; + + //! The derivative of the pressure with respect to the volume + /*! + * Calculated at the current conditions. temperature and mole number kept + * constant + */ + mutable double 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 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 dpdni_; + +public: + //! 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/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 0847868362..0af23bfbf5 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -1,1009 +1,1041 @@ -/** - * @file MixtureFugacityTP.cpp - * Methods file for a derived class of ThermoPhase that handles - * non-ideal mixtures based on the fugacity models (see \ref thermoprops and - * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). - */ - -// 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/MixtureFugacityTP.h" -#include "cantera/base/stringUtils.h" -#include "cantera/base/ctml.h" - -using namespace std; - -namespace Cantera -{ - -const double MixtureFugacityTP::omega_a = 0.; -const double MixtureFugacityTP::omega_b = 0.; -const double MixtureFugacityTP::omega_vc = 0.; - -MixtureFugacityTP::MixtureFugacityTP() : - iState_(FLUID_GAS), - forcedState_(FLUID_UNDEFINED) -{ -} - -int MixtureFugacityTP::standardStateConvention() const -{ - return cSS_CONVENTION_TEMPERATURE; -} - -void MixtureFugacityTP::setForcedSolutionBranch(int solnBranch) -{ - forcedState_ = solnBranch; -} - -int MixtureFugacityTP::forcedSolutionBranch() const -{ - return forcedState_; -} - -int MixtureFugacityTP::reportSolnBranchActual() const -{ - return iState_; -} - -// ---- Molar Thermodynamic Properties --------------------------- -double MixtureFugacityTP::enthalpy_mole() const -{ - _updateReferenceStateThermo(); - double h_ideal = RT() * mean_X(m_h0_RT); - double h_nonideal = hresid(); - return h_ideal + h_nonideal; -} - - -double MixtureFugacityTP::entropy_mole() const -{ - _updateReferenceStateThermo(); - double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - - std::log(pressure()/refPressure())); - double sr_nonideal = sresid(); - return sr_ideal + sr_nonideal; -} - -// ---- Partial Molar Properties of the Solution ----------------- - -void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const -{ - getChemPotentials(muRT); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RT(); - } -} - -// ----- Thermodynamic Values for the Species Standard States States ---- - -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++) { - g[k] = RT() * (g[k] + tmp); - } -} - -void MixtureFugacityTP::getEnthalpy_RT(doublereal* hrt) const -{ - getEnthalpy_RT_ref(hrt); -} - -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++) { - sr[k] -= tmp; - } -} - -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++) { - grt[k] += tmp; - } -} - -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++) { - g[k] += tmp; - } -} - -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; - } -} - -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(); - } -} - -// ----- Thermodynamic Values for the Species Reference States ---- - -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); -} - -void MixtureFugacityTP::getGibbs_ref(doublereal* g) const -{ - const vector_fp& gibbsrt = gibbs_RT_ref(); - scale(gibbsrt.begin(), gibbsrt.end(), g, RT()); -} - -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(); - } -} - -void MixtureFugacityTP::setStateFromXML(const XML_Node& state) -{ - int doTP = 0; - string comp = getChildValue(state,"moleFractions"); - if (comp != "") { - // not overloaded in current object -> phase state is not calculated. - setMoleFractionsByName(comp); - doTP = 1; - } else { - comp = getChildValue(state,"massFractions"); - if (comp != "") { - // not overloaded in current object -> phase state is not calculated. - setMassFractionsByName(comp); - doTP = 1; - } - } - double t = temperature(); - if (state.hasChild("temperature")) { - t = getFloat(state, "temperature", "temperature"); - doTP = 1; - } - if (state.hasChild("pressure")) { - double p = getFloat(state, "pressure", "pressure"); - setState_TP(t, p); - } else if (state.hasChild("density")) { - double rho = getFloat(state, "density", "density"); - setState_TR(t, rho); - } else if (doTP) { - double rho = density(); - setState_TR(t, rho); - } -} - -bool MixtureFugacityTP::addSpecies(shared_ptr spec) -{ - bool added = ThermoPhase::addSpecies(spec); - if (added) { - if (m_kk == 1) { - moleFractions_.push_back(1.0); - } else { - moleFractions_.push_back(0.0); - } - m_h0_RT.push_back(0.0); - m_cp0_R.push_back(0.0); - m_g0_RT.push_back(0.0); - m_s0_R.push_back(0.0); - } - return added; -} - -void MixtureFugacityTP::setTemperature(const doublereal temp) -{ - Phase::setTemperature(temp); - _updateReferenceStateThermo(); - // depends on mole fraction and temperature - updateMixingExpressions(); - iState_ = phaseState(true); -} - -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. - - double t = temperature(); - double rhoNow = density(); - if (forcedState_ == FLUID_UNDEFINED) { - double rho = densityCalc(t, p, iState_, rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - } else { - if (rho < -1.5) { - rho = densityCalc(t, p, FLUID_UNDEFINED , rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } - } else if (forcedState_ == FLUID_GAS) { - // Normal density calculation - if (iState_ < FLUID_LIQUID_0) { - double rho = densityCalc(t, p, iState_, rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - if (iState_ >= FLUID_LIQUID_0) { - throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); - } - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } - } else if (forcedState_ > FLUID_LIQUID_0) { - if (iState_ >= FLUID_LIQUID_0) { - double rho = densityCalc(t, p, iState_, rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - if (iState_ == FLUID_GAS) { - throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); - } - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } - } -} - -void MixtureFugacityTP::compositionChanged() -{ - Phase::compositionChanged(); - getMoleFractions(moleFractions_.data()); - updateMixingExpressions(); -} - -void MixtureFugacityTP::getActivityConcentrations(doublereal* c) const -{ - 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 -{ - return pressure() * meanMolecularWeight() / (density() * RT()); -} - -doublereal MixtureFugacityTP::sresid() const -{ - throw NotImplementedError("MixtureFugacityTP::sresid"); -} - -doublereal MixtureFugacityTP::hresid() const -{ - throw NotImplementedError("MixtureFugacityTP::hresid"); -} - -doublereal MixtureFugacityTP::psatEst(doublereal TKelvin) const -{ - doublereal pcrit = critPressure(); - doublereal tt = critTemperature() / TKelvin; - if (tt < 1.0) { - return pcrit; - } - doublereal lpr = -0.8734*tt*tt - 3.4522*tt + 4.2918; - return pcrit*exp(lpr); -} - -doublereal MixtureFugacityTP::liquidVolEst(doublereal TKelvin, doublereal& pres) const -{ - throw NotImplementedError("MixtureFugacityTP::liquidVolEst"); -} - -doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, - int phase, doublereal rhoguess) -{ - doublereal tcrit = critTemperature(); - doublereal mmw = meanMolecularWeight(); - if (rhoguess == -1.0) { - if (phase != -1) { - if (TKelvin > tcrit) { - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } else { - if (phase == FLUID_GAS || phase == FLUID_SUPERCRIT) { - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } else if (phase >= 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 - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } - } - - double molarVolBase = mmw / rhoguess; - double molarVolLast = molarVolBase; - double vc = mmw / critDensity(); - - // molar volume of the spinodal at the current temperature and mole - // fractions. this will be updated as we go. - double molarVolSpinodal = vc; - bool conv = false; - - // We start on one side of the vc and stick with that side - bool gasSide = molarVolBase > vc; - if (gasSide) { - molarVolLast = (GasConstant * TKelvin)/presPa; - } else { - molarVolLast = liquidVolEst(TKelvin, presPa); - } - - // OK, now we do a small solve to calculate the molar volume given the T,P - // value. The algorithm is taken from dfind() - for (int n = 0; n < 200; n++) { - // Calculate the predicted reduced pressure, pred0, based on the current - // tau and dd. Calculate the derivative of the predicted pressure wrt - // the molar volume. This routine also returns the pressure, presBase - double presBase; - double dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase); - - // If dpdV is positive, then we are in the middle of the 2 phase region - // and beyond the spinodal stability curve. We need to adjust the - // initial guess outwards and start a new iteration. - if (dpdVBase >= 0.0) { - if (TKelvin > tcrit) { - throw CanteraError("MixtureFugacityTP::densityCalc", - "T > tcrit unexpectedly"); - } - - // TODO Spawn a calculation for the value of the spinodal point that - // is very accurate. Answer the question as to whether a - // solution is possible on the current side of the vapor dome. - if (gasSide) { - if (molarVolBase >= vc) { - molarVolSpinodal = molarVolBase; - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } else { - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } - } else { - if (molarVolBase <= vc) { - molarVolSpinodal = molarVolBase; - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } else { - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } - } - continue; - } - - // Check for convergence - if (fabs(presBase-presPa) < 1.0E-30 + 1.0E-8 * presPa) { - conv = true; - break; - } - - // Dampen and crop the update - doublereal dpdV = dpdVBase; - if (n < 10) { - dpdV = dpdVBase * 1.5; - } - - // Formulate the update to the molar volume by Newton's method. Then, - // crop it to a max value of 0.1 times the current volume - double delMV = - (presBase - presPa) / dpdV; - if ((!gasSide || delMV < 0.0) && fabs(delMV) > 0.2 * molarVolBase) { - delMV = delMV / fabs(delMV) * 0.2 * molarVolBase; - } - // Only go 1/10 the way towards the spinodal at any one time. - if (TKelvin < tcrit) { - if (gasSide) { - if (delMV < 0.0 && -delMV > 0.5 * (molarVolBase - molarVolSpinodal)) { - delMV = - 0.5 * (molarVolBase - molarVolSpinodal); - } - } else { - if (delMV > 0.0 && delMV > 0.5 * (molarVolSpinodal - molarVolBase)) { - delMV = 0.5 * (molarVolSpinodal - molarVolBase); - } - } - } - // updated the molar volume value - molarVolLast = molarVolBase; - molarVolBase += delMV; - - if (fabs(delMV/molarVolBase) < 1.0E-14) { - conv = true; - break; - } - - // Check for negative molar volumes - if (molarVolBase <= 0.0) { - molarVolBase = std::min(1.0E-30, fabs(delMV*1.0E-4)); - } - } - - // Check for convergence, and return 0.0 if it wasn't achieved. - double densBase = 0.0; - if (! conv) { - molarVolBase = 0.0; - throw CanteraError("MixtureFugacityTP::densityCalc", "Process did not converge"); - } else { - densBase = mmw / molarVolBase; - } - return densBase; -} - -void MixtureFugacityTP::updateMixingExpressions() -{ -} - -int MixtureFugacityTP::corr0(doublereal TKelvin, doublereal pres, doublereal& densLiqGuess, - doublereal& densGasGuess, doublereal& liqGRT, doublereal& gasGRT) -{ - int retn = 0; - doublereal densLiq = densityCalc(TKelvin, pres, FLUID_LIQUID_0, densLiqGuess); - if (densLiq <= 0.0) { - retn = -1; - } else { - densLiqGuess = densLiq; - setState_TR(TKelvin, densLiq); - liqGRT = gibbs_mole() / RT(); - } - - doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, densGasGuess); - if (densGas <= 0.0) { - if (retn == -1) { - throw CanteraError("MixtureFugacityTP::corr0", - "Error occurred trying to find gas density at (T,P) = {} {}", - TKelvin, pres); - } - retn = -2; - } else { - densGasGuess = densGas; - setState_TR(TKelvin, densGas); - gasGRT = gibbs_mole() / RT(); - } - return retn; -} - -int MixtureFugacityTP::phaseState(bool checkState) const -{ - int state = iState_; - if (checkState) { - double t = temperature(); - double tcrit = critTemperature(); - double rhocrit = critDensity(); - if (t >= tcrit) { - return FLUID_SUPERCRIT; - } - double tmid = tcrit - 100.; - if (tmid < 0.0) { - tmid = tcrit / 2.0; - } - double pp = psatEst(tmid); - double mmw = meanMolecularWeight(); - double molVolLiqTmid = liquidVolEst(tmid, pp); - double molVolGasTmid = GasConstant * tmid / pp; - double densLiqTmid = mmw / molVolLiqTmid; - double densGasTmid = mmw / molVolGasTmid; - double densMidTmid = 0.5 * (densLiqTmid + densGasTmid); - doublereal rhoMid = rhocrit + (t - tcrit) * (rhocrit - densMidTmid) / (tcrit - tmid); - - double rho = density(); - int iStateGuess = FLUID_LIQUID_0; - if (rho < rhoMid) { - iStateGuess = FLUID_GAS; - } - double molarVol = mmw / rho; - double presCalc; - - double dpdv = dpdVCalc(t, molarVol, presCalc); - if (dpdv < 0.0) { - state = iStateGuess; - } else { - state = FLUID_UNSTABLE; - } - } - return state; -} - -doublereal MixtureFugacityTP::densSpinodalLiquid() const -{ - throw NotImplementedError("MixtureFugacityTP::densSpinodalLiquid"); -} - -doublereal MixtureFugacityTP::densSpinodalGas() const -{ - throw NotImplementedError("MixtureFugacityTP::densSpinodalGas"); -} - -doublereal MixtureFugacityTP::satPressure(doublereal TKelvin) -{ - doublereal molarVolGas; - doublereal molarVolLiquid; - return calculatePsat(TKelvin, molarVolGas, molarVolLiquid); -} - -doublereal MixtureFugacityTP::calculatePsat(doublereal TKelvin, doublereal& molarVolGas, - doublereal& molarVolLiquid) -{ - // The algorithm for this routine has undergone quite a bit of work. It - // probably needs more work. However, it seems now to be fairly robust. The - // key requirement is to find an initial pressure where both the liquid and - // the gas exist. This is not as easy as it sounds, and it gets exceedingly - // hard as the critical temperature is approached from below. Once we have - // this initial state, then we seek to equilibrate the Gibbs free energies - // of the gas and liquid and use the formula - // - // dp = VdG - // - // to create an update condition for deltaP using - // - // - (Gliq - Ggas) = (Vliq - Vgas) (deltaP) - // - // @TODO Suggestions for the future would be to switch it to an algorithm - // that uses the gas molar volume and the liquid molar volumes as the - // fundamental unknowns. - - // we need this because this is a non-const routine that is public - setTemperature(TKelvin); - double densSave = density(); - double tempSave = temperature(); - double pres; - doublereal mw = meanMolecularWeight(); - if (TKelvin < critTemperature()) { - pres = psatEst(TKelvin); - // trial value = Psat from correlation - doublereal volLiquid = liquidVolEst(TKelvin, pres); - double RhoLiquidGood = mw / volLiquid; - double RhoGasGood = pres * mw / (GasConstant * TKelvin); - doublereal delGRT = 1.0E6; - doublereal liqGRT, gasGRT; - - // First part of the calculation involves finding a pressure at which - // the gas and the liquid state coexists. - doublereal presLiquid = 0.; - doublereal presGas; - doublereal presBase = pres; - bool foundLiquid = false; - bool foundGas = false; - - doublereal densLiquid = densityCalc(TKelvin, presBase, FLUID_LIQUID_0, RhoLiquidGood); - if (densLiquid > 0.0) { - foundLiquid = true; - presLiquid = pres; - RhoLiquidGood = densLiquid; - } - if (!foundLiquid) { - for (int i = 0; i < 50; i++) { - pres = 1.1 * pres; - densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); - if (densLiquid > 0.0) { - foundLiquid = true; - presLiquid = pres; - RhoLiquidGood = densLiquid; - break; - } - } - } - - pres = presBase; - doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); - if (densGas <= 0.0) { - foundGas = false; - } else { - foundGas = true; - presGas = pres; - RhoGasGood = densGas; - } - if (!foundGas) { - for (int i = 0; i < 50; i++) { - pres = 0.9 * pres; - densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); - if (densGas > 0.0) { - foundGas = true; - presGas = pres; - RhoGasGood = densGas; - break; - } - } - } - - if (foundGas && foundLiquid && presGas != presLiquid) { - pres = 0.5 * (presLiquid + presGas); - bool goodLiq; - bool goodGas; - for (int i = 0; i < 50; i++) { - densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); - if (densLiquid <= 0.0) { - goodLiq = false; - } else { - goodLiq = true; - RhoLiquidGood = densLiquid; - presLiquid = pres; - } - densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); - if (densGas <= 0.0) { - goodGas = false; - } else { - goodGas = true; - RhoGasGood = densGas; - presGas = pres; - } - if (goodGas && goodLiq) { - break; - } - if (!goodLiq && !goodGas) { - pres = 0.5 * (pres + presLiquid); - } - if (goodLiq || goodGas) { - pres = 0.5 * (presLiquid + presGas); - } - } - } - if (!foundGas || !foundLiquid) { - warn_user("MixtureFugacityTP::calculatePsat", - "could not find a starting pressure; exiting."); - return 0.0; - } - if (presGas != presLiquid) { - warn_user("MixtureFugacityTP::calculatePsat", - "could not find a starting pressure; exiting"); - return 0.0; - } - - pres = presGas; - double presLast = pres; - double RhoGas = RhoGasGood; - double RhoLiquid = RhoLiquidGood; - - // Now that we have found a good pressure we can proceed with the algorithm. - for (int i = 0; i < 20; i++) { - int stab = corr0(TKelvin, pres, RhoLiquid, RhoGas, liqGRT, gasGRT); - if (stab == 0) { - presLast = pres; - delGRT = liqGRT - gasGRT; - doublereal delV = mw * (1.0/RhoLiquid - 1.0/RhoGas); - doublereal dp = - delGRT * GasConstant * TKelvin / delV; - - if (fabs(dp) > 0.1 * pres) { - if (dp > 0.0) { - dp = 0.1 * pres; - } else { - dp = -0.1 * pres; - } - } - pres += dp; - } else if (stab == -1) { - delGRT = 1.0E6; - if (presLast > pres) { - pres = 0.5 * (presLast + pres); - } else { - // we are stuck here - try this - pres = 1.1 * pres; - } - } else if (stab == -2) { - if (presLast < pres) { - pres = 0.5 * (presLast + pres); - } else { - // we are stuck here - try this - pres = 0.9 * pres; - } - } - molarVolGas = mw / RhoGas; - molarVolLiquid = mw / RhoLiquid; - - if (fabs(delGRT) < 1.0E-8) { - // converged - break; - } - } - - molarVolGas = mw / RhoGas; - molarVolLiquid = mw / RhoLiquid; - // Put the fluid in the desired end condition - setState_TR(tempSave, densSave); - return pres; - } else { - pres = critPressure(); - setState_TP(TKelvin, pres); - molarVolGas = mw / density(); - molarVolLiquid = molarVolGas; - setState_TR(tempSave, densSave); - } - return pres; -} - -doublereal MixtureFugacityTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const -{ - throw NotImplementedError("MixtureFugacityTP::pressureCalc"); -} - -doublereal MixtureFugacityTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const -{ - throw NotImplementedError("MixtureFugacityTP::dpdVCalc"); -} - -void MixtureFugacityTP::_updateReferenceStateThermo() const -{ - double Tnow = temperature(); - - // If the temperature has changed since the last time these - // properties were computed, recompute them. - if (m_tlast != Tnow) { - m_spthermo.update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]); - m_tlast = Tnow; - - // update the species Gibbs functions - for (size_t k = 0; k < m_kk; k++) { - m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k]; - } - doublereal pref = refPressure(); - if (pref <= 0.0) { - throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", "neg ref pressure"); - } - } -} - -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) const -{ - double tmp; - fill_n(Vroot, 3, 0.0); - if (T <= 0.0) { - throw CanteraError("MixtureFugacityTP::CubicSolve()", "negative temperature T = {}", T); - } - - double pc = omega_b * GasConstant * tc / b; - double vc = omega_vc * GasConstant * tc / pc; - - // 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::CubicSolve()", "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; - } - - // 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("PengRobinson::CubicSolve(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("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly 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("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly 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("PengRobinson::CubicSolve(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; -} - -} +/** + * @file MixtureFugacityTP.cpp + * Methods file for a derived class of ThermoPhase that handles + * non-ideal mixtures based on the fugacity models (see \ref thermoprops and + * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). + */ + +// 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/MixtureFugacityTP.h" +#include "cantera/base/stringUtils.h" +#include "cantera/base/ctml.h" + +using namespace std; + +namespace Cantera +{ + +const double MixtureFugacityTP::omega_a = 0.; +const double MixtureFugacityTP::omega_b = 0.; +const double MixtureFugacityTP::omega_vc = 0.; + +MixtureFugacityTP::MixtureFugacityTP() : + iState_(FLUID_GAS), + forcedState_(FLUID_UNDEFINED) +{ +} + +int MixtureFugacityTP::standardStateConvention() const +{ + return cSS_CONVENTION_TEMPERATURE; +} + +void MixtureFugacityTP::setForcedSolutionBranch(int solnBranch) +{ + forcedState_ = solnBranch; +} + +int MixtureFugacityTP::forcedSolutionBranch() const +{ + return forcedState_; +} + +int MixtureFugacityTP::reportSolnBranchActual() const +{ + return iState_; +} + +// ---- Molar Thermodynamic Properties --------------------------- +double MixtureFugacityTP::enthalpy_mole() const +{ + _updateReferenceStateThermo(); + double h_ideal = RT() * mean_X(m_h0_RT); + double h_nonideal = hresid(); + return h_ideal + h_nonideal; +} + + +double MixtureFugacityTP::entropy_mole() const +{ + _updateReferenceStateThermo(); + double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() + - std::log(pressure()/refPressure())); + double sr_nonideal = sresid(); + return sr_ideal + sr_nonideal; +} + +// ---- Partial Molar Properties of the Solution ----------------- + +void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const +{ + getChemPotentials(muRT); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RT(); + } +} + +// ----- Thermodynamic Values for the Species Standard States States ---- + +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++) { + g[k] = RT() * (g[k] + tmp); + } +} + +void MixtureFugacityTP::getEnthalpy_RT(doublereal* hrt) const +{ + getEnthalpy_RT_ref(hrt); +} + +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++) { + sr[k] -= tmp; + } +} + +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++) { + grt[k] += tmp; + } +} + +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++) { + g[k] += tmp; + } +} + +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; + } +} + +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(); + } +} + +// ----- Thermodynamic Values for the Species Reference States ---- + +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); +} + +void MixtureFugacityTP::getGibbs_ref(doublereal* g) const +{ + const vector_fp& gibbsrt = gibbs_RT_ref(); + scale(gibbsrt.begin(), gibbsrt.end(), g, RT()); +} + +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(); + } +} + +void MixtureFugacityTP::setStateFromXML(const XML_Node& state) +{ + int doTP = 0; + string comp = getChildValue(state,"moleFractions"); + if (comp != "") { + // not overloaded in current object -> phase state is not calculated. + setMoleFractionsByName(comp); + doTP = 1; + } else { + comp = getChildValue(state,"massFractions"); + if (comp != "") { + // not overloaded in current object -> phase state is not calculated. + setMassFractionsByName(comp); + doTP = 1; + } + } + double t = temperature(); + if (state.hasChild("temperature")) { + t = getFloat(state, "temperature", "temperature"); + doTP = 1; + } + if (state.hasChild("pressure")) { + double p = getFloat(state, "pressure", "pressure"); + setState_TP(t, p); + } else if (state.hasChild("density")) { + double rho = getFloat(state, "density", "density"); + setState_TR(t, rho); + } else if (doTP) { + double rho = density(); + setState_TR(t, rho); + } +} + +bool MixtureFugacityTP::addSpecies(shared_ptr spec) +{ + bool added = ThermoPhase::addSpecies(spec); + if (added) { + if (m_kk == 1) { + moleFractions_.push_back(1.0); + } else { + moleFractions_.push_back(0.0); + } + m_h0_RT.push_back(0.0); + m_cp0_R.push_back(0.0); + m_g0_RT.push_back(0.0); + m_s0_R.push_back(0.0); + } + return added; +} + +void MixtureFugacityTP::setTemperature(const doublereal temp) +{ + Phase::setTemperature(temp); + _updateReferenceStateThermo(); + // depends on mole fraction and temperature + updateMixingExpressions(); + iState_ = phaseState(true); +} + +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. + + double t = temperature(); + double rhoNow = density(); + if (forcedState_ == FLUID_UNDEFINED) { + double rho = densityCalc(t, p, iState_, rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + } else { + if (rho < -1.5) { + rho = densityCalc(t, p, FLUID_UNDEFINED , rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } + } else if (forcedState_ == FLUID_GAS) { + // Normal density calculation + if (iState_ < FLUID_LIQUID_0) { + double rho = densityCalc(t, p, iState_, rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + if (iState_ >= FLUID_LIQUID_0) { + throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); + } + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } + } else if (forcedState_ > FLUID_LIQUID_0) { + if (iState_ >= FLUID_LIQUID_0) { + double rho = densityCalc(t, p, iState_, rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + if (iState_ == FLUID_GAS) { + throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); + } + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } + } +} + +void MixtureFugacityTP::compositionChanged() +{ + Phase::compositionChanged(); + getMoleFractions(moleFractions_.data()); + updateMixingExpressions(); +} + +void MixtureFugacityTP::getActivityConcentrations(doublereal* c) const +{ + 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 +{ + return pressure() * meanMolecularWeight() / (density() * RT()); +} + +doublereal MixtureFugacityTP::sresid() const +{ + throw NotImplementedError("MixtureFugacityTP::sresid"); +} + +doublereal MixtureFugacityTP::hresid() const +{ + throw NotImplementedError("MixtureFugacityTP::hresid"); +} + +doublereal MixtureFugacityTP::psatEst(doublereal TKelvin) const +{ + doublereal pcrit = critPressure(); + doublereal tt = critTemperature() / TKelvin; + if (tt < 1.0) { + return pcrit; + } + doublereal lpr = -0.8734*tt*tt - 3.4522*tt + 4.2918; + return pcrit*exp(lpr); +} + +doublereal MixtureFugacityTP::liquidVolEst(doublereal TKelvin, doublereal& pres) const +{ + throw NotImplementedError("MixtureFugacityTP::liquidVolEst"); +} + +doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, + int phase, doublereal rhoguess) +{ + doublereal tcrit = critTemperature(); + doublereal mmw = meanMolecularWeight(); + if (rhoguess == -1.0) { + if (phase != -1) { + if (TKelvin > tcrit) { + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } else { + if (phase == FLUID_GAS || phase == FLUID_SUPERCRIT) { + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } else if (phase >= 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 + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } + } + + double molarVolBase = mmw / rhoguess; + double molarVolLast = molarVolBase; + double vc = mmw / critDensity(); + + // molar volume of the spinodal at the current temperature and mole + // fractions. this will be updated as we go. + double molarVolSpinodal = vc; + bool conv = false; + + // We start on one side of the vc and stick with that side + bool gasSide = molarVolBase > vc; + if (gasSide) { + molarVolLast = (GasConstant * TKelvin)/presPa; + } else { + molarVolLast = liquidVolEst(TKelvin, presPa); + } + + // OK, now we do a small solve to calculate the molar volume given the T,P + // value. The algorithm is taken from dfind() + for (int n = 0; n < 200; n++) { + // Calculate the predicted reduced pressure, pred0, based on the current + // tau and dd. Calculate the derivative of the predicted pressure wrt + // the molar volume. This routine also returns the pressure, presBase + double presBase; + double dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase); + + // If dpdV is positive, then we are in the middle of the 2 phase region + // and beyond the spinodal stability curve. We need to adjust the + // initial guess outwards and start a new iteration. + if (dpdVBase >= 0.0) { + if (TKelvin > tcrit) { + throw CanteraError("MixtureFugacityTP::densityCalc", + "T > tcrit unexpectedly"); + } + + // TODO Spawn a calculation for the value of the spinodal point that + // is very accurate. Answer the question as to whether a + // solution is possible on the current side of the vapor dome. + if (gasSide) { + if (molarVolBase >= vc) { + molarVolSpinodal = molarVolBase; + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } else { + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } + } else { + if (molarVolBase <= vc) { + molarVolSpinodal = molarVolBase; + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } else { + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } + } + continue; + } + + // Check for convergence + if (fabs(presBase-presPa) < 1.0E-30 + 1.0E-8 * presPa) { + conv = true; + break; + } + + // Dampen and crop the update + doublereal dpdV = dpdVBase; + if (n < 10) { + dpdV = dpdVBase * 1.5; + } + + // Formulate the update to the molar volume by Newton's method. Then, + // crop it to a max value of 0.1 times the current volume + double delMV = - (presBase - presPa) / dpdV; + if ((!gasSide || delMV < 0.0) && fabs(delMV) > 0.2 * molarVolBase) { + delMV = delMV / fabs(delMV) * 0.2 * molarVolBase; + } + // Only go 1/10 the way towards the spinodal at any one time. + if (TKelvin < tcrit) { + if (gasSide) { + if (delMV < 0.0 && -delMV > 0.5 * (molarVolBase - molarVolSpinodal)) { + delMV = - 0.5 * (molarVolBase - molarVolSpinodal); + } + } else { + if (delMV > 0.0 && delMV > 0.5 * (molarVolSpinodal - molarVolBase)) { + delMV = 0.5 * (molarVolSpinodal - molarVolBase); + } + } + } + // updated the molar volume value + molarVolLast = molarVolBase; + molarVolBase += delMV; + + if (fabs(delMV/molarVolBase) < 1.0E-14) { + conv = true; + break; + } + + // Check for negative molar volumes + if (molarVolBase <= 0.0) { + molarVolBase = std::min(1.0E-30, fabs(delMV*1.0E-4)); + } + } + + // Check for convergence, and return 0.0 if it wasn't achieved. + double densBase = 0.0; + if (! conv) { + molarVolBase = 0.0; + throw CanteraError("MixtureFugacityTP::densityCalc", "Process did not converge"); + } else { + densBase = mmw / molarVolBase; + } + return densBase; +} + +void MixtureFugacityTP::setToEquilState(const doublereal* mu_RT) +{ + double tmp, tmp2; + _updateReferenceStateThermo(); + getGibbs_RT_ref(m_tmpV.data()); + + // Within the method, we protect against inf results if the exponent is too + // high. + // + // If it is too low, we set the partial pressure to zero. This capability is + // needed by the elemental potential method. + doublereal pres = 0.0; + double m_p0 = refPressure(); + for (size_t k = 0; k < m_kk; k++) { + tmp = -m_tmpV[k] + mu_RT[k]; + if (tmp < -600.) { + m_pp[k] = 0.0; + } + else if (tmp > 500.0) { + tmp2 = tmp / 500.; + tmp2 *= tmp2; + m_pp[k] = m_p0 * exp(500.) * tmp2; + } + else { + m_pp[k] = m_p0 * exp(tmp); + } + pres += m_pp[k]; + } + // set state + setState_PX(pres, &m_pp[0]); +} + +void MixtureFugacityTP::updateMixingExpressions() +{ +} + +int MixtureFugacityTP::corr0(doublereal TKelvin, doublereal pres, doublereal& densLiqGuess, + doublereal& densGasGuess, doublereal& liqGRT, doublereal& gasGRT) +{ + int retn = 0; + doublereal densLiq = densityCalc(TKelvin, pres, FLUID_LIQUID_0, densLiqGuess); + if (densLiq <= 0.0) { + retn = -1; + } else { + densLiqGuess = densLiq; + setState_TR(TKelvin, densLiq); + liqGRT = gibbs_mole() / RT(); + } + + doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, densGasGuess); + if (densGas <= 0.0) { + if (retn == -1) { + throw CanteraError("MixtureFugacityTP::corr0", + "Error occurred trying to find gas density at (T,P) = {} {}", + TKelvin, pres); + } + retn = -2; + } else { + densGasGuess = densGas; + setState_TR(TKelvin, densGas); + gasGRT = gibbs_mole() / RT(); + } + return retn; +} + +int MixtureFugacityTP::phaseState(bool checkState) const +{ + int state = iState_; + if (checkState) { + double t = temperature(); + double tcrit = critTemperature(); + double rhocrit = critDensity(); + if (t >= tcrit) { + return FLUID_SUPERCRIT; + } + double tmid = tcrit - 100.; + if (tmid < 0.0) { + tmid = tcrit / 2.0; + } + double pp = psatEst(tmid); + double mmw = meanMolecularWeight(); + double molVolLiqTmid = liquidVolEst(tmid, pp); + double molVolGasTmid = GasConstant * tmid / pp; + double densLiqTmid = mmw / molVolLiqTmid; + double densGasTmid = mmw / molVolGasTmid; + double densMidTmid = 0.5 * (densLiqTmid + densGasTmid); + doublereal rhoMid = rhocrit + (t - tcrit) * (rhocrit - densMidTmid) / (tcrit - tmid); + + double rho = density(); + int iStateGuess = FLUID_LIQUID_0; + if (rho < rhoMid) { + iStateGuess = FLUID_GAS; + } + double molarVol = mmw / rho; + double presCalc; + + double dpdv = dpdVCalc(t, molarVol, presCalc); + if (dpdv < 0.0) { + state = iStateGuess; + } else { + state = FLUID_UNSTABLE; + } + } + return state; +} + +doublereal MixtureFugacityTP::densSpinodalLiquid() const +{ + throw NotImplementedError("MixtureFugacityTP::densSpinodalLiquid"); +} + +doublereal MixtureFugacityTP::densSpinodalGas() const +{ + throw NotImplementedError("MixtureFugacityTP::densSpinodalGas"); +} + +doublereal MixtureFugacityTP::satPressure(doublereal TKelvin) +{ + doublereal molarVolGas; + doublereal molarVolLiquid; + return calculatePsat(TKelvin, molarVolGas, molarVolLiquid); +} + +doublereal MixtureFugacityTP::calculatePsat(doublereal TKelvin, doublereal& molarVolGas, + doublereal& molarVolLiquid) +{ + // The algorithm for this routine has undergone quite a bit of work. It + // probably needs more work. However, it seems now to be fairly robust. The + // key requirement is to find an initial pressure where both the liquid and + // the gas exist. This is not as easy as it sounds, and it gets exceedingly + // hard as the critical temperature is approached from below. Once we have + // this initial state, then we seek to equilibrate the Gibbs free energies + // of the gas and liquid and use the formula + // + // dp = VdG + // + // to create an update condition for deltaP using + // + // - (Gliq - Ggas) = (Vliq - Vgas) (deltaP) + // + // @TODO Suggestions for the future would be to switch it to an algorithm + // that uses the gas molar volume and the liquid molar volumes as the + // fundamental unknowns. + + // we need this because this is a non-const routine that is public + setTemperature(TKelvin); + double densSave = density(); + double tempSave = temperature(); + double pres; + doublereal mw = meanMolecularWeight(); + if (TKelvin < critTemperature()) { + pres = psatEst(TKelvin); + // trial value = Psat from correlation + doublereal volLiquid = liquidVolEst(TKelvin, pres); + double RhoLiquidGood = mw / volLiquid; + double RhoGasGood = pres * mw / (GasConstant * TKelvin); + doublereal delGRT = 1.0E6; + doublereal liqGRT, gasGRT; + + // First part of the calculation involves finding a pressure at which + // the gas and the liquid state coexists. + doublereal presLiquid = 0.; + doublereal presGas; + doublereal presBase = pres; + bool foundLiquid = false; + bool foundGas = false; + + doublereal densLiquid = densityCalc(TKelvin, presBase, FLUID_LIQUID_0, RhoLiquidGood); + if (densLiquid > 0.0) { + foundLiquid = true; + presLiquid = pres; + RhoLiquidGood = densLiquid; + } + if (!foundLiquid) { + for (int i = 0; i < 50; i++) { + pres = 1.1 * pres; + densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); + if (densLiquid > 0.0) { + foundLiquid = true; + presLiquid = pres; + RhoLiquidGood = densLiquid; + break; + } + } + } + + pres = presBase; + doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); + if (densGas <= 0.0) { + foundGas = false; + } else { + foundGas = true; + presGas = pres; + RhoGasGood = densGas; + } + if (!foundGas) { + for (int i = 0; i < 50; i++) { + pres = 0.9 * pres; + densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); + if (densGas > 0.0) { + foundGas = true; + presGas = pres; + RhoGasGood = densGas; + break; + } + } + } + + if (foundGas && foundLiquid && presGas != presLiquid) { + pres = 0.5 * (presLiquid + presGas); + bool goodLiq; + bool goodGas; + for (int i = 0; i < 50; i++) { + densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); + if (densLiquid <= 0.0) { + goodLiq = false; + } else { + goodLiq = true; + RhoLiquidGood = densLiquid; + presLiquid = pres; + } + densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); + if (densGas <= 0.0) { + goodGas = false; + } else { + goodGas = true; + RhoGasGood = densGas; + presGas = pres; + } + if (goodGas && goodLiq) { + break; + } + if (!goodLiq && !goodGas) { + pres = 0.5 * (pres + presLiquid); + } + if (goodLiq || goodGas) { + pres = 0.5 * (presLiquid + presGas); + } + } + } + if (!foundGas || !foundLiquid) { + warn_user("MixtureFugacityTP::calculatePsat", + "could not find a starting pressure; exiting."); + return 0.0; + } + if (presGas != presLiquid) { + warn_user("MixtureFugacityTP::calculatePsat", + "could not find a starting pressure; exiting"); + return 0.0; + } + + pres = presGas; + double presLast = pres; + double RhoGas = RhoGasGood; + double RhoLiquid = RhoLiquidGood; + + // Now that we have found a good pressure we can proceed with the algorithm. + for (int i = 0; i < 20; i++) { + int stab = corr0(TKelvin, pres, RhoLiquid, RhoGas, liqGRT, gasGRT); + if (stab == 0) { + presLast = pres; + delGRT = liqGRT - gasGRT; + doublereal delV = mw * (1.0/RhoLiquid - 1.0/RhoGas); + doublereal dp = - delGRT * GasConstant * TKelvin / delV; + + if (fabs(dp) > 0.1 * pres) { + if (dp > 0.0) { + dp = 0.1 * pres; + } else { + dp = -0.1 * pres; + } + } + pres += dp; + } else if (stab == -1) { + delGRT = 1.0E6; + if (presLast > pres) { + pres = 0.5 * (presLast + pres); + } else { + // we are stuck here - try this + pres = 1.1 * pres; + } + } else if (stab == -2) { + if (presLast < pres) { + pres = 0.5 * (presLast + pres); + } else { + // we are stuck here - try this + pres = 0.9 * pres; + } + } + molarVolGas = mw / RhoGas; + molarVolLiquid = mw / RhoLiquid; + + if (fabs(delGRT) < 1.0E-8) { + // converged + break; + } + } + + molarVolGas = mw / RhoGas; + molarVolLiquid = mw / RhoLiquid; + // Put the fluid in the desired end condition + setState_TR(tempSave, densSave); + return pres; + } else { + pres = critPressure(); + setState_TP(TKelvin, pres); + molarVolGas = mw / density(); + molarVolLiquid = molarVolGas; + setState_TR(tempSave, densSave); + } + return pres; +} + +doublereal MixtureFugacityTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const +{ + throw NotImplementedError("MixtureFugacityTP::pressureCalc"); +} + +doublereal MixtureFugacityTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const +{ + throw NotImplementedError("MixtureFugacityTP::dpdVCalc"); +} + +void MixtureFugacityTP::_updateReferenceStateThermo() const +{ + double Tnow = temperature(); + + // If the temperature has changed since the last time these + // properties were computed, recompute them. + if (m_tlast != Tnow) { + m_spthermo.update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]); + m_tlast = Tnow; + + // update the species Gibbs functions + for (size_t k = 0; k < m_kk; k++) { + m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k]; + } + doublereal pref = refPressure(); + if (pref <= 0.0) { + throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", "neg ref pressure"); + } + } +} + +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) const +{ + double tmp; + fill_n(Vroot, 3, 0.0); + if (T <= 0.0) { + throw CanteraError("MixtureFugacityTP::CubicSolve()", "negative temperature T = {}", T); + } + + double pc = omega_b * GasConstant * tc / b; + double vc = omega_vc * GasConstant * tc / pc; + + // 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::CubicSolve()", "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; + } + + // 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("PengRobinson::CubicSolve(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("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly 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("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly 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("PengRobinson::CubicSolve(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 index 073861174b..1f366a1111 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -1,948 +1,918 @@ -//! @file PengRobinson.cpp - -// This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. - -#include "cantera/thermo/PengRobinson.h" -#include "cantera/thermo/ThermoFactory.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() : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); -} - -PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - initThermoFile(infile, id_); -} - -void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("PengRobinson::calculateAlpha", - "Unknown species '{}'.", species); - } - - // Calculate value of kappa (independent of temperature) - // w is an acentric factor of species and must be specified in the CTI file - if (isnan(w)){ - throw CanteraError("PengRobinson::calculateAlpha", - "No acentric factor loaded."); - } else if (w <= 0.491) { - kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; - } else { - kappa_vec_[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 + kappa_vec_[k] * (1 - sqt_T_r); - alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; -} - -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); - } - size_t counter = k + m_kk * k; - a_coeff_vec(0, counter) = a; - // we store this locally because it is used below to calculate a_Alpha: - double aAlpha_k = a*alpha_vec_Curr_[k]; - aAlpha_coeff_vec(0, counter) = 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(a_coeff_vec(0, j + m_kk * j) * a); - double aAlpha_j = a*alpha_vec_Curr_[j]; - double a_Alpha = sqrt(aAlpha_j*aAlpha_k); - if (a_coeff_vec(0, j + m_kk * k) == 0) { - a_coeff_vec(0, j + m_kk * k) = a0kj; - aAlpha_coeff_vec(0, j + m_kk * k) = a_Alpha; - a_coeff_vec(0, k + m_kk * j) = a0kj; - aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; - } - } - a_coeff_vec.getRow(0, a_vec_Curr_.data()); - aAlpha_coeff_vec.getRow(0, aAlpha_vec_Curr_.data()); - b_vec_Curr_[k] = b; -} - -void PengRobinson::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double alpha) -{ - 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); - } - - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; - a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; - aAlpha_vec_Curr_[counter1] = aAlpha_vec_Curr_[counter2] = a0*alpha; -} - -// ------------Molar Thermodynamic Properties ------------------------- - -double PengRobinson::cp_mole() const -{ - _updateReferenceStateThermo(); - double T = temperature(); - double mv = molarVolume(); - double vpb = mv + (1 + M_SQRT2)*m_b_current; - double vmb = mv + (1 - M_SQRT2)*m_b_current; - pressureDerivatives(); - double cpref = GasConstant * mean_X(m_cp0_R); - double dHdT_V = cpref + mv * dpdT_ - GasConstant - + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); - return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_; -} - -double PengRobinson::cv_mole() const -{ - _updateReferenceStateThermo(); - double T = temperature(); - pressureDerivatives(); - return (cp_mole() + T* dpdT_* dpdT_ / 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_current - m_b_current * m_b_current; - double pp = GasConstant * T / (mv - m_b_current) - m_aAlpha_current / 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 T = temperature(); - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double vmb = mv - m_b_current; - 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; - ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) - + RTkelvin * b_vec_Curr_[k] / vmb - - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* b_vec_Curr_[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_RT(double* muRT) const -{ - getChemPotentials(muRT); - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RTkelvin; - } -} - -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_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - double pres = pressure(); - double refP = refPressure(); - double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); - - for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; - - mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) - + RTkelvin * log(mv / vmb) - + RTkelvin * b_vec_Curr_[k] / vmb - - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* b_vec_Curr_[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 dpdni_ - double T = temperature(); - double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double den2 = den*den; - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - dpdni_[k] = RTkelvin /vmb + RTkelvin * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / den - + 2 * vmb * m_aAlpha_current * b_vec_Curr_[k] / den2; - } - - double daAlphadT = daAlpha_dT(); - double fac = T * daAlphadT - m_aAlpha_current; - - pressureDerivatives(); - double fac2 = mv + T * dpdT_ / dpdV_; - double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; - for (size_t k = 0; k < m_kk; k++) { - double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac - + (mv * b_vec_Curr_[k]) /(m_b_current*den) * fac; - hbar[k] = hbar[k] + hE_v; - hbar[k] -= fac2 * 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_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double refP = refPressure(); - double daAlphadT = daAlpha_dT(); - double coeff1 = 0; - double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - - // 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(dalphadT_vec_Curr_[i] / alpha_vec_Curr_[i]); - } - m_pp[k] = m_pp[k] * dalphadT_vec_Curr_[k] / alpha_vec_Curr_[k]; - } - - - for (size_t k = 0; k < m_kk; k++) { - coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; - sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) - + GasConstant - + GasConstant * log(mv / vmb) - + GasConstant * b_vec_Curr_[k] / vmb - - coeff1* log(vpb2 / vmb2) / den1 - - b_vec_Curr_[k] * mv * daAlphadT / den2 / m_b_current; - } - pressureDerivatives(); - getPartialMolarVolumes(m_partialMolarVolumes.data()); - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -= m_partialMolarVolumes[k] * dpdT_; - } -} - -void PengRobinson::getPartialMolarIntEnergies(double* ubar) const -{ - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); -} - -void PengRobinson::getPartialMolarCp(double* cpbar) const -{ - getCp_R(cpbar); - scale(cpbar, cpbar+m_kk, cpbar, GasConstant); -} - -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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - } - } - - double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb = mv + m_b_current; - double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double fac2 = fac * fac; - double RTkelvin = RT(); - - for (size_t k = 0; k < m_kk; k++) { - double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * b_vec_Curr_[k] / vmb - + RTkelvin * m_b_current * b_vec_Curr_[k] /(vmb * vmb) - - 2 * mv * m_pp[k] / fac - + 2 * mv * vmb * m_aAlpha_current * b_vec_Curr_[k] / fac2 - ); - double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) - + m_aAlpha_current/fac - - 2 * mv* vpb *m_aAlpha_current / fac2 - ); - vbar[k] = num / denom; - } -} - -double PengRobinson::speciesCritTemperature(double a, double b) const -{ - double pc, tc, vc; - calcCriticalConditions(a, b, pc, tc, vc); - return tc; -} - -double PengRobinson::critTemperature() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return tc; -} - -double PengRobinson::critPressure() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc; -} - -double PengRobinson::critVolume() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return vc; -} - -double PengRobinson::critCompressibility() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc*vc/tc/GasConstant; -} - -double PengRobinson::critDensity() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - double mmw = meanMolecularWeight(); - return mmw / vc; -} - -void PengRobinson::setToEquilState(const double* mu_RT) -{ - double tmp, tmp2; - _updateReferenceStateThermo(); - getGibbs_RT_ref(m_tmpV.data()); - - // Within the method, we protect against inf results if the exponent is too - // high. - // - // If it is too low, we set the partial pressure to zero. This capability is - // needed by the elemental potential method. - double pres = 0.0; - double m_p0 = refPressure(); - for (size_t k = 0; k < m_kk; k++) { - tmp = -m_tmpV[k] + mu_RT[k]; - if (tmp < -600.) { - m_pp[k] = 0.0; - } else if (tmp > 500.0) { - tmp2 = tmp / 500.; - tmp2 *= tmp2; - m_pp[k] = m_p0 * exp(500.) * tmp2; - } else { - m_pp[k] = m_p0 * exp(tmp); - } - pres += m_pp[k]; - } - // set state - setState_PX(pres, &m_pp[0]); -} - -bool PengRobinson::addSpecies(shared_ptr spec) -{ - bool added = MixtureFugacityTP::addSpecies(spec); - if (added) { - a_vec_Curr_.resize(m_kk * m_kk, 0.0); - b_vec_Curr_.push_back(0.0); - a_vec_Curr_.push_back(0.0); - aAlpha_vec_Curr_.resize(m_kk * m_kk, 0.0); - aAlpha_vec_Curr_.push_back(0.0); - kappa_vec_.push_back(0.0); - - alpha_vec_Curr_.push_back(0.0); - a_coeff_vec.resize(1, m_kk * m_kk, 0.0); - aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); - dalphadT_vec_Curr_.push_back(0.0); - d2alphadT2_.push_back(0.0); - - m_pp.push_back(0.0); - m_tmpV.push_back(0.0); - m_partialMolarVolumes.push_back(0.0); - 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; - - } - - //Assuming no temperature dependence - 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"].as(); - if (eos.getString("model", "") != "Peng-Robinson") { - throw InputFileError("PengRobinson::initThermo", eos, - "Expected species equation of state to be 'Peng-Robinson', " - "but got '{}' instead", eos.getString("model", "")); - } - double a0 = 0, a1 = 0; - if (eos["a"].isScalar()) { - a0 = eos.convert("a", "Pa*m^6/kmol^2"); - } else { - auto avec = eos["a"].asVector(2); - a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); - a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K"); - } - double b = eos.convert("b", "m^3/kmol"); - // unitless acentric factor: - double w = eos.getDouble("w_ac",NAN); - - calculateAlpha(item.first, a0, b, w); - 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, a1 = 0; - if (item2.second.isScalar()) { - a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); - } else { - auto avec = item2.second.asVector(2); - a0 = units.convert(avec[0], "Pa*m^6/kmol^2"); - a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K"); - } - setBinaryCoeffs(item.first, item2.first, a0, a1); - } - } - } 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 (a_coeff_vec(0, k + m_kk * k) == 0.0) { - // coeffs[0] = a0, coeffs[1] = b; - vector coeffs = getCoeff(item.first); - - // Check if species was found in the database of critical - // properties, and assign the results - if (!isnan(coeffs[0])) { - // Assuming no temperature dependence (i.e. a1 = 0) - calculateAlpha(item.first, coeffs[0], coeffs[1], coeffs[2]); - setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); - } - } - } - } -} - -double PengRobinson::sresid() const -{ - double molarV = molarVolume(); - double hh = m_b_current / molarV; - double zz = z(); - double alpha_1 = daAlpha_dT(); - double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; - double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; - double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); - 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_current; - double vmb = molarV + (1 - M_SQRT2) *m_b_current; - double fac = 1 / (2.0 * M_SQRT2 * m_b_current); - return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); -} - -double PengRobinson::liquidVolEst(double T, double& presGuess) const -{ - double v = m_b_current * 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_current 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; - NSolns_ = solveCubic(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); - - double molarVolLast = Vroot_[0]; - if (NSolns_ >= 2) { - if (phaseRequested >= FLUID_LIQUID_0) { - molarVolLast = Vroot_[0]; - } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[2]; - } else { - if (volGuess > Vroot_[1]) { - molarVolLast = Vroot_[2]; - } else { - molarVolLast = Vroot_[0]; - } - } - } else if (NSolns_ == 1) { - if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else if (NSolns_ == -1) { - if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[0]; - } else if (T > tcrit) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else { - molarVolLast = 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_current, m_b_current, m_aAlpha_current, 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_current, m_b_current, m_aAlpha_current, 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::pressureCalc(double T, double molarVol) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - double pres = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; - return pres; -} - -double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current/ den; - - double vpb = molarVol + m_b_current; - double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); - return dpdv; -} - -void PengRobinson::pressureDerivatives() const -{ - double T = temperature(); - double mv = molarVolume(); - double pres; - - dpdV_ = dpdVCalc(T, mv, pres); - double vmb = mv - m_b_current; - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); -} - -void PengRobinson::updateMixingExpressions() -{ - double temp = temperature(); - //Update aAlpha_i - double sqt_alpha; - - // Update indiviual alpha - for (size_t j = 0; j < m_kk; j++) { - size_t counter = j * m_kk + j; - double critTemp_j = speciesCritTemperature(a_vec_Curr_[counter],b_vec_Curr_[j]); - sqt_alpha = 1 + kappa_vec_[j] * (1 - sqrt(temp / critTemp_j)); - alpha_vec_Curr_[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++) { - size_t counter = i * m_kk + j; - a_vec_Curr_[counter] = a_coeff_vec(0, counter); - aAlpha_vec_Curr_[counter] = sqrt(alpha_vec_Curr_[i] * alpha_vec_Curr_[j]) * a_coeff_vec(0, counter); - } - } - - m_b_current = 0.0; - m_a_current = 0.0; - m_aAlpha_current = 0.0; - - calculateAB(m_a_current,m_b_current,m_aAlpha_current); -} - -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] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - double a_vec_Curr = a_coeff_vec(0, counter); - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - aAlphaCalc += aAlpha_vec_Curr_[counter] * moleFractions_[i] * moleFractions_[j]; - } - } -} - -double PengRobinson::daAlpha_dT() const -{ - double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; - double coeff1, coeff2; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; - // Calculate first derivative of alpha for individual species - Tc = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); - sqtTr = sqrt(temperature() / Tc); //we need species critical temperature - coeff1 = 1 / (Tc*sqtTr); - coeff2 = sqtTr - 1; - k = kappa_vec_[i]; - dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); - } - //Calculate mixture derivative - for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; - for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j * m_kk + j; - temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alpha_vec_Curr_[i] * alpha_vec_Curr_[j])); - daAlphadT += moleFractions_[i] * moleFractions_[j] * temp - * (dalphadT_vec_Curr_[j] * alpha_vec_Curr_[i] + dalphadT_vec_Curr_[i] * alpha_vec_Curr_[j]); - } - } - return daAlphadT; -} - -double PengRobinson::d2aAlpha_dT2() const -{ - double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; - double Tcrit_i = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[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 = kappa_vec_[i]; - dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); - d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); - } - - //Calculate mixture derivative - for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; - alphai = alpha_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j + m_kk * j; - alphaj = alpha_vec_Curr_[j]; - alphaij = alphai * alphaj; - temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); - num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); - fac1 = -(0.5 / alphaij)*num*num; - fac2 = alphaj * d2alphadT2_[i] + alphai *d2alphadT2_[j] + 2. * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); //d2alphadT2_[counter1]; // - } - } - return d2aAlphadT2; -} - -void PengRobinson::calcCriticalConditions(double a, double b, - double& pc, double& tc, double& vc) const -{ - if (b <= 0.0) { - tc = 1000000.; - pc = 1.0E13; - vc = omega_vc * GasConstant * tc / pc; - return; - } - if (a <= 0.0) { - tc = 0.0; - pc = 0.0; - vc = 2.0 * b; - return; - } - tc = a * omega_b / (b * omega_a * GasConstant); - pc = omega_b * GasConstant * tc / 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); - - int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); - - return nSolnValues; -} - -} +//! @file PengRobinson.cpp + +// This file is part of Cantera. See License.txt in the top-level directory or +// at http://www.cantera.org/license.txt for license and copyright information. + +#include "cantera/thermo/PengRobinson.h" +#include "cantera/thermo/ThermoFactory.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() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); +} + +PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + initThermoFile(infile, id_); +} + +void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("PengRobinson::calculateAlpha", + "Unknown species '{}'.", species); + } + + // Calculate value of kappa (independent of temperature) + // w is an acentric factor of species and must be specified in the CTI file + if (isnan(w)){ + throw CanteraError("PengRobinson::calculateAlpha", + "No acentric factor loaded."); + } else if (w <= 0.491) { + kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; + } else { + kappa_vec_[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 + kappa_vec_[k] * (1 - sqt_T_r); + alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; +} + +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); + } + size_t counter = k + m_kk * k; + a_coeff_vec(0, counter) = a; + // we store this locally because it is used below to calculate a_Alpha: + double aAlpha_k = a*alpha_vec_Curr_[k]; + aAlpha_coeff_vec(0, counter) = 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(a_coeff_vec(0, j + m_kk * j) * a); + double aAlpha_j = a*alpha_vec_Curr_[j]; + double a_Alpha = sqrt(aAlpha_j*aAlpha_k); + if (a_coeff_vec(0, j + m_kk * k) == 0) { + a_coeff_vec(0, j + m_kk * k) = a0kj; + aAlpha_coeff_vec(0, j + m_kk * k) = a_Alpha; + a_coeff_vec(0, k + m_kk * j) = a0kj; + aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; + } + } + a_coeff_vec.getRow(0, a_vec_Curr_.data()); + aAlpha_coeff_vec.getRow(0, aAlpha_vec_Curr_.data()); + b_vec_Curr_[k] = b; +} + +void PengRobinson::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double alpha) +{ + 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); + } + + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; + aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; + a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; + aAlpha_vec_Curr_[counter1] = aAlpha_vec_Curr_[counter2] = a0*alpha; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +double PengRobinson::cp_mole() const +{ + _updateReferenceStateThermo(); + double T = temperature(); + double mv = molarVolume(); + double vpb = mv + (1 + M_SQRT2)*m_b_current; + double vmb = mv + (1 - M_SQRT2)*m_b_current; + pressureDerivatives(); + double cpref = GasConstant * mean_X(m_cp0_R); + double dHdT_V = cpref + mv * dpdT_ - GasConstant + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); + return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_; +} + +double PengRobinson::cv_mole() const +{ + _updateReferenceStateThermo(); + double T = temperature(); + pressureDerivatives(); + return (cp_mole() + T* dpdT_* dpdT_ / 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_current - m_b_current * m_b_current; + double pp = GasConstant * T / (mv - m_b_current) - m_aAlpha_current / 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 T = temperature(); + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double vmb = mv - m_b_current; + 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) + + RTkelvin * b_vec_Curr_[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* b_vec_Curr_[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_RT(double* muRT) const +{ + getChemPotentials(muRT); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RTkelvin; + } +} + +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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + double pres = pressure(); + double refP = refPressure(); + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + + mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + + RTkelvin * log(mv / vmb) + + RTkelvin * b_vec_Curr_[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* b_vec_Curr_[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 dpdni_ + double T = temperature(); + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double den2 = den*den; + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + dpdni_[k] = RTkelvin /vmb + RTkelvin * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + + 2 * vmb * m_aAlpha_current * b_vec_Curr_[k] / den2; + } + + double daAlphadT = daAlpha_dT(); + double fac = T * daAlphadT - m_aAlpha_current; + + pressureDerivatives(); + double fac2 = mv + T * dpdT_ / dpdV_; + double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac + + (mv * b_vec_Curr_[k]) /(m_b_current*den) * fac; + hbar[k] = hbar[k] + hE_v; + hbar[k] -= fac2 * 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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double refP = refPressure(); + double daAlphadT = daAlpha_dT(); + double coeff1 = 0; + double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + + // 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(dalphadT_vec_Curr_[i] / alpha_vec_Curr_[i]); + } + m_pp[k] = m_pp[k] * dalphadT_vec_Curr_[k] / alpha_vec_Curr_[k]; + } + + + for (size_t k = 0; k < m_kk; k++) { + coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; + sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) + + GasConstant + + GasConstant * log(mv / vmb) + + GasConstant * b_vec_Curr_[k] / vmb + - coeff1* log(vpb2 / vmb2) / den1 + - b_vec_Curr_[k] * mv * daAlphadT / den2 / m_b_current; + } + pressureDerivatives(); + getPartialMolarVolumes(m_partialMolarVolumes.data()); + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -= m_partialMolarVolumes[k] * dpdT_; + } +} + +void PengRobinson::getPartialMolarIntEnergies(double* ubar) const +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void PengRobinson::getPartialMolarCp(double* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + } + } + + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb = mv + m_b_current; + double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double fac2 = fac * fac; + double RTkelvin = RT(); + + for (size_t k = 0; k < m_kk; k++) { + double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * b_vec_Curr_[k] / vmb + + RTkelvin * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + - 2 * mv * m_pp[k] / fac + + 2 * mv * vmb * m_aAlpha_current * b_vec_Curr_[k] / fac2 + ); + double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) + + m_aAlpha_current/fac + - 2 * mv* vpb *m_aAlpha_current / fac2 + ); + vbar[k] = num / denom; + } +} + +double PengRobinson::speciesCritTemperature(double a, double b) const +{ + double pc, tc, vc; + calcCriticalConditions(a, b, pc, tc, vc); + return tc; +} + +double PengRobinson::critTemperature() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return tc; +} + +double PengRobinson::critPressure() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc; +} + +double PengRobinson::critVolume() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return vc; +} + +double PengRobinson::critCompressibility() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc*vc/tc/GasConstant; +} + +double PengRobinson::critDensity() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + double mmw = meanMolecularWeight(); + return mmw / vc; +} + +bool PengRobinson::addSpecies(shared_ptr spec) +{ + bool added = MixtureFugacityTP::addSpecies(spec); + if (added) { + a_vec_Curr_.resize(m_kk * m_kk, 0.0); + b_vec_Curr_.push_back(0.0); + a_vec_Curr_.push_back(0.0); + aAlpha_vec_Curr_.resize(m_kk * m_kk, 0.0); + aAlpha_vec_Curr_.push_back(0.0); + kappa_vec_.push_back(0.0); + + alpha_vec_Curr_.push_back(0.0); + a_coeff_vec.resize(1, m_kk * m_kk, 0.0); + aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); + dalphadT_vec_Curr_.push_back(0.0); + d2alphadT2_.push_back(0.0); + + m_pp.push_back(0.0); + m_tmpV.push_back(0.0); + m_partialMolarVolumes.push_back(0.0); + 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; + + } + + //Assuming no temperature dependence + 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"].as(); + if (eos.getString("model", "") != "Peng-Robinson") { + throw InputFileError("PengRobinson::initThermo", eos, + "Expected species equation of state to be 'Peng-Robinson', " + "but got '{}' instead", eos.getString("model", "")); + } + double a0 = 0, a1 = 0; + if (eos["a"].isScalar()) { + a0 = eos.convert("a", "Pa*m^6/kmol^2"); + } else { + auto avec = eos["a"].asVector(2); + a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); + a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K"); + } + double b = eos.convert("b", "m^3/kmol"); + // unitless acentric factor: + double w = eos.getDouble("w_ac",NAN); + + calculateAlpha(item.first, a0, b, w); + 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, a1 = 0; + if (item2.second.isScalar()) { + a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); + } else { + auto avec = item2.second.asVector(2); + a0 = units.convert(avec[0], "Pa*m^6/kmol^2"); + a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K"); + } + setBinaryCoeffs(item.first, item2.first, a0, a1); + } + } + } 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 (a_coeff_vec(0, k + m_kk * k) == 0.0) { + // coeffs[0] = a0, coeffs[1] = b; + vector coeffs = getCoeff(item.first); + + // Check if species was found in the database of critical + // properties, and assign the results + if (!isnan(coeffs[0])) { + // Assuming no temperature dependence (i.e. a1 = 0) + calculateAlpha(item.first, coeffs[0], coeffs[1], coeffs[2]); + setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); + } + } + } + } +} + +double PengRobinson::sresid() const +{ + double molarV = molarVolume(); + double hh = m_b_current / molarV; + double zz = z(); + double alpha_1 = daAlpha_dT(); + double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; + double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; + double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); + 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_current; + double vmb = molarV + (1 - M_SQRT2) *m_b_current; + double fac = 1 / (2.0 * M_SQRT2 * m_b_current); + return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); +} + +double PengRobinson::liquidVolEst(double T, double& presGuess) const +{ + double v = m_b_current * 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_current 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; + NSolns_ = solveCubic(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + + double molarVolLast = Vroot_[0]; + if (NSolns_ >= 2) { + if (phaseRequested >= FLUID_LIQUID_0) { + molarVolLast = Vroot_[0]; + } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[2]; + } else { + if (volGuess > Vroot_[1]) { + molarVolLast = Vroot_[2]; + } else { + molarVolLast = Vroot_[0]; + } + } + } else if (NSolns_ == 1) { + if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else if (NSolns_ == -1) { + if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[0]; + } else if (T > tcrit) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else { + molarVolLast = 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_current, m_b_current, m_aAlpha_current, 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_current, m_b_current, m_aAlpha_current, 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::pressureCalc(double T, double molarVol) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + double pres = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; + return pres; +} + +double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current/ den; + + double vpb = molarVol + m_b_current; + double vmb = molarVol - m_b_current; + double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); + return dpdv; +} + +void PengRobinson::pressureDerivatives() const +{ + double T = temperature(); + double mv = molarVolume(); + double pres; + + dpdV_ = dpdVCalc(T, mv, pres); + double vmb = mv - m_b_current; + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); +} + +void PengRobinson::updateMixingExpressions() +{ + double temp = temperature(); + //Update aAlpha_i + double sqt_alpha; + + // Update indiviual alpha + for (size_t j = 0; j < m_kk; j++) { + size_t counter = j * m_kk + j; + double critTemp_j = speciesCritTemperature(a_vec_Curr_[counter],b_vec_Curr_[j]); + sqt_alpha = 1 + kappa_vec_[j] * (1 - sqrt(temp / critTemp_j)); + alpha_vec_Curr_[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++) { + size_t counter = i * m_kk + j; + a_vec_Curr_[counter] = a_coeff_vec(0, counter); + aAlpha_vec_Curr_[counter] = sqrt(alpha_vec_Curr_[i] * alpha_vec_Curr_[j]) * a_coeff_vec(0, counter); + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + m_aAlpha_current = 0.0; + + calculateAB(m_a_current,m_b_current,m_aAlpha_current); +} + +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] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + double a_vec_Curr = a_coeff_vec(0, counter); + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += aAlpha_vec_Curr_[counter] * moleFractions_[i] * moleFractions_[j]; + } + } +} + +double PengRobinson::daAlpha_dT() const +{ + double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; + double coeff1, coeff2; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + // Calculate first derivative of alpha for individual species + Tc = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); + sqtTr = sqrt(temperature() / Tc); //we need species critical temperature + coeff1 = 1 / (Tc*sqtTr); + coeff2 = sqtTr - 1; + k = kappa_vec_[i]; + dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + } + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j * m_kk + j; + temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alpha_vec_Curr_[i] * alpha_vec_Curr_[j])); + daAlphadT += moleFractions_[i] * moleFractions_[j] * temp + * (dalphadT_vec_Curr_[j] * alpha_vec_Curr_[i] + dalphadT_vec_Curr_[i] * alpha_vec_Curr_[j]); + } + } + return daAlphadT; +} + +double PengRobinson::d2aAlpha_dT2() const +{ + double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + double Tcrit_i = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[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 = kappa_vec_[i]; + dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); + } + + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + alphai = alpha_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j + m_kk * j; + alphaj = alpha_vec_Curr_[j]; + alphaij = alphai * alphaj; + temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); + num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); + fac1 = -(0.5 / alphaij)*num*num; + fac2 = alphaj * d2alphadT2_[i] + alphai *d2alphadT2_[j] + 2. * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); //d2alphadT2_[counter1]; // + } + } + return d2aAlphadT2; +} + +void PengRobinson::calcCriticalConditions(double a, double b, + double& pc, double& tc, double& vc) const +{ + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + tc = a * omega_b / (b * omega_a * GasConstant); + pc = omega_b * GasConstant * tc / 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); + + int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, aAlpha, Vroot, an, bn, cn, dn, tc); + + return nSolnValues; +} + +} diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 455c1e067c..e8bec742cb 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -1085,3 +1085,1102 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl } } +======= +//! @file RedlichKwongMFTP.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/RedlichKwongMFTP.h" +#include "cantera/thermo/ThermoFactory.h" +#include "cantera/base/stringUtils.h" +#include "cantera/base/ctml.h" + +#include + +#include + +using namespace std; +namespace bmt = boost::math::tools; + +namespace Cantera +{ + +const doublereal RedlichKwongMFTP::omega_a = 4.27480233540E-01; +const doublereal RedlichKwongMFTP::omega_b = 8.66403499650E-02; +const doublereal RedlichKwongMFTP::omega_vc = 3.33333333333333E-01; + +RedlichKwongMFTP::RedlichKwongMFTP() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); +} + +RedlichKwongMFTP::RedlichKwongMFTP(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + initThermoFile(infile, id_); +} + +RedlichKwongMFTP::RedlichKwongMFTP(XML_Node& phaseRefRoot, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + importPhase(phaseRefRoot, this); +} + +void RedlichKwongMFTP::setSpeciesCoeffs(const std::string& species, + double a0, double a1, double b) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + + if (a1 != 0.0) { + m_formTempParam = 1; // expression is temperature-dependent + } + + size_t counter = k + m_kk * k; + a_coeff_vec(0, counter) = a0; + a_coeff_vec(1, counter) = a1; + + // standard mixing rule for cross-species interaction term + for (size_t j = 0; j < m_kk; j++) { + if (k == j) { + continue; + } + + // a_coeff_vec(0) is initialized to NaN to mark uninitialized species + if (isnan(a_coeff_vec(0, j + m_kk * j))) { + // The diagonal element of the jth species has not yet been defined. + continue; + } else if (isnan(a_coeff_vec(0, j + m_kk * k))) { + // Only use the mixing rules if the off-diagonal element has not already been defined by a + // user-specified crossFluidParameters entry: + double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a0); + double a1kj = sqrt(a_coeff_vec(1, j + m_kk * j) * a1); + a_coeff_vec(0, j + m_kk * k) = a0kj; + a_coeff_vec(1, j + m_kk * k) = a1kj; + a_coeff_vec(0, k + m_kk * j) = a0kj; + a_coeff_vec(1, k + m_kk * j) = a1kj; + } + } + a_coeff_vec.getRow(0, a_vec_Curr_.data()); + b_vec_Curr_[k] = b; +} + +void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1) +{ + size_t ki = speciesIndex(species_i); + if (ki == npos) { + throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_i); + } + size_t kj = speciesIndex(species_j); + if (kj == npos) { + throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_j); + } + + if (a1 != 0.0) { + m_formTempParam = 1; // expression is temperature-dependent + } + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; + a_coeff_vec(1, counter1) = a_coeff_vec(1, counter2) = a1; + a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +doublereal RedlichKwongMFTP::cp_mole() const +{ + _updateReferenceStateThermo(); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal vpb = mv + m_b_current; + pressureDerivatives(); + doublereal cpref = GasConstant * mean_X(m_cp0_R); + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + doublereal dHdT_V = (cpref + mv * dpdT_ - GasConstant - 1.0 / (2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv) * fac + +1.0/(m_b_current * sqt) * log(vpb/mv) * (-0.5 * dadt)); + return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; +} + +doublereal RedlichKwongMFTP::cv_mole() const +{ + _updateReferenceStateThermo(); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal vpb = mv + m_b_current; + doublereal cvref = GasConstant * (mean_X(m_cp0_R) - 1.0); + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + return (cvref - 1.0/(2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv)*fac + +1.0/(m_b_current * sqt) * log(vpb/mv)*(-0.5*dadt)); +} + +doublereal RedlichKwongMFTP::pressure() const +{ + _updateReferenceStateThermo(); + + // Get a copy of the private variables stored in the State object + doublereal T = temperature(); + double molarV = meanMolecularWeight() / density(); + double pp = GasConstant * T/(molarV - m_b_current) - m_a_current/(sqrt(T) * molarV * (molarV + m_b_current)); + return pp; +} + +doublereal RedlichKwongMFTP::standardConcentration(size_t k) const +{ + getStandardVolumes(m_tmpV.data()); + return 1.0 / m_tmpV[k]; +} + +void RedlichKwongMFTP::getActivityCoefficients(doublereal* ac) const +{ + doublereal mv = molarVolume(); + doublereal sqt = sqrt(temperature()); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + doublereal pres = pressure(); + + for (size_t k = 0; k < m_kk; k++) { + ac[k] = (- RT() * log(pres * mv / RT()) + + RT() * log(mv / vmb) + + RT() * b_vec_Curr_[k] / vmb + - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) + + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) + - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) + ); + } + for (size_t k = 0; k < m_kk; k++) { + ac[k] = exp(ac[k]/RT()); + } +} + +// ---- Partial Molar Properties of the Solution ----------------- + +void RedlichKwongMFTP::getChemPotentials_RT(doublereal* muRT) const +{ + getChemPotentials(muRT); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RT(); + } +} + +void RedlichKwongMFTP::getChemPotentials(doublereal* mu) const +{ + getGibbs_ref(mu); + for (size_t k = 0; k < m_kk; k++) { + double xx = std::max(SmallNumber, moleFraction(k)); + mu[k] += RT()*(log(xx)); + } + + doublereal mv = molarVolume(); + doublereal sqt = sqrt(temperature()); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + doublereal pres = pressure(); + doublereal refP = refPressure(); + + for (size_t k = 0; k < m_kk; k++) { + mu[k] += (RT() * log(pres/refP) - RT() * log(pres * mv / RT()) + + RT() * log(mv / vmb) + + RT() * b_vec_Curr_[k] / vmb + - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) + + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) + - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) + ); + } +} + +void RedlichKwongMFTP::getPartialMolarEnthalpies(doublereal* hbar) const +{ + // First we get the reference state contributions + getEnthalpy_RT_ref(hbar); + scale(hbar, hbar+m_kk, hbar, RT()); + + // We calculate dpdni_ + doublereal TKelvin = temperature(); + doublereal mv = molarVolume(); + doublereal sqt = sqrt(TKelvin); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + dpdni_[k] = RT()/vmb + RT() * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / (sqt * mv * vpb) + + m_a_current * b_vec_Curr_[k]/(sqt * mv * vpb * vpb); + } + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += 2.0 * moleFractions_[i] * TKelvin * a_coeff_vec(1,counter) - 3.0 * moleFractions_[i] * a_vec_Curr_[counter]; + } + } + + pressureDerivatives(); + doublereal fac2 = mv + TKelvin * dpdT_ / dpdV_; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = (mv * dpdni_[k] - RT() - b_vec_Curr_[k]/ (m_b_current * m_b_current * sqt) * log(vpb/mv)*fac + + 1.0 / (m_b_current * sqt) * log(vpb/mv) * m_tmpV[k] + + b_vec_Curr_[k] / vpb / (m_b_current * sqt) * fac); + hbar[k] = hbar[k] + hE_v; + hbar[k] -= fac2 * dpdni_[k]; + } +} + +void RedlichKwongMFTP::getPartialMolarEntropies(doublereal* sbar) const +{ + getEntropy_R_ref(sbar); + scale(sbar, sbar+m_kk, sbar, GasConstant); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal refP = refPressure(); + + for (size_t k = 0; k < m_kk; k++) { + doublereal xx = std::max(SmallNumber, moleFraction(k)); + sbar[k] += GasConstant * (- log(xx)); + } + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); + } + } + + doublereal dadt = da_dt(); + doublereal fac = dadt - m_a_current / (2.0 * TKelvin); + doublereal vmb = mv - m_b_current; + doublereal vpb = mv + m_b_current; + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -=(GasConstant * log(GasConstant * TKelvin / (refP * mv)) + + GasConstant + + GasConstant * log(mv/vmb) + + GasConstant * b_vec_Curr_[k]/vmb + + m_pp[k]/(m_b_current * TKelvin * sqt) * log(vpb/mv) + - 2.0 * m_tmpV[k]/(m_b_current * sqt) * log(vpb/mv) + + b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) * fac + - 1.0 / (m_b_current * sqt) * b_vec_Curr_[k] / vpb * fac + ); + } + + pressureDerivatives(); + getPartialMolarVolumes(m_partialMolarVolumes.data()); + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -= -m_partialMolarVolumes[k] * dpdT_; + } +} + +void RedlichKwongMFTP::getPartialMolarIntEnergies(doublereal* ubar) const +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void RedlichKwongMFTP::getPartialMolarCp(doublereal* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); + } + } + + doublereal sqt = sqrt(temperature()); + doublereal mv = molarVolume(); + doublereal vmb = mv - m_b_current; + doublereal vpb = mv + m_b_current; + for (size_t k = 0; k < m_kk; k++) { + doublereal num = (RT() + RT() * m_b_current/ vmb + RT() * b_vec_Curr_[k] / vmb + + RT() * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + - 2.0 * m_pp[k] / (sqt * vpb) + + m_a_current * b_vec_Curr_[k] / (sqt * vpb * vpb) + ); + doublereal denom = (pressure() + RT() * m_b_current/(vmb * vmb) - m_a_current / (sqt * vpb * vpb) + ); + vbar[k] = num / denom; + } +} + +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); + if (added) { + a_vec_Curr_.resize(m_kk * m_kk, 0.0); + + // Initialize a_vec and b_vec to NaN, to screen for species with + // pureFluidParameters which are undefined in the input file: + b_vec_Curr_.push_back(NAN); + a_coeff_vec.resize(2, m_kk * m_kk, NAN); + + m_pp.push_back(0.0); + m_tmpV.push_back(0.0); + m_partialMolarVolumes.push_back(0.0); + dpdni_.push_back(0.0); + } + return added; +} + +void RedlichKwongMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) +{ + if (phaseNode.hasChild("thermo")) { + XML_Node& thermoNode = phaseNode.child("thermo"); + std::string model = thermoNode["model"]; + if (model != "RedlichKwong" && model != "RedlichKwongMFTP") { + throw CanteraError("RedlichKwongMFTP::initThermoXML", + "Unknown thermo model : " + model); + } + + // Reset any coefficients which may have been set using values from + // 'critProperties.xml' as part of non-XML initialization, so that + // off-diagonal elements can be correctly initialized + a_coeff_vec.data().assign(a_coeff_vec.data().size(), NAN); + + // Go get all of the coefficients and factors in the + // activityCoefficients XML block + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + + // Loop through the children and read out fluid parameters. Process + // all the pureFluidParameters, first: + // Loop back through the "activityCoefficients" children and process the + // crossFluidParameters in the XML tree: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { + readXMLPureFluid(xmlACChild); + } else if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { + readXMLCrossFluid(xmlACChild); + } + } + } + // If any species exist which have undefined pureFluidParameters, + // search the database in 'critProperties.xml' to find critical + // temperature and pressure to calculate a and b. + + // Loop through all species in the CTI file + size_t iSpecies = 0; + + for (size_t i = 0; i < m_kk; i++) { + string iName = speciesName(i); + + // Get the index of the species + iSpecies = speciesIndex(iName); + + // Check if a and b are already populated (only the diagonal elements of a). + size_t counter = iSpecies + m_kk * iSpecies; + + // If not, then search the database: + if (isnan(a_coeff_vec(0, counter))) { + + vector coeffArray; + + // Search the database for the species name and calculate + // coefficients a and b, from critical properties: + // coeffArray[0] = a0, coeffArray[1] = b; + coeffArray = getCoeff(iName); + + // Check if species was found in the database of critical properties, + // and assign the results + if (!isnan(coeffArray[0])) { + //Assuming no temperature dependence (i,e a1 = 0) + setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); + } + } + } + } + + MixtureFugacityTP::initThermoXML(phaseNode, id); +} + +void RedlichKwongMFTP::initThermo() +{ + for (auto& item : m_species) { + // Read a and b coefficients 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", "Redlich-Kwong"); + double a0 = 0, a1 = 0; + if (eos["a"].isScalar()) { + a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); + } else { + auto avec = eos["a"].asVector(2); + a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); + a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); + } + double b = eos.convert("b", "m^3/kmol"); + setSpeciesCoeffs(item.first, a0, a1, b); + 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, a1 = 0; + if (item2.second.isScalar()) { + a0 = units.convert(item2.second, "Pa*m^6/kmol^2*K^0.5"); + } else { + auto avec = item2.second.asVector(2); + a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); + a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); + } + setBinaryCoeffs(item.first, item2.first, a0, a1); + } + } + } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { + // coeffs[0] = a0, coeffs[1] = b; + vector coeffs = getCoeff(item.first); + + // Check if species was found in the database of critical + // properties, and assign the results + if (!isnan(coeffs[0])) { + // Assuming no temperature dependence (i.e. a1 = 0) + setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); + } + } + } + } +} + +vector RedlichKwongMFTP::getCoeff(const std::string& iName) +{ + vector_fp spCoeff{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 specie iName to current database species + // dbName: + if (iNameLower == dbName) { + // Read from database and calculate a and b coefficients + double vParams; + double T_crit=0.; + double P_crit=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("RedlichKwongMFTP::getCoeff", + "Critical Temperature must be positive "); + } + T_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::getCoeff", + "Critical Temperature not in database "); + } + 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("RedlichKwongMFTP::getCoeff", + "Critical Pressure must be positive "); + } + P_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::getCoeff", + "Critical Pressure not in database "); + } + + //Assuming no temperature dependence + spCoeff[0] = omega_a * pow(GasConstant, 2) * pow(T_crit, 2.5) / P_crit; //coeff a + spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b + break; + } + } + return spCoeff; +} + +void RedlichKwongMFTP::readXMLPureFluid(XML_Node& pureFluidParam) +{ + string xname = pureFluidParam.name(); + if (xname != "pureFluidParameters") { + throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", + "Incorrect name for processing this routine: " + xname); + } + + double a0 = 0.0; + double a1 = 0.0; + double b = 0.0; + for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { + XML_Node& xmlChild = pureFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + string iModel = toLowerCopy(xmlChild.attrib("model")); + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + + if (iModel == "constant" && vParams.size() == 1) { + a0 = vParams[0]; + a1 = 0; + } else if (iModel == "linear_a" && vParams.size() == 2) { + a0 = vParams[0]; + a1 = vParams[1]; + } else { + throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", + "unknown model or incorrect number of parameters"); + } + + } else if (nodeName == "b_coeff") { + b = getFloatCurrent(xmlChild, "toSI"); + } + } + setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, a1, b); +} + +void RedlichKwongMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) +{ + string xname = CrossFluidParam.name(); + if (xname != "crossFluidParameters") { + throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", + "Incorrect name for processing this routine: " + xname); + } + + string iName = CrossFluidParam.attrib("species1"); + string jName = CrossFluidParam.attrib("species2"); + + size_t num = CrossFluidParam.nChildren(); + for (size_t iChild = 0; iChild < num; iChild++) { + XML_Node& xmlChild = CrossFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + string iModel = toLowerCopy(xmlChild.attrib("model")); + if (iModel == "constant" && vParams.size() == 1) { + setBinaryCoeffs(iName, jName, vParams[0], 0.0); + } else if (iModel == "linear_a") { + setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); + } else { + throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", + "unknown model ({}) or wrong number of parameters ({})", + iModel, vParams.size()); + } + } + } +} + +void RedlichKwongMFTP::setParametersFromXML(const XML_Node& thermoNode) +{ + MixtureFugacityTP::setParametersFromXML(thermoNode); + std::string model = thermoNode["model"]; +} + +doublereal RedlichKwongMFTP::sresid() const +{ + // note this agrees with tpx + doublereal rho = density(); + doublereal mmw = meanMolecularWeight(); + doublereal molarV = mmw / rho; + double hh = m_b_current / molarV; + doublereal zz = z(); + doublereal dadt = da_dt(); + doublereal T = temperature(); + doublereal sqT = sqrt(T); + doublereal fac = dadt - m_a_current / (2.0 * T); + double sresid_mol_R = log(zz*(1.0 - hh)) + log(1.0 + hh) * fac / (sqT * GasConstant * m_b_current); + return GasConstant * sresid_mol_R; +} + +doublereal RedlichKwongMFTP::hresid() const +{ + // note this agrees with tpx + doublereal rho = density(); + doublereal mmw = meanMolecularWeight(); + doublereal molarV = mmw / rho; + double hh = m_b_current / molarV; + doublereal zz = z(); + doublereal dadt = da_dt(); + doublereal T = temperature(); + doublereal sqT = sqrt(T); + doublereal fac = T * dadt - 3.0 *m_a_current / (2.0); + return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqT * m_b_current); +} + +doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGuess) const +{ + double v = m_b_current * 1.1; + double atmp; + double btmp; + calculateAB(TKelvin, atmp, btmp); + doublereal pres = std::max(psatEst(TKelvin), presGuess); + double Vroot[3]; + bool foundLiq = false; + int m = 0; + while (m < 100 && !foundLiq) { + int nsol = CubicCall(TKelvin, pres, atmp, btmp, 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; +} + +doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, int phaseRequested, doublereal rhoguess) +{ + // It's necessary to set the temperature so that m_a_current is set correctly. + setTemperature(TKelvin); + double tcrit = critTemperature(); + double mmw = meanMolecularWeight(); + if (rhoguess == -1.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 + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } + + + doublereal volguess = mmw / rhoguess; + NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); + + doublereal molarVolLast = Vroot_[0]; + if (NSolns_ >= 2) { + if (phaseRequested >= FLUID_LIQUID_0) { + molarVolLast = Vroot_[0]; + } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[2]; + } else { + if (volguess > Vroot_[1]) { + molarVolLast = Vroot_[2]; + } else { + molarVolLast = Vroot_[0]; + } + } + } else if (NSolns_ == 1) { + if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else if (NSolns_ == -1) { + if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[0]; + } else if (TKelvin > tcrit) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else { + molarVolLast = Vroot_[0]; + return -1.0; + } + return mmw / molarVolLast; +} + +doublereal RedlichKwongMFTP::densSpinodalLiquid() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); + + doublereal mmw = meanMolecularWeight(); + return mmw / (0.5 * (vv.first + vv.second)); +} + +doublereal RedlichKwongMFTP::densSpinodalGas() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); + + doublereal mmw = meanMolecularWeight(); + 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); + presCalc = GasConstant * TKelvin / (molarVol - m_b_current) + - m_a_current / (sqt * molarVol * (molarVol + m_b_current)); + + doublereal vpb = molarVol + m_b_current; + doublereal vmb = molarVol - m_b_current; + doublereal dpdv = (- GasConstant * TKelvin / (vmb * vmb) + + m_a_current * (2 * molarVol + m_b_current) / (sqt * molarVol * molarVol * vpb * vpb)); + return dpdv; +} + +void RedlichKwongMFTP::pressureDerivatives() const +{ + doublereal TKelvin = temperature(); + doublereal mv = molarVolume(); + doublereal pres; + + dpdV_ = dpdVCalc(TKelvin, mv, pres); + doublereal sqt = sqrt(TKelvin); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + doublereal dadt = da_dt(); + doublereal fac = dadt - m_a_current/(2.0 * TKelvin); + dpdT_ = (GasConstant / vmb - fac / (sqt * mv * vpb)); +} + +void RedlichKwongMFTP::updateMixingExpressions() +{ + double temp = temperature(); + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + a_vec_Curr_[counter] = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; + } + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + for (size_t i = 0; i < m_kk; i++) { + m_b_current += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + } + } + if (isnan(m_b_current)) { + // One or more species do not have specified coefficients. + fmt::memory_buffer b; + for (size_t k = 0; k < m_kk; k++) { + if (isnan(b_vec_Curr_[k])) { + if (b.size() > 0) { + format_to(b, ", {}", speciesName(k)); + } else { + format_to(b, "{}", speciesName(k)); + } + } + } + throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", + "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); + } +} + +void RedlichKwongMFTP::calculateAB(doublereal temp, doublereal& aCalc, doublereal& bCalc) const +{ + bCalc = 0.0; + aCalc = 0.0; + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + bCalc += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + doublereal a_vec_Curr = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + } + } + } else { + for (size_t i = 0; i < m_kk; i++) { + bCalc += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + doublereal a_vec_Curr = a_coeff_vec(0,counter); + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + } + } + } +} + +doublereal RedlichKwongMFTP::da_dt() const +{ + doublereal dadT = 0.0; + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + dadT+= a_coeff_vec(1,counter) * moleFractions_[i] * moleFractions_[j]; + } + } + } + return dadT; +} + +void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, + doublereal& pc, doublereal& tc, doublereal& vc) const +{ + if (m_formTempParam != 0) { + a = a0_coeff; + } + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + double tmp = a * omega_b / (b * omega_a * GasConstant); + double pp = 2./3.; + doublereal sqrttc, f, dfdt, deltatc; + + if (m_formTempParam == 0) { + tc = pow(tmp, pp); + } else { + tc = pow(tmp, pp); + for (int j = 0; j < 10; j++) { + sqrttc = sqrt(tc); + f = omega_a * b * GasConstant * tc * sqrttc / omega_b - aT_coeff * tc - a0_coeff; + dfdt = 1.5 * omega_a * b * GasConstant * sqrttc / omega_b - aT_coeff; + deltatc = - f / dfdt; + tc += deltatc; + } + if (deltatc > 0.1) { + throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); + } + } + + pc = omega_b * GasConstant * tc / b; + vc = omega_vc * GasConstant * tc / pc; +} + +int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const +{ + + // Derive the coefficients of the cubic polynomial to solve. + doublereal an = 1.0; + 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); + double pp = 2./3.; + double tc = pow(tmp, pp); + + int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc); + + return nSolnValues; +} + +} +>>>>>>> 6a88472a9... Moved setToEquil function to the parent class, 'MixtureFugacityTP". diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 7814f426f9..a6bc2f6e89 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -1,312 +1,312 @@ -#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/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(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] = { - -457361607.71983075, - -457350560.54839599, - -457340699.25698096, - -457331842.5539279, - -457323844.32100844, - -457316585.4752928, - -457309967.99120748, - -457303910.44199038, - -457298344.62820804 - }; - - 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 p1[6] = { - 1.7084253549322079e+002, - 1.6543121742659784e+002, - 1.6066148681014121e+002, - 1.5639259178086871e+002, - 1.525268502259365e+002, - 1.4899341020317422e+002 - }; - // Phase change between temperatures 4 & 5: - const double p2[6] = { - 7.3025772038910179e+002, - 7.0307777665949902e+002, - 6.7179381832541878e+002, - 6.3389192023192868e+002, - 5.8250166044528487e+002, - 3.8226318921022073e+002 - }; - // Supercritical; no discontinuity in rho values: - const double p3[6] = { - 7.8626284773748239e+002, - 7.6743023689871097e+002, - 7.4747463917603955e+002, - 7.2620055080831412e+002, - 7.0335270498118734e+002, - 6.7859003092723128e+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(),p1[i],1.e-8); - - test_phase->setState_TP(temp, 7388370.); - EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); - - test_phase->setState_TP(temp, 9236712.5); - EXPECT_NEAR(test_phase->density(),p3[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 99.9%, balance H2). - * 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<10; i++) - { - const double temp = 296 + i * 2; - 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); - } - } -} - -}; +#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/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(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] = { + -457361607.71983075, + -457350560.54839599, + -457340699.25698096, + -457331842.5539279, + -457323844.32100844, + -457316585.4752928, + -457309967.99120748, + -457303910.44199038, + -457298344.62820804 + }; + + 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 p1[6] = { + 1.7084253549322079e+002, + 1.6543121742659784e+002, + 1.6066148681014121e+002, + 1.5639259178086871e+002, + 1.525268502259365e+002, + 1.4899341020317422e+002 + }; + // Phase change between temperatures 4 & 5: + const double p2[6] = { + 7.3025772038910179e+002, + 7.0307777665949902e+002, + 6.7179381832541878e+002, + 6.3389192023192868e+002, + 5.8250166044528487e+002, + 3.8226318921022073e+002 + }; + // Supercritical; no discontinuity in rho values: + const double p3[6] = { + 7.8626284773748239e+002, + 7.6743023689871097e+002, + 7.4747463917603955e+002, + 7.2620055080831412e+002, + 7.0335270498118734e+002, + 6.7859003092723128e+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(),p1[i],1.e-8); + + test_phase->setState_TP(temp, 7388370.); + EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); + + test_phase->setState_TP(temp, 9236712.5); + EXPECT_NEAR(test_phase->density(),p3[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 99.9%, balance H2). + * 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<10; i++) + { + const double temp = 296 + i * 2; + 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); + } + } +} + +}; From e6f6b9d89a050682bd14109436ee59d1b7294eb9 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 11:05:57 -0600 Subject: [PATCH 035/110] Removing dependancy of EoS specific omega_a, omega_b parameters from solveCubic function in MixtureFugacityTP class. --- include/cantera/thermo/MixtureFugacityTP.h | 14 +- include/cantera/thermo/PengRobinson.h | 3 - src/thermo/MixtureFugacityTP.cpp | 9 +- src/thermo/PengRobinson.cpp | 4 +- src/thermo/RedlichKwongMFTP.cpp | 1101 ++++++++++++++++++++ 5 files changed, 1106 insertions(+), 25 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 1037e5f8de..e687da4ab9 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -528,7 +528,7 @@ class MixtureFugacityTP : public ThermoPhase */ 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) const; + double bn, double cn, double dn, double tc, double vc) const; //@} @@ -564,18 +564,6 @@ class MixtureFugacityTP : public ThermoPhase mutable vector_fp m_s0_R; public: - //! 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; - //! Temporary storage - length = m_kk. mutable vector_fp m_pp; }; diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 1e17036650..c512e84b24 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -316,9 +316,6 @@ class PengRobinson : public MixtureFugacityTP double Vroot_[3]; - //! Temporary storage - length = m_kk. - //mutable vector_fp m_pp; - //! Temporary storage - length = m_kk. mutable vector_fp m_tmpV; diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 0af23bfbf5..22642edadb 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -17,10 +17,6 @@ using namespace std; namespace Cantera { -const double MixtureFugacityTP::omega_a = 0.; -const double MixtureFugacityTP::omega_b = 0.; -const double MixtureFugacityTP::omega_vc = 0.; - MixtureFugacityTP::MixtureFugacityTP() : iState_(FLUID_GAS), forcedState_(FLUID_UNDEFINED) @@ -838,7 +834,7 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const 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) const + double bn, double cn, double dn, double tc, double vc) const { double tmp; fill_n(Vroot, 3, 0.0); @@ -846,9 +842,6 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, throw CanteraError("MixtureFugacityTP::CubicSolve()", "negative temperature T = {}", T); } - double pc = omega_b * GasConstant * tc / b; - double vc = omega_vc * GasConstant * tc / pc; - // Derive the center of the cubic, x_N double xN = - bn /(3 * an); diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 1f366a1111..b45546ffdc 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -909,8 +909,10 @@ int PengRobinson::solveCubic(double T, double pres, double a, double b, double a 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); + 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 e8bec742cb..aec827a400 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -2184,3 +2184,1104 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl } >>>>>>> 6a88472a9... Moved setToEquil function to the parent class, 'MixtureFugacityTP". +======= +//! @file RedlichKwongMFTP.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/RedlichKwongMFTP.h" +#include "cantera/thermo/ThermoFactory.h" +#include "cantera/base/stringUtils.h" +#include "cantera/base/ctml.h" + +#include + +#include + +using namespace std; +namespace bmt = boost::math::tools; + +namespace Cantera +{ + +const doublereal RedlichKwongMFTP::omega_a = 4.27480233540E-01; +const doublereal RedlichKwongMFTP::omega_b = 8.66403499650E-02; +const doublereal RedlichKwongMFTP::omega_vc = 3.33333333333333E-01; + +RedlichKwongMFTP::RedlichKwongMFTP() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); +} + +RedlichKwongMFTP::RedlichKwongMFTP(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + initThermoFile(infile, id_); +} + +RedlichKwongMFTP::RedlichKwongMFTP(XML_Node& phaseRefRoot, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + importPhase(phaseRefRoot, this); +} + +void RedlichKwongMFTP::setSpeciesCoeffs(const std::string& species, + double a0, double a1, double b) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + + if (a1 != 0.0) { + m_formTempParam = 1; // expression is temperature-dependent + } + + size_t counter = k + m_kk * k; + a_coeff_vec(0, counter) = a0; + a_coeff_vec(1, counter) = a1; + + // standard mixing rule for cross-species interaction term + for (size_t j = 0; j < m_kk; j++) { + if (k == j) { + continue; + } + + // a_coeff_vec(0) is initialized to NaN to mark uninitialized species + if (isnan(a_coeff_vec(0, j + m_kk * j))) { + // The diagonal element of the jth species has not yet been defined. + continue; + } else if (isnan(a_coeff_vec(0, j + m_kk * k))) { + // Only use the mixing rules if the off-diagonal element has not already been defined by a + // user-specified crossFluidParameters entry: + double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a0); + double a1kj = sqrt(a_coeff_vec(1, j + m_kk * j) * a1); + a_coeff_vec(0, j + m_kk * k) = a0kj; + a_coeff_vec(1, j + m_kk * k) = a1kj; + a_coeff_vec(0, k + m_kk * j) = a0kj; + a_coeff_vec(1, k + m_kk * j) = a1kj; + } + } + a_coeff_vec.getRow(0, a_vec_Curr_.data()); + b_vec_Curr_[k] = b; +} + +void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1) +{ + size_t ki = speciesIndex(species_i); + if (ki == npos) { + throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_i); + } + size_t kj = speciesIndex(species_j); + if (kj == npos) { + throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_j); + } + + if (a1 != 0.0) { + m_formTempParam = 1; // expression is temperature-dependent + } + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; + a_coeff_vec(1, counter1) = a_coeff_vec(1, counter2) = a1; + a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +doublereal RedlichKwongMFTP::cp_mole() const +{ + _updateReferenceStateThermo(); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal vpb = mv + m_b_current; + pressureDerivatives(); + doublereal cpref = GasConstant * mean_X(m_cp0_R); + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + doublereal dHdT_V = (cpref + mv * dpdT_ - GasConstant - 1.0 / (2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv) * fac + +1.0/(m_b_current * sqt) * log(vpb/mv) * (-0.5 * dadt)); + return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; +} + +doublereal RedlichKwongMFTP::cv_mole() const +{ + _updateReferenceStateThermo(); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal vpb = mv + m_b_current; + doublereal cvref = GasConstant * (mean_X(m_cp0_R) - 1.0); + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + return (cvref - 1.0/(2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv)*fac + +1.0/(m_b_current * sqt) * log(vpb/mv)*(-0.5*dadt)); +} + +doublereal RedlichKwongMFTP::pressure() const +{ + _updateReferenceStateThermo(); + + // Get a copy of the private variables stored in the State object + doublereal T = temperature(); + double molarV = meanMolecularWeight() / density(); + double pp = GasConstant * T/(molarV - m_b_current) - m_a_current/(sqrt(T) * molarV * (molarV + m_b_current)); + return pp; +} + +doublereal RedlichKwongMFTP::standardConcentration(size_t k) const +{ + getStandardVolumes(m_tmpV.data()); + return 1.0 / m_tmpV[k]; +} + +void RedlichKwongMFTP::getActivityCoefficients(doublereal* ac) const +{ + doublereal mv = molarVolume(); + doublereal sqt = sqrt(temperature()); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + doublereal pres = pressure(); + + for (size_t k = 0; k < m_kk; k++) { + ac[k] = (- RT() * log(pres * mv / RT()) + + RT() * log(mv / vmb) + + RT() * b_vec_Curr_[k] / vmb + - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) + + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) + - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) + ); + } + for (size_t k = 0; k < m_kk; k++) { + ac[k] = exp(ac[k]/RT()); + } +} + +// ---- Partial Molar Properties of the Solution ----------------- + +void RedlichKwongMFTP::getChemPotentials_RT(doublereal* muRT) const +{ + getChemPotentials(muRT); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RT(); + } +} + +void RedlichKwongMFTP::getChemPotentials(doublereal* mu) const +{ + getGibbs_ref(mu); + for (size_t k = 0; k < m_kk; k++) { + double xx = std::max(SmallNumber, moleFraction(k)); + mu[k] += RT()*(log(xx)); + } + + doublereal mv = molarVolume(); + doublereal sqt = sqrt(temperature()); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + doublereal pres = pressure(); + doublereal refP = refPressure(); + + for (size_t k = 0; k < m_kk; k++) { + mu[k] += (RT() * log(pres/refP) - RT() * log(pres * mv / RT()) + + RT() * log(mv / vmb) + + RT() * b_vec_Curr_[k] / vmb + - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) + + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) + - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) + ); + } +} + +void RedlichKwongMFTP::getPartialMolarEnthalpies(doublereal* hbar) const +{ + // First we get the reference state contributions + getEnthalpy_RT_ref(hbar); + scale(hbar, hbar+m_kk, hbar, RT()); + + // We calculate dpdni_ + doublereal TKelvin = temperature(); + doublereal mv = molarVolume(); + doublereal sqt = sqrt(TKelvin); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + dpdni_[k] = RT()/vmb + RT() * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / (sqt * mv * vpb) + + m_a_current * b_vec_Curr_[k]/(sqt * mv * vpb * vpb); + } + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += 2.0 * moleFractions_[i] * TKelvin * a_coeff_vec(1,counter) - 3.0 * moleFractions_[i] * a_vec_Curr_[counter]; + } + } + + pressureDerivatives(); + doublereal fac2 = mv + TKelvin * dpdT_ / dpdV_; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = (mv * dpdni_[k] - RT() - b_vec_Curr_[k]/ (m_b_current * m_b_current * sqt) * log(vpb/mv)*fac + + 1.0 / (m_b_current * sqt) * log(vpb/mv) * m_tmpV[k] + + b_vec_Curr_[k] / vpb / (m_b_current * sqt) * fac); + hbar[k] = hbar[k] + hE_v; + hbar[k] -= fac2 * dpdni_[k]; + } +} + +void RedlichKwongMFTP::getPartialMolarEntropies(doublereal* sbar) const +{ + getEntropy_R_ref(sbar); + scale(sbar, sbar+m_kk, sbar, GasConstant); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal refP = refPressure(); + + for (size_t k = 0; k < m_kk; k++) { + doublereal xx = std::max(SmallNumber, moleFraction(k)); + sbar[k] += GasConstant * (- log(xx)); + } + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); + } + } + + doublereal dadt = da_dt(); + doublereal fac = dadt - m_a_current / (2.0 * TKelvin); + doublereal vmb = mv - m_b_current; + doublereal vpb = mv + m_b_current; + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -=(GasConstant * log(GasConstant * TKelvin / (refP * mv)) + + GasConstant + + GasConstant * log(mv/vmb) + + GasConstant * b_vec_Curr_[k]/vmb + + m_pp[k]/(m_b_current * TKelvin * sqt) * log(vpb/mv) + - 2.0 * m_tmpV[k]/(m_b_current * sqt) * log(vpb/mv) + + b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) * fac + - 1.0 / (m_b_current * sqt) * b_vec_Curr_[k] / vpb * fac + ); + } + + pressureDerivatives(); + getPartialMolarVolumes(m_partialMolarVolumes.data()); + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -= -m_partialMolarVolumes[k] * dpdT_; + } +} + +void RedlichKwongMFTP::getPartialMolarIntEnergies(doublereal* ubar) const +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void RedlichKwongMFTP::getPartialMolarCp(doublereal* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); + } + } + + doublereal sqt = sqrt(temperature()); + doublereal mv = molarVolume(); + doublereal vmb = mv - m_b_current; + doublereal vpb = mv + m_b_current; + for (size_t k = 0; k < m_kk; k++) { + doublereal num = (RT() + RT() * m_b_current/ vmb + RT() * b_vec_Curr_[k] / vmb + + RT() * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + - 2.0 * m_pp[k] / (sqt * vpb) + + m_a_current * b_vec_Curr_[k] / (sqt * vpb * vpb) + ); + doublereal denom = (pressure() + RT() * m_b_current/(vmb * vmb) - m_a_current / (sqt * vpb * vpb) + ); + vbar[k] = num / denom; + } +} + +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); + if (added) { + a_vec_Curr_.resize(m_kk * m_kk, 0.0); + + // Initialize a_vec and b_vec to NaN, to screen for species with + // pureFluidParameters which are undefined in the input file: + b_vec_Curr_.push_back(NAN); + a_coeff_vec.resize(2, m_kk * m_kk, NAN); + + m_pp.push_back(0.0); + m_tmpV.push_back(0.0); + m_partialMolarVolumes.push_back(0.0); + dpdni_.push_back(0.0); + } + return added; +} + +void RedlichKwongMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) +{ + if (phaseNode.hasChild("thermo")) { + XML_Node& thermoNode = phaseNode.child("thermo"); + std::string model = thermoNode["model"]; + if (model != "RedlichKwong" && model != "RedlichKwongMFTP") { + throw CanteraError("RedlichKwongMFTP::initThermoXML", + "Unknown thermo model : " + model); + } + + // Reset any coefficients which may have been set using values from + // 'critProperties.xml' as part of non-XML initialization, so that + // off-diagonal elements can be correctly initialized + a_coeff_vec.data().assign(a_coeff_vec.data().size(), NAN); + + // Go get all of the coefficients and factors in the + // activityCoefficients XML block + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + + // Loop through the children and read out fluid parameters. Process + // all the pureFluidParameters, first: + // Loop back through the "activityCoefficients" children and process the + // crossFluidParameters in the XML tree: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { + readXMLPureFluid(xmlACChild); + } else if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { + readXMLCrossFluid(xmlACChild); + } + } + } + // If any species exist which have undefined pureFluidParameters, + // search the database in 'critProperties.xml' to find critical + // temperature and pressure to calculate a and b. + + // Loop through all species in the CTI file + size_t iSpecies = 0; + + for (size_t i = 0; i < m_kk; i++) { + string iName = speciesName(i); + + // Get the index of the species + iSpecies = speciesIndex(iName); + + // Check if a and b are already populated (only the diagonal elements of a). + size_t counter = iSpecies + m_kk * iSpecies; + + // If not, then search the database: + if (isnan(a_coeff_vec(0, counter))) { + + vector coeffArray; + + // Search the database for the species name and calculate + // coefficients a and b, from critical properties: + // coeffArray[0] = a0, coeffArray[1] = b; + coeffArray = getCoeff(iName); + + // Check if species was found in the database of critical properties, + // and assign the results + if (!isnan(coeffArray[0])) { + //Assuming no temperature dependence (i,e a1 = 0) + setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); + } + } + } + } + + MixtureFugacityTP::initThermoXML(phaseNode, id); +} + +void RedlichKwongMFTP::initThermo() +{ + for (auto& item : m_species) { + // Read a and b coefficients 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", "Redlich-Kwong"); + double a0 = 0, a1 = 0; + if (eos["a"].isScalar()) { + a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); + } else { + auto avec = eos["a"].asVector(2); + a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); + a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); + } + double b = eos.convert("b", "m^3/kmol"); + setSpeciesCoeffs(item.first, a0, a1, b); + 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, a1 = 0; + if (item2.second.isScalar()) { + a0 = units.convert(item2.second, "Pa*m^6/kmol^2*K^0.5"); + } else { + auto avec = item2.second.asVector(2); + a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); + a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); + } + setBinaryCoeffs(item.first, item2.first, a0, a1); + } + } + } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { + // coeffs[0] = a0, coeffs[1] = b; + vector coeffs = getCoeff(item.first); + + // Check if species was found in the database of critical + // properties, and assign the results + if (!isnan(coeffs[0])) { + // Assuming no temperature dependence (i.e. a1 = 0) + setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); + } + } + } + } +} + +vector RedlichKwongMFTP::getCoeff(const std::string& iName) +{ + vector_fp spCoeff{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 specie iName to current database species + // dbName: + if (iNameLower == dbName) { + // Read from database and calculate a and b coefficients + double vParams; + double T_crit=0.; + double P_crit=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("RedlichKwongMFTP::getCoeff", + "Critical Temperature must be positive "); + } + T_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::getCoeff", + "Critical Temperature not in database "); + } + 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("RedlichKwongMFTP::getCoeff", + "Critical Pressure must be positive "); + } + P_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::getCoeff", + "Critical Pressure not in database "); + } + + //Assuming no temperature dependence + spCoeff[0] = omega_a * pow(GasConstant, 2) * pow(T_crit, 2.5) / P_crit; //coeff a + spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b + break; + } + } + return spCoeff; +} + +void RedlichKwongMFTP::readXMLPureFluid(XML_Node& pureFluidParam) +{ + string xname = pureFluidParam.name(); + if (xname != "pureFluidParameters") { + throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", + "Incorrect name for processing this routine: " + xname); + } + + double a0 = 0.0; + double a1 = 0.0; + double b = 0.0; + for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { + XML_Node& xmlChild = pureFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + string iModel = toLowerCopy(xmlChild.attrib("model")); + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + + if (iModel == "constant" && vParams.size() == 1) { + a0 = vParams[0]; + a1 = 0; + } else if (iModel == "linear_a" && vParams.size() == 2) { + a0 = vParams[0]; + a1 = vParams[1]; + } else { + throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", + "unknown model or incorrect number of parameters"); + } + + } else if (nodeName == "b_coeff") { + b = getFloatCurrent(xmlChild, "toSI"); + } + } + setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, a1, b); +} + +void RedlichKwongMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) +{ + string xname = CrossFluidParam.name(); + if (xname != "crossFluidParameters") { + throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", + "Incorrect name for processing this routine: " + xname); + } + + string iName = CrossFluidParam.attrib("species1"); + string jName = CrossFluidParam.attrib("species2"); + + size_t num = CrossFluidParam.nChildren(); + for (size_t iChild = 0; iChild < num; iChild++) { + XML_Node& xmlChild = CrossFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + string iModel = toLowerCopy(xmlChild.attrib("model")); + if (iModel == "constant" && vParams.size() == 1) { + setBinaryCoeffs(iName, jName, vParams[0], 0.0); + } else if (iModel == "linear_a") { + setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); + } else { + throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", + "unknown model ({}) or wrong number of parameters ({})", + iModel, vParams.size()); + } + } + } +} + +void RedlichKwongMFTP::setParametersFromXML(const XML_Node& thermoNode) +{ + MixtureFugacityTP::setParametersFromXML(thermoNode); + std::string model = thermoNode["model"]; +} + +doublereal RedlichKwongMFTP::sresid() const +{ + // note this agrees with tpx + doublereal rho = density(); + doublereal mmw = meanMolecularWeight(); + doublereal molarV = mmw / rho; + double hh = m_b_current / molarV; + doublereal zz = z(); + doublereal dadt = da_dt(); + doublereal T = temperature(); + doublereal sqT = sqrt(T); + doublereal fac = dadt - m_a_current / (2.0 * T); + double sresid_mol_R = log(zz*(1.0 - hh)) + log(1.0 + hh) * fac / (sqT * GasConstant * m_b_current); + return GasConstant * sresid_mol_R; +} + +doublereal RedlichKwongMFTP::hresid() const +{ + // note this agrees with tpx + doublereal rho = density(); + doublereal mmw = meanMolecularWeight(); + doublereal molarV = mmw / rho; + double hh = m_b_current / molarV; + doublereal zz = z(); + doublereal dadt = da_dt(); + doublereal T = temperature(); + doublereal sqT = sqrt(T); + doublereal fac = T * dadt - 3.0 *m_a_current / (2.0); + return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqT * m_b_current); +} + +doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGuess) const +{ + double v = m_b_current * 1.1; + double atmp; + double btmp; + calculateAB(TKelvin, atmp, btmp); + doublereal pres = std::max(psatEst(TKelvin), presGuess); + double Vroot[3]; + bool foundLiq = false; + int m = 0; + while (m < 100 && !foundLiq) { + int nsol = CubicCall(TKelvin, pres, atmp, btmp, 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; +} + +doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, int phaseRequested, doublereal rhoguess) +{ + // It's necessary to set the temperature so that m_a_current is set correctly. + setTemperature(TKelvin); + double tcrit = critTemperature(); + double mmw = meanMolecularWeight(); + if (rhoguess == -1.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 + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } + + + doublereal volguess = mmw / rhoguess; + NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); + + doublereal molarVolLast = Vroot_[0]; + if (NSolns_ >= 2) { + if (phaseRequested >= FLUID_LIQUID_0) { + molarVolLast = Vroot_[0]; + } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[2]; + } else { + if (volguess > Vroot_[1]) { + molarVolLast = Vroot_[2]; + } else { + molarVolLast = Vroot_[0]; + } + } + } else if (NSolns_ == 1) { + if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else if (NSolns_ == -1) { + if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[0]; + } else if (TKelvin > tcrit) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else { + molarVolLast = Vroot_[0]; + return -1.0; + } + return mmw / molarVolLast; +} + +doublereal RedlichKwongMFTP::densSpinodalLiquid() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); + + doublereal mmw = meanMolecularWeight(); + return mmw / (0.5 * (vv.first + vv.second)); +} + +doublereal RedlichKwongMFTP::densSpinodalGas() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); + + doublereal mmw = meanMolecularWeight(); + 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); + presCalc = GasConstant * TKelvin / (molarVol - m_b_current) + - m_a_current / (sqt * molarVol * (molarVol + m_b_current)); + + doublereal vpb = molarVol + m_b_current; + doublereal vmb = molarVol - m_b_current; + doublereal dpdv = (- GasConstant * TKelvin / (vmb * vmb) + + m_a_current * (2 * molarVol + m_b_current) / (sqt * molarVol * molarVol * vpb * vpb)); + return dpdv; +} + +void RedlichKwongMFTP::pressureDerivatives() const +{ + doublereal TKelvin = temperature(); + doublereal mv = molarVolume(); + doublereal pres; + + dpdV_ = dpdVCalc(TKelvin, mv, pres); + doublereal sqt = sqrt(TKelvin); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + doublereal dadt = da_dt(); + doublereal fac = dadt - m_a_current/(2.0 * TKelvin); + dpdT_ = (GasConstant / vmb - fac / (sqt * mv * vpb)); +} + +void RedlichKwongMFTP::updateMixingExpressions() +{ + double temp = temperature(); + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + a_vec_Curr_[counter] = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; + } + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + for (size_t i = 0; i < m_kk; i++) { + m_b_current += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + } + } + if (isnan(m_b_current)) { + // One or more species do not have specified coefficients. + fmt::memory_buffer b; + for (size_t k = 0; k < m_kk; k++) { + if (isnan(b_vec_Curr_[k])) { + if (b.size() > 0) { + format_to(b, ", {}", speciesName(k)); + } else { + format_to(b, "{}", speciesName(k)); + } + } + } + throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", + "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); + } +} + +void RedlichKwongMFTP::calculateAB(doublereal temp, doublereal& aCalc, doublereal& bCalc) const +{ + bCalc = 0.0; + aCalc = 0.0; + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + bCalc += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + doublereal a_vec_Curr = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + } + } + } else { + for (size_t i = 0; i < m_kk; i++) { + bCalc += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + doublereal a_vec_Curr = a_coeff_vec(0,counter); + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + } + } + } +} + +doublereal RedlichKwongMFTP::da_dt() const +{ + doublereal dadT = 0.0; + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + dadT+= a_coeff_vec(1,counter) * moleFractions_[i] * moleFractions_[j]; + } + } + } + return dadT; +} + +void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, + doublereal& pc, doublereal& tc, doublereal& vc) const +{ + if (m_formTempParam != 0) { + a = a0_coeff; + } + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + double tmp = a * omega_b / (b * omega_a * GasConstant); + double pp = 2./3.; + doublereal sqrttc, f, dfdt, deltatc; + + if (m_formTempParam == 0) { + tc = pow(tmp, pp); + } else { + tc = pow(tmp, pp); + for (int j = 0; j < 10; j++) { + sqrttc = sqrt(tc); + f = omega_a * b * GasConstant * tc * sqrttc / omega_b - aT_coeff * tc - a0_coeff; + dfdt = 1.5 * omega_a * b * GasConstant * sqrttc / omega_b - aT_coeff; + deltatc = - f / dfdt; + tc += deltatc; + } + if (deltatc > 0.1) { + throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); + } + } + + pc = omega_b * GasConstant * tc / b; + vc = omega_vc * GasConstant * tc / pc; +} + +int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const +{ + + // Derive the coefficients of the cubic polynomial to solve. + doublereal an = 1.0; + 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); + double pp = 2./3.; + double tc = pow(tmp, pp); + double pc = omega_b * GasConstant * tc / b; + double vc = omega_vc * GasConstant * tc / pc; + + int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, pc); + + return nSolnValues; +} + +} +>>>>>>> a933404b7... Removing dependancy of EoS specific omega_a, omega_b parameters from solveCubic From 04f0a801c58b9534232e47c02562c36b1841ec67 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 13:07:35 -0600 Subject: [PATCH 036/110] Cleaning up the redundant code, correcting indentations and tabs --- include/cantera/thermo/PengRobinson.h | 25 ++-- src/thermo/MixtureFugacityTP.cpp | 22 +-- src/thermo/PengRobinson.cpp | 205 +++++++++++++------------- 3 files changed, 126 insertions(+), 126 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index c512e84b24..9471ca1c0a 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -153,6 +153,7 @@ class PengRobinson : public MixtureFugacityTP * \alpha = [1 + \kappa(1 - sqrt{T/T_crit}]^2 * kappa is a function calulated based on the accentric factor. * Units: unitless + * a and b are species-specific coefficients used in P-R EoS, w is the acentric factor. */ virtual void calculateAlpha(const std::string& species, double a, double b, double w); //@} @@ -301,20 +302,20 @@ class PengRobinson : public MixtureFugacityTP double m_aAlpha_current; // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk - vector_fp a_vec_Curr_; - vector_fp b_vec_Curr_; - vector_fp aAlpha_vec_Curr_; - vector_fp alpha_vec_Curr_; - vector_fp kappa_vec_; - mutable vector_fp dalphadT_vec_Curr_; - mutable vector_fp d2alphadT2_; + vector_fp m_a_vec_Curr; + vector_fp m_b_vec_Curr; + vector_fp m_aAlpha_vec_Curr; + vector_fp m_alpha_vec_Curr; + vector_fp m_kappa_vec; + mutable vector_fp m_dalphadT_vec_Curr; + mutable vector_fp m_d2alphadT2; Array2D a_coeff_vec; Array2D aAlpha_coeff_vec; - int NSolns_; + int m_NSolns; - double Vroot_[3]; + double m_Vroot[3]; //! Temporary storage - length = m_kk. mutable vector_fp m_tmpV; @@ -327,21 +328,21 @@ class PengRobinson : public MixtureFugacityTP * Calculated at the current conditions. temperature and mole number kept * constant */ - mutable double dpdV_; + 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 dpdT_; + 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 dpdni_; + mutable vector_fp m_dpdni; public: //! Omega constants: a0 (= omega_a) and b0 (= omega_b) values used in Peng-Robinson equation of state diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 22642edadb..0f5f5f1589 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -56,10 +56,10 @@ double MixtureFugacityTP::enthalpy_mole() const double MixtureFugacityTP::entropy_mole() const { _updateReferenceStateThermo(); - double sr_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - - std::log(pressure()/refPressure())); - double sr_nonideal = sresid(); - return sr_ideal + sr_nonideal; + 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 ----------------- @@ -836,10 +836,9 @@ 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 { - double tmp; fill_n(Vroot, 3, 0.0); if (T <= 0.0) { - throw CanteraError("MixtureFugacityTP::CubicSolve()", "negative temperature T = {}", T); + throw CanteraError("MixtureFugacityTP::CubicSolve", "negative temperature T = {}", T); } // Derive the center of the cubic, x_N @@ -885,7 +884,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, //check if y = h if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) { if (disc > 1e-10) { - throw CanteraError("MixtureFugacityTP::CubicSolve()", "value of yN and h are too high, unrealistic roots may be obtained"); + throw CanteraError("MixtureFugacityTP::CubicSolve", "value of yN and h are too high, unrealistic roots may be obtained"); } disc = 0.0; } @@ -902,6 +901,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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); @@ -940,7 +940,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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("PengRobinson::CubicSolve(T = {}, p = {}):" + writelog("MixtureFugacityTP::CubicSolve(T = {}, p = {}):" " WARNING roots have merged: {}, {}\n", T, pres, Vroot[i], Vroot[j]); } @@ -960,7 +960,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::CubicSolve", "Inconsistancy in cubic solver : solver is poorly conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -968,7 +968,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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("PengRobinson::CubicSolve()", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::CubicSolve", "Inconsistancy in cubic solver : solver is poorly conditioned."); } delta = -delta; Vroot[0] = xN + delta; @@ -1000,7 +1000,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, } } if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) { - writelog("PengRobinson::CubicSolve(T = {}, p = {}): " + writelog("MixtureFugacityTP::CubicSolve(T = {}, p = {}): " "WARNING root didn't converge V = {}", T, pres, Vroot[i]); writelogendl(); } diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index b45546ffdc..f034b73571 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -11,7 +11,7 @@ #include #define _USE_MATH_DEFINES -#include +#include using namespace std; namespace bmt = boost::math::tools; @@ -28,11 +28,11 @@ PengRobinson::PengRobinson() : m_b_current(0.0), m_a_current(0.0), m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) + m_NSolns(0), + m_dpdV(0.0), + m_dpdT(0.0) { - fill_n(Vroot_, 3, 0.0); + fill_n(m_Vroot, 3, 0.0); } PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : @@ -40,11 +40,11 @@ PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : m_b_current(0.0), m_a_current(0.0), m_aAlpha_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) + m_NSolns(0), + m_dpdV(0.0), + m_dpdT(0.0) { - fill_n(Vroot_, 3, 0.0); + fill_n(m_Vroot, 3, 0.0); initThermoFile(infile, id_); } @@ -58,24 +58,23 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b // Calculate value of kappa (independent of temperature) // w is an acentric factor of species and must be specified in the CTI file - if (isnan(w)){ + if (isnan(w)) { throw CanteraError("PengRobinson::calculateAlpha", "No acentric factor loaded."); } else if (w <= 0.491) { - kappa_vec_[k] = 0.37464 + 1.54226*w - 0.26992*w*w; + m_kappa_vec[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { - kappa_vec_[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; + m_kappa_vec[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 + kappa_vec_[k] * (1 - sqt_T_r); - alpha_vec_Curr_[k] = sqt_alpha*sqt_alpha; + double sqt_alpha = 1 + m_kappa_vec[k] * (1 - sqt_T_r); + m_alpha_vec_Curr[k] = sqt_alpha*sqt_alpha; } -void PengRobinson::setSpeciesCoeffs(const std::string& species, - double a, double b, double w) +void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double b, double w) { size_t k = speciesIndex(species); if (k == npos) { @@ -85,7 +84,7 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, size_t counter = k + m_kk * k; a_coeff_vec(0, counter) = a; // we store this locally because it is used below to calculate a_Alpha: - double aAlpha_k = a*alpha_vec_Curr_[k]; + double aAlpha_k = a*m_alpha_vec_Curr[k]; aAlpha_coeff_vec(0, counter) = aAlpha_k; // standard mixing rule for cross-species interaction term @@ -94,7 +93,7 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, continue; } double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a); - double aAlpha_j = a*alpha_vec_Curr_[j]; + double aAlpha_j = a*m_alpha_vec_Curr[j]; double a_Alpha = sqrt(aAlpha_j*aAlpha_k); if (a_coeff_vec(0, j + m_kk * k) == 0) { a_coeff_vec(0, j + m_kk * k) = a0kj; @@ -103,9 +102,9 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; } } - a_coeff_vec.getRow(0, a_vec_Curr_.data()); - aAlpha_coeff_vec.getRow(0, aAlpha_vec_Curr_.data()); - b_vec_Curr_[k] = b; + a_coeff_vec.getRow(0, m_a_vec_Curr.data()); + aAlpha_coeff_vec.getRow(0, m_aAlpha_vec_Curr.data()); + m_b_vec_Curr[k] = b; } void PengRobinson::setBinaryCoeffs(const std::string& species_i, @@ -126,8 +125,8 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, size_t counter2 = kj + m_kk * ki; a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; - a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; - aAlpha_vec_Curr_[counter1] = aAlpha_vec_Curr_[counter2] = a0*alpha; + m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; + m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; } // ------------Molar Thermodynamic Properties ------------------------- @@ -141,9 +140,9 @@ double PengRobinson::cp_mole() const double vmb = mv + (1 - M_SQRT2)*m_b_current; pressureDerivatives(); double cpref = GasConstant * mean_X(m_cp0_R); - double dHdT_V = cpref + mv * dpdT_ - GasConstant + double dHdT_V = cpref + mv * m_dpdT - GasConstant + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); - return dHdT_V - (mv + T * dpdT_ / dpdV_) * dpdT_; + return dHdT_V - (mv + T * m_dpdT / m_dpdV) * m_dpdT; } double PengRobinson::cv_mole() const @@ -151,7 +150,7 @@ double PengRobinson::cv_mole() const _updateReferenceStateThermo(); double T = temperature(); pressureDerivatives(); - return (cp_mole() + T* dpdT_* dpdT_ / dpdV_); + return (cp_mole() + T* m_dpdT* m_dpdT / m_dpdV); } double PengRobinson::pressure() const @@ -184,7 +183,7 @@ void PengRobinson::getActivityCoefficients(double* ac) const m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; } } double num = 0; @@ -192,11 +191,11 @@ void PengRobinson::getActivityCoefficients(double* ac) const double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); double RTkelvin = RT(); for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) - + RTkelvin * b_vec_Curr_[k] / vmb + + RTkelvin * m_b_vec_Curr[k] / vmb - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* b_vec_Curr_[k] * mv/den2 + - m_aAlpha_current* m_b_vec_Curr[k] * mv/den2 ); } for (size_t k = 0; k < m_kk; k++) { @@ -233,7 +232,7 @@ void PengRobinson::getChemPotentials(double* mu) const m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; } } double pres = pressure(); @@ -243,13 +242,13 @@ void PengRobinson::getChemPotentials(double* mu) const double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* b_vec_Curr_[k]; + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + RTkelvin * log(mv / vmb) - + RTkelvin * b_vec_Curr_[k] / vmb + + RTkelvin * m_b_vec_Curr[k] / vmb - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* b_vec_Curr_[k] * mv/den2 + - m_aAlpha_current* m_b_vec_Curr[k] * mv/den2 ); } } @@ -260,7 +259,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const getEnthalpy_RT_ref(hbar); scale(hbar, hbar+m_kk, hbar, RT()); - // We calculate dpdni_ + // We calculate m_dpdni double T = temperature(); double mv = molarVolume(); double vmb = mv - m_b_current; @@ -271,7 +270,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; } } @@ -279,21 +278,21 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const double den2 = den*den; double RTkelvin = RT(); for (size_t k = 0; k < m_kk; k++) { - dpdni_[k] = RTkelvin /vmb + RTkelvin * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / den - + 2 * vmb * m_aAlpha_current * b_vec_Curr_[k] / den2; + m_dpdni[k] = RTkelvin /vmb + RTkelvin * m_b_vec_Curr[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + + 2 * vmb * m_aAlpha_current * m_b_vec_Curr[k] / den2; } double daAlphadT = daAlpha_dT(); double fac = T * daAlphadT - m_aAlpha_current; pressureDerivatives(); - double fac2 = mv + T * dpdT_ / dpdV_; - double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; + double fac2 = mv + T * m_dpdT / m_dpdV; + double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; for (size_t k = 0; k < m_kk; k++) { - double hE_v = mv * dpdni_[k] - RTkelvin + (2 * m_b_current - b_vec_Curr_[k]) / fac3 * log(vpb2 / vmb2)*fac - + (mv * b_vec_Curr_[k]) /(m_b_current*den) * fac; + double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2)*fac + + (mv * m_b_vec_Curr[k]) /(m_b_current*den) * fac; hbar[k] = hbar[k] + hE_v; - hbar[k] -= fac2 * dpdni_[k]; + hbar[k] -= fac2 * m_dpdni[k]; } } @@ -319,26 +318,26 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const m_tmpV[k] = 0; for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(dalphadT_vec_Curr_[i] / alpha_vec_Curr_[i]); + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); } - m_pp[k] = m_pp[k] * dalphadT_vec_Curr_[k] / alpha_vec_Curr_[k]; + m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; } for (size_t k = 0; k < m_kk; k++) { - coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * b_vec_Curr_[k]; + coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_vec_Curr[k]; sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) + GasConstant + GasConstant * log(mv / vmb) - + GasConstant * b_vec_Curr_[k] / vmb + + GasConstant * m_b_vec_Curr[k] / vmb - coeff1* log(vpb2 / vmb2) / den1 - - b_vec_Curr_[k] * mv * daAlphadT / den2 / m_b_current; + - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; } pressureDerivatives(); getPartialMolarVolumes(m_partialMolarVolumes.data()); for (size_t k = 0; k < m_kk; k++) { - sbar[k] -= m_partialMolarVolumes[k] * dpdT_; + sbar[k] -= m_partialMolarVolumes[k] * m_dpdT; } } @@ -360,7 +359,7 @@ void PengRobinson::getPartialMolarVolumes(double* vbar) const m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * aAlpha_vec_Curr_[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; } } @@ -372,10 +371,10 @@ void PengRobinson::getPartialMolarVolumes(double* vbar) const double RTkelvin = RT(); for (size_t k = 0; k < m_kk; k++) { - double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * b_vec_Curr_[k] / vmb - + RTkelvin * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * m_b_vec_Curr[k] / vmb + + RTkelvin * m_b_current * m_b_vec_Curr[k] /(vmb * vmb) - 2 * mv * m_pp[k] / fac - + 2 * mv * vmb * m_aAlpha_current * b_vec_Curr_[k] / fac2 + + 2 * mv * vmb * m_aAlpha_current * m_b_vec_Curr[k] / fac2 ); double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) + m_aAlpha_current/fac @@ -432,23 +431,23 @@ bool PengRobinson::addSpecies(shared_ptr spec) { bool added = MixtureFugacityTP::addSpecies(spec); if (added) { - a_vec_Curr_.resize(m_kk * m_kk, 0.0); - b_vec_Curr_.push_back(0.0); - a_vec_Curr_.push_back(0.0); - aAlpha_vec_Curr_.resize(m_kk * m_kk, 0.0); - aAlpha_vec_Curr_.push_back(0.0); - kappa_vec_.push_back(0.0); - - alpha_vec_Curr_.push_back(0.0); + m_a_vec_Curr.resize(m_kk * m_kk, 0.0); + m_b_vec_Curr.push_back(0.0); + m_a_vec_Curr.push_back(0.0); + m_aAlpha_vec_Curr.resize(m_kk * m_kk, 0.0); + m_aAlpha_vec_Curr.push_back(0.0); + m_kappa_vec.push_back(0.0); + + m_alpha_vec_Curr.push_back(0.0); a_coeff_vec.resize(1, m_kk * m_kk, 0.0); aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); - dalphadT_vec_Curr_.push_back(0.0); - d2alphadT2_.push_back(0.0); + m_dalphadT_vec_Curr.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); - dpdni_.push_back(0.0); + m_dpdni.push_back(0.0); } return added; } @@ -661,37 +660,37 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do } double volGuess = mmw / rhoGuess; - NSolns_ = solveCubic(T, presPa, m_a_current, m_b_current, m_aAlpha_current, Vroot_); + m_NSolns = solveCubic(T, presPa, m_a_current, m_b_current, m_aAlpha_current, m_Vroot); - double molarVolLast = Vroot_[0]; - if (NSolns_ >= 2) { + double molarVolLast = m_Vroot[0]; + if (m_NSolns >= 2) { if (phaseRequested >= FLUID_LIQUID_0) { - molarVolLast = Vroot_[0]; + molarVolLast = m_Vroot[0]; } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[2]; + molarVolLast = m_Vroot[2]; } else { - if (volGuess > Vroot_[1]) { - molarVolLast = Vroot_[2]; + if (volGuess > m_Vroot[1]) { + molarVolLast = m_Vroot[2]; } else { - molarVolLast = Vroot_[0]; + molarVolLast = m_Vroot[0]; } } - } else if (NSolns_ == 1) { + } else if (m_NSolns == 1) { if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { - molarVolLast = Vroot_[0]; + molarVolLast = m_Vroot[0]; } else { return -2.0; } - } else if (NSolns_ == -1) { + } else if (m_NSolns == -1) { if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[0]; + molarVolLast = m_Vroot[0]; } else if (T > tcrit) { - molarVolLast = Vroot_[0]; + molarVolLast = m_Vroot[0]; } else { return -2.0; } } else { - molarVolLast = Vroot_[0]; + molarVolLast = m_Vroot[0]; return -1.0; } return mmw / molarVolLast; @@ -765,10 +764,10 @@ void PengRobinson::pressureDerivatives() const double mv = molarVolume(); double pres; - dpdV_ = dpdVCalc(T, mv, pres); + m_dpdV = dpdVCalc(T, mv, pres); double vmb = mv - m_b_current; double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - dpdT_ = (GasConstant / vmb - daAlpha_dT() / den); + m_dpdT = (GasConstant / vmb - daAlpha_dT() / den); } void PengRobinson::updateMixingExpressions() @@ -780,17 +779,17 @@ void PengRobinson::updateMixingExpressions() // Update indiviual alpha for (size_t j = 0; j < m_kk; j++) { size_t counter = j * m_kk + j; - double critTemp_j = speciesCritTemperature(a_vec_Curr_[counter],b_vec_Curr_[j]); - sqt_alpha = 1 + kappa_vec_[j] * (1 - sqrt(temp / critTemp_j)); - alpha_vec_Curr_[j] = sqt_alpha*sqt_alpha; + double critTemp_j = speciesCritTemperature(m_a_vec_Curr[counter],m_b_vec_Curr[j]); + sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); + m_alpha_vec_Curr[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++) { size_t counter = i * m_kk + j; - a_vec_Curr_[counter] = a_coeff_vec(0, counter); - aAlpha_vec_Curr_[counter] = sqrt(alpha_vec_Curr_[i] * alpha_vec_Curr_[j]) * a_coeff_vec(0, counter); + m_a_vec_Curr[counter] = a_coeff_vec(0, counter); + m_aAlpha_vec_Curr[counter] = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * a_coeff_vec(0, counter); } } @@ -807,12 +806,12 @@ void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) aCalc = 0.0; aAlphaCalc = 0.0; for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; + bCalc += moleFractions_[i] * m_b_vec_Curr[i]; for (size_t j = 0; j < m_kk; j++) { size_t counter = i * m_kk + j; double a_vec_Curr = a_coeff_vec(0, counter); aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - aAlphaCalc += aAlpha_vec_Curr_[counter] * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += m_aAlpha_vec_Curr[counter] * moleFractions_[i] * moleFractions_[j]; } } } @@ -824,21 +823,21 @@ double PengRobinson::daAlpha_dT() const for (size_t i = 0; i < m_kk; i++) { size_t counter = i + m_kk * i; // Calculate first derivative of alpha for individual species - Tc = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); + Tc = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[i]); sqtTr = sqrt(temperature() / Tc); //we need species critical temperature coeff1 = 1 / (Tc*sqtTr); coeff2 = sqtTr - 1; - k = kappa_vec_[i]; - dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); + k = m_kappa_vec[i]; + m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); } //Calculate mixture derivative for (size_t i = 0; i < m_kk; i++) { size_t counter1 = i + m_kk * i; for (size_t j = 0; j < m_kk; j++) { size_t counter2 = j * m_kk + j; - temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alpha_vec_Curr_[i] * alpha_vec_Curr_[j])); + temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j])); daAlphadT += moleFractions_[i] * moleFractions_[j] * temp - * (dalphadT_vec_Curr_[j] * alpha_vec_Curr_[i] + dalphadT_vec_Curr_[i] * alpha_vec_Curr_[j]); + * (m_dalphadT_vec_Curr[j] * m_alpha_vec_Curr[i] + m_dalphadT_vec_Curr[i] * m_alpha_vec_Curr[j]); } } return daAlphadT; @@ -849,29 +848,29 @@ double PengRobinson::d2aAlpha_dT2() const double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; for (size_t i = 0; i < m_kk; i++) { size_t counter = i + m_kk * i; - double Tcrit_i = speciesCritTemperature(a_vec_Curr_[counter], b_vec_Curr_[i]); + double Tcrit_i = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[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 = kappa_vec_[i]; - dalphadT_vec_Curr_[i] = coeff1 *(k* k*coeff2 - k); - d2alphadT2_[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); + double k = m_kappa_vec[i]; + m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); + m_d2alphadT2[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); } //Calculate mixture derivative for (size_t i = 0; i < m_kk; i++) { size_t counter1 = i + m_kk * i; - alphai = alpha_vec_Curr_[i]; + alphai = m_alpha_vec_Curr[i]; for (size_t j = 0; j < m_kk; j++) { size_t counter2 = j + m_kk * j; - alphaj = alpha_vec_Curr_[j]; + alphaj = m_alpha_vec_Curr[j]; alphaij = alphai * alphaj; - temp = 0.5 * sqrt((a_vec_Curr_[counter1] * a_vec_Curr_[counter2]) / (alphaij)); - num = (dalphadT_vec_Curr_[j] * alphai + dalphadT_vec_Curr_[i] * alphaj); + temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (alphaij)); + num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); fac1 = -(0.5 / alphaij)*num*num; - fac2 = alphaj * d2alphadT2_[i] + alphai *d2alphadT2_[j] + 2. * dalphadT_vec_Curr_[i] * dalphadT_vec_Curr_[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); //d2alphadT2_[counter1]; // + fac2 = alphaj * m_d2alphadT2[i] + alphai *m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); //m_d2alphadT2[counter1]; // } } return d2aAlphadT2; From 92b66d5b30cc73cecd15d293f5f0a4f1aa0c32c9 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 14:36:43 -0600 Subject: [PATCH 037/110] Removed redundant calculations using a 2D array aAlpha_coeff_vec --- include/cantera/thermo/PengRobinson.h | 1 - src/thermo/PengRobinson.cpp | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 9471ca1c0a..85540c95a6 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -311,7 +311,6 @@ class PengRobinson : public MixtureFugacityTP mutable vector_fp m_d2alphadT2; Array2D a_coeff_vec; - Array2D aAlpha_coeff_vec; int m_NSolns; diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index f034b73571..b571e03cab 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -85,7 +85,7 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double a_coeff_vec(0, counter) = a; // we store this locally because it is used below to calculate a_Alpha: double aAlpha_k = a*m_alpha_vec_Curr[k]; - aAlpha_coeff_vec(0, counter) = aAlpha_k; + m_aAlpha_vec_Curr[counter] = aAlpha_k; // standard mixing rule for cross-species interaction term for (size_t j = 0; j < m_kk; j++) { @@ -97,13 +97,12 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double double a_Alpha = sqrt(aAlpha_j*aAlpha_k); if (a_coeff_vec(0, j + m_kk * k) == 0) { a_coeff_vec(0, j + m_kk * k) = a0kj; - aAlpha_coeff_vec(0, j + m_kk * k) = a_Alpha; + m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; a_coeff_vec(0, k + m_kk * j) = a0kj; - aAlpha_coeff_vec(0, k + m_kk * j) = a_Alpha; + m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; } } a_coeff_vec.getRow(0, m_a_vec_Curr.data()); - aAlpha_coeff_vec.getRow(0, m_aAlpha_vec_Curr.data()); m_b_vec_Curr[k] = b; } @@ -124,7 +123,7 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, size_t counter1 = ki + m_kk * kj; size_t counter2 = kj + m_kk * ki; a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; + //aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; } @@ -440,7 +439,7 @@ bool PengRobinson::addSpecies(shared_ptr spec) m_alpha_vec_Curr.push_back(0.0); a_coeff_vec.resize(1, m_kk * m_kk, 0.0); - aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); + //aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); m_dalphadT_vec_Curr.push_back(0.0); m_d2alphadT2.push_back(0.0); From 29c684481c3e97c2b309e36bfb7ccdaa4df73388 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 14:42:20 -0600 Subject: [PATCH 038/110] Redefined species specific internal functions as protected instead of public. --- include/cantera/thermo/PengRobinson.h | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 85540c95a6..7a52abba56 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -236,14 +236,20 @@ class PengRobinson : public MixtureFugacityTP virtual double sresid() const; virtual double hresid() const; -public: - virtual double liquidVolEst(double TKelvin, double& pres) const; - virtual double densityCalc(double TKelvin, double pressure, int phase, double rhoguess); + 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 pressureCalc(double TKelvin, double molarVol) const; + virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + + // Special functions not inherited from MixtureFugacityTP - virtual double densSpinodalLiquid() const; - virtual double densSpinodalGas() const; - virtual double pressureCalc(double TKelvin, double molarVol) const; - virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + double daAlpha_dT() const; + double d2aAlpha_dT2() const; + +public: //! Calculate dpdV and dpdT at the current conditions /*! @@ -268,12 +274,7 @@ class PengRobinson : public MixtureFugacityTP * @param aCalc (output) Returns the a value * @param bCalc (output) Returns the b value. */ - void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; - - // Special functions not inherited from MixtureFugacityTP - - double daAlpha_dT() const; - double d2aAlpha_dT2() const; + void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) const; From 83bc2bea402a570eab712d7330f3c673e53e3046 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 16:03:43 -0600 Subject: [PATCH 039/110] Removing getChemPotentials_RT function from PengRobinsonMFTP since it is already defined in the parent class MixtureFugacityTP. --- include/cantera/thermo/PengRobinson.h | 3 +-- src/thermo/PengRobinson.cpp | 11 ----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 7a52abba56..c4e85cf02f 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -138,8 +138,7 @@ class PengRobinson : public MixtureFugacityTP * @param mu Output vector of non-dimensional species chemical potentials * Length: m_kk. */ - virtual void getChemPotentials_RT(double* mu) const; - + virtual void getChemPotentials(double* mu) const; virtual void getPartialMolarEnthalpies(double* hbar) const; virtual void getPartialMolarEntropies(double* sbar) const; diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index b571e03cab..0131b4b394 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -123,7 +123,6 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, size_t counter1 = ki + m_kk * kj; size_t counter2 = kj + m_kk * ki; a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - //aAlpha_coeff_vec(0, counter1) = aAlpha_coeff_vec(0, counter2) = a0*alpha; m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; } @@ -204,15 +203,6 @@ void PengRobinson::getActivityCoefficients(double* ac) const // ---- Partial Molar Properties of the Solution ----------------- -void PengRobinson::getChemPotentials_RT(double* muRT) const -{ - getChemPotentials(muRT); - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RTkelvin; - } -} - void PengRobinson::getChemPotentials(double* mu) const { getGibbs_ref(mu); @@ -439,7 +429,6 @@ bool PengRobinson::addSpecies(shared_ptr spec) m_alpha_vec_Curr.push_back(0.0); a_coeff_vec.resize(1, m_kk * m_kk, 0.0); - //aAlpha_coeff_vec.resize(1, m_kk * m_kk, 0.0); m_dalphadT_vec_Curr.push_back(0.0); m_d2alphadT2.push_back(0.0); From 768053c101e3360f2366297ebb626f06ba0a5c70 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 8 Apr 2020 20:42:03 -0600 Subject: [PATCH 040/110] Removing a_coeff_vec to avoid duplication and using m_a_vec_Curr instead everywhere. --- include/cantera/thermo/PengRobinson.h | 2 -- src/thermo/PengRobinson.cpp | 24 ++++++++++-------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index c4e85cf02f..3683cb96bc 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -310,8 +310,6 @@ class PengRobinson : public MixtureFugacityTP mutable vector_fp m_dalphadT_vec_Curr; mutable vector_fp m_d2alphadT2; - Array2D a_coeff_vec; - int m_NSolns; double m_Vroot[3]; diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 0131b4b394..39c2bf8ed2 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -82,7 +82,7 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double "Unknown species '{}'.", species); } size_t counter = k + m_kk * k; - a_coeff_vec(0, counter) = a; + m_a_vec_Curr[counter] = a; // we store this locally because it is used below to calculate a_Alpha: double aAlpha_k = a*m_alpha_vec_Curr[k]; m_aAlpha_vec_Curr[counter] = aAlpha_k; @@ -92,17 +92,16 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double if (k == j) { continue; } - double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a); + double a0kj = sqrt(m_a_vec_Curr[j + m_kk * j] * a); double aAlpha_j = a*m_alpha_vec_Curr[j]; double a_Alpha = sqrt(aAlpha_j*aAlpha_k); - if (a_coeff_vec(0, j + m_kk * k) == 0) { - a_coeff_vec(0, j + m_kk * k) = a0kj; + if (m_a_vec_Curr[j + m_kk * k] == 0) { + m_a_vec_Curr[j + m_kk * k] = a0kj; m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; - a_coeff_vec(0, k + m_kk * j) = a0kj; + m_a_vec_Curr[k + m_kk * j] = a0kj; m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; } } - a_coeff_vec.getRow(0, m_a_vec_Curr.data()); m_b_vec_Curr[k] = b; } @@ -122,8 +121,7 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, size_t counter1 = ki + m_kk * kj; size_t counter2 = kj + m_kk * ki; - a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; + m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; } @@ -308,7 +306,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1, counter) *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); + m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter] *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); } m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; } @@ -428,7 +426,6 @@ bool PengRobinson::addSpecies(shared_ptr spec) m_kappa_vec.push_back(0.0); m_alpha_vec_Curr.push_back(0.0); - a_coeff_vec.resize(1, m_kk * m_kk, 0.0); m_dalphadT_vec_Curr.push_back(0.0); m_d2alphadT2.push_back(0.0); @@ -557,7 +554,7 @@ void PengRobinson::initThermo() // 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 (a_coeff_vec(0, k + m_kk * k) == 0.0) { + if (m_a_vec_Curr[k + m_kk * k] == 0.0) { // coeffs[0] = a0, coeffs[1] = b; vector coeffs = getCoeff(item.first); @@ -776,8 +773,7 @@ void PengRobinson::updateMixingExpressions() for (size_t i = 0; i < m_kk; i++) { for (size_t j = 0; j < m_kk; j++) { size_t counter = i * m_kk + j; - m_a_vec_Curr[counter] = a_coeff_vec(0, counter); - m_aAlpha_vec_Curr[counter] = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * a_coeff_vec(0, counter); + m_aAlpha_vec_Curr[counter] = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr[counter]; } } @@ -797,7 +793,7 @@ void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) bCalc += moleFractions_[i] * m_b_vec_Curr[i]; for (size_t j = 0; j < m_kk; j++) { size_t counter = i * m_kk + j; - double a_vec_Curr = a_coeff_vec(0, counter); + double a_vec_Curr = m_a_vec_Curr[counter]; aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; aAlphaCalc += m_aAlpha_vec_Curr[counter] * moleFractions_[i] * moleFractions_[j]; } From 7e69a65936c915f32e2a42b29bcb1c91655a0788 Mon Sep 17 00:00:00 2001 From: gkogekar Date: Sat, 6 Jun 2020 13:53:13 -0600 Subject: [PATCH 041/110] Fixing indentation and white spaces --- include/cantera/thermo/MixtureFugacityTP.h | 18 +- include/cantera/thermo/PengRobinson.h | 104 +- src/thermo/MixtureFugacityTP.cpp | 6 +- src/thermo/PengRobinson.cpp | 50 +- src/thermo/RedlichKwongMFTP.cpp | 1101 ++++++++++++++++++++ test/data/co2_PR_example.cti | 1 - test/data/co2_PR_example.yaml | 2 +- test/thermo/PengRobinson_Test.cpp | 12 +- 8 files changed, 1201 insertions(+), 93 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index e687da4ab9..f015f51418 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -491,7 +491,7 @@ class MixtureFugacityTP : public ThermoPhase * @return The saturation pressure at the given temperature */ virtual doublereal satPressure(doublereal TKelvin); - virtual void setToEquilState(const doublereal* lambda_RT); + virtual void setToEquilState(const doublereal* lambda_RT); virtual void getActivityConcentrations(double* c) const; protected: @@ -522,13 +522,15 @@ class MixtureFugacityTP : public ThermoPhase /*! * * 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. - * The cubic equation is solved using Nickall's method (Ref: The Mathematical Gazette(1993), 77(November), 354�359, https://www.jstor.org/stable/3619777) + * 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) */ 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; + double aAlpha, double Vroot[3], double an, + double bn, double cn, double dn, double tc, double vc) const; //@} @@ -564,8 +566,8 @@ class MixtureFugacityTP : public ThermoPhase mutable vector_fp m_s0_R; public: - //! Temporary storage - length = m_kk. - mutable vector_fp m_pp; + //! Temporary storage - length = m_kk. + mutable vector_fp m_pp; }; } diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 3683cb96bc..ad73ef33dc 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -26,12 +26,12 @@ class PengRobinson : public MixtureFugacityTP PengRobinson(); //! Construct and initialize a PengRobinson object directly from an - //! ASCII input file + //! input file /*! * @param infile Name of the input file containing the phase YAML data * to set up the object * @param id ID of the phase in the input file. Defaults to the empty - * string. + * string. */ PengRobinson(const std::string& infile, const std::string& id=""); @@ -51,15 +51,15 @@ class PengRobinson : public MixtureFugacityTP //! 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$. + * 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 } + * P = \frac{RT}{v-b_{mix}} - \frac{\left(\alpha a\right)_{mix}}{v^2 + 2b_{mix}v - b_{mix}^2} * \f] * - * where: + * where: * * \f[ * \alpha = \left[ 1 + \kappa \left(1-T_r^{0.5}\right)\right]^2 @@ -72,19 +72,19 @@ class PengRobinson : public MixtureFugacityTP * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 * \f] * - *Coefficients a_mix, b_mix and (a \alpha)_{mix} are caclulated as + * 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[ + * 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[ + * b_{mix} = \sum_i X_i b_i + * \f] * - *\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] + * \f] * * */ @@ -100,15 +100,15 @@ class PengRobinson : public MixtureFugacityTP /*! * This is defined as the concentration by which the generalized * concentration is normalized to produce the activity. In many cases, this - * quantity will be the same for all species in a phase. - * 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$. + * quantity will be the same for all species in a phase. + * 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 m3 kmol-1. + * Returns the standard Concentration in units of m^3 / kmol. */ virtual double standardConcentration(size_t k=0) const; @@ -132,8 +132,6 @@ class PengRobinson : public MixtureFugacityTP * \f$ \mu_k / \hat R T \f$. * Units: unitless * - * We close the loop on this function here calling getChemPotentials() and - * then dividing by RT. No need for child classes to handle. * * @param mu Output vector of non-dimensional species chemical potentials * Length: m_kk. @@ -147,13 +145,17 @@ class PengRobinson : public MixtureFugacityTP virtual void getPartialMolarVolumes(double* vbar) const; //! Calculate the temperature dependent interaction parameter alpha needed for P-R EoS - /* - * 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. - * Units: unitless - * a and b are species-specific coefficients used in P-R EoS, w is the acentric factor. - */ + /*! + * The temperature dependent parameter in P-R EoS is calculated as + * \f$ \alpha = [1 + \kappa(1 - sqrt{T/T_crit})]^2 \f$ + * kappa is a function calulated based on the acentric factor. + * Units: unitless + * + * @param a species-specific coefficients used in P-R EoS + * @param b species-specific coefficients used in P-R EoS + * @param w the acentric factor + */ + virtual void calculateAlpha(const std::string& species, double a, double b, double w); //@} /// @name Critical State Properties. @@ -185,10 +187,10 @@ class PengRobinson : public MixtureFugacityTP * 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] + * \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] + * \f[ b_k = 0.08664 R T_c/ P_c \f] * * @param iName Name of the species */ @@ -196,27 +198,27 @@ class PengRobinson : public MixtureFugacityTP //! Set the pure fluid interaction parameters for a species /*! - * The "a" parameter for species *i* in the Peng-Robinson model is assumed + * The *a* parameter for species *i* in the Peng-Robinson model is assumed * to be a linear function of temperature: * \f[ a = a_0 + a_1 T \f] * * @param species Name of the species * @param a0 constant term in the expression for the "a" parameter - * of the specified species [Pa-m^6/kmol^2] + * of the specified species [Pa-m^6/kmol^2] * @param a1 temperature-proportional term in the expression for the - * "a" parameter of the specified species [Pa-m^6/kmol^2/K] + * "a" parameter of the specified species [Pa-m^6/kmol^2/K] * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] * @param alpha dimensionless function of T_r and \omega * @param omega acentric factor */ void setSpeciesCoeffs(const std::string& species, double a0, double b, - double w); + 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] + * \f[ a_{ij} = \sqrt{a_{i, 0} a_{j, 0}} + \sqrt{a_{i, 1} a_{j, 1}} T \f] * * This function overrides the defaults with the specified parameters: * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] @@ -225,7 +227,7 @@ class PengRobinson : public MixtureFugacityTP * @param species_j Name of the other species * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] * @param a1 temperature-proportional term in the "a" expression - * [Pa-m^6/kmol^2/K] + * [Pa-m^6/kmol^2/K] */ void setBinaryCoeffs(const std::string& species_i, const std::string& species_j, double a0, double a1); @@ -235,18 +237,18 @@ class PengRobinson : public 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 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 pressureCalc(double TKelvin, double molarVol) const; - virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + virtual double densSpinodalLiquid() const; + virtual double densSpinodalGas() const; + virtual double pressureCalc(double TKelvin, double molarVol) const; + virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; - // Special functions not inherited from MixtureFugacityTP + // Special functions not inherited from MixtureFugacityTP - double daAlpha_dT() const; - double d2aAlpha_dT2() const; + double daAlpha_dT() const; + double d2aAlpha_dT2() const; public: @@ -273,18 +275,18 @@ class PengRobinson : public MixtureFugacityTP * @param aCalc (output) Returns the a value * @param bCalc (output) Returns the b value. */ - void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; + void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; void calcCriticalConditions(double a, double b,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; + double Vroot[3]) const; protected: //! Form of the temperature parameterization /*! - * 0 = There is no temperature parameterization of a or b - * 1 = The a_ij parameter is a linear function of the temperature + * - 0 = There is no temperature parameterization of a + * - 1 = The a_ij parameter is a linear function of the temperature */ int m_formTempParam; diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 0f5f5f1589..63f6a5130d 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -56,7 +56,7 @@ double MixtureFugacityTP::enthalpy_mole() const double MixtureFugacityTP::entropy_mole() const { _updateReferenceStateThermo(); - double s_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() + double s_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - std::log(pressure()/refPressure())); double s_nonideal = sresid(); return s_ideal + s_nonideal; @@ -958,7 +958,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, // 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. + // In this case, tmp and delta must be equal. if (fabs(tmp - delta) > 1.0E-9) { throw CanteraError("MixtureFugacityTP::CubicSolve", "Inconsistancy in cubic solver : solver is poorly conditioned."); } @@ -1008,7 +1008,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, if (nSolnValues == 1) { // Determine the phase of the single root. - // nSolnValues = 1 represents the gas phase by default. + // nSolnValues = 1 represents the gas phase by default. if (T > tc) { if (Vroot[0] < vc) { // Supercritical phase diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 39c2bf8ed2..e70054dd5a 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -1,7 +1,7 @@ //! @file PengRobinson.cpp // This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. +// at https://cantera.org/license.txt for license and copyright information. #include "cantera/thermo/PengRobinson.h" #include "cantera/thermo/ThermoFactory.h" @@ -82,7 +82,7 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double "Unknown species '{}'.", species); } size_t counter = k + m_kk * k; - m_a_vec_Curr[counter] = a; + m_a_vec_Curr[counter] = a; // we store this locally because it is used below to calculate a_Alpha: double aAlpha_k = a*m_alpha_vec_Curr[k]; m_aAlpha_vec_Curr[counter] = aAlpha_k; @@ -96,10 +96,10 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double double aAlpha_j = a*m_alpha_vec_Curr[j]; double a_Alpha = sqrt(aAlpha_j*aAlpha_k); if (m_a_vec_Curr[j + m_kk * k] == 0) { - m_a_vec_Curr[j + m_kk * k] = a0kj; - m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; - m_a_vec_Curr[k + m_kk * j] = a0kj; - m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; + m_a_vec_Curr[j + m_kk * k] = a0kj; + m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; + m_a_vec_Curr[k + m_kk * j] = a0kj; + m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; } } m_b_vec_Curr[k] = b; @@ -121,7 +121,7 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, size_t counter1 = ki + m_kk * kj; size_t counter2 = kj + m_kk * ki; - m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; + m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; } @@ -306,7 +306,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t i = 0; i < m_kk; i++) { size_t counter = k + m_kk*i; m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter] *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); + m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter] *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); } m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; } @@ -315,11 +315,11 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t k = 0; k < m_kk; k++) { coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_vec_Curr[k]; sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) - + GasConstant - + GasConstant * log(mv / vmb) - + GasConstant * m_b_vec_Curr[k] / vmb - - coeff1* log(vpb2 / vmb2) / den1 - - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; + + GasConstant + + GasConstant * log(mv / vmb) + + GasConstant * m_b_vec_Curr[k] / vmb + - coeff1* log(vpb2 / vmb2) / den1 + - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; } pressureDerivatives(); getPartialMolarVolumes(m_partialMolarVolumes.data()); @@ -359,14 +359,14 @@ void PengRobinson::getPartialMolarVolumes(double* vbar) const for (size_t k = 0; k < m_kk; k++) { double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * m_b_vec_Curr[k] / vmb - + RTkelvin * m_b_current * m_b_vec_Curr[k] /(vmb * vmb) - - 2 * mv * m_pp[k] / fac - + 2 * mv * vmb * m_aAlpha_current * m_b_vec_Curr[k] / fac2 - ); + + RTkelvin * m_b_current * m_b_vec_Curr[k] /(vmb * vmb) + - 2 * mv * m_pp[k] / fac + + 2 * mv * vmb * m_aAlpha_current * m_b_vec_Curr[k] / fac2 + ); double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) - + m_aAlpha_current/fac - - 2 * mv* vpb *m_aAlpha_current / fac2 - ); + + m_aAlpha_current/fac + - 2 * mv* vpb *m_aAlpha_current / fac2 + ); vbar[k] = num / denom; } } @@ -470,7 +470,7 @@ vector PengRobinson::getCoeff(const std::string& iName) } if (vParams <= 0.0) { //Assuming that Pc and Tc are non zero. throw CanteraError("PengRobinson::getCoeff", - "Critical Temperature must be positive "); + "Critical Temperature must be positive"); } T_crit = vParams; } @@ -483,7 +483,7 @@ vector PengRobinson::getCoeff(const std::string& iName) } if (vParams <= 0.0) { //Assuming that Pc and Tc are non zero. throw CanteraError("PengRobinson::getCoeff", - "Critical Pressure must be positive "); + "Critical Pressure must be positive"); } P_crit = vParams; } @@ -854,7 +854,7 @@ double PengRobinson::d2aAlpha_dT2() const num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); fac1 = -(0.5 / alphaij)*num*num; fac2 = alphaj * m_d2alphadT2[i] + alphai *m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); //m_d2alphadT2[counter1]; // + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); } } return d2aAlphadT2; @@ -892,8 +892,8 @@ int PengRobinson::solveCubic(double T, double pres, double a, double b, double a 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; + 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); diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index aec827a400..6c3bf151a8 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -3285,3 +3285,1104 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl } >>>>>>> a933404b7... Removing dependancy of EoS specific omega_a, omega_b parameters from solveCubic +======= +//! @file RedlichKwongMFTP.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/RedlichKwongMFTP.h" +#include "cantera/thermo/ThermoFactory.h" +#include "cantera/base/stringUtils.h" +#include "cantera/base/ctml.h" + +#include + +#include + +using namespace std; +namespace bmt = boost::math::tools; + +namespace Cantera +{ + +const doublereal RedlichKwongMFTP::omega_a = 4.27480233540E-01; +const doublereal RedlichKwongMFTP::omega_b = 8.66403499650E-02; +const doublereal RedlichKwongMFTP::omega_vc = 3.33333333333333E-01; + +RedlichKwongMFTP::RedlichKwongMFTP() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); +} + +RedlichKwongMFTP::RedlichKwongMFTP(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + initThermoFile(infile, id_); +} + +RedlichKwongMFTP::RedlichKwongMFTP(XML_Node& phaseRefRoot, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + NSolns_(0), + dpdV_(0.0), + dpdT_(0.0) +{ + fill_n(Vroot_, 3, 0.0); + importPhase(phaseRefRoot, this); +} + +void RedlichKwongMFTP::setSpeciesCoeffs(const std::string& species, + double a0, double a1, double b) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", + "Unknown species '{}'.", species); + } + + if (a1 != 0.0) { + m_formTempParam = 1; // expression is temperature-dependent + } + + size_t counter = k + m_kk * k; + a_coeff_vec(0, counter) = a0; + a_coeff_vec(1, counter) = a1; + + // standard mixing rule for cross-species interaction term + for (size_t j = 0; j < m_kk; j++) { + if (k == j) { + continue; + } + + // a_coeff_vec(0) is initialized to NaN to mark uninitialized species + if (isnan(a_coeff_vec(0, j + m_kk * j))) { + // The diagonal element of the jth species has not yet been defined. + continue; + } else if (isnan(a_coeff_vec(0, j + m_kk * k))) { + // Only use the mixing rules if the off-diagonal element has not already been defined by a + // user-specified crossFluidParameters entry: + double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a0); + double a1kj = sqrt(a_coeff_vec(1, j + m_kk * j) * a1); + a_coeff_vec(0, j + m_kk * k) = a0kj; + a_coeff_vec(1, j + m_kk * k) = a1kj; + a_coeff_vec(0, k + m_kk * j) = a0kj; + a_coeff_vec(1, k + m_kk * j) = a1kj; + } + } + a_coeff_vec.getRow(0, a_vec_Curr_.data()); + b_vec_Curr_[k] = b; +} + +void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1) +{ + size_t ki = speciesIndex(species_i); + if (ki == npos) { + throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_i); + } + size_t kj = speciesIndex(species_j); + if (kj == npos) { + throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", + "Unknown species '{}'.", species_j); + } + + if (a1 != 0.0) { + m_formTempParam = 1; // expression is temperature-dependent + } + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; + a_coeff_vec(1, counter1) = a_coeff_vec(1, counter2) = a1; + a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +doublereal RedlichKwongMFTP::cp_mole() const +{ + _updateReferenceStateThermo(); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal vpb = mv + m_b_current; + pressureDerivatives(); + doublereal cpref = GasConstant * mean_X(m_cp0_R); + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + doublereal dHdT_V = (cpref + mv * dpdT_ - GasConstant - 1.0 / (2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv) * fac + +1.0/(m_b_current * sqt) * log(vpb/mv) * (-0.5 * dadt)); + return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; +} + +doublereal RedlichKwongMFTP::cv_mole() const +{ + _updateReferenceStateThermo(); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal vpb = mv + m_b_current; + doublereal cvref = GasConstant * (mean_X(m_cp0_R) - 1.0); + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + return (cvref - 1.0/(2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv)*fac + +1.0/(m_b_current * sqt) * log(vpb/mv)*(-0.5*dadt)); +} + +doublereal RedlichKwongMFTP::pressure() const +{ + _updateReferenceStateThermo(); + + // Get a copy of the private variables stored in the State object + doublereal T = temperature(); + double molarV = meanMolecularWeight() / density(); + double pp = GasConstant * T/(molarV - m_b_current) - m_a_current/(sqrt(T) * molarV * (molarV + m_b_current)); + return pp; +} + +doublereal RedlichKwongMFTP::standardConcentration(size_t k) const +{ + getStandardVolumes(m_tmpV.data()); + return 1.0 / m_tmpV[k]; +} + +void RedlichKwongMFTP::getActivityCoefficients(doublereal* ac) const +{ + doublereal mv = molarVolume(); + doublereal sqt = sqrt(temperature()); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + doublereal pres = pressure(); + + for (size_t k = 0; k < m_kk; k++) { + ac[k] = (- RT() * log(pres * mv / RT()) + + RT() * log(mv / vmb) + + RT() * b_vec_Curr_[k] / vmb + - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) + + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) + - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) + ); + } + for (size_t k = 0; k < m_kk; k++) { + ac[k] = exp(ac[k]/RT()); + } +} + +// ---- Partial Molar Properties of the Solution ----------------- + +void RedlichKwongMFTP::getChemPotentials_RT(doublereal* muRT) const +{ + getChemPotentials(muRT); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RT(); + } +} + +void RedlichKwongMFTP::getChemPotentials(doublereal* mu) const +{ + getGibbs_ref(mu); + for (size_t k = 0; k < m_kk; k++) { + double xx = std::max(SmallNumber, moleFraction(k)); + mu[k] += RT()*(log(xx)); + } + + doublereal mv = molarVolume(); + doublereal sqt = sqrt(temperature()); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + doublereal pres = pressure(); + doublereal refP = refPressure(); + + for (size_t k = 0; k < m_kk; k++) { + mu[k] += (RT() * log(pres/refP) - RT() * log(pres * mv / RT()) + + RT() * log(mv / vmb) + + RT() * b_vec_Curr_[k] / vmb + - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) + + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) + - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) + ); + } +} + +void RedlichKwongMFTP::getPartialMolarEnthalpies(doublereal* hbar) const +{ + // First we get the reference state contributions + getEnthalpy_RT_ref(hbar); + scale(hbar, hbar+m_kk, hbar, RT()); + + // We calculate dpdni_ + doublereal TKelvin = temperature(); + doublereal mv = molarVolume(); + doublereal sqt = sqrt(TKelvin); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + dpdni_[k] = RT()/vmb + RT() * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / (sqt * mv * vpb) + + m_a_current * b_vec_Curr_[k]/(sqt * mv * vpb * vpb); + } + doublereal dadt = da_dt(); + doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; + + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += 2.0 * moleFractions_[i] * TKelvin * a_coeff_vec(1,counter) - 3.0 * moleFractions_[i] * a_vec_Curr_[counter]; + } + } + + pressureDerivatives(); + doublereal fac2 = mv + TKelvin * dpdT_ / dpdV_; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = (mv * dpdni_[k] - RT() - b_vec_Curr_[k]/ (m_b_current * m_b_current * sqt) * log(vpb/mv)*fac + + 1.0 / (m_b_current * sqt) * log(vpb/mv) * m_tmpV[k] + + b_vec_Curr_[k] / vpb / (m_b_current * sqt) * fac); + hbar[k] = hbar[k] + hE_v; + hbar[k] -= fac2 * dpdni_[k]; + } +} + +void RedlichKwongMFTP::getPartialMolarEntropies(doublereal* sbar) const +{ + getEntropy_R_ref(sbar); + scale(sbar, sbar+m_kk, sbar, GasConstant); + doublereal TKelvin = temperature(); + doublereal sqt = sqrt(TKelvin); + doublereal mv = molarVolume(); + doublereal refP = refPressure(); + + for (size_t k = 0; k < m_kk; k++) { + doublereal xx = std::max(SmallNumber, moleFraction(k)); + sbar[k] += GasConstant * (- log(xx)); + } + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); + } + } + + doublereal dadt = da_dt(); + doublereal fac = dadt - m_a_current / (2.0 * TKelvin); + doublereal vmb = mv - m_b_current; + doublereal vpb = mv + m_b_current; + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -=(GasConstant * log(GasConstant * TKelvin / (refP * mv)) + + GasConstant + + GasConstant * log(mv/vmb) + + GasConstant * b_vec_Curr_[k]/vmb + + m_pp[k]/(m_b_current * TKelvin * sqt) * log(vpb/mv) + - 2.0 * m_tmpV[k]/(m_b_current * sqt) * log(vpb/mv) + + b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) * fac + - 1.0 / (m_b_current * sqt) * b_vec_Curr_[k] / vpb * fac + ); + } + + pressureDerivatives(); + getPartialMolarVolumes(m_partialMolarVolumes.data()); + for (size_t k = 0; k < m_kk; k++) { + sbar[k] -= -m_partialMolarVolumes[k] * dpdT_; + } +} + +void RedlichKwongMFTP::getPartialMolarIntEnergies(doublereal* ubar) const +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void RedlichKwongMFTP::getPartialMolarCp(doublereal* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; + } + } + for (size_t k = 0; k < m_kk; k++) { + m_tmpV[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); + } + } + + doublereal sqt = sqrt(temperature()); + doublereal mv = molarVolume(); + doublereal vmb = mv - m_b_current; + doublereal vpb = mv + m_b_current; + for (size_t k = 0; k < m_kk; k++) { + doublereal num = (RT() + RT() * m_b_current/ vmb + RT() * b_vec_Curr_[k] / vmb + + RT() * m_b_current * b_vec_Curr_[k] /(vmb * vmb) + - 2.0 * m_pp[k] / (sqt * vpb) + + m_a_current * b_vec_Curr_[k] / (sqt * vpb * vpb) + ); + doublereal denom = (pressure() + RT() * m_b_current/(vmb * vmb) - m_a_current / (sqt * vpb * vpb) + ); + vbar[k] = num / denom; + } +} + +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); + if (added) { + a_vec_Curr_.resize(m_kk * m_kk, 0.0); + + // Initialize a_vec and b_vec to NaN, to screen for species with + // pureFluidParameters which are undefined in the input file: + b_vec_Curr_.push_back(NAN); + a_coeff_vec.resize(2, m_kk * m_kk, NAN); + + m_pp.push_back(0.0); + m_tmpV.push_back(0.0); + m_partialMolarVolumes.push_back(0.0); + dpdni_.push_back(0.0); + } + return added; +} + +void RedlichKwongMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) +{ + if (phaseNode.hasChild("thermo")) { + XML_Node& thermoNode = phaseNode.child("thermo"); + std::string model = thermoNode["model"]; + if (model != "RedlichKwong" && model != "RedlichKwongMFTP") { + throw CanteraError("RedlichKwongMFTP::initThermoXML", + "Unknown thermo model : " + model); + } + + // Reset any coefficients which may have been set using values from + // 'critProperties.xml' as part of non-XML initialization, so that + // off-diagonal elements can be correctly initialized + a_coeff_vec.data().assign(a_coeff_vec.data().size(), NAN); + + // Go get all of the coefficients and factors in the + // activityCoefficients XML block + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + + // Loop through the children and read out fluid parameters. Process + // all the pureFluidParameters, first: + // Loop back through the "activityCoefficients" children and process the + // crossFluidParameters in the XML tree: + for (size_t i = 0; i < acNode.nChildren(); i++) { + XML_Node& xmlACChild = acNode.child(i); + if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { + readXMLPureFluid(xmlACChild); + } else if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { + readXMLCrossFluid(xmlACChild); + } + } + } + // If any species exist which have undefined pureFluidParameters, + // search the database in 'critProperties.xml' to find critical + // temperature and pressure to calculate a and b. + + // Loop through all species in the CTI file + size_t iSpecies = 0; + + for (size_t i = 0; i < m_kk; i++) { + string iName = speciesName(i); + + // Get the index of the species + iSpecies = speciesIndex(iName); + + // Check if a and b are already populated (only the diagonal elements of a). + size_t counter = iSpecies + m_kk * iSpecies; + + // If not, then search the database: + if (isnan(a_coeff_vec(0, counter))) { + + vector coeffArray; + + // Search the database for the species name and calculate + // coefficients a and b, from critical properties: + // coeffArray[0] = a0, coeffArray[1] = b; + coeffArray = getCoeff(iName); + + // Check if species was found in the database of critical properties, + // and assign the results + if (!isnan(coeffArray[0])) { + //Assuming no temperature dependence (i,e a1 = 0) + setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); + } + } + } + } + + MixtureFugacityTP::initThermoXML(phaseNode, id); +} + +void RedlichKwongMFTP::initThermo() +{ + for (auto& item : m_species) { + // Read a and b coefficients 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", "Redlich-Kwong"); + double a0 = 0, a1 = 0; + if (eos["a"].isScalar()) { + a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); + } else { + auto avec = eos["a"].asVector(2); + a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); + a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); + } + double b = eos.convert("b", "m^3/kmol"); + setSpeciesCoeffs(item.first, a0, a1, b); + 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, a1 = 0; + if (item2.second.isScalar()) { + a0 = units.convert(item2.second, "Pa*m^6/kmol^2*K^0.5"); + } else { + auto avec = item2.second.asVector(2); + a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); + a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); + } + setBinaryCoeffs(item.first, item2.first, a0, a1); + } + } + } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { + // coeffs[0] = a0, coeffs[1] = b; + vector coeffs = getCoeff(item.first); + + // Check if species was found in the database of critical + // properties, and assign the results + if (!isnan(coeffs[0])) { + // Assuming no temperature dependence (i.e. a1 = 0) + setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); + } + } + } + } +} + +vector RedlichKwongMFTP::getCoeff(const std::string& iName) +{ + vector_fp spCoeff{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 specie iName to current database species + // dbName: + if (iNameLower == dbName) { + // Read from database and calculate a and b coefficients + double vParams; + double T_crit=0.; + double P_crit=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("RedlichKwongMFTP::getCoeff", + "Critical Temperature must be positive "); + } + T_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::getCoeff", + "Critical Temperature not in database "); + } + 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("RedlichKwongMFTP::getCoeff", + "Critical Pressure must be positive "); + } + P_crit = vParams; + } else { + throw CanteraError("RedlichKwongMFTP::getCoeff", + "Critical Pressure not in database "); + } + + //Assuming no temperature dependence + spCoeff[0] = omega_a * pow(GasConstant, 2) * pow(T_crit, 2.5) / P_crit; //coeff a + spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b + break; + } + } + return spCoeff; +} + +void RedlichKwongMFTP::readXMLPureFluid(XML_Node& pureFluidParam) +{ + string xname = pureFluidParam.name(); + if (xname != "pureFluidParameters") { + throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", + "Incorrect name for processing this routine: " + xname); + } + + double a0 = 0.0; + double a1 = 0.0; + double b = 0.0; + for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { + XML_Node& xmlChild = pureFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + string iModel = toLowerCopy(xmlChild.attrib("model")); + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + + if (iModel == "constant" && vParams.size() == 1) { + a0 = vParams[0]; + a1 = 0; + } else if (iModel == "linear_a" && vParams.size() == 2) { + a0 = vParams[0]; + a1 = vParams[1]; + } else { + throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", + "unknown model or incorrect number of parameters"); + } + + } else if (nodeName == "b_coeff") { + b = getFloatCurrent(xmlChild, "toSI"); + } + } + setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, a1, b); +} + +void RedlichKwongMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) +{ + string xname = CrossFluidParam.name(); + if (xname != "crossFluidParameters") { + throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", + "Incorrect name for processing this routine: " + xname); + } + + string iName = CrossFluidParam.attrib("species1"); + string jName = CrossFluidParam.attrib("species2"); + + size_t num = CrossFluidParam.nChildren(); + for (size_t iChild = 0; iChild < num; iChild++) { + XML_Node& xmlChild = CrossFluidParam.child(iChild); + string nodeName = toLowerCopy(xmlChild.name()); + + if (nodeName == "a_coeff") { + vector_fp vParams; + getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); + string iModel = toLowerCopy(xmlChild.attrib("model")); + if (iModel == "constant" && vParams.size() == 1) { + setBinaryCoeffs(iName, jName, vParams[0], 0.0); + } else if (iModel == "linear_a") { + setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); + } else { + throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", + "unknown model ({}) or wrong number of parameters ({})", + iModel, vParams.size()); + } + } + } +} + +void RedlichKwongMFTP::setParametersFromXML(const XML_Node& thermoNode) +{ + MixtureFugacityTP::setParametersFromXML(thermoNode); + std::string model = thermoNode["model"]; +} + +doublereal RedlichKwongMFTP::sresid() const +{ + // note this agrees with tpx + doublereal rho = density(); + doublereal mmw = meanMolecularWeight(); + doublereal molarV = mmw / rho; + double hh = m_b_current / molarV; + doublereal zz = z(); + doublereal dadt = da_dt(); + doublereal T = temperature(); + doublereal sqT = sqrt(T); + doublereal fac = dadt - m_a_current / (2.0 * T); + double sresid_mol_R = log(zz*(1.0 - hh)) + log(1.0 + hh) * fac / (sqT * GasConstant * m_b_current); + return GasConstant * sresid_mol_R; +} + +doublereal RedlichKwongMFTP::hresid() const +{ + // note this agrees with tpx + doublereal rho = density(); + doublereal mmw = meanMolecularWeight(); + doublereal molarV = mmw / rho; + double hh = m_b_current / molarV; + doublereal zz = z(); + doublereal dadt = da_dt(); + doublereal T = temperature(); + doublereal sqT = sqrt(T); + doublereal fac = T * dadt - 3.0 *m_a_current / (2.0); + return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqT * m_b_current); +} + +doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGuess) const +{ + double v = m_b_current * 1.1; + double atmp; + double btmp; + calculateAB(TKelvin, atmp, btmp); + doublereal pres = std::max(psatEst(TKelvin), presGuess); + double Vroot[3]; + bool foundLiq = false; + int m = 0; + while (m < 100 && !foundLiq) { + int nsol = CubicCall(TKelvin, pres, atmp, btmp, 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; +} + +doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, int phaseRequested, doublereal rhoguess) +{ + // It's necessary to set the temperature so that m_a_current is set correctly. + setTemperature(TKelvin); + double tcrit = critTemperature(); + double mmw = meanMolecularWeight(); + if (rhoguess == -1.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 + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } + + + doublereal volguess = mmw / rhoguess; + NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); + + doublereal molarVolLast = Vroot_[0]; + if (NSolns_ >= 2) { + if (phaseRequested >= FLUID_LIQUID_0) { + molarVolLast = Vroot_[0]; + } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[2]; + } else { + if (volguess > Vroot_[1]) { + molarVolLast = Vroot_[2]; + } else { + molarVolLast = Vroot_[0]; + } + } + } else if (NSolns_ == 1) { + if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else if (NSolns_ == -1) { + if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { + molarVolLast = Vroot_[0]; + } else if (TKelvin > tcrit) { + molarVolLast = Vroot_[0]; + } else { + return -2.0; + } + } else { + molarVolLast = Vroot_[0]; + return -1.0; + } + return mmw / molarVolLast; +} + +doublereal RedlichKwongMFTP::densSpinodalLiquid() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); + + doublereal mmw = meanMolecularWeight(); + return mmw / (0.5 * (vv.first + vv.second)); +} + +doublereal RedlichKwongMFTP::densSpinodalGas() const +{ + double Vroot[3]; + double T = temperature(); + int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); + + doublereal mmw = meanMolecularWeight(); + 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); + presCalc = GasConstant * TKelvin / (molarVol - m_b_current) + - m_a_current / (sqt * molarVol * (molarVol + m_b_current)); + + doublereal vpb = molarVol + m_b_current; + doublereal vmb = molarVol - m_b_current; + doublereal dpdv = (- GasConstant * TKelvin / (vmb * vmb) + + m_a_current * (2 * molarVol + m_b_current) / (sqt * molarVol * molarVol * vpb * vpb)); + return dpdv; +} + +void RedlichKwongMFTP::pressureDerivatives() const +{ + doublereal TKelvin = temperature(); + doublereal mv = molarVolume(); + doublereal pres; + + dpdV_ = dpdVCalc(TKelvin, mv, pres); + doublereal sqt = sqrt(TKelvin); + doublereal vpb = mv + m_b_current; + doublereal vmb = mv - m_b_current; + doublereal dadt = da_dt(); + doublereal fac = dadt - m_a_current/(2.0 * TKelvin); + dpdT_ = (GasConstant / vmb - fac / (sqt * mv * vpb)); +} + +void RedlichKwongMFTP::updateMixingExpressions() +{ + double temp = temperature(); + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + a_vec_Curr_[counter] = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; + } + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + for (size_t i = 0; i < m_kk; i++) { + m_b_current += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; + } + } + if (isnan(m_b_current)) { + // One or more species do not have specified coefficients. + fmt::memory_buffer b; + for (size_t k = 0; k < m_kk; k++) { + if (isnan(b_vec_Curr_[k])) { + if (b.size() > 0) { + format_to(b, ", {}", speciesName(k)); + } else { + format_to(b, "{}", speciesName(k)); + } + } + } + throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", + "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); + } +} + +void RedlichKwongMFTP::calculateAB(doublereal temp, doublereal& aCalc, doublereal& bCalc) const +{ + bCalc = 0.0; + aCalc = 0.0; + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + bCalc += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + doublereal a_vec_Curr = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + } + } + } else { + for (size_t i = 0; i < m_kk; i++) { + bCalc += moleFractions_[i] * b_vec_Curr_[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + doublereal a_vec_Curr = a_coeff_vec(0,counter); + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + } + } + } +} + +doublereal RedlichKwongMFTP::da_dt() const +{ + doublereal dadT = 0.0; + if (m_formTempParam == 1) { + for (size_t i = 0; i < m_kk; i++) { + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + dadT+= a_coeff_vec(1,counter) * moleFractions_[i] * moleFractions_[j]; + } + } + } + return dadT; +} + +void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, + doublereal& pc, doublereal& tc, doublereal& vc) const +{ + if (m_formTempParam != 0) { + a = a0_coeff; + } + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + double tmp = a * omega_b / (b * omega_a * GasConstant); + double pp = 2./3.; + doublereal sqrttc, f, dfdt, deltatc; + + if (m_formTempParam == 0) { + tc = pow(tmp, pp); + } else { + tc = pow(tmp, pp); + for (int j = 0; j < 10; j++) { + sqrttc = sqrt(tc); + f = omega_a * b * GasConstant * tc * sqrttc / omega_b - aT_coeff * tc - a0_coeff; + dfdt = 1.5 * omega_a * b * GasConstant * sqrttc / omega_b - aT_coeff; + deltatc = - f / dfdt; + tc += deltatc; + } + if (deltatc > 0.1) { + throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); + } + } + + pc = omega_b * GasConstant * tc / b; + vc = omega_vc * GasConstant * tc / pc; +} + +int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const +{ + + // Derive the coefficients of the cubic polynomial to solve. + doublereal an = 1.0; + 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); + double pp = 2./3.; + double tc = pow(tmp, pp); + double pc = omega_b * GasConstant * tc / b; + double vc = omega_vc * GasConstant * tc / pc; + + int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, pc); + + return nSolnValues; +} + +} +>>>>>>> a27b14695... Fixing indentation and white spaces diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti index 147816cd9d..f43605b7ae 100644 --- a/test/data/co2_PR_example.cti +++ b/test/data/co2_PR_example.cti @@ -1,4 +1,3 @@ -# Transport data from file ../transport/gri30_tran.dat. units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml index ee159f4f5e..cb0f817671 100644 --- a/test/data/co2_PR_example.yaml +++ b/test/data/co2_PR_example.yaml @@ -121,4 +121,4 @@ species: reactions: - equation: CO2 + H2 <=> CO + H2O # Reaction 1 - rate-constant: {A: 1.2E+3, b: 0, Ea: 0} \ No newline at end of file + rate-constant: {A: 1.2E+3, b: 0, Ea: 0} diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index a6bc2f6e89..1c7e00a638 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -115,8 +115,10 @@ TEST_F(PengRobinson_Test, activityCoeffs) 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)); + 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) @@ -196,7 +198,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 99.9%, balance H2). - * Values of a_coeff, b_coeff are calculated based on the the critical temperature and pressure values of CO2 as follows: + * 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 @@ -222,7 +225,8 @@ TEST_F(PengRobinson_Test, getPressure) 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); + 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); } } From fded71591685de279e9bb0d22a51de93bedc4a89 Mon Sep 17 00:00:00 2001 From: gkogekar Date: Mon, 15 Jun 2020 17:14:14 -0600 Subject: [PATCH 042/110] Modifying comments and correcting typos --- include/cantera/thermo/PengRobinson.h | 14 +++++++------- src/thermo/MixtureFugacityTP.cpp | 12 ++++++------ src/thermo/PengRobinson.cpp | 14 +++++++------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index ad73ef33dc..06612b612e 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -68,14 +68,14 @@ class PengRobinson : public MixtureFugacityTP * and * * \f[ - * \kappa = \left(0.37464 + 1.54226\omega - 0.26992\omega^2\right) for omega <= 0.491 - * \kappa = \left(0.379642 + 1.487503\omega - 0.164423\omega^2 + 0.016667\omega^3 \right) for omega > 0.491 + * \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} + * 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[ @@ -83,7 +83,7 @@ class PengRobinson : public MixtureFugacityTP * \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} + * {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] * * @@ -144,10 +144,10 @@ class PengRobinson : public MixtureFugacityTP virtual void getPartialMolarCp(double* cpbar) const; virtual void getPartialMolarVolumes(double* vbar) const; - //! Calculate the temperature dependent interaction parameter alpha needed for P-R EoS + //! Calculate the temperature dependent interaction parameter \f$\alpha\f$ needed for P-R EoS /*! * The temperature dependent parameter in P-R EoS is calculated as - * \f$ \alpha = [1 + \kappa(1 - sqrt{T/T_crit})]^2 \f$ + * \f$ \alpha = [1 + \kappa(1 - \sqrt{T/T_{crit}})]^2 \f$ * kappa is a function calulated based on the acentric factor. * Units: unitless * @@ -286,7 +286,7 @@ class PengRobinson : public MixtureFugacityTP //! Form of the temperature parameterization /*! * - 0 = There is no temperature parameterization of a - * - 1 = The a_ij parameter is a linear function of the temperature + * - 1 = The \f$a_{ij} \f$ parameter is a linear function of the temperature */ int m_formTempParam; diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 63f6a5130d..1708f25ce1 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -838,7 +838,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, { fill_n(Vroot, 3, 0.0); if (T <= 0.0) { - throw CanteraError("MixtureFugacityTP::CubicSolve", "negative temperature T = {}", T); + throw CanteraError("MixtureFugacityTP::solveCubic", "negative temperature T = {}", T); } // Derive the center of the cubic, x_N @@ -884,7 +884,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, //check if y = h if (fabs(fabs(h) - fabs(yN)) < 1.0E-10) { if (disc > 1e-10) { - throw CanteraError("MixtureFugacityTP::CubicSolve", "value of yN and h are too high, unrealistic roots may be obtained"); + throw CanteraError("MixtureFugacityTP::solveCubic", "value of yN and h are too high, unrealistic roots may be obtained"); } disc = 0.0; } @@ -940,7 +940,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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::CubicSolve(T = {}, p = {}):" + writelog("MixtureFugacityTP::solveCubic(T = {}, p = {}):" " WARNING roots have merged: {}, {}\n", T, pres, Vroot[i], Vroot[j]); } @@ -960,7 +960,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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::CubicSolve", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::solveCubic", "Inconsistancy in cubic solver : solver is poorly conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -968,7 +968,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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::CubicSolve", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::solveCubic", "Inconsistancy in cubic solver : solver is poorly conditioned."); } delta = -delta; Vroot[0] = xN + delta; @@ -1000,7 +1000,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, } } if ((fabs(res) > 1.0E-14) && (fabs(res) > 1.0E-14 * fabs(dresdV) * fabs(Vroot[i]))) { - writelog("MixtureFugacityTP::CubicSolve(T = {}, p = {}): " + writelog("MixtureFugacityTP::solveCubic(T = {}, p = {}): " "WARNING root didn't converge V = {}", T, pres, Vroot[i]); writelogendl(); } diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index e70054dd5a..8b822eb824 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -137,7 +137,7 @@ double PengRobinson::cp_mole() const pressureDerivatives(); double cpref = GasConstant * mean_X(m_cp0_R); double dHdT_V = cpref + mv * m_dpdT - GasConstant - + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); return dHdT_V - (mv + T * m_dpdT / m_dpdV) * m_dpdT; } @@ -277,7 +277,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; for (size_t k = 0; k < m_kk; k++) { double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2)*fac - + (mv * m_b_vec_Curr[k]) /(m_b_current*den) * fac; + + (mv * m_b_vec_Curr[k]) /(m_b_current*den) * fac; hbar[k] = hbar[k] + hE_v; hbar[k] -= fac2 * m_dpdni[k]; } @@ -315,11 +315,11 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const for (size_t k = 0; k < m_kk; k++) { coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_vec_Curr[k]; sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) - + GasConstant - + GasConstant * log(mv / vmb) - + GasConstant * m_b_vec_Curr[k] / vmb - - coeff1* log(vpb2 / vmb2) / den1 - - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; + + GasConstant + + GasConstant * log(mv / vmb) + + GasConstant * m_b_vec_Curr[k] / vmb + - coeff1* log(vpb2 / vmb2) / den1 + - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; } pressureDerivatives(); getPartialMolarVolumes(m_partialMolarVolumes.data()); From df22aa76042688e0c5f62862c15ff4617a076116 Mon Sep 17 00:00:00 2001 From: gkogekar Date: Tue, 16 Jun 2020 14:12:53 -0600 Subject: [PATCH 043/110] Replacing line endings with LF instead of CRLF --- include/cantera/thermo/MixtureFugacityTP.h | 1147 ++++--- include/cantera/thermo/PengRobinson.h | 722 ++--- include/cantera/thermo/RedlichKwongMFTP.h | 1 - src/thermo/MixtureFugacityTP.cpp | 2068 ++++++------ src/thermo/PengRobinson.cpp | 1806 +++++------ src/thermo/RedlichKwongMFTP.cpp | 3301 +------------------- 6 files changed, 2874 insertions(+), 6171 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index f015f51418..9cda61785e 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -1,574 +1,573 @@ -/** - * @file MixtureFugacityTP.h - * Header file for a derived class of ThermoPhase that handles - * non-ideal mixtures based on the fugacity models (see \ref thermoprops and - * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). - */ - -// 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_MIXTUREFUGACITYTP_H -#define CT_MIXTUREFUGACITYTP_H - -#include "ThermoPhase.h" -#include "cantera/numerics/ResidEval.h" - -namespace Cantera -{ -//! Various states of the Fugacity object. In general there can be multiple liquid -//! objects for a single phase identified with each species. - -#define FLUID_UNSTABLE -4 -#define FLUID_UNDEFINED -3 -#define FLUID_SUPERCRIT -2 -#define FLUID_GAS -1 -#define FLUID_LIQUID_0 0 -#define FLUID_LIQUID_1 1 -#define FLUID_LIQUID_2 2 -#define FLUID_LIQUID_3 3 -#define FLUID_LIQUID_4 4 -#define FLUID_LIQUID_5 5 -#define FLUID_LIQUID_6 6 -#define FLUID_LIQUID_7 7 -#define FLUID_LIQUID_8 8 -#define FLUID_LIQUID_9 9 - -/** - * @ingroup thermoprops - * - * This is a filter class for ThermoPhase that implements some preparatory steps - * for efficiently handling mixture of gases that whose standard states are - * defined as ideal gases, but which describe also non-ideal solutions. In - * addition a multicomponent liquid phase below the critical temperature of the - * mixture is also allowed. The main subclass is currently a mixture Redlich- - * Kwong class. - * - * @attention This class currently does not have any test cases or examples. Its - * implementation may be incomplete, and future changes to Cantera may - * unexpectedly cause this class to stop working. If you use this class, - * please consider contributing examples or test cases. In the absence of - * new tests or examples, this class may be deprecated and removed in a - * future version of Cantera. See - * https://github.com/Cantera/cantera/issues/267 for additional information. - * - * Several concepts are introduced. The first concept is there are temporary - * variables for holding the species standard state values of Cp, H, S, G, and V - * at the last temperature and pressure called. These functions are not - * recalculated if a new call is made using the previous temperature and - * pressure. - * - * The other concept is that the current state of the mixture is tracked. The - * state variable is either GAS, LIQUID, or SUPERCRIT fluid. Additionally, the - * variable LiquidContent is used and may vary between 0 and 1. - * - * Typically, only one liquid phase is allowed to be formed within these - * classes. Additionally, there is an inherent contradiction between three phase - * models and the ThermoPhase class. The ThermoPhase class is really only meant - * to represent a single instantiation of a phase. The three phase models may be - * in equilibrium with multiple phases of the fluid in equilibrium with each - * other. This has yet to be resolved. - * - * This class is usually used for non-ideal gases. - */ -class MixtureFugacityTP : public ThermoPhase -{ -public: - //! @name Constructors and Duplicators for MixtureFugacityTP - //! @{ - - //! Constructor. - MixtureFugacityTP(); - - //! @} - //! @name Utilities - //! @{ - - virtual std::string type() const { - return "MixtureFugacity"; - } - - virtual int standardStateConvention() const; - - //! Set the solution branch to force the ThermoPhase to exist on one branch - //! or another - /*! - * @param solnBranch Branch that the solution is restricted to. the value - * -1 means gas. The value -2 means unrestricted. Values of zero or - * greater refer to species dominated condensed phases. - */ - virtual void setForcedSolutionBranch(int solnBranch); - - //! Report the solution branch which the solution is restricted to - /*! - * @return Branch that the solution is restricted to. the value -1 means - * gas. The value -2 means unrestricted. Values of zero or greater - * refer to species dominated condensed phases. - */ - virtual int forcedSolutionBranch() const; - - //! Report the solution branch which the solution is actually on - /*! - * @return Branch that the solution is restricted to. the value -1 means - * gas. The value -2 means superfluid.. Values of zero or greater refer - * to species dominated condensed phases. - */ - virtual int reportSolnBranchActual() const; - - virtual void getdlnActCoeffdlnN_diag(doublereal* dlnActCoeffdlnN_diag) const { - throw NotImplementedError("MixtureFugacityTP::getdlnActCoeffdlnN_diag"); - } - - - //! @name Molar Thermodynamic properties - //! @{ - - virtual double enthalpy_mole() const; - virtual doublereal entropy_mole() const; - - //@} - /// @name Partial Molar Properties of the Solution - //@{ - - //! Get the array of non-dimensional species chemical potentials - //! These are partial molar Gibbs free energies. - /*! - * \f$ \mu_k / \hat R T \f$. - * Units: unitless - * - * We close the loop on this function, here, calling getChemPotentials() and - * then dividing by RT. No need for child classes to handle. - * - * @param mu Output vector of non-dimensional species chemical potentials - * Length: m_kk. - */ - virtual void getChemPotentials_RT(doublereal* mu) const; - - //@} - /*! - * @name Properties of the Standard State of the Species in the Solution - * - * Within MixtureFugacityTP, these properties are calculated via a common - * routine, _updateStandardStateThermo(), which must be overloaded in - * inherited objects. The values are cached within this object, and are not - * recalculated unless the temperature or pressure changes. - */ - //@{ - - //! Get the array of chemical potentials at unit activity. - /*! - * These are the standard state chemical potentials \f$ \mu^0_k(T,P) - * \f$. The values are evaluated at the current temperature and pressure. - * - * 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. - * - * @param mu Output vector of standard state chemical potentials. - * length = m_kk. units are J / kmol. - */ - virtual void getStandardChemPotentials(doublereal* mu) const; - - //! Get the nondimensional Enthalpy functions for the species at their - //! standard states at the current *T* and *P* of the solution. - /*! - * 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. - * - * @param hrt Output vector of standard state enthalpies. - * length = m_kk. units are unitless. - */ - virtual void getEnthalpy_RT(doublereal* hrt) const; - - //! Get the array of nondimensional Enthalpy functions for the standard - //! state species at the current *T* and *P* of the solution. - /*! - * 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. - * - * @param sr Output vector of nondimensional standard state entropies. - * length = m_kk. - */ - virtual void getEntropy_R(doublereal* sr) const; - - //! Get the nondimensional Gibbs functions for the species at their standard - //! states of solution at the current T and P of the solution. - /*! - * 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. - * - * @param grt Output vector of nondimensional standard state Gibbs free - * energies. length = m_kk. - */ - virtual void getGibbs_RT(doublereal* grt) const; - - //! Get the pure Gibbs free energies of each species. Species are assumed to - //! be in their standard states. - /*! - * This is the same as getStandardChemPotentials(). - * - * @param[out] gpure Array of standard state Gibbs free energies. length = - * m_kk. units are J/kmol. - */ - virtual void getPureGibbs(doublereal* gpure) const; - - //! Returns the vector of nondimensional internal Energies of the standard - //! state at the current temperature and pressure of the solution for each - //! species. - /*! - * 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. - * - * \f[ - * u^{ss}_k(T,P) = h^{ss}_k(T) - P * V^{ss}_k - * \f] - * - * @param urt Output vector of nondimensional standard state internal - * energies. length = m_kk. - */ - virtual void getIntEnergy_RT(doublereal* urt) const; - - //! Get the nondimensional Heat Capacities at constant pressure for the - //! standard state of the species at the current T and P. - /*! - * 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. - * - * @param cpr Output vector containing the the nondimensional Heat - * Capacities at constant pressure for the standard state of - * the species. Length: m_kk. - */ - virtual void getCp_R(doublereal* cpr) const; - - //! Get the molar volumes of each species in their standard states at the - //! current *T* and *P* of the solution. - /*! - * 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. - * - * units = m^3 / kmol - * - * @param vol Output vector of species volumes. length = m_kk. - * units = m^3 / kmol - */ - virtual void getStandardVolumes(doublereal* vol) const; - // @} - - //! Set the temperature of the phase - /*! - * Currently this passes down to setState_TP(). It does not make sense to - * calculate the standard state without first setting T and P. - * - * @param temp Temperature (kelvin) - */ - virtual void setTemperature(const doublereal temp); - - //! Set the internally stored pressure (Pa) at constant temperature and - //! composition - /*! - * Currently this passes down to setState_TP(). It does not make sense to - * calculate the standard state without first setting T and P. - * - * @param p input Pressure (Pa) - */ - virtual void setPressure(doublereal p); - -protected: - virtual void compositionChanged(); - - //! Updates the reference state thermodynamic functions at the current T of - //! the solution. - /*! - * This function must be called for every call to functions in this class. - * It checks to see whether the temperature has changed and thus the ss - * thermodynamics functions for all of the species must be recalculated. - * - * This function is responsible for updating the following internal members: - * - * - m_h0_RT; - * - m_cp0_R; - * - m_g0_RT; - * - 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 - /*! - * There are also temporary variables for holding the species reference- - * state values of Cp, H, S, and V at the last temperature and reference - * pressure called. These functions are not recalculated if a new call is - * made using the previous temperature. All calculations are done within the - * routine _updateRefStateThermo(). - */ - //@{ - - virtual void getEnthalpy_RT_ref(doublereal* hrt) const; - virtual void getGibbs_RT_ref(doublereal* grt) const; - -protected: - //! Returns the vector of nondimensional Gibbs free energies of the - //! reference state at the current temperature of the solution and the - //! reference pressure for the species. - /*! - * @return Output vector contains the nondimensional Gibbs free energies - * of the reference state of the species - * length = m_kk, units = dimensionless. - */ - const vector_fp& gibbs_RT_ref() const; - -public: - virtual void getGibbs_ref(doublereal* g) const; - virtual void getEntropy_R_ref(doublereal* er) const; - virtual void getCp_R_ref(doublereal* cprt) const; - virtual void getStandardVolumes_ref(doublereal* vol) 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 setStateFromXML(const XML_Node& state); - -protected: - //! @name Special Functions for fugacity classes - //! @{ - - //! Calculate the value of z - /*! - * \f[ - * z = \frac{P v}{R T} - * \f] - * - * returns the value of z - */ - doublereal z() const; - - //! Calculate the deviation terms for the total entropy of the mixture from - //! the ideal gas mixture - /* - * Here we use the current state conditions - * - * @returns the change in entropy in units of J kmol-1 K-1. - */ - virtual doublereal sresid() const; - - //! Calculate the deviation terms for the total enthalpy of the mixture from - //! the ideal gas mixture - /* - * Here we use the current state conditions - * - * @returns the change in entropy in units of J kmol-1. - */ - virtual doublereal hresid() const; - - //! Estimate for the saturation pressure - /*! - * Note: this is only used as a starting guess for later routines that - * actually calculate an accurate value for the saturation pressure. - * - * @param TKelvin temperature in kelvin - * @return the estimated saturation pressure at the given temperature - */ - virtual doublereal psatEst(doublereal TKelvin) const; - -public: - //! Estimate for the molar volume of the liquid - /*! - * Note: this is only used as a starting guess for later routines that - * actually calculate an accurate value for the liquid molar volume. This - * routine doesn't change the state of the system. - * - * @param TKelvin temperature in kelvin - * @param pres Pressure in Pa. This is used as an initial guess. If the - * routine needs to change the pressure to find a stable - * liquid state, the new pressure is returned in this - * variable. - * @returns the estimate of the liquid volume. If the liquid can't be - * found, this routine returns -1. - */ - virtual doublereal liquidVolEst(doublereal TKelvin, doublereal& pres) const; - - //! Calculates the density given the temperature and the pressure and a - //! guess at the density. - /*! - * Note, below T_c, this is a multivalued function. We do not cross the - * vapor dome in this. This is protected because it is called during - * 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) - * @param phaseRequested int representing the phase whose density we are - * requesting. If we put a gas or liquid phase here, we will attempt to - * find a volume in that part of the volume space, only, in this - * routine. A value of FLUID_UNDEFINED means that we will accept - * anything. - * @param rhoguess Guessed density of the fluid. A value of -1.0 indicates - * that there is no guessed density - * @return We return the density of the fluid at the requested phase. If - * we have not found any acceptable density we return a -1. If we - * have found an acceptable density at a different phase, we - * return a -2. - */ - virtual doublereal densityCalc(doublereal TKelvin, doublereal pressure, int phaseRequested, - doublereal rhoguess); - -protected: - //! Utility routine in the calculation of the saturation pressure - /*! - * @param TKelvin temperature (kelvin) - * @param pres pressure (Pascal) - * @param[out] densLiq density of liquid - * @param[out] densGas density of gas - * @param[out] liqGRT deltaG/RT of liquid - * @param[out] gasGRT deltaG/RT of gas - */ - int corr0(doublereal TKelvin, doublereal pres, doublereal& densLiq, - doublereal& densGas, doublereal& liqGRT, doublereal& gasGRT); - -public: - //! Returns the Phase State flag for the current state of the object - /*! - * @param checkState If true, this function does a complete check to see - * where in parameters space we are - * - * There are three values: - * - WATER_GAS below the critical temperature but below the critical density - * - WATER_LIQUID below the critical temperature but above the critical density - * - WATER_SUPERCRIT above the critical temperature - */ - int phaseState(bool checkState = false) const; - - //! Return the value of the density at the liquid spinodal point (on the - //! liquid side) for the current temperature. - /*! - * @returns the density with units of kg m-3 - */ - virtual doublereal densSpinodalLiquid() const; - - //! Return the value of the density at the gas spinodal point (on the gas - //! side) for the current temperature. - /*! - * @returns the density with units of kg m-3 - */ - virtual doublereal densSpinodalGas() const; - -public: - //! Calculate the saturation pressure at the current mixture content for the - //! given temperature - /*! - * @param TKelvin (input) Temperature (Kelvin) - * @param molarVolGas (return) Molar volume of the gas - * @param molarVolLiquid (return) Molar volume of the liquid - * @returns the saturation pressure at the given temperature - */ - doublereal calculatePsat(doublereal TKelvin, doublereal& molarVolGas, - doublereal& molarVolLiquid); - -public: - //! Calculate the saturation pressure at the current mixture content for the - //! given temperature - /*! - * @param TKelvin Temperature (Kelvin) - * @return The saturation pressure at the given temperature - */ - virtual doublereal satPressure(doublereal TKelvin); - virtual void setToEquilState(const doublereal* lambda_RT); - virtual void getActivityConcentrations(double* c) const; - -protected: - //! Calculate the pressure given the temperature and the molar volume - /*! - * @param TKelvin temperature in kelvin - * @param molarVol molar volume ( m3/kmol) - * @returns the pressure. - */ - virtual doublereal pressureCalc(doublereal TKelvin, doublereal molarVol) const; - - //! Calculate the pressure and the pressure derivative given the temperature - //! and the molar volume - /*! - * Temperature and mole number are held constant - * - * @param TKelvin temperature in kelvin - * @param molarVol molar volume ( m3/kmol) - * @param presCalc Returns the pressure. - * @returns the derivative of the pressure wrt the molar volume - */ - virtual doublereal dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const; - - virtual void updateMixingExpressions(); - - - //! Solve the cubic equation of state - /*! - * - * Returns the number of solutions found. 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) - */ - 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; - - //@} - -protected: - //! Storage for the current values of the mole fractions of the species - /*! - * This vector is kept up-to-date when some the setState functions are called. - */ - vector_fp moleFractions_; - - //! Current state of the fluid - /*! - * There are three possible states of the fluid: - * - FLUID_GAS - * - FLUID_LIQUID - * - FLUID_SUPERCRIT - */ - int iState_; - - //! Force the system to be on a particular side of the spinodal curve - int forcedState_; - - //! Temporary storage for dimensionless reference state enthalpies - mutable vector_fp m_h0_RT; - - //! Temporary storage for dimensionless reference state heat capacities - mutable vector_fp m_cp0_R; - - //! Temporary storage for dimensionless reference state Gibbs energies - mutable vector_fp m_g0_RT; - - //! Temporary storage for dimensionless reference state entropies - mutable vector_fp m_s0_R; - -public: - //! Temporary storage - length = m_kk. - mutable vector_fp m_pp; -}; -} - -#endif +/** + * @file MixtureFugacityTP.h + * Header file for a derived class of ThermoPhase that handles + * non-ideal mixtures based on the fugacity models (see \ref thermoprops and + * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). + */ + +// 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_MIXTUREFUGACITYTP_H +#define CT_MIXTUREFUGACITYTP_H + +#include "ThermoPhase.h" +#include "cantera/numerics/ResidEval.h" + +namespace Cantera +{ +//! Various states of the Fugacity object. In general there can be multiple liquid +//! objects for a single phase identified with each species. + +#define FLUID_UNSTABLE -4 +#define FLUID_UNDEFINED -3 +#define FLUID_SUPERCRIT -2 +#define FLUID_GAS -1 +#define FLUID_LIQUID_0 0 +#define FLUID_LIQUID_1 1 +#define FLUID_LIQUID_2 2 +#define FLUID_LIQUID_3 3 +#define FLUID_LIQUID_4 4 +#define FLUID_LIQUID_5 5 +#define FLUID_LIQUID_6 6 +#define FLUID_LIQUID_7 7 +#define FLUID_LIQUID_8 8 +#define FLUID_LIQUID_9 9 + +/** + * @ingroup thermoprops + * + * This is a filter class for ThermoPhase that implements some preparatory steps + * for efficiently handling mixture of gases that whose standard states are + * defined as ideal gases, but which describe also non-ideal solutions. In + * addition a multicomponent liquid phase below the critical temperature of the + * mixture is also allowed. The main subclass is currently a mixture Redlich- + * Kwong class. + * + * @attention This class currently does not have any test cases or examples. Its + * implementation may be incomplete, and future changes to Cantera may + * unexpectedly cause this class to stop working. If you use this class, + * please consider contributing examples or test cases. In the absence of + * new tests or examples, this class may be deprecated and removed in a + * future version of Cantera. See + * https://github.com/Cantera/cantera/issues/267 for additional information. + * + * Several concepts are introduced. The first concept is there are temporary + * variables for holding the species standard state values of Cp, H, S, G, and V + * at the last temperature and pressure called. These functions are not + * recalculated if a new call is made using the previous temperature and + * pressure. + * + * The other concept is that the current state of the mixture is tracked. The + * state variable is either GAS, LIQUID, or SUPERCRIT fluid. Additionally, the + * variable LiquidContent is used and may vary between 0 and 1. + * + * Typically, only one liquid phase is allowed to be formed within these + * classes. Additionally, there is an inherent contradiction between three phase + * models and the ThermoPhase class. The ThermoPhase class is really only meant + * to represent a single instantiation of a phase. The three phase models may be + * in equilibrium with multiple phases of the fluid in equilibrium with each + * other. This has yet to be resolved. + * + * This class is usually used for non-ideal gases. + */ +class MixtureFugacityTP : public ThermoPhase +{ +public: + //! @name Constructors and Duplicators for MixtureFugacityTP + //! @{ + + //! Constructor. + MixtureFugacityTP(); + + //! @} + //! @name Utilities + //! @{ + + virtual std::string type() const { + return "MixtureFugacity"; + } + + virtual int standardStateConvention() const; + + //! Set the solution branch to force the ThermoPhase to exist on one branch + //! or another + /*! + * @param solnBranch Branch that the solution is restricted to. the value + * -1 means gas. The value -2 means unrestricted. Values of zero or + * greater refer to species dominated condensed phases. + */ + virtual void setForcedSolutionBranch(int solnBranch); + + //! Report the solution branch which the solution is restricted to + /*! + * @return Branch that the solution is restricted to. the value -1 means + * gas. The value -2 means unrestricted. Values of zero or greater + * refer to species dominated condensed phases. + */ + virtual int forcedSolutionBranch() const; + + //! Report the solution branch which the solution is actually on + /*! + * @return Branch that the solution is restricted to. the value -1 means + * gas. The value -2 means superfluid.. Values of zero or greater refer + * to species dominated condensed phases. + */ + virtual int reportSolnBranchActual() const; + + virtual void getdlnActCoeffdlnN_diag(doublereal* dlnActCoeffdlnN_diag) const { + throw NotImplementedError("MixtureFugacityTP::getdlnActCoeffdlnN_diag"); + } + + + //! @name Molar Thermodynamic properties + //! @{ + + virtual double enthalpy_mole() const; + virtual doublereal entropy_mole() const; + + //@} + /// @name Partial Molar Properties of the Solution + //@{ + + //! Get the array of non-dimensional species chemical potentials + //! These are partial molar Gibbs free energies. + /*! + * \f$ \mu_k / \hat R T \f$. + * Units: unitless + * + * We close the loop on this function, here, calling getChemPotentials() and + * then dividing by RT. No need for child classes to handle. + * + * @param mu Output vector of non-dimensional species chemical potentials + * Length: m_kk. + */ + virtual void getChemPotentials_RT(doublereal* mu) const; + + //@} + /*! + * @name Properties of the Standard State of the Species in the Solution + * + * Within MixtureFugacityTP, these properties are calculated via a common + * routine, _updateStandardStateThermo(), which must be overloaded in + * inherited objects. The values are cached within this object, and are not + * recalculated unless the temperature or pressure changes. + */ + //@{ + + //! Get the array of chemical potentials at unit activity. + /*! + * These are the standard state chemical potentials \f$ \mu^0_k(T,P) + * \f$. The values are evaluated at the current temperature and pressure. + * + * 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. + * + * @param mu Output vector of standard state chemical potentials. + * length = m_kk. units are J / kmol. + */ + virtual void getStandardChemPotentials(doublereal* mu) const; + + //! Get the nondimensional Enthalpy functions for the species at their + //! standard states at the current *T* and *P* of the solution. + /*! + * 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. + * + * @param hrt Output vector of standard state enthalpies. + * length = m_kk. units are unitless. + */ + virtual void getEnthalpy_RT(doublereal* hrt) const; + + //! Get the array of nondimensional Enthalpy functions for the standard + //! state species at the current *T* and *P* of the solution. + /*! + * 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. + * + * @param sr Output vector of nondimensional standard state entropies. + * length = m_kk. + */ + virtual void getEntropy_R(doublereal* sr) const; + + //! Get the nondimensional Gibbs functions for the species at their standard + //! states of solution at the current T and P of the solution. + /*! + * 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. + * + * @param grt Output vector of nondimensional standard state Gibbs free + * energies. length = m_kk. + */ + virtual void getGibbs_RT(doublereal* grt) const; + + //! Get the pure Gibbs free energies of each species. Species are assumed to + //! be in their standard states. + /*! + * This is the same as getStandardChemPotentials(). + * + * @param[out] gpure Array of standard state Gibbs free energies. length = + * m_kk. units are J/kmol. + */ + virtual void getPureGibbs(doublereal* gpure) const; + + //! Returns the vector of nondimensional internal Energies of the standard + //! state at the current temperature and pressure of the solution for each + //! species. + /*! + * 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. + * + * \f[ + * u^{ss}_k(T,P) = h^{ss}_k(T) - P * V^{ss}_k + * \f] + * + * @param urt Output vector of nondimensional standard state internal + * energies. length = m_kk. + */ + virtual void getIntEnergy_RT(doublereal* urt) const; + + //! Get the nondimensional Heat Capacities at constant pressure for the + //! standard state of the species at the current T and P. + /*! + * 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. + * + * @param cpr Output vector containing the the nondimensional Heat + * Capacities at constant pressure for the standard state of + * the species. Length: m_kk. + */ + virtual void getCp_R(doublereal* cpr) const; + + //! Get the molar volumes of each species in their standard states at the + //! current *T* and *P* of the solution. + /*! + * 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. + * + * units = m^3 / kmol + * + * @param vol Output vector of species volumes. length = m_kk. + * units = m^3 / kmol + */ + virtual void getStandardVolumes(doublereal* vol) const; + // @} + + //! Set the temperature of the phase + /*! + * Currently this passes down to setState_TP(). It does not make sense to + * calculate the standard state without first setting T and P. + * + * @param temp Temperature (kelvin) + */ + virtual void setTemperature(const doublereal temp); + + //! Set the internally stored pressure (Pa) at constant temperature and + //! composition + /*! + * Currently this passes down to setState_TP(). It does not make sense to + * calculate the standard state without first setting T and P. + * + * @param p input Pressure (Pa) + */ + virtual void setPressure(doublereal p); + +protected: + virtual void compositionChanged(); + + //! Updates the reference state thermodynamic functions at the current T of + //! the solution. + /*! + * This function must be called for every call to functions in this class. + * It checks to see whether the temperature has changed and thus the ss + * thermodynamics functions for all of the species must be recalculated. + * + * This function is responsible for updating the following internal members: + * + * - m_h0_RT; + * - m_cp0_R; + * - m_g0_RT; + * - 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 + /*! + * There are also temporary variables for holding the species reference- + * state values of Cp, H, S, and V at the last temperature and reference + * pressure called. These functions are not recalculated if a new call is + * made using the previous temperature. All calculations are done within the + * routine _updateRefStateThermo(). + */ + //@{ + + virtual void getEnthalpy_RT_ref(doublereal* hrt) const; + virtual void getGibbs_RT_ref(doublereal* grt) const; + +protected: + //! Returns the vector of nondimensional Gibbs free energies of the + //! reference state at the current temperature of the solution and the + //! reference pressure for the species. + /*! + * @return Output vector contains the nondimensional Gibbs free energies + * of the reference state of the species + * length = m_kk, units = dimensionless. + */ + const vector_fp& gibbs_RT_ref() const; + +public: + virtual void getGibbs_ref(doublereal* g) const; + virtual void getEntropy_R_ref(doublereal* er) const; + virtual void getCp_R_ref(doublereal* cprt) const; + virtual void getStandardVolumes_ref(doublereal* vol) 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 setStateFromXML(const XML_Node& state); + +protected: + //! @name Special Functions for fugacity classes + //! @{ + + //! Calculate the value of z + /*! + * \f[ + * z = \frac{P v}{R T} + * \f] + * + * returns the value of z + */ + doublereal z() const; + + //! Calculate the deviation terms for the total entropy of the mixture from + //! the ideal gas mixture + /* + * Here we use the current state conditions + * + * @returns the change in entropy in units of J kmol-1 K-1. + */ + virtual doublereal sresid() const; + + //! Calculate the deviation terms for the total enthalpy of the mixture from + //! the ideal gas mixture + /* + * Here we use the current state conditions + * + * @returns the change in entropy in units of J kmol-1. + */ + virtual doublereal hresid() const; + + //! Estimate for the saturation pressure + /*! + * Note: this is only used as a starting guess for later routines that + * actually calculate an accurate value for the saturation pressure. + * + * @param TKelvin temperature in kelvin + * @return the estimated saturation pressure at the given temperature + */ + virtual doublereal psatEst(doublereal TKelvin) const; + +public: + //! Estimate for the molar volume of the liquid + /*! + * Note: this is only used as a starting guess for later routines that + * actually calculate an accurate value for the liquid molar volume. This + * routine doesn't change the state of the system. + * + * @param TKelvin temperature in kelvin + * @param pres Pressure in Pa. This is used as an initial guess. If the + * routine needs to change the pressure to find a stable + * liquid state, the new pressure is returned in this + * variable. + * @returns the estimate of the liquid volume. If the liquid can't be + * found, this routine returns -1. + */ + virtual doublereal liquidVolEst(doublereal TKelvin, doublereal& pres) const; + + //! Calculates the density given the temperature and the pressure and a + //! guess at the density. + /*! + * Note, below T_c, this is a multivalued function. We do not cross the + * vapor dome in this. This is protected because it is called during + * setState_TP() routines. Infinite loops would result if it were not + * protected. + * + * + * @param TKelvin Temperature in Kelvin + * @param pressure Pressure in Pascals (Newton/m**2) + * @param phaseRequested int representing the phase whose density we are + * requesting. If we put a gas or liquid phase here, we will attempt to + * find a volume in that part of the volume space, only, in this + * routine. A value of FLUID_UNDEFINED means that we will accept + * anything. + * @param rhoguess Guessed density of the fluid. A value of -1.0 indicates + * that there is no guessed density + * @return We return the density of the fluid at the requested phase. If + * we have not found any acceptable density we return a -1. If we + * have found an acceptable density at a different phase, we + * return a -2. + */ + virtual doublereal densityCalc(doublereal TKelvin, doublereal pressure, int phaseRequested, + doublereal rhoguess); + +protected: + //! Utility routine in the calculation of the saturation pressure + /*! + * @param TKelvin temperature (kelvin) + * @param pres pressure (Pascal) + * @param[out] densLiq density of liquid + * @param[out] densGas density of gas + * @param[out] liqGRT deltaG/RT of liquid + * @param[out] gasGRT deltaG/RT of gas + */ + int corr0(doublereal TKelvin, doublereal pres, doublereal& densLiq, + doublereal& densGas, doublereal& liqGRT, doublereal& gasGRT); + +public: + //! Returns the Phase State flag for the current state of the object + /*! + * @param checkState If true, this function does a complete check to see + * where in parameters space we are + * + * There are three values: + * - WATER_GAS below the critical temperature but below the critical density + * - WATER_LIQUID below the critical temperature but above the critical density + * - WATER_SUPERCRIT above the critical temperature + */ + int phaseState(bool checkState = false) const; + + //! Return the value of the density at the liquid spinodal point (on the + //! liquid side) for the current temperature. + /*! + * @returns the density with units of kg m-3 + */ + virtual doublereal densSpinodalLiquid() const; + + //! Return the value of the density at the gas spinodal point (on the gas + //! side) for the current temperature. + /*! + * @returns the density with units of kg m-3 + */ + virtual doublereal densSpinodalGas() const; + +public: + //! Calculate the saturation pressure at the current mixture content for the + //! given temperature + /*! + * @param TKelvin (input) Temperature (Kelvin) + * @param molarVolGas (return) Molar volume of the gas + * @param molarVolLiquid (return) Molar volume of the liquid + * @returns the saturation pressure at the given temperature + */ + doublereal calculatePsat(doublereal TKelvin, doublereal& molarVolGas, + doublereal& molarVolLiquid); + +public: + //! Calculate the saturation pressure at the current mixture content for the + //! given temperature + /*! + * @param TKelvin Temperature (Kelvin) + * @return The saturation pressure at the given temperature + */ + virtual doublereal satPressure(doublereal TKelvin); + virtual void setToEquilState(const doublereal* lambda_RT); + virtual void getActivityConcentrations(double* c) const; + +protected: + //! Calculate the pressure given the temperature and the molar volume + /*! + * @param TKelvin temperature in kelvin + * @param molarVol molar volume ( m3/kmol) + * @returns the pressure. + */ + virtual doublereal pressureCalc(doublereal TKelvin, doublereal molarVol) const; + + //! Calculate the pressure and the pressure derivative given the temperature + //! and the molar volume + /*! + * Temperature and mole number are held constant + * + * @param TKelvin temperature in kelvin + * @param molarVol molar volume ( m3/kmol) + * @param presCalc Returns the pressure. + * @returns the derivative of the pressure wrt the molar volume + */ + virtual doublereal dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const; + + virtual void updateMixingExpressions(); + + + //! Solve the cubic equation of state + /*! + * + * Returns the number of solutions found. 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) + */ + 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; + + //@} + +protected: + //! Storage for the current values of the mole fractions of the species + /*! + * This vector is kept up-to-date when some the setState functions are called. + */ + vector_fp moleFractions_; + + //! Current state of the fluid + /*! + * There are three possible states of the fluid: + * - FLUID_GAS + * - FLUID_LIQUID + * - FLUID_SUPERCRIT + */ + int iState_; + + //! Force the system to be on a particular side of the spinodal curve + int forcedState_; + + //! Temporary storage for dimensionless reference state enthalpies + mutable vector_fp m_h0_RT; + + //! Temporary storage for dimensionless reference state heat capacities + mutable vector_fp m_cp0_R; + + //! Temporary storage for dimensionless reference state Gibbs energies + mutable vector_fp m_g0_RT; + + //! Temporary storage for dimensionless reference state entropies + mutable vector_fp m_s0_R; + +public: + //! Temporary storage - length = m_kk. + mutable vector_fp m_pp; +}; +} + +#endif diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 06612b612e..a0e2ef7664 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -1,361 +1,361 @@ -//! @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: - //! @name Constructors and Duplicators - //! @{ - - //! Base constructor. - PengRobinson(); - - //! Construct and initialize a PengRobinson object directly from an - //! input file - /*! - * @param infile Name of the input file containing the phase YAML data - * to set up the object - * @param id ID of the phase in the input file. Defaults to the empty - * string. - */ - 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; - - // @} - -public: - - //! 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. In many cases, this - * quantity will be the same for all species in a phase. - * 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 - //@{ - - //! Get the array of non-dimensional species chemical potentials. - //! These are partial molar Gibbs free energies. - /*! - * \f$ \mu_k / \hat R T \f$. - * Units: unitless - * - * - * @param mu Output vector of non-dimensional species chemical potentials - * Length: m_kk. - */ - - 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 the temperature dependent interaction parameter \f$\alpha\f$ needed for P-R EoS - /*! - * The temperature dependent parameter in P-R EoS is calculated as - * \f$ \alpha = [1 + \kappa(1 - \sqrt{T/T_{crit}})]^2 \f$ - * kappa is a function calulated based on the acentric factor. - * Units: unitless - * - * @param a species-specific coefficients used in P-R EoS - * @param b species-specific coefficients used in P-R EoS - * @param w the acentric factor - */ - - virtual void calculateAlpha(const std::string& species, double a, double b, double w); - //@} - /// @name Critical State Properties. - //@{ - - virtual double critTemperature() const; - virtual double critPressure() const; - virtual double critVolume() const; - virtual double critCompressibility() const; - virtual double critDensity() const; - virtual double speciesCritTemperature(double a, double b) const; - -public: - //@} - //! @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 std::vector getCoeff(const std::string& iName); - - //! Set the pure fluid interaction parameters for a species - /*! - * The *a* parameter for species *i* in the Peng-Robinson model is assumed - * to be a linear function of temperature: - * \f[ a = a_0 + a_1 T \f] - * - * @param species Name of the species - * @param a0 constant term in the expression for the "a" parameter - * of the specified species [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the expression for the - * "a" parameter of the specified species [Pa-m^6/kmol^2/K] - * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] - * @param alpha dimensionless function of T_r and \omega - * @param omega acentric factor - */ - void setSpeciesCoeffs(const std::string& species, double a0, 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] - * - * This function overrides the defaults with the specified parameters: - * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] - * - * @param species_i Name of one species - * @param species_j Name of the other species - * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the "a" expression - * [Pa-m^6/kmol^2/K] - */ - void setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1); - -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 pressureCalc(double TKelvin, double molarVol) const; - virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; - - // Special functions not inherited from MixtureFugacityTP - - double daAlpha_dT() const; - double d2aAlpha_dT2() const; - -public: - - //! Calculate dpdV and dpdT at the current conditions - /*! - * These are stored internally. - */ - void pressureDerivatives() const; - - //! 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. - */ - virtual void updateMixingExpressions(); - - //! Calculate the a and the b 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 temp Temperature (TKelvin) - * @param aCalc (output) Returns the a value - * @param bCalc (output) Returns the b value. - */ - void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; - - void calcCriticalConditions(double a, double b,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: - //! Form of the temperature parameterization - /*! - * - 0 = There is no temperature parameterization of a - * - 1 = The \f$a_{ij} \f$ parameter is a linear function of the temperature - */ - int m_formTempParam; - - //! Value of b in the equation of state - /*! - * m_b_current is a function of the temperature and the mole fractions. - */ - double m_b_current; - - //! Value of a and alpha in the equation of state - /*! - * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. - */ - double m_a_current; - double m_aAlpha_current; - - // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk - vector_fp m_a_vec_Curr; - vector_fp m_b_vec_Curr; - vector_fp m_aAlpha_vec_Curr; - vector_fp m_alpha_vec_Curr; - vector_fp m_kappa_vec; - mutable vector_fp m_dalphadT_vec_Curr; - mutable vector_fp m_d2alphadT2; - - int m_NSolns; - - double m_Vroot[3]; - - //! Temporary storage - length = m_kk. - mutable vector_fp m_tmpV; - - // 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; - -public: - //! 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 +//! @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: + //! @name Constructors and Duplicators + //! @{ + + //! Base constructor. + PengRobinson(); + + //! Construct and initialize a PengRobinson object directly from an + //! input file + /*! + * @param infile Name of the input file containing the phase YAML data + * to set up the object + * @param id ID of the phase in the input file. Defaults to the empty + * string. + */ + 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; + + // @} + +public: + + //! 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. In many cases, this + * quantity will be the same for all species in a phase. + * 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 + //@{ + + //! Get the array of non-dimensional species chemical potentials. + //! These are partial molar Gibbs free energies. + /*! + * \f$ \mu_k / \hat R T \f$. + * Units: unitless + * + * + * @param mu Output vector of non-dimensional species chemical potentials + * Length: m_kk. + */ + + 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 the temperature dependent interaction parameter \f$\alpha\f$ needed for P-R EoS + /*! + * The temperature dependent parameter in P-R EoS is calculated as + * \f$ \alpha = [1 + \kappa(1 - \sqrt{T/T_{crit}})]^2 \f$ + * kappa is a function calulated based on the acentric factor. + * Units: unitless + * + * @param a species-specific coefficients used in P-R EoS + * @param b species-specific coefficients used in P-R EoS + * @param w the acentric factor + */ + + virtual void calculateAlpha(const std::string& species, double a, double b, double w); + //@} + /// @name Critical State Properties. + //@{ + + virtual double critTemperature() const; + virtual double critPressure() const; + virtual double critVolume() const; + virtual double critCompressibility() const; + virtual double critDensity() const; + virtual double speciesCritTemperature(double a, double b) const; + +public: + //@} + //! @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 std::vector getCoeff(const std::string& iName); + + //! Set the pure fluid interaction parameters for a species + /*! + * The *a* parameter for species *i* in the Peng-Robinson model is assumed + * to be a linear function of temperature: + * \f[ a = a_0 + a_1 T \f] + * + * @param species Name of the species + * @param a0 constant term in the expression for the "a" parameter + * of the specified species [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the expression for the + * "a" parameter of the specified species [Pa-m^6/kmol^2/K] + * @param b "b" parameter in the Peng-Robinson model [m^3/kmol] + * @param alpha dimensionless function of T_r and \omega + * @param omega acentric factor + */ + void setSpeciesCoeffs(const std::string& species, double a0, 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] + * + * This function overrides the defaults with the specified parameters: + * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] + * + * @param species_i Name of one species + * @param species_j Name of the other species + * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] + * @param a1 temperature-proportional term in the "a" expression + * [Pa-m^6/kmol^2/K] + */ + void setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double a1); + +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 pressureCalc(double TKelvin, double molarVol) const; + virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; + + // Special functions not inherited from MixtureFugacityTP + + double daAlpha_dT() const; + double d2aAlpha_dT2() const; + +public: + + //! Calculate dpdV and dpdT at the current conditions + /*! + * These are stored internally. + */ + void pressureDerivatives() const; + + //! 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. + */ + virtual void updateMixingExpressions(); + + //! Calculate the a and the b 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 temp Temperature (TKelvin) + * @param aCalc (output) Returns the a value + * @param bCalc (output) Returns the b value. + */ + void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; + + void calcCriticalConditions(double a, double b,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: + //! Form of the temperature parameterization + /*! + * - 0 = There is no temperature parameterization of a + * - 1 = The \f$a_{ij} \f$ parameter is a linear function of the temperature + */ + int m_formTempParam; + + //! Value of b in the equation of state + /*! + * m_b_current is a function of the temperature and the mole fractions. + */ + double m_b_current; + + //! Value of a and alpha in the equation of state + /*! + * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. + */ + double m_a_current; + double m_aAlpha_current; + + // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk + vector_fp m_a_vec_Curr; + vector_fp m_b_vec_Curr; + vector_fp m_aAlpha_vec_Curr; + vector_fp m_alpha_vec_Curr; + vector_fp m_kappa_vec; + mutable vector_fp m_dalphadT_vec_Curr; + mutable vector_fp m_d2alphadT2; + + int m_NSolns; + + double m_Vroot[3]; + + //! Temporary storage - length = m_kk. + mutable vector_fp m_tmpV; + + // 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; + +public: + //! 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 768786d138..67b42cafce 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -71,7 +71,6 @@ class RedlichKwongMFTP : public MixtureFugacityTP public: - //! 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 diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 1708f25ce1..4d0ce9e0a9 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -1,1034 +1,1034 @@ -/** - * @file MixtureFugacityTP.cpp - * Methods file for a derived class of ThermoPhase that handles - * non-ideal mixtures based on the fugacity models (see \ref thermoprops and - * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). - */ - -// 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/MixtureFugacityTP.h" -#include "cantera/base/stringUtils.h" -#include "cantera/base/ctml.h" - -using namespace std; - -namespace Cantera -{ - -MixtureFugacityTP::MixtureFugacityTP() : - iState_(FLUID_GAS), - forcedState_(FLUID_UNDEFINED) -{ -} - -int MixtureFugacityTP::standardStateConvention() const -{ - return cSS_CONVENTION_TEMPERATURE; -} - -void MixtureFugacityTP::setForcedSolutionBranch(int solnBranch) -{ - forcedState_ = solnBranch; -} - -int MixtureFugacityTP::forcedSolutionBranch() const -{ - return forcedState_; -} - -int MixtureFugacityTP::reportSolnBranchActual() const -{ - return iState_; -} - -// ---- Molar Thermodynamic Properties --------------------------- -double MixtureFugacityTP::enthalpy_mole() const -{ - _updateReferenceStateThermo(); - double h_ideal = RT() * mean_X(m_h0_RT); - double h_nonideal = hresid(); - return h_ideal + h_nonideal; -} - - -double MixtureFugacityTP::entropy_mole() const -{ - _updateReferenceStateThermo(); - 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 -{ - getChemPotentials(muRT); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RT(); - } -} - -// ----- Thermodynamic Values for the Species Standard States States ---- - -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++) { - g[k] = RT() * (g[k] + tmp); - } -} - -void MixtureFugacityTP::getEnthalpy_RT(doublereal* hrt) const -{ - getEnthalpy_RT_ref(hrt); -} - -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++) { - sr[k] -= tmp; - } -} - -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++) { - grt[k] += tmp; - } -} - -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++) { - g[k] += tmp; - } -} - -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; - } -} - -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(); - } -} - -// ----- Thermodynamic Values for the Species Reference States ---- - -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); -} - -void MixtureFugacityTP::getGibbs_ref(doublereal* g) const -{ - const vector_fp& gibbsrt = gibbs_RT_ref(); - scale(gibbsrt.begin(), gibbsrt.end(), g, RT()); -} - -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(); - } -} - -void MixtureFugacityTP::setStateFromXML(const XML_Node& state) -{ - int doTP = 0; - string comp = getChildValue(state,"moleFractions"); - if (comp != "") { - // not overloaded in current object -> phase state is not calculated. - setMoleFractionsByName(comp); - doTP = 1; - } else { - comp = getChildValue(state,"massFractions"); - if (comp != "") { - // not overloaded in current object -> phase state is not calculated. - setMassFractionsByName(comp); - doTP = 1; - } - } - double t = temperature(); - if (state.hasChild("temperature")) { - t = getFloat(state, "temperature", "temperature"); - doTP = 1; - } - if (state.hasChild("pressure")) { - double p = getFloat(state, "pressure", "pressure"); - setState_TP(t, p); - } else if (state.hasChild("density")) { - double rho = getFloat(state, "density", "density"); - setState_TR(t, rho); - } else if (doTP) { - double rho = density(); - setState_TR(t, rho); - } -} - -bool MixtureFugacityTP::addSpecies(shared_ptr spec) -{ - bool added = ThermoPhase::addSpecies(spec); - if (added) { - if (m_kk == 1) { - moleFractions_.push_back(1.0); - } else { - moleFractions_.push_back(0.0); - } - m_h0_RT.push_back(0.0); - m_cp0_R.push_back(0.0); - m_g0_RT.push_back(0.0); - m_s0_R.push_back(0.0); - } - return added; -} - -void MixtureFugacityTP::setTemperature(const doublereal temp) -{ - Phase::setTemperature(temp); - _updateReferenceStateThermo(); - // depends on mole fraction and temperature - updateMixingExpressions(); - iState_ = phaseState(true); -} - -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. - - double t = temperature(); - double rhoNow = density(); - if (forcedState_ == FLUID_UNDEFINED) { - double rho = densityCalc(t, p, iState_, rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - } else { - if (rho < -1.5) { - rho = densityCalc(t, p, FLUID_UNDEFINED , rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } - } else if (forcedState_ == FLUID_GAS) { - // Normal density calculation - if (iState_ < FLUID_LIQUID_0) { - double rho = densityCalc(t, p, iState_, rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - if (iState_ >= FLUID_LIQUID_0) { - throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); - } - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } - } else if (forcedState_ > FLUID_LIQUID_0) { - if (iState_ >= FLUID_LIQUID_0) { - double rho = densityCalc(t, p, iState_, rhoNow); - if (rho > 0.0) { - setDensity(rho); - iState_ = phaseState(true); - if (iState_ == FLUID_GAS) { - throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); - } - } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); - } - } - } -} - -void MixtureFugacityTP::compositionChanged() -{ - Phase::compositionChanged(); - getMoleFractions(moleFractions_.data()); - updateMixingExpressions(); -} - -void MixtureFugacityTP::getActivityConcentrations(doublereal* c) const -{ - 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 -{ - return pressure() * meanMolecularWeight() / (density() * RT()); -} - -doublereal MixtureFugacityTP::sresid() const -{ - throw NotImplementedError("MixtureFugacityTP::sresid"); -} - -doublereal MixtureFugacityTP::hresid() const -{ - throw NotImplementedError("MixtureFugacityTP::hresid"); -} - -doublereal MixtureFugacityTP::psatEst(doublereal TKelvin) const -{ - doublereal pcrit = critPressure(); - doublereal tt = critTemperature() / TKelvin; - if (tt < 1.0) { - return pcrit; - } - doublereal lpr = -0.8734*tt*tt - 3.4522*tt + 4.2918; - return pcrit*exp(lpr); -} - -doublereal MixtureFugacityTP::liquidVolEst(doublereal TKelvin, doublereal& pres) const -{ - throw NotImplementedError("MixtureFugacityTP::liquidVolEst"); -} - -doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, - int phase, doublereal rhoguess) -{ - doublereal tcrit = critTemperature(); - doublereal mmw = meanMolecularWeight(); - if (rhoguess == -1.0) { - if (phase != -1) { - if (TKelvin > tcrit) { - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } else { - if (phase == FLUID_GAS || phase == FLUID_SUPERCRIT) { - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } else if (phase >= 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 - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } - } - - double molarVolBase = mmw / rhoguess; - double molarVolLast = molarVolBase; - double vc = mmw / critDensity(); - - // molar volume of the spinodal at the current temperature and mole - // fractions. this will be updated as we go. - double molarVolSpinodal = vc; - bool conv = false; - - // We start on one side of the vc and stick with that side - bool gasSide = molarVolBase > vc; - if (gasSide) { - molarVolLast = (GasConstant * TKelvin)/presPa; - } else { - molarVolLast = liquidVolEst(TKelvin, presPa); - } - - // OK, now we do a small solve to calculate the molar volume given the T,P - // value. The algorithm is taken from dfind() - for (int n = 0; n < 200; n++) { - // Calculate the predicted reduced pressure, pred0, based on the current - // tau and dd. Calculate the derivative of the predicted pressure wrt - // the molar volume. This routine also returns the pressure, presBase - double presBase; - double dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase); - - // If dpdV is positive, then we are in the middle of the 2 phase region - // and beyond the spinodal stability curve. We need to adjust the - // initial guess outwards and start a new iteration. - if (dpdVBase >= 0.0) { - if (TKelvin > tcrit) { - throw CanteraError("MixtureFugacityTP::densityCalc", - "T > tcrit unexpectedly"); - } - - // TODO Spawn a calculation for the value of the spinodal point that - // is very accurate. Answer the question as to whether a - // solution is possible on the current side of the vapor dome. - if (gasSide) { - if (molarVolBase >= vc) { - molarVolSpinodal = molarVolBase; - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } else { - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } - } else { - if (molarVolBase <= vc) { - molarVolSpinodal = molarVolBase; - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } else { - molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); - } - } - continue; - } - - // Check for convergence - if (fabs(presBase-presPa) < 1.0E-30 + 1.0E-8 * presPa) { - conv = true; - break; - } - - // Dampen and crop the update - doublereal dpdV = dpdVBase; - if (n < 10) { - dpdV = dpdVBase * 1.5; - } - - // Formulate the update to the molar volume by Newton's method. Then, - // crop it to a max value of 0.1 times the current volume - double delMV = - (presBase - presPa) / dpdV; - if ((!gasSide || delMV < 0.0) && fabs(delMV) > 0.2 * molarVolBase) { - delMV = delMV / fabs(delMV) * 0.2 * molarVolBase; - } - // Only go 1/10 the way towards the spinodal at any one time. - if (TKelvin < tcrit) { - if (gasSide) { - if (delMV < 0.0 && -delMV > 0.5 * (molarVolBase - molarVolSpinodal)) { - delMV = - 0.5 * (molarVolBase - molarVolSpinodal); - } - } else { - if (delMV > 0.0 && delMV > 0.5 * (molarVolSpinodal - molarVolBase)) { - delMV = 0.5 * (molarVolSpinodal - molarVolBase); - } - } - } - // updated the molar volume value - molarVolLast = molarVolBase; - molarVolBase += delMV; - - if (fabs(delMV/molarVolBase) < 1.0E-14) { - conv = true; - break; - } - - // Check for negative molar volumes - if (molarVolBase <= 0.0) { - molarVolBase = std::min(1.0E-30, fabs(delMV*1.0E-4)); - } - } - - // Check for convergence, and return 0.0 if it wasn't achieved. - double densBase = 0.0; - if (! conv) { - molarVolBase = 0.0; - throw CanteraError("MixtureFugacityTP::densityCalc", "Process did not converge"); - } else { - densBase = mmw / molarVolBase; - } - return densBase; -} - -void MixtureFugacityTP::setToEquilState(const doublereal* mu_RT) -{ - double tmp, tmp2; - _updateReferenceStateThermo(); - getGibbs_RT_ref(m_tmpV.data()); - - // Within the method, we protect against inf results if the exponent is too - // high. - // - // If it is too low, we set the partial pressure to zero. This capability is - // needed by the elemental potential method. - doublereal pres = 0.0; - double m_p0 = refPressure(); - for (size_t k = 0; k < m_kk; k++) { - tmp = -m_tmpV[k] + mu_RT[k]; - if (tmp < -600.) { - m_pp[k] = 0.0; - } - else if (tmp > 500.0) { - tmp2 = tmp / 500.; - tmp2 *= tmp2; - m_pp[k] = m_p0 * exp(500.) * tmp2; - } - else { - m_pp[k] = m_p0 * exp(tmp); - } - pres += m_pp[k]; - } - // set state - setState_PX(pres, &m_pp[0]); -} - -void MixtureFugacityTP::updateMixingExpressions() -{ -} - -int MixtureFugacityTP::corr0(doublereal TKelvin, doublereal pres, doublereal& densLiqGuess, - doublereal& densGasGuess, doublereal& liqGRT, doublereal& gasGRT) -{ - int retn = 0; - doublereal densLiq = densityCalc(TKelvin, pres, FLUID_LIQUID_0, densLiqGuess); - if (densLiq <= 0.0) { - retn = -1; - } else { - densLiqGuess = densLiq; - setState_TR(TKelvin, densLiq); - liqGRT = gibbs_mole() / RT(); - } - - doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, densGasGuess); - if (densGas <= 0.0) { - if (retn == -1) { - throw CanteraError("MixtureFugacityTP::corr0", - "Error occurred trying to find gas density at (T,P) = {} {}", - TKelvin, pres); - } - retn = -2; - } else { - densGasGuess = densGas; - setState_TR(TKelvin, densGas); - gasGRT = gibbs_mole() / RT(); - } - return retn; -} - -int MixtureFugacityTP::phaseState(bool checkState) const -{ - int state = iState_; - if (checkState) { - double t = temperature(); - double tcrit = critTemperature(); - double rhocrit = critDensity(); - if (t >= tcrit) { - return FLUID_SUPERCRIT; - } - double tmid = tcrit - 100.; - if (tmid < 0.0) { - tmid = tcrit / 2.0; - } - double pp = psatEst(tmid); - double mmw = meanMolecularWeight(); - double molVolLiqTmid = liquidVolEst(tmid, pp); - double molVolGasTmid = GasConstant * tmid / pp; - double densLiqTmid = mmw / molVolLiqTmid; - double densGasTmid = mmw / molVolGasTmid; - double densMidTmid = 0.5 * (densLiqTmid + densGasTmid); - doublereal rhoMid = rhocrit + (t - tcrit) * (rhocrit - densMidTmid) / (tcrit - tmid); - - double rho = density(); - int iStateGuess = FLUID_LIQUID_0; - if (rho < rhoMid) { - iStateGuess = FLUID_GAS; - } - double molarVol = mmw / rho; - double presCalc; - - double dpdv = dpdVCalc(t, molarVol, presCalc); - if (dpdv < 0.0) { - state = iStateGuess; - } else { - state = FLUID_UNSTABLE; - } - } - return state; -} - -doublereal MixtureFugacityTP::densSpinodalLiquid() const -{ - throw NotImplementedError("MixtureFugacityTP::densSpinodalLiquid"); -} - -doublereal MixtureFugacityTP::densSpinodalGas() const -{ - throw NotImplementedError("MixtureFugacityTP::densSpinodalGas"); -} - -doublereal MixtureFugacityTP::satPressure(doublereal TKelvin) -{ - doublereal molarVolGas; - doublereal molarVolLiquid; - return calculatePsat(TKelvin, molarVolGas, molarVolLiquid); -} - -doublereal MixtureFugacityTP::calculatePsat(doublereal TKelvin, doublereal& molarVolGas, - doublereal& molarVolLiquid) -{ - // The algorithm for this routine has undergone quite a bit of work. It - // probably needs more work. However, it seems now to be fairly robust. The - // key requirement is to find an initial pressure where both the liquid and - // the gas exist. This is not as easy as it sounds, and it gets exceedingly - // hard as the critical temperature is approached from below. Once we have - // this initial state, then we seek to equilibrate the Gibbs free energies - // of the gas and liquid and use the formula - // - // dp = VdG - // - // to create an update condition for deltaP using - // - // - (Gliq - Ggas) = (Vliq - Vgas) (deltaP) - // - // @TODO Suggestions for the future would be to switch it to an algorithm - // that uses the gas molar volume and the liquid molar volumes as the - // fundamental unknowns. - - // we need this because this is a non-const routine that is public - setTemperature(TKelvin); - double densSave = density(); - double tempSave = temperature(); - double pres; - doublereal mw = meanMolecularWeight(); - if (TKelvin < critTemperature()) { - pres = psatEst(TKelvin); - // trial value = Psat from correlation - doublereal volLiquid = liquidVolEst(TKelvin, pres); - double RhoLiquidGood = mw / volLiquid; - double RhoGasGood = pres * mw / (GasConstant * TKelvin); - doublereal delGRT = 1.0E6; - doublereal liqGRT, gasGRT; - - // First part of the calculation involves finding a pressure at which - // the gas and the liquid state coexists. - doublereal presLiquid = 0.; - doublereal presGas; - doublereal presBase = pres; - bool foundLiquid = false; - bool foundGas = false; - - doublereal densLiquid = densityCalc(TKelvin, presBase, FLUID_LIQUID_0, RhoLiquidGood); - if (densLiquid > 0.0) { - foundLiquid = true; - presLiquid = pres; - RhoLiquidGood = densLiquid; - } - if (!foundLiquid) { - for (int i = 0; i < 50; i++) { - pres = 1.1 * pres; - densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); - if (densLiquid > 0.0) { - foundLiquid = true; - presLiquid = pres; - RhoLiquidGood = densLiquid; - break; - } - } - } - - pres = presBase; - doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); - if (densGas <= 0.0) { - foundGas = false; - } else { - foundGas = true; - presGas = pres; - RhoGasGood = densGas; - } - if (!foundGas) { - for (int i = 0; i < 50; i++) { - pres = 0.9 * pres; - densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); - if (densGas > 0.0) { - foundGas = true; - presGas = pres; - RhoGasGood = densGas; - break; - } - } - } - - if (foundGas && foundLiquid && presGas != presLiquid) { - pres = 0.5 * (presLiquid + presGas); - bool goodLiq; - bool goodGas; - for (int i = 0; i < 50; i++) { - densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); - if (densLiquid <= 0.0) { - goodLiq = false; - } else { - goodLiq = true; - RhoLiquidGood = densLiquid; - presLiquid = pres; - } - densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); - if (densGas <= 0.0) { - goodGas = false; - } else { - goodGas = true; - RhoGasGood = densGas; - presGas = pres; - } - if (goodGas && goodLiq) { - break; - } - if (!goodLiq && !goodGas) { - pres = 0.5 * (pres + presLiquid); - } - if (goodLiq || goodGas) { - pres = 0.5 * (presLiquid + presGas); - } - } - } - if (!foundGas || !foundLiquid) { - warn_user("MixtureFugacityTP::calculatePsat", - "could not find a starting pressure; exiting."); - return 0.0; - } - if (presGas != presLiquid) { - warn_user("MixtureFugacityTP::calculatePsat", - "could not find a starting pressure; exiting"); - return 0.0; - } - - pres = presGas; - double presLast = pres; - double RhoGas = RhoGasGood; - double RhoLiquid = RhoLiquidGood; - - // Now that we have found a good pressure we can proceed with the algorithm. - for (int i = 0; i < 20; i++) { - int stab = corr0(TKelvin, pres, RhoLiquid, RhoGas, liqGRT, gasGRT); - if (stab == 0) { - presLast = pres; - delGRT = liqGRT - gasGRT; - doublereal delV = mw * (1.0/RhoLiquid - 1.0/RhoGas); - doublereal dp = - delGRT * GasConstant * TKelvin / delV; - - if (fabs(dp) > 0.1 * pres) { - if (dp > 0.0) { - dp = 0.1 * pres; - } else { - dp = -0.1 * pres; - } - } - pres += dp; - } else if (stab == -1) { - delGRT = 1.0E6; - if (presLast > pres) { - pres = 0.5 * (presLast + pres); - } else { - // we are stuck here - try this - pres = 1.1 * pres; - } - } else if (stab == -2) { - if (presLast < pres) { - pres = 0.5 * (presLast + pres); - } else { - // we are stuck here - try this - pres = 0.9 * pres; - } - } - molarVolGas = mw / RhoGas; - molarVolLiquid = mw / RhoLiquid; - - if (fabs(delGRT) < 1.0E-8) { - // converged - break; - } - } - - molarVolGas = mw / RhoGas; - molarVolLiquid = mw / RhoLiquid; - // Put the fluid in the desired end condition - setState_TR(tempSave, densSave); - return pres; - } else { - pres = critPressure(); - setState_TP(TKelvin, pres); - molarVolGas = mw / density(); - molarVolLiquid = molarVolGas; - setState_TR(tempSave, densSave); - } - return pres; -} - -doublereal MixtureFugacityTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const -{ - throw NotImplementedError("MixtureFugacityTP::pressureCalc"); -} - -doublereal MixtureFugacityTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const -{ - throw NotImplementedError("MixtureFugacityTP::dpdVCalc"); -} - -void MixtureFugacityTP::_updateReferenceStateThermo() const -{ - double Tnow = temperature(); - - // If the temperature has changed since the last time these - // properties were computed, recompute them. - if (m_tlast != Tnow) { - m_spthermo.update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]); - m_tlast = Tnow; - - // update the species Gibbs functions - for (size_t k = 0; k < m_kk; k++) { - m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k]; - } - doublereal pref = refPressure(); - if (pref <= 0.0) { - throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", "neg ref pressure"); - } - } -} - -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", "Inconsistancy in cubic solver : solver is poorly 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", "Inconsistancy in cubic solver : solver is poorly 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; -} - -} +/** + * @file MixtureFugacityTP.cpp + * Methods file for a derived class of ThermoPhase that handles + * non-ideal mixtures based on the fugacity models (see \ref thermoprops and + * class \link Cantera::MixtureFugacityTP MixtureFugacityTP\endlink). + */ + +// 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/MixtureFugacityTP.h" +#include "cantera/base/stringUtils.h" +#include "cantera/base/ctml.h" + +using namespace std; + +namespace Cantera +{ + +MixtureFugacityTP::MixtureFugacityTP() : + iState_(FLUID_GAS), + forcedState_(FLUID_UNDEFINED) +{ +} + +int MixtureFugacityTP::standardStateConvention() const +{ + return cSS_CONVENTION_TEMPERATURE; +} + +void MixtureFugacityTP::setForcedSolutionBranch(int solnBranch) +{ + forcedState_ = solnBranch; +} + +int MixtureFugacityTP::forcedSolutionBranch() const +{ + return forcedState_; +} + +int MixtureFugacityTP::reportSolnBranchActual() const +{ + return iState_; +} + +// ---- Molar Thermodynamic Properties --------------------------- +double MixtureFugacityTP::enthalpy_mole() const +{ + _updateReferenceStateThermo(); + double h_ideal = RT() * mean_X(m_h0_RT); + double h_nonideal = hresid(); + return h_ideal + h_nonideal; +} + + +double MixtureFugacityTP::entropy_mole() const +{ + _updateReferenceStateThermo(); + 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 +{ + getChemPotentials(muRT); + for (size_t k = 0; k < m_kk; k++) { + muRT[k] *= 1.0 / RT(); + } +} + +// ----- Thermodynamic Values for the Species Standard States States ---- + +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++) { + g[k] = RT() * (g[k] + tmp); + } +} + +void MixtureFugacityTP::getEnthalpy_RT(doublereal* hrt) const +{ + getEnthalpy_RT_ref(hrt); +} + +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++) { + sr[k] -= tmp; + } +} + +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++) { + grt[k] += tmp; + } +} + +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++) { + g[k] += tmp; + } +} + +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; + } +} + +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(); + } +} + +// ----- Thermodynamic Values for the Species Reference States ---- + +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); +} + +void MixtureFugacityTP::getGibbs_ref(doublereal* g) const +{ + const vector_fp& gibbsrt = gibbs_RT_ref(); + scale(gibbsrt.begin(), gibbsrt.end(), g, RT()); +} + +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(); + } +} + +void MixtureFugacityTP::setStateFromXML(const XML_Node& state) +{ + int doTP = 0; + string comp = getChildValue(state,"moleFractions"); + if (comp != "") { + // not overloaded in current object -> phase state is not calculated. + setMoleFractionsByName(comp); + doTP = 1; + } else { + comp = getChildValue(state,"massFractions"); + if (comp != "") { + // not overloaded in current object -> phase state is not calculated. + setMassFractionsByName(comp); + doTP = 1; + } + } + double t = temperature(); + if (state.hasChild("temperature")) { + t = getFloat(state, "temperature", "temperature"); + doTP = 1; + } + if (state.hasChild("pressure")) { + double p = getFloat(state, "pressure", "pressure"); + setState_TP(t, p); + } else if (state.hasChild("density")) { + double rho = getFloat(state, "density", "density"); + setState_TR(t, rho); + } else if (doTP) { + double rho = density(); + setState_TR(t, rho); + } +} + +bool MixtureFugacityTP::addSpecies(shared_ptr spec) +{ + bool added = ThermoPhase::addSpecies(spec); + if (added) { + if (m_kk == 1) { + moleFractions_.push_back(1.0); + } else { + moleFractions_.push_back(0.0); + } + m_h0_RT.push_back(0.0); + m_cp0_R.push_back(0.0); + m_g0_RT.push_back(0.0); + m_s0_R.push_back(0.0); + } + return added; +} + +void MixtureFugacityTP::setTemperature(const doublereal temp) +{ + Phase::setTemperature(temp); + _updateReferenceStateThermo(); + // depends on mole fraction and temperature + updateMixingExpressions(); + iState_ = phaseState(true); +} + +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. + + double t = temperature(); + double rhoNow = density(); + if (forcedState_ == FLUID_UNDEFINED) { + double rho = densityCalc(t, p, iState_, rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + } else { + if (rho < -1.5) { + rho = densityCalc(t, p, FLUID_UNDEFINED , rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } + } else if (forcedState_ == FLUID_GAS) { + // Normal density calculation + if (iState_ < FLUID_LIQUID_0) { + double rho = densityCalc(t, p, iState_, rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + if (iState_ >= FLUID_LIQUID_0) { + throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); + } + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } + } else if (forcedState_ > FLUID_LIQUID_0) { + if (iState_ >= FLUID_LIQUID_0) { + double rho = densityCalc(t, p, iState_, rhoNow); + if (rho > 0.0) { + setDensity(rho); + iState_ = phaseState(true); + if (iState_ == FLUID_GAS) { + throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); + } + } else { + throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + } + } + } +} + +void MixtureFugacityTP::compositionChanged() +{ + Phase::compositionChanged(); + getMoleFractions(moleFractions_.data()); + updateMixingExpressions(); +} + +void MixtureFugacityTP::getActivityConcentrations(doublereal* c) const +{ + 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 +{ + return pressure() * meanMolecularWeight() / (density() * RT()); +} + +doublereal MixtureFugacityTP::sresid() const +{ + throw NotImplementedError("MixtureFugacityTP::sresid"); +} + +doublereal MixtureFugacityTP::hresid() const +{ + throw NotImplementedError("MixtureFugacityTP::hresid"); +} + +doublereal MixtureFugacityTP::psatEst(doublereal TKelvin) const +{ + doublereal pcrit = critPressure(); + doublereal tt = critTemperature() / TKelvin; + if (tt < 1.0) { + return pcrit; + } + doublereal lpr = -0.8734*tt*tt - 3.4522*tt + 4.2918; + return pcrit*exp(lpr); +} + +doublereal MixtureFugacityTP::liquidVolEst(doublereal TKelvin, doublereal& pres) const +{ + throw NotImplementedError("MixtureFugacityTP::liquidVolEst"); +} + +doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, + int phase, doublereal rhoguess) +{ + doublereal tcrit = critTemperature(); + doublereal mmw = meanMolecularWeight(); + if (rhoguess == -1.0) { + if (phase != -1) { + if (TKelvin > tcrit) { + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } else { + if (phase == FLUID_GAS || phase == FLUID_SUPERCRIT) { + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } else if (phase >= 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 + rhoguess = presPa * mmw / (GasConstant * TKelvin); + } + } + + double molarVolBase = mmw / rhoguess; + double molarVolLast = molarVolBase; + double vc = mmw / critDensity(); + + // molar volume of the spinodal at the current temperature and mole + // fractions. this will be updated as we go. + double molarVolSpinodal = vc; + bool conv = false; + + // We start on one side of the vc and stick with that side + bool gasSide = molarVolBase > vc; + if (gasSide) { + molarVolLast = (GasConstant * TKelvin)/presPa; + } else { + molarVolLast = liquidVolEst(TKelvin, presPa); + } + + // OK, now we do a small solve to calculate the molar volume given the T,P + // value. The algorithm is taken from dfind() + for (int n = 0; n < 200; n++) { + // Calculate the predicted reduced pressure, pred0, based on the current + // tau and dd. Calculate the derivative of the predicted pressure wrt + // the molar volume. This routine also returns the pressure, presBase + double presBase; + double dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase); + + // If dpdV is positive, then we are in the middle of the 2 phase region + // and beyond the spinodal stability curve. We need to adjust the + // initial guess outwards and start a new iteration. + if (dpdVBase >= 0.0) { + if (TKelvin > tcrit) { + throw CanteraError("MixtureFugacityTP::densityCalc", + "T > tcrit unexpectedly"); + } + + // TODO Spawn a calculation for the value of the spinodal point that + // is very accurate. Answer the question as to whether a + // solution is possible on the current side of the vapor dome. + if (gasSide) { + if (molarVolBase >= vc) { + molarVolSpinodal = molarVolBase; + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } else { + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } + } else { + if (molarVolBase <= vc) { + molarVolSpinodal = molarVolBase; + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } else { + molarVolBase = 0.5 * (molarVolLast + molarVolSpinodal); + } + } + continue; + } + + // Check for convergence + if (fabs(presBase-presPa) < 1.0E-30 + 1.0E-8 * presPa) { + conv = true; + break; + } + + // Dampen and crop the update + doublereal dpdV = dpdVBase; + if (n < 10) { + dpdV = dpdVBase * 1.5; + } + + // Formulate the update to the molar volume by Newton's method. Then, + // crop it to a max value of 0.1 times the current volume + double delMV = - (presBase - presPa) / dpdV; + if ((!gasSide || delMV < 0.0) && fabs(delMV) > 0.2 * molarVolBase) { + delMV = delMV / fabs(delMV) * 0.2 * molarVolBase; + } + // Only go 1/10 the way towards the spinodal at any one time. + if (TKelvin < tcrit) { + if (gasSide) { + if (delMV < 0.0 && -delMV > 0.5 * (molarVolBase - molarVolSpinodal)) { + delMV = - 0.5 * (molarVolBase - molarVolSpinodal); + } + } else { + if (delMV > 0.0 && delMV > 0.5 * (molarVolSpinodal - molarVolBase)) { + delMV = 0.5 * (molarVolSpinodal - molarVolBase); + } + } + } + // updated the molar volume value + molarVolLast = molarVolBase; + molarVolBase += delMV; + + if (fabs(delMV/molarVolBase) < 1.0E-14) { + conv = true; + break; + } + + // Check for negative molar volumes + if (molarVolBase <= 0.0) { + molarVolBase = std::min(1.0E-30, fabs(delMV*1.0E-4)); + } + } + + // Check for convergence, and return 0.0 if it wasn't achieved. + double densBase = 0.0; + if (! conv) { + molarVolBase = 0.0; + throw CanteraError("MixtureFugacityTP::densityCalc", "Process did not converge"); + } else { + densBase = mmw / molarVolBase; + } + return densBase; +} + +void MixtureFugacityTP::setToEquilState(const doublereal* mu_RT) +{ + double tmp, tmp2; + _updateReferenceStateThermo(); + getGibbs_RT_ref(m_tmpV.data()); + + // Within the method, we protect against inf results if the exponent is too + // high. + // + // If it is too low, we set the partial pressure to zero. This capability is + // needed by the elemental potential method. + doublereal pres = 0.0; + double m_p0 = refPressure(); + for (size_t k = 0; k < m_kk; k++) { + tmp = -m_tmpV[k] + mu_RT[k]; + if (tmp < -600.) { + m_pp[k] = 0.0; + } + else if (tmp > 500.0) { + tmp2 = tmp / 500.; + tmp2 *= tmp2; + m_pp[k] = m_p0 * exp(500.) * tmp2; + } + else { + m_pp[k] = m_p0 * exp(tmp); + } + pres += m_pp[k]; + } + // set state + setState_PX(pres, &m_pp[0]); +} + +void MixtureFugacityTP::updateMixingExpressions() +{ +} + +int MixtureFugacityTP::corr0(doublereal TKelvin, doublereal pres, doublereal& densLiqGuess, + doublereal& densGasGuess, doublereal& liqGRT, doublereal& gasGRT) +{ + int retn = 0; + doublereal densLiq = densityCalc(TKelvin, pres, FLUID_LIQUID_0, densLiqGuess); + if (densLiq <= 0.0) { + retn = -1; + } else { + densLiqGuess = densLiq; + setState_TR(TKelvin, densLiq); + liqGRT = gibbs_mole() / RT(); + } + + doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, densGasGuess); + if (densGas <= 0.0) { + if (retn == -1) { + throw CanteraError("MixtureFugacityTP::corr0", + "Error occurred trying to find gas density at (T,P) = {} {}", + TKelvin, pres); + } + retn = -2; + } else { + densGasGuess = densGas; + setState_TR(TKelvin, densGas); + gasGRT = gibbs_mole() / RT(); + } + return retn; +} + +int MixtureFugacityTP::phaseState(bool checkState) const +{ + int state = iState_; + if (checkState) { + double t = temperature(); + double tcrit = critTemperature(); + double rhocrit = critDensity(); + if (t >= tcrit) { + return FLUID_SUPERCRIT; + } + double tmid = tcrit - 100.; + if (tmid < 0.0) { + tmid = tcrit / 2.0; + } + double pp = psatEst(tmid); + double mmw = meanMolecularWeight(); + double molVolLiqTmid = liquidVolEst(tmid, pp); + double molVolGasTmid = GasConstant * tmid / pp; + double densLiqTmid = mmw / molVolLiqTmid; + double densGasTmid = mmw / molVolGasTmid; + double densMidTmid = 0.5 * (densLiqTmid + densGasTmid); + doublereal rhoMid = rhocrit + (t - tcrit) * (rhocrit - densMidTmid) / (tcrit - tmid); + + double rho = density(); + int iStateGuess = FLUID_LIQUID_0; + if (rho < rhoMid) { + iStateGuess = FLUID_GAS; + } + double molarVol = mmw / rho; + double presCalc; + + double dpdv = dpdVCalc(t, molarVol, presCalc); + if (dpdv < 0.0) { + state = iStateGuess; + } else { + state = FLUID_UNSTABLE; + } + } + return state; +} + +doublereal MixtureFugacityTP::densSpinodalLiquid() const +{ + throw NotImplementedError("MixtureFugacityTP::densSpinodalLiquid"); +} + +doublereal MixtureFugacityTP::densSpinodalGas() const +{ + throw NotImplementedError("MixtureFugacityTP::densSpinodalGas"); +} + +doublereal MixtureFugacityTP::satPressure(doublereal TKelvin) +{ + doublereal molarVolGas; + doublereal molarVolLiquid; + return calculatePsat(TKelvin, molarVolGas, molarVolLiquid); +} + +doublereal MixtureFugacityTP::calculatePsat(doublereal TKelvin, doublereal& molarVolGas, + doublereal& molarVolLiquid) +{ + // The algorithm for this routine has undergone quite a bit of work. It + // probably needs more work. However, it seems now to be fairly robust. The + // key requirement is to find an initial pressure where both the liquid and + // the gas exist. This is not as easy as it sounds, and it gets exceedingly + // hard as the critical temperature is approached from below. Once we have + // this initial state, then we seek to equilibrate the Gibbs free energies + // of the gas and liquid and use the formula + // + // dp = VdG + // + // to create an update condition for deltaP using + // + // - (Gliq - Ggas) = (Vliq - Vgas) (deltaP) + // + // @TODO Suggestions for the future would be to switch it to an algorithm + // that uses the gas molar volume and the liquid molar volumes as the + // fundamental unknowns. + + // we need this because this is a non-const routine that is public + setTemperature(TKelvin); + double densSave = density(); + double tempSave = temperature(); + double pres; + doublereal mw = meanMolecularWeight(); + if (TKelvin < critTemperature()) { + pres = psatEst(TKelvin); + // trial value = Psat from correlation + doublereal volLiquid = liquidVolEst(TKelvin, pres); + double RhoLiquidGood = mw / volLiquid; + double RhoGasGood = pres * mw / (GasConstant * TKelvin); + doublereal delGRT = 1.0E6; + doublereal liqGRT, gasGRT; + + // First part of the calculation involves finding a pressure at which + // the gas and the liquid state coexists. + doublereal presLiquid = 0.; + doublereal presGas; + doublereal presBase = pres; + bool foundLiquid = false; + bool foundGas = false; + + doublereal densLiquid = densityCalc(TKelvin, presBase, FLUID_LIQUID_0, RhoLiquidGood); + if (densLiquid > 0.0) { + foundLiquid = true; + presLiquid = pres; + RhoLiquidGood = densLiquid; + } + if (!foundLiquid) { + for (int i = 0; i < 50; i++) { + pres = 1.1 * pres; + densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); + if (densLiquid > 0.0) { + foundLiquid = true; + presLiquid = pres; + RhoLiquidGood = densLiquid; + break; + } + } + } + + pres = presBase; + doublereal densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); + if (densGas <= 0.0) { + foundGas = false; + } else { + foundGas = true; + presGas = pres; + RhoGasGood = densGas; + } + if (!foundGas) { + for (int i = 0; i < 50; i++) { + pres = 0.9 * pres; + densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); + if (densGas > 0.0) { + foundGas = true; + presGas = pres; + RhoGasGood = densGas; + break; + } + } + } + + if (foundGas && foundLiquid && presGas != presLiquid) { + pres = 0.5 * (presLiquid + presGas); + bool goodLiq; + bool goodGas; + for (int i = 0; i < 50; i++) { + densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); + if (densLiquid <= 0.0) { + goodLiq = false; + } else { + goodLiq = true; + RhoLiquidGood = densLiquid; + presLiquid = pres; + } + densGas = densityCalc(TKelvin, pres, FLUID_GAS, RhoGasGood); + if (densGas <= 0.0) { + goodGas = false; + } else { + goodGas = true; + RhoGasGood = densGas; + presGas = pres; + } + if (goodGas && goodLiq) { + break; + } + if (!goodLiq && !goodGas) { + pres = 0.5 * (pres + presLiquid); + } + if (goodLiq || goodGas) { + pres = 0.5 * (presLiquid + presGas); + } + } + } + if (!foundGas || !foundLiquid) { + warn_user("MixtureFugacityTP::calculatePsat", + "could not find a starting pressure; exiting."); + return 0.0; + } + if (presGas != presLiquid) { + warn_user("MixtureFugacityTP::calculatePsat", + "could not find a starting pressure; exiting"); + return 0.0; + } + + pres = presGas; + double presLast = pres; + double RhoGas = RhoGasGood; + double RhoLiquid = RhoLiquidGood; + + // Now that we have found a good pressure we can proceed with the algorithm. + for (int i = 0; i < 20; i++) { + int stab = corr0(TKelvin, pres, RhoLiquid, RhoGas, liqGRT, gasGRT); + if (stab == 0) { + presLast = pres; + delGRT = liqGRT - gasGRT; + doublereal delV = mw * (1.0/RhoLiquid - 1.0/RhoGas); + doublereal dp = - delGRT * GasConstant * TKelvin / delV; + + if (fabs(dp) > 0.1 * pres) { + if (dp > 0.0) { + dp = 0.1 * pres; + } else { + dp = -0.1 * pres; + } + } + pres += dp; + } else if (stab == -1) { + delGRT = 1.0E6; + if (presLast > pres) { + pres = 0.5 * (presLast + pres); + } else { + // we are stuck here - try this + pres = 1.1 * pres; + } + } else if (stab == -2) { + if (presLast < pres) { + pres = 0.5 * (presLast + pres); + } else { + // we are stuck here - try this + pres = 0.9 * pres; + } + } + molarVolGas = mw / RhoGas; + molarVolLiquid = mw / RhoLiquid; + + if (fabs(delGRT) < 1.0E-8) { + // converged + break; + } + } + + molarVolGas = mw / RhoGas; + molarVolLiquid = mw / RhoLiquid; + // Put the fluid in the desired end condition + setState_TR(tempSave, densSave); + return pres; + } else { + pres = critPressure(); + setState_TP(TKelvin, pres); + molarVolGas = mw / density(); + molarVolLiquid = molarVolGas; + setState_TR(tempSave, densSave); + } + return pres; +} + +doublereal MixtureFugacityTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const +{ + throw NotImplementedError("MixtureFugacityTP::pressureCalc"); +} + +doublereal MixtureFugacityTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const +{ + throw NotImplementedError("MixtureFugacityTP::dpdVCalc"); +} + +void MixtureFugacityTP::_updateReferenceStateThermo() const +{ + double Tnow = temperature(); + + // If the temperature has changed since the last time these + // properties were computed, recompute them. + if (m_tlast != Tnow) { + m_spthermo.update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]); + m_tlast = Tnow; + + // update the species Gibbs functions + for (size_t k = 0; k < m_kk; k++) { + m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k]; + } + doublereal pref = refPressure(); + if (pref <= 0.0) { + throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", "neg ref pressure"); + } + } +} + +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", "Inconsistancy in cubic solver : solver is poorly 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", "Inconsistancy in cubic solver : solver is poorly 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 index 8b822eb824..1b83b0dccc 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -1,903 +1,903 @@ -//! @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/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() : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - m_NSolns(0), - m_dpdV(0.0), - m_dpdT(0.0) -{ - fill_n(m_Vroot, 3, 0.0); -} - -PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(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::calculateAlpha(const std::string& species, double a, double b, double w) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("PengRobinson::calculateAlpha", - "Unknown species '{}'.", species); - } - - // Calculate value of kappa (independent of temperature) - // w is an acentric factor of species and must be specified in the CTI file - if (isnan(w)) { - throw CanteraError("PengRobinson::calculateAlpha", - "No acentric factor loaded."); - } else if (w <= 0.491) { - m_kappa_vec[k] = 0.37464 + 1.54226*w - 0.26992*w*w; - } else { - m_kappa_vec[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_vec[k] * (1 - sqt_T_r); - m_alpha_vec_Curr[k] = sqt_alpha*sqt_alpha; -} - -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); - } - size_t counter = k + m_kk * k; - m_a_vec_Curr[counter] = a; - // we store this locally because it is used below to calculate a_Alpha: - double aAlpha_k = a*m_alpha_vec_Curr[k]; - m_aAlpha_vec_Curr[counter] = 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_vec_Curr[j + m_kk * j] * a); - double aAlpha_j = a*m_alpha_vec_Curr[j]; - double a_Alpha = sqrt(aAlpha_j*aAlpha_k); - if (m_a_vec_Curr[j + m_kk * k] == 0) { - m_a_vec_Curr[j + m_kk * k] = a0kj; - m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; - m_a_vec_Curr[k + m_kk * j] = a0kj; - m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; - } - } - m_b_vec_Curr[k] = b; -} - -void PengRobinson::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double alpha) -{ - 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); - } - - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; - m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; -} - -// ------------Molar Thermodynamic Properties ------------------------- - -double PengRobinson::cp_mole() const -{ - _updateReferenceStateThermo(); - double T = temperature(); - double mv = molarVolume(); - double vpb = mv + (1 + M_SQRT2)*m_b_current; - double vmb = mv + (1 - M_SQRT2)*m_b_current; - pressureDerivatives(); - double cpref = GasConstant * mean_X(m_cp0_R); - double dHdT_V = cpref + mv * m_dpdT - GasConstant - + 1.0 / (2.0 * M_SQRT2 *m_b_current) * 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(); - pressureDerivatives(); - 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_current - m_b_current * m_b_current; - double pp = GasConstant * T / (mv - m_b_current) - m_aAlpha_current / 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 T = temperature(); - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double vmb = mv - m_b_current; - 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - } - } - double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; - ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) - + RTkelvin * m_b_vec_Curr[k] / vmb - - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* m_b_vec_Curr[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_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - } - } - double pres = pressure(); - double refP = refPressure(); - double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); - - for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; - - mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) - + RTkelvin * log(mv / vmb) - + RTkelvin * m_b_vec_Curr[k] / vmb - - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* m_b_vec_Curr[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_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - } - } - - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double den2 = den*den; - double RTkelvin = RT(); - for (size_t k = 0; k < m_kk; k++) { - m_dpdni[k] = RTkelvin /vmb + RTkelvin * m_b_vec_Curr[k] / (vmb * vmb) - 2.0 * m_pp[k] / den - + 2 * vmb * m_aAlpha_current * m_b_vec_Curr[k] / den2; - } - - double daAlphadT = daAlpha_dT(); - double fac = T * daAlphadT - m_aAlpha_current; - - pressureDerivatives(); - double fac2 = mv + T * m_dpdT / m_dpdV; - double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; - for (size_t k = 0; k < m_kk; k++) { - double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2)*fac - + (mv * m_b_vec_Curr[k]) /(m_b_current*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_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double refP = refPressure(); - double daAlphadT = daAlpha_dT(); - double coeff1 = 0; - double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - - // 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter] *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); - } - m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; - } - - - for (size_t k = 0; k < m_kk; k++) { - coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_vec_Curr[k]; - sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) - + GasConstant - + GasConstant * log(mv / vmb) - + GasConstant * m_b_vec_Curr[k] / vmb - - coeff1* log(vpb2 / vmb2) / den1 - - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; - } - pressureDerivatives(); - 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 -{ - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); -} - -void PengRobinson::getPartialMolarCp(double* cpbar) const -{ - getCp_R(cpbar); - scale(cpbar, cpbar+m_kk, cpbar, GasConstant); -} - -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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - } - } - - double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb = mv + m_b_current; - double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double fac2 = fac * fac; - double RTkelvin = RT(); - - for (size_t k = 0; k < m_kk; k++) { - double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * m_b_vec_Curr[k] / vmb - + RTkelvin * m_b_current * m_b_vec_Curr[k] /(vmb * vmb) - - 2 * mv * m_pp[k] / fac - + 2 * mv * vmb * m_aAlpha_current * m_b_vec_Curr[k] / fac2 - ); - double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) - + m_aAlpha_current/fac - - 2 * mv* vpb *m_aAlpha_current / fac2 - ); - vbar[k] = num / denom; - } -} - -double PengRobinson::speciesCritTemperature(double a, double b) const -{ - double pc, tc, vc; - calcCriticalConditions(a, b, pc, tc, vc); - return tc; -} - -double PengRobinson::critTemperature() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return tc; -} - -double PengRobinson::critPressure() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc; -} - -double PengRobinson::critVolume() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return vc; -} - -double PengRobinson::critCompressibility() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc*vc/tc/GasConstant; -} - -double PengRobinson::critDensity() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - double mmw = meanMolecularWeight(); - return mmw / vc; -} - -bool PengRobinson::addSpecies(shared_ptr spec) -{ - bool added = MixtureFugacityTP::addSpecies(spec); - if (added) { - m_a_vec_Curr.resize(m_kk * m_kk, 0.0); - m_b_vec_Curr.push_back(0.0); - m_a_vec_Curr.push_back(0.0); - m_aAlpha_vec_Curr.resize(m_kk * m_kk, 0.0); - m_aAlpha_vec_Curr.push_back(0.0); - m_kappa_vec.push_back(0.0); - - m_alpha_vec_Curr.push_back(0.0); - m_dalphadT_vec_Curr.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; - - } - - //Assuming no temperature dependence - 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"].as(); - if (eos.getString("model", "") != "Peng-Robinson") { - throw InputFileError("PengRobinson::initThermo", eos, - "Expected species equation of state to be 'Peng-Robinson', " - "but got '{}' instead", eos.getString("model", "")); - } - double a0 = 0, a1 = 0; - if (eos["a"].isScalar()) { - a0 = eos.convert("a", "Pa*m^6/kmol^2"); - } else { - auto avec = eos["a"].asVector(2); - a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); - a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K"); - } - double b = eos.convert("b", "m^3/kmol"); - // unitless acentric factor: - double w = eos.getDouble("w_ac",NAN); - - calculateAlpha(item.first, a0, b, w); - 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, a1 = 0; - if (item2.second.isScalar()) { - a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); - } else { - auto avec = item2.second.asVector(2); - a0 = units.convert(avec[0], "Pa*m^6/kmol^2"); - a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K"); - } - setBinaryCoeffs(item.first, item2.first, a0, a1); - } - } - } 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_vec_Curr[k + m_kk * k] == 0.0) { - // coeffs[0] = a0, coeffs[1] = b; - vector coeffs = getCoeff(item.first); - - // Check if species was found in the database of critical - // properties, and assign the results - if (!isnan(coeffs[0])) { - // Assuming no temperature dependence (i.e. a1 = 0) - calculateAlpha(item.first, coeffs[0], coeffs[1], coeffs[2]); - setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); - } - } - } - } -} - -double PengRobinson::sresid() const -{ - double molarV = molarVolume(); - double hh = m_b_current / molarV; - double zz = z(); - double alpha_1 = daAlpha_dT(); - double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; - double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; - double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); - 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_current; - double vmb = molarV + (1 - M_SQRT2) *m_b_current; - double fac = 1 / (2.0 * M_SQRT2 * m_b_current); - return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); -} - -double PengRobinson::liquidVolEst(double T, double& presGuess) const -{ - double v = m_b_current * 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_current 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_current, m_b_current, m_aAlpha_current, 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_current, m_b_current, m_aAlpha_current, 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_current, m_b_current, m_aAlpha_current, 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::pressureCalc(double T, double molarVol) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - double pres = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; - return pres; -} - -double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current/ den; - - double vpb = molarVol + m_b_current; - double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); - return dpdv; -} - -void PengRobinson::pressureDerivatives() const -{ - double T = temperature(); - double mv = molarVolume(); - double pres; - - m_dpdV = dpdVCalc(T, mv, pres); - double vmb = mv - m_b_current; - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - m_dpdT = (GasConstant / vmb - daAlpha_dT() / den); -} - -void PengRobinson::updateMixingExpressions() -{ - double temp = temperature(); - //Update aAlpha_i - double sqt_alpha; - - // Update indiviual alpha - for (size_t j = 0; j < m_kk; j++) { - size_t counter = j * m_kk + j; - double critTemp_j = speciesCritTemperature(m_a_vec_Curr[counter],m_b_vec_Curr[j]); - sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); - m_alpha_vec_Curr[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++) { - size_t counter = i * m_kk + j; - m_aAlpha_vec_Curr[counter] = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr[counter]; - } - } - - m_b_current = 0.0; - m_a_current = 0.0; - m_aAlpha_current = 0.0; - - calculateAB(m_a_current,m_b_current,m_aAlpha_current); -} - -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_vec_Curr[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - double a_vec_Curr = m_a_vec_Curr[counter]; - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - aAlphaCalc += m_aAlpha_vec_Curr[counter] * moleFractions_[i] * moleFractions_[j]; - } - } -} - -double PengRobinson::daAlpha_dT() const -{ - double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; - double coeff1, coeff2; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; - // Calculate first derivative of alpha for individual species - Tc = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[i]); - sqtTr = sqrt(temperature() / Tc); //we need species critical temperature - coeff1 = 1 / (Tc*sqtTr); - coeff2 = sqtTr - 1; - k = m_kappa_vec[i]; - m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); - } - //Calculate mixture derivative - for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; - for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j * m_kk + j; - temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j])); - daAlphadT += moleFractions_[i] * moleFractions_[j] * temp - * (m_dalphadT_vec_Curr[j] * m_alpha_vec_Curr[i] + m_dalphadT_vec_Curr[i] * m_alpha_vec_Curr[j]); - } - } - return daAlphadT; -} - -double PengRobinson::d2aAlpha_dT2() const -{ - double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; - double Tcrit_i = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[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_vec[i]; - m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); - m_d2alphadT2[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); - } - - //Calculate mixture derivative - for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; - alphai = m_alpha_vec_Curr[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j + m_kk * j; - alphaj = m_alpha_vec_Curr[j]; - alphaij = alphai * alphaj; - temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (alphaij)); - num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); - fac1 = -(0.5 / alphaij)*num*num; - fac2 = alphaj * m_d2alphadT2[i] + alphai *m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); - } - } - return d2aAlphadT2; -} - -void PengRobinson::calcCriticalConditions(double a, double b, - double& pc, double& tc, double& vc) const -{ - if (b <= 0.0) { - tc = 1000000.; - pc = 1.0E13; - vc = omega_vc * GasConstant * tc / pc; - return; - } - if (a <= 0.0) { - tc = 0.0; - pc = 0.0; - vc = 2.0 * b; - return; - } - tc = a * omega_b / (b * omega_a * GasConstant); - pc = omega_b * GasConstant * tc / 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; -} - -} +//! @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/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() : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(0.0), + m_NSolns(0), + m_dpdV(0.0), + m_dpdT(0.0) +{ + fill_n(m_Vroot, 3, 0.0); +} + +PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : + m_formTempParam(0), + m_b_current(0.0), + m_a_current(0.0), + m_aAlpha_current(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::calculateAlpha(const std::string& species, double a, double b, double w) +{ + size_t k = speciesIndex(species); + if (k == npos) { + throw CanteraError("PengRobinson::calculateAlpha", + "Unknown species '{}'.", species); + } + + // Calculate value of kappa (independent of temperature) + // w is an acentric factor of species and must be specified in the CTI file + if (isnan(w)) { + throw CanteraError("PengRobinson::calculateAlpha", + "No acentric factor loaded."); + } else if (w <= 0.491) { + m_kappa_vec[k] = 0.37464 + 1.54226*w - 0.26992*w*w; + } else { + m_kappa_vec[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_vec[k] * (1 - sqt_T_r); + m_alpha_vec_Curr[k] = sqt_alpha*sqt_alpha; +} + +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); + } + size_t counter = k + m_kk * k; + m_a_vec_Curr[counter] = a; + // we store this locally because it is used below to calculate a_Alpha: + double aAlpha_k = a*m_alpha_vec_Curr[k]; + m_aAlpha_vec_Curr[counter] = 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_vec_Curr[j + m_kk * j] * a); + double aAlpha_j = a*m_alpha_vec_Curr[j]; + double a_Alpha = sqrt(aAlpha_j*aAlpha_k); + if (m_a_vec_Curr[j + m_kk * k] == 0) { + m_a_vec_Curr[j + m_kk * k] = a0kj; + m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; + m_a_vec_Curr[k + m_kk * j] = a0kj; + m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; + } + } + m_b_vec_Curr[k] = b; +} + +void PengRobinson::setBinaryCoeffs(const std::string& species_i, + const std::string& species_j, double a0, double alpha) +{ + 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); + } + + size_t counter1 = ki + m_kk * kj; + size_t counter2 = kj + m_kk * ki; + m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; + m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; +} + +// ------------Molar Thermodynamic Properties ------------------------- + +double PengRobinson::cp_mole() const +{ + _updateReferenceStateThermo(); + double T = temperature(); + double mv = molarVolume(); + double vpb = mv + (1 + M_SQRT2)*m_b_current; + double vmb = mv + (1 - M_SQRT2)*m_b_current; + pressureDerivatives(); + double cpref = GasConstant * mean_X(m_cp0_R); + double dHdT_V = cpref + mv * m_dpdT - GasConstant + + 1.0 / (2.0 * M_SQRT2 *m_b_current) * 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(); + pressureDerivatives(); + 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_current - m_b_current * m_b_current; + double pp = GasConstant * T / (mv - m_b_current) - m_aAlpha_current / 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 T = temperature(); + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double vmb = mv - m_b_current; + 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + } + } + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; + ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) + + RTkelvin * m_b_vec_Curr[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* m_b_vec_Curr[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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + } + } + double pres = pressure(); + double refP = refPressure(); + double num = 0; + double den = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + + for (size_t k = 0; k < m_kk; k++) { + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; + + mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + + RTkelvin * log(mv / vmb) + + RTkelvin * m_b_vec_Curr[k] / vmb + - (num /den) * log(vpb2/vmb2) + - m_aAlpha_current* m_b_vec_Curr[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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + + for (size_t k = 0; k < m_kk; k++) { + m_pp[k] = 0.0; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + } + } + + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double den2 = den*den; + double RTkelvin = RT(); + for (size_t k = 0; k < m_kk; k++) { + m_dpdni[k] = RTkelvin /vmb + RTkelvin * m_b_vec_Curr[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + + 2 * vmb * m_aAlpha_current * m_b_vec_Curr[k] / den2; + } + + double daAlphadT = daAlpha_dT(); + double fac = T * daAlphadT - m_aAlpha_current; + + pressureDerivatives(); + double fac2 = mv + T * m_dpdT / m_dpdV; + double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; + for (size_t k = 0; k < m_kk; k++) { + double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2)*fac + + (mv * m_b_vec_Curr[k]) /(m_b_current*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_current; + double vpb2 = mv + (1 + M_SQRT2)*m_b_current; + double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + double refP = refPressure(); + double daAlphadT = daAlpha_dT(); + double coeff1 = 0; + double den1 = 2 * M_SQRT2 * m_b_current * m_b_current; + double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + + // 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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter] *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); + } + m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; + } + + + for (size_t k = 0; k < m_kk; k++) { + coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_vec_Curr[k]; + sbar[k] += GasConstant * log(GasConstant * T / (refP * mv)) + + GasConstant + + GasConstant * log(mv / vmb) + + GasConstant * m_b_vec_Curr[k] / vmb + - coeff1* log(vpb2 / vmb2) / den1 + - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; + } + pressureDerivatives(); + 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 +{ + getIntEnergy_RT(ubar); + scale(ubar, ubar+m_kk, ubar, RT()); +} + +void PengRobinson::getPartialMolarCp(double* cpbar) const +{ + getCp_R(cpbar); + scale(cpbar, cpbar+m_kk, cpbar, GasConstant); +} + +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++) { + size_t counter = k + m_kk*i; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + } + } + + double mv = molarVolume(); + double vmb = mv - m_b_current; + double vpb = mv + m_b_current; + double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double fac2 = fac * fac; + double RTkelvin = RT(); + + for (size_t k = 0; k < m_kk; k++) { + double num = (RTkelvin + RTkelvin * m_b_current/ vmb + RTkelvin * m_b_vec_Curr[k] / vmb + + RTkelvin * m_b_current * m_b_vec_Curr[k] /(vmb * vmb) + - 2 * mv * m_pp[k] / fac + + 2 * mv * vmb * m_aAlpha_current * m_b_vec_Curr[k] / fac2 + ); + double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) + + m_aAlpha_current/fac + - 2 * mv* vpb *m_aAlpha_current / fac2 + ); + vbar[k] = num / denom; + } +} + +double PengRobinson::speciesCritTemperature(double a, double b) const +{ + double pc, tc, vc; + calcCriticalConditions(a, b, pc, tc, vc); + return tc; +} + +double PengRobinson::critTemperature() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return tc; +} + +double PengRobinson::critPressure() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc; +} + +double PengRobinson::critVolume() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return vc; +} + +double PengRobinson::critCompressibility() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + return pc*vc/tc/GasConstant; +} + +double PengRobinson::critDensity() const +{ + double pc, tc, vc; + calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + double mmw = meanMolecularWeight(); + return mmw / vc; +} + +bool PengRobinson::addSpecies(shared_ptr spec) +{ + bool added = MixtureFugacityTP::addSpecies(spec); + if (added) { + m_a_vec_Curr.resize(m_kk * m_kk, 0.0); + m_b_vec_Curr.push_back(0.0); + m_a_vec_Curr.push_back(0.0); + m_aAlpha_vec_Curr.resize(m_kk * m_kk, 0.0); + m_aAlpha_vec_Curr.push_back(0.0); + m_kappa_vec.push_back(0.0); + + m_alpha_vec_Curr.push_back(0.0); + m_dalphadT_vec_Curr.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; + + } + + //Assuming no temperature dependence + 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"].as(); + if (eos.getString("model", "") != "Peng-Robinson") { + throw InputFileError("PengRobinson::initThermo", eos, + "Expected species equation of state to be 'Peng-Robinson', " + "but got '{}' instead", eos.getString("model", "")); + } + double a0 = 0, a1 = 0; + if (eos["a"].isScalar()) { + a0 = eos.convert("a", "Pa*m^6/kmol^2"); + } else { + auto avec = eos["a"].asVector(2); + a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); + a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K"); + } + double b = eos.convert("b", "m^3/kmol"); + // unitless acentric factor: + double w = eos.getDouble("w_ac",NAN); + + calculateAlpha(item.first, a0, b, w); + 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, a1 = 0; + if (item2.second.isScalar()) { + a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); + } else { + auto avec = item2.second.asVector(2); + a0 = units.convert(avec[0], "Pa*m^6/kmol^2"); + a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K"); + } + setBinaryCoeffs(item.first, item2.first, a0, a1); + } + } + } 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_vec_Curr[k + m_kk * k] == 0.0) { + // coeffs[0] = a0, coeffs[1] = b; + vector coeffs = getCoeff(item.first); + + // Check if species was found in the database of critical + // properties, and assign the results + if (!isnan(coeffs[0])) { + // Assuming no temperature dependence (i.e. a1 = 0) + calculateAlpha(item.first, coeffs[0], coeffs[1], coeffs[2]); + setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); + } + } + } + } +} + +double PengRobinson::sresid() const +{ + double molarV = molarVolume(); + double hh = m_b_current / molarV; + double zz = z(); + double alpha_1 = daAlpha_dT(); + double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; + double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; + double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); + 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_current; + double vmb = molarV + (1 - M_SQRT2) *m_b_current; + double fac = 1 / (2.0 * M_SQRT2 * m_b_current); + return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); +} + +double PengRobinson::liquidVolEst(double T, double& presGuess) const +{ + double v = m_b_current * 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_current 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_current, m_b_current, m_aAlpha_current, 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_current, m_b_current, m_aAlpha_current, 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_current, m_b_current, m_aAlpha_current, 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::pressureCalc(double T, double molarVol) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + double pres = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; + return pres; +} + +double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const +{ + double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; + presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current/ den; + + double vpb = molarVol + m_b_current; + double vmb = molarVol - m_b_current; + double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); + return dpdv; +} + +void PengRobinson::pressureDerivatives() const +{ + double T = temperature(); + double mv = molarVolume(); + double pres; + + m_dpdV = dpdVCalc(T, mv, pres); + double vmb = mv - m_b_current; + double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + m_dpdT = (GasConstant / vmb - daAlpha_dT() / den); +} + +void PengRobinson::updateMixingExpressions() +{ + double temp = temperature(); + //Update aAlpha_i + double sqt_alpha; + + // Update indiviual alpha + for (size_t j = 0; j < m_kk; j++) { + size_t counter = j * m_kk + j; + double critTemp_j = speciesCritTemperature(m_a_vec_Curr[counter],m_b_vec_Curr[j]); + sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); + m_alpha_vec_Curr[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++) { + size_t counter = i * m_kk + j; + m_aAlpha_vec_Curr[counter] = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr[counter]; + } + } + + m_b_current = 0.0; + m_a_current = 0.0; + m_aAlpha_current = 0.0; + + calculateAB(m_a_current,m_b_current,m_aAlpha_current); +} + +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_vec_Curr[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter = i * m_kk + j; + double a_vec_Curr = m_a_vec_Curr[counter]; + aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += m_aAlpha_vec_Curr[counter] * moleFractions_[i] * moleFractions_[j]; + } + } +} + +double PengRobinson::daAlpha_dT() const +{ + double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; + double coeff1, coeff2; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + // Calculate first derivative of alpha for individual species + Tc = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[i]); + sqtTr = sqrt(temperature() / Tc); //we need species critical temperature + coeff1 = 1 / (Tc*sqtTr); + coeff2 = sqtTr - 1; + k = m_kappa_vec[i]; + m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); + } + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j * m_kk + j; + temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j])); + daAlphadT += moleFractions_[i] * moleFractions_[j] * temp + * (m_dalphadT_vec_Curr[j] * m_alpha_vec_Curr[i] + m_dalphadT_vec_Curr[i] * m_alpha_vec_Curr[j]); + } + } + return daAlphadT; +} + +double PengRobinson::d2aAlpha_dT2() const +{ + double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; + for (size_t i = 0; i < m_kk; i++) { + size_t counter = i + m_kk * i; + double Tcrit_i = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[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_vec[i]; + m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); + m_d2alphadT2[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); + } + + //Calculate mixture derivative + for (size_t i = 0; i < m_kk; i++) { + size_t counter1 = i + m_kk * i; + alphai = m_alpha_vec_Curr[i]; + for (size_t j = 0; j < m_kk; j++) { + size_t counter2 = j + m_kk * j; + alphaj = m_alpha_vec_Curr[j]; + alphaij = alphai * alphaj; + temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (alphaij)); + num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); + fac1 = -(0.5 / alphaij)*num*num; + fac2 = alphaj * m_d2alphadT2[i] + alphai *m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); + } + } + return d2aAlphadT2; +} + +void PengRobinson::calcCriticalConditions(double a, double b, + double& pc, double& tc, double& vc) const +{ + if (b <= 0.0) { + tc = 1000000.; + pc = 1.0E13; + vc = omega_vc * GasConstant * tc / pc; + return; + } + if (a <= 0.0) { + tc = 0.0; + pc = 0.0; + vc = 2.0 * b; + return; + } + tc = a * omega_b / (b * omega_a * GasConstant); + pc = omega_b * GasConstant * tc / 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 6c3bf151a8..81750c5d88 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -1065,3304 +1065,6 @@ void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, double vc = omega_vc * GasConstant * tc / pc; } -int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const -{ - - // Derive the coefficients of the cubic polynomial to solve. - doublereal an = 1.0; - 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); - double pp = 2./3.; - double tc = pow(tmp, pp); - - int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc); - - return nSolnValues; -} - -} -======= -//! @file RedlichKwongMFTP.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/RedlichKwongMFTP.h" -#include "cantera/thermo/ThermoFactory.h" -#include "cantera/base/stringUtils.h" -#include "cantera/base/ctml.h" - -#include - -#include - -using namespace std; -namespace bmt = boost::math::tools; - -namespace Cantera -{ - -const doublereal RedlichKwongMFTP::omega_a = 4.27480233540E-01; -const doublereal RedlichKwongMFTP::omega_b = 8.66403499650E-02; -const doublereal RedlichKwongMFTP::omega_vc = 3.33333333333333E-01; - -RedlichKwongMFTP::RedlichKwongMFTP() : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); -} - -RedlichKwongMFTP::RedlichKwongMFTP(const std::string& infile, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - initThermoFile(infile, id_); -} - -RedlichKwongMFTP::RedlichKwongMFTP(XML_Node& phaseRefRoot, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - importPhase(phaseRefRoot, this); -} - -void RedlichKwongMFTP::setSpeciesCoeffs(const std::string& species, - double a0, double a1, double b) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } - - if (a1 != 0.0) { - m_formTempParam = 1; // expression is temperature-dependent - } - - size_t counter = k + m_kk * k; - a_coeff_vec(0, counter) = a0; - a_coeff_vec(1, counter) = a1; - - // standard mixing rule for cross-species interaction term - for (size_t j = 0; j < m_kk; j++) { - if (k == j) { - continue; - } - - // a_coeff_vec(0) is initialized to NaN to mark uninitialized species - if (isnan(a_coeff_vec(0, j + m_kk * j))) { - // The diagonal element of the jth species has not yet been defined. - continue; - } else if (isnan(a_coeff_vec(0, j + m_kk * k))) { - // Only use the mixing rules if the off-diagonal element has not already been defined by a - // user-specified crossFluidParameters entry: - double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a0); - double a1kj = sqrt(a_coeff_vec(1, j + m_kk * j) * a1); - a_coeff_vec(0, j + m_kk * k) = a0kj; - a_coeff_vec(1, j + m_kk * k) = a1kj; - a_coeff_vec(0, k + m_kk * j) = a0kj; - a_coeff_vec(1, k + m_kk * j) = a1kj; - } - } - a_coeff_vec.getRow(0, a_vec_Curr_.data()); - b_vec_Curr_[k] = b; -} - -void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1) -{ - size_t ki = speciesIndex(species_i); - if (ki == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_i); - } - size_t kj = speciesIndex(species_j); - if (kj == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_j); - } - - if (a1 != 0.0) { - m_formTempParam = 1; // expression is temperature-dependent - } - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - a_coeff_vec(1, counter1) = a_coeff_vec(1, counter2) = a1; - a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; -} - -// ------------Molar Thermodynamic Properties ------------------------- - -doublereal RedlichKwongMFTP::cp_mole() const -{ - _updateReferenceStateThermo(); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal vpb = mv + m_b_current; - pressureDerivatives(); - doublereal cpref = GasConstant * mean_X(m_cp0_R); - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - doublereal dHdT_V = (cpref + mv * dpdT_ - GasConstant - 1.0 / (2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv) * fac - +1.0/(m_b_current * sqt) * log(vpb/mv) * (-0.5 * dadt)); - return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; -} - -doublereal RedlichKwongMFTP::cv_mole() const -{ - _updateReferenceStateThermo(); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal vpb = mv + m_b_current; - doublereal cvref = GasConstant * (mean_X(m_cp0_R) - 1.0); - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - return (cvref - 1.0/(2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv)*fac - +1.0/(m_b_current * sqt) * log(vpb/mv)*(-0.5*dadt)); -} - -doublereal RedlichKwongMFTP::pressure() const -{ - _updateReferenceStateThermo(); - - // Get a copy of the private variables stored in the State object - doublereal T = temperature(); - double molarV = meanMolecularWeight() / density(); - double pp = GasConstant * T/(molarV - m_b_current) - m_a_current/(sqrt(T) * molarV * (molarV + m_b_current)); - return pp; -} - -doublereal RedlichKwongMFTP::standardConcentration(size_t k) const -{ - getStandardVolumes(m_tmpV.data()); - return 1.0 / m_tmpV[k]; -} - -void RedlichKwongMFTP::getActivityCoefficients(doublereal* ac) const -{ - doublereal mv = molarVolume(); - doublereal sqt = sqrt(temperature()); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - doublereal pres = pressure(); - - for (size_t k = 0; k < m_kk; k++) { - ac[k] = (- RT() * log(pres * mv / RT()) - + RT() * log(mv / vmb) - + RT() * b_vec_Curr_[k] / vmb - - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) - + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) - - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) - ); - } - for (size_t k = 0; k < m_kk; k++) { - ac[k] = exp(ac[k]/RT()); - } -} - -// ---- Partial Molar Properties of the Solution ----------------- - -void RedlichKwongMFTP::getChemPotentials_RT(doublereal* muRT) const -{ - getChemPotentials(muRT); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RT(); - } -} - -void RedlichKwongMFTP::getChemPotentials(doublereal* mu) const -{ - getGibbs_ref(mu); - for (size_t k = 0; k < m_kk; k++) { - double xx = std::max(SmallNumber, moleFraction(k)); - mu[k] += RT()*(log(xx)); - } - - doublereal mv = molarVolume(); - doublereal sqt = sqrt(temperature()); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - doublereal pres = pressure(); - doublereal refP = refPressure(); - - for (size_t k = 0; k < m_kk; k++) { - mu[k] += (RT() * log(pres/refP) - RT() * log(pres * mv / RT()) - + RT() * log(mv / vmb) - + RT() * b_vec_Curr_[k] / vmb - - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) - + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) - - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) - ); - } -} - -void RedlichKwongMFTP::getPartialMolarEnthalpies(doublereal* hbar) const -{ - // First we get the reference state contributions - getEnthalpy_RT_ref(hbar); - scale(hbar, hbar+m_kk, hbar, RT()); - - // We calculate dpdni_ - doublereal TKelvin = temperature(); - doublereal mv = molarVolume(); - doublereal sqt = sqrt(TKelvin); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - dpdni_[k] = RT()/vmb + RT() * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / (sqt * mv * vpb) - + m_a_current * b_vec_Curr_[k]/(sqt * mv * vpb * vpb); - } - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += 2.0 * moleFractions_[i] * TKelvin * a_coeff_vec(1,counter) - 3.0 * moleFractions_[i] * a_vec_Curr_[counter]; - } - } - - pressureDerivatives(); - doublereal fac2 = mv + TKelvin * dpdT_ / dpdV_; - for (size_t k = 0; k < m_kk; k++) { - double hE_v = (mv * dpdni_[k] - RT() - b_vec_Curr_[k]/ (m_b_current * m_b_current * sqt) * log(vpb/mv)*fac - + 1.0 / (m_b_current * sqt) * log(vpb/mv) * m_tmpV[k] - + b_vec_Curr_[k] / vpb / (m_b_current * sqt) * fac); - hbar[k] = hbar[k] + hE_v; - hbar[k] -= fac2 * dpdni_[k]; - } -} - -void RedlichKwongMFTP::getPartialMolarEntropies(doublereal* sbar) const -{ - getEntropy_R_ref(sbar); - scale(sbar, sbar+m_kk, sbar, GasConstant); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal refP = refPressure(); - - for (size_t k = 0; k < m_kk; k++) { - doublereal xx = std::max(SmallNumber, moleFraction(k)); - sbar[k] += GasConstant * (- log(xx)); - } - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); - } - } - - doublereal dadt = da_dt(); - doublereal fac = dadt - m_a_current / (2.0 * TKelvin); - doublereal vmb = mv - m_b_current; - doublereal vpb = mv + m_b_current; - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -=(GasConstant * log(GasConstant * TKelvin / (refP * mv)) - + GasConstant - + GasConstant * log(mv/vmb) - + GasConstant * b_vec_Curr_[k]/vmb - + m_pp[k]/(m_b_current * TKelvin * sqt) * log(vpb/mv) - - 2.0 * m_tmpV[k]/(m_b_current * sqt) * log(vpb/mv) - + b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) * fac - - 1.0 / (m_b_current * sqt) * b_vec_Curr_[k] / vpb * fac - ); - } - - pressureDerivatives(); - getPartialMolarVolumes(m_partialMolarVolumes.data()); - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -= -m_partialMolarVolumes[k] * dpdT_; - } -} - -void RedlichKwongMFTP::getPartialMolarIntEnergies(doublereal* ubar) const -{ - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); -} - -void RedlichKwongMFTP::getPartialMolarCp(doublereal* cpbar) const -{ - getCp_R(cpbar); - scale(cpbar, cpbar+m_kk, cpbar, GasConstant); -} - -void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); - } - } - - doublereal sqt = sqrt(temperature()); - doublereal mv = molarVolume(); - doublereal vmb = mv - m_b_current; - doublereal vpb = mv + m_b_current; - for (size_t k = 0; k < m_kk; k++) { - doublereal num = (RT() + RT() * m_b_current/ vmb + RT() * b_vec_Curr_[k] / vmb - + RT() * m_b_current * b_vec_Curr_[k] /(vmb * vmb) - - 2.0 * m_pp[k] / (sqt * vpb) - + m_a_current * b_vec_Curr_[k] / (sqt * vpb * vpb) - ); - doublereal denom = (pressure() + RT() * m_b_current/(vmb * vmb) - m_a_current / (sqt * vpb * vpb) - ); - vbar[k] = num / denom; - } -} - -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); - if (added) { - a_vec_Curr_.resize(m_kk * m_kk, 0.0); - - // Initialize a_vec and b_vec to NaN, to screen for species with - // pureFluidParameters which are undefined in the input file: - b_vec_Curr_.push_back(NAN); - a_coeff_vec.resize(2, m_kk * m_kk, NAN); - - m_pp.push_back(0.0); - m_tmpV.push_back(0.0); - m_partialMolarVolumes.push_back(0.0); - dpdni_.push_back(0.0); - } - return added; -} - -void RedlichKwongMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) -{ - if (phaseNode.hasChild("thermo")) { - XML_Node& thermoNode = phaseNode.child("thermo"); - std::string model = thermoNode["model"]; - if (model != "RedlichKwong" && model != "RedlichKwongMFTP") { - throw CanteraError("RedlichKwongMFTP::initThermoXML", - "Unknown thermo model : " + model); - } - - // Reset any coefficients which may have been set using values from - // 'critProperties.xml' as part of non-XML initialization, so that - // off-diagonal elements can be correctly initialized - a_coeff_vec.data().assign(a_coeff_vec.data().size(), NAN); - - // Go get all of the coefficients and factors in the - // activityCoefficients XML block - if (thermoNode.hasChild("activityCoefficients")) { - XML_Node& acNode = thermoNode.child("activityCoefficients"); - - // Loop through the children and read out fluid parameters. Process - // all the pureFluidParameters, first: - // Loop back through the "activityCoefficients" children and process the - // crossFluidParameters in the XML tree: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { - readXMLPureFluid(xmlACChild); - } else if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { - readXMLCrossFluid(xmlACChild); - } - } - } - // If any species exist which have undefined pureFluidParameters, - // search the database in 'critProperties.xml' to find critical - // temperature and pressure to calculate a and b. - - // Loop through all species in the CTI file - size_t iSpecies = 0; - - for (size_t i = 0; i < m_kk; i++) { - string iName = speciesName(i); - - // Get the index of the species - iSpecies = speciesIndex(iName); - - // Check if a and b are already populated (only the diagonal elements of a). - size_t counter = iSpecies + m_kk * iSpecies; - - // If not, then search the database: - if (isnan(a_coeff_vec(0, counter))) { - - vector coeffArray; - - // Search the database for the species name and calculate - // coefficients a and b, from critical properties: - // coeffArray[0] = a0, coeffArray[1] = b; - coeffArray = getCoeff(iName); - - // Check if species was found in the database of critical properties, - // and assign the results - if (!isnan(coeffArray[0])) { - //Assuming no temperature dependence (i,e a1 = 0) - setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); - } - } - } - } - - MixtureFugacityTP::initThermoXML(phaseNode, id); -} - -void RedlichKwongMFTP::initThermo() -{ - for (auto& item : m_species) { - // Read a and b coefficients 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", "Redlich-Kwong"); - double a0 = 0, a1 = 0; - if (eos["a"].isScalar()) { - a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); - } else { - auto avec = eos["a"].asVector(2); - a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); - a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); - } - double b = eos.convert("b", "m^3/kmol"); - setSpeciesCoeffs(item.first, a0, a1, b); - 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, a1 = 0; - if (item2.second.isScalar()) { - a0 = units.convert(item2.second, "Pa*m^6/kmol^2*K^0.5"); - } else { - auto avec = item2.second.asVector(2); - a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); - a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); - } - setBinaryCoeffs(item.first, item2.first, a0, a1); - } - } - } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { - // coeffs[0] = a0, coeffs[1] = b; - vector coeffs = getCoeff(item.first); - - // Check if species was found in the database of critical - // properties, and assign the results - if (!isnan(coeffs[0])) { - // Assuming no temperature dependence (i.e. a1 = 0) - setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); - } - } - } - } -} - -vector RedlichKwongMFTP::getCoeff(const std::string& iName) -{ - vector_fp spCoeff{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 specie iName to current database species - // dbName: - if (iNameLower == dbName) { - // Read from database and calculate a and b coefficients - double vParams; - double T_crit=0.; - double P_crit=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("RedlichKwongMFTP::getCoeff", - "Critical Temperature must be positive "); - } - T_crit = vParams; - } else { - throw CanteraError("RedlichKwongMFTP::getCoeff", - "Critical Temperature not in database "); - } - 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("RedlichKwongMFTP::getCoeff", - "Critical Pressure must be positive "); - } - P_crit = vParams; - } else { - throw CanteraError("RedlichKwongMFTP::getCoeff", - "Critical Pressure not in database "); - } - - //Assuming no temperature dependence - spCoeff[0] = omega_a * pow(GasConstant, 2) * pow(T_crit, 2.5) / P_crit; //coeff a - spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b - break; - } - } - return spCoeff; -} - -void RedlichKwongMFTP::readXMLPureFluid(XML_Node& pureFluidParam) -{ - string xname = pureFluidParam.name(); - if (xname != "pureFluidParameters") { - throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", - "Incorrect name for processing this routine: " + xname); - } - - double a0 = 0.0; - double a1 = 0.0; - double b = 0.0; - for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { - XML_Node& xmlChild = pureFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - string iModel = toLowerCopy(xmlChild.attrib("model")); - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - - if (iModel == "constant" && vParams.size() == 1) { - a0 = vParams[0]; - a1 = 0; - } else if (iModel == "linear_a" && vParams.size() == 2) { - a0 = vParams[0]; - a1 = vParams[1]; - } else { - throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", - "unknown model or incorrect number of parameters"); - } - - } else if (nodeName == "b_coeff") { - b = getFloatCurrent(xmlChild, "toSI"); - } - } - setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, a1, b); -} - -void RedlichKwongMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) -{ - string xname = CrossFluidParam.name(); - if (xname != "crossFluidParameters") { - throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", - "Incorrect name for processing this routine: " + xname); - } - - string iName = CrossFluidParam.attrib("species1"); - string jName = CrossFluidParam.attrib("species2"); - - size_t num = CrossFluidParam.nChildren(); - for (size_t iChild = 0; iChild < num; iChild++) { - XML_Node& xmlChild = CrossFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - string iModel = toLowerCopy(xmlChild.attrib("model")); - if (iModel == "constant" && vParams.size() == 1) { - setBinaryCoeffs(iName, jName, vParams[0], 0.0); - } else if (iModel == "linear_a") { - setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); - } else { - throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", - "unknown model ({}) or wrong number of parameters ({})", - iModel, vParams.size()); - } - } - } -} - -void RedlichKwongMFTP::setParametersFromXML(const XML_Node& thermoNode) -{ - MixtureFugacityTP::setParametersFromXML(thermoNode); - std::string model = thermoNode["model"]; -} - -doublereal RedlichKwongMFTP::sresid() const -{ - // note this agrees with tpx - doublereal rho = density(); - doublereal mmw = meanMolecularWeight(); - doublereal molarV = mmw / rho; - double hh = m_b_current / molarV; - doublereal zz = z(); - doublereal dadt = da_dt(); - doublereal T = temperature(); - doublereal sqT = sqrt(T); - doublereal fac = dadt - m_a_current / (2.0 * T); - double sresid_mol_R = log(zz*(1.0 - hh)) + log(1.0 + hh) * fac / (sqT * GasConstant * m_b_current); - return GasConstant * sresid_mol_R; -} - -doublereal RedlichKwongMFTP::hresid() const -{ - // note this agrees with tpx - doublereal rho = density(); - doublereal mmw = meanMolecularWeight(); - doublereal molarV = mmw / rho; - double hh = m_b_current / molarV; - doublereal zz = z(); - doublereal dadt = da_dt(); - doublereal T = temperature(); - doublereal sqT = sqrt(T); - doublereal fac = T * dadt - 3.0 *m_a_current / (2.0); - return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqT * m_b_current); -} - -doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGuess) const -{ - double v = m_b_current * 1.1; - double atmp; - double btmp; - calculateAB(TKelvin, atmp, btmp); - doublereal pres = std::max(psatEst(TKelvin), presGuess); - double Vroot[3]; - bool foundLiq = false; - int m = 0; - while (m < 100 && !foundLiq) { - int nsol = CubicCall(TKelvin, pres, atmp, btmp, 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; -} - -doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, int phaseRequested, doublereal rhoguess) -{ - // It's necessary to set the temperature so that m_a_current is set correctly. - setTemperature(TKelvin); - double tcrit = critTemperature(); - double mmw = meanMolecularWeight(); - if (rhoguess == -1.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 - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } - - - doublereal volguess = mmw / rhoguess; - NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); - - doublereal molarVolLast = Vroot_[0]; - if (NSolns_ >= 2) { - if (phaseRequested >= FLUID_LIQUID_0) { - molarVolLast = Vroot_[0]; - } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[2]; - } else { - if (volguess > Vroot_[1]) { - molarVolLast = Vroot_[2]; - } else { - molarVolLast = Vroot_[0]; - } - } - } else if (NSolns_ == 1) { - if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else if (NSolns_ == -1) { - if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[0]; - } else if (TKelvin > tcrit) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else { - molarVolLast = Vroot_[0]; - return -1.0; - } - return mmw / molarVolLast; -} - -doublereal RedlichKwongMFTP::densSpinodalLiquid() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); - - doublereal mmw = meanMolecularWeight(); - return mmw / (0.5 * (vv.first + vv.second)); -} - -doublereal RedlichKwongMFTP::densSpinodalGas() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); - - doublereal mmw = meanMolecularWeight(); - 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); - presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - - m_a_current / (sqt * molarVol * (molarVol + m_b_current)); - - doublereal vpb = molarVol + m_b_current; - doublereal vmb = molarVol - m_b_current; - doublereal dpdv = (- GasConstant * TKelvin / (vmb * vmb) - + m_a_current * (2 * molarVol + m_b_current) / (sqt * molarVol * molarVol * vpb * vpb)); - return dpdv; -} - -void RedlichKwongMFTP::pressureDerivatives() const -{ - doublereal TKelvin = temperature(); - doublereal mv = molarVolume(); - doublereal pres; - - dpdV_ = dpdVCalc(TKelvin, mv, pres); - doublereal sqt = sqrt(TKelvin); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - doublereal dadt = da_dt(); - doublereal fac = dadt - m_a_current/(2.0 * TKelvin); - dpdT_ = (GasConstant / vmb - fac / (sqt * mv * vpb)); -} - -void RedlichKwongMFTP::updateMixingExpressions() -{ - double temp = temperature(); - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - a_vec_Curr_[counter] = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; - } - } - } - - m_b_current = 0.0; - m_a_current = 0.0; - for (size_t i = 0; i < m_kk; i++) { - m_b_current += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - } - } - if (isnan(m_b_current)) { - // One or more species do not have specified coefficients. - fmt::memory_buffer b; - for (size_t k = 0; k < m_kk; k++) { - if (isnan(b_vec_Curr_[k])) { - if (b.size() > 0) { - format_to(b, ", {}", speciesName(k)); - } else { - format_to(b, "{}", speciesName(k)); - } - } - } - throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", - "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); - } -} - -void RedlichKwongMFTP::calculateAB(doublereal temp, doublereal& aCalc, doublereal& bCalc) const -{ - bCalc = 0.0; - aCalc = 0.0; - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - doublereal a_vec_Curr = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - } - } - } else { - for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - doublereal a_vec_Curr = a_coeff_vec(0,counter); - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - } - } - } -} - -doublereal RedlichKwongMFTP::da_dt() const -{ - doublereal dadT = 0.0; - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - dadT+= a_coeff_vec(1,counter) * moleFractions_[i] * moleFractions_[j]; - } - } - } - return dadT; -} - -void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, - doublereal& pc, doublereal& tc, doublereal& vc) const -{ - if (m_formTempParam != 0) { - a = a0_coeff; - } - if (b <= 0.0) { - tc = 1000000.; - pc = 1.0E13; - vc = omega_vc * GasConstant * tc / pc; - return; - } - if (a <= 0.0) { - tc = 0.0; - pc = 0.0; - vc = 2.0 * b; - return; - } - double tmp = a * omega_b / (b * omega_a * GasConstant); - double pp = 2./3.; - doublereal sqrttc, f, dfdt, deltatc; - - if (m_formTempParam == 0) { - tc = pow(tmp, pp); - } else { - tc = pow(tmp, pp); - for (int j = 0; j < 10; j++) { - sqrttc = sqrt(tc); - f = omega_a * b * GasConstant * tc * sqrttc / omega_b - aT_coeff * tc - a0_coeff; - dfdt = 1.5 * omega_a * b * GasConstant * sqrttc / omega_b - aT_coeff; - deltatc = - f / dfdt; - tc += deltatc; - } - if (deltatc > 0.1) { - throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); - } - } - - pc = omega_b * GasConstant * tc / b; - vc = omega_vc * GasConstant * tc / pc; -} - -int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const -{ - - // Derive the coefficients of the cubic polynomial to solve. - doublereal an = 1.0; - 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); - double pp = 2./3.; - double tc = pow(tmp, pp); - - int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc); - - return nSolnValues; -} - -} ->>>>>>> 6a88472a9... Moved setToEquil function to the parent class, 'MixtureFugacityTP". -======= -//! @file RedlichKwongMFTP.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/RedlichKwongMFTP.h" -#include "cantera/thermo/ThermoFactory.h" -#include "cantera/base/stringUtils.h" -#include "cantera/base/ctml.h" - -#include - -#include - -using namespace std; -namespace bmt = boost::math::tools; - -namespace Cantera -{ - -const doublereal RedlichKwongMFTP::omega_a = 4.27480233540E-01; -const doublereal RedlichKwongMFTP::omega_b = 8.66403499650E-02; -const doublereal RedlichKwongMFTP::omega_vc = 3.33333333333333E-01; - -RedlichKwongMFTP::RedlichKwongMFTP() : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); -} - -RedlichKwongMFTP::RedlichKwongMFTP(const std::string& infile, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - initThermoFile(infile, id_); -} - -RedlichKwongMFTP::RedlichKwongMFTP(XML_Node& phaseRefRoot, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - importPhase(phaseRefRoot, this); -} - -void RedlichKwongMFTP::setSpeciesCoeffs(const std::string& species, - double a0, double a1, double b) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } - - if (a1 != 0.0) { - m_formTempParam = 1; // expression is temperature-dependent - } - - size_t counter = k + m_kk * k; - a_coeff_vec(0, counter) = a0; - a_coeff_vec(1, counter) = a1; - - // standard mixing rule for cross-species interaction term - for (size_t j = 0; j < m_kk; j++) { - if (k == j) { - continue; - } - - // a_coeff_vec(0) is initialized to NaN to mark uninitialized species - if (isnan(a_coeff_vec(0, j + m_kk * j))) { - // The diagonal element of the jth species has not yet been defined. - continue; - } else if (isnan(a_coeff_vec(0, j + m_kk * k))) { - // Only use the mixing rules if the off-diagonal element has not already been defined by a - // user-specified crossFluidParameters entry: - double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a0); - double a1kj = sqrt(a_coeff_vec(1, j + m_kk * j) * a1); - a_coeff_vec(0, j + m_kk * k) = a0kj; - a_coeff_vec(1, j + m_kk * k) = a1kj; - a_coeff_vec(0, k + m_kk * j) = a0kj; - a_coeff_vec(1, k + m_kk * j) = a1kj; - } - } - a_coeff_vec.getRow(0, a_vec_Curr_.data()); - b_vec_Curr_[k] = b; -} - -void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1) -{ - size_t ki = speciesIndex(species_i); - if (ki == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_i); - } - size_t kj = speciesIndex(species_j); - if (kj == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_j); - } - - if (a1 != 0.0) { - m_formTempParam = 1; // expression is temperature-dependent - } - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - a_coeff_vec(1, counter1) = a_coeff_vec(1, counter2) = a1; - a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; -} - -// ------------Molar Thermodynamic Properties ------------------------- - -doublereal RedlichKwongMFTP::cp_mole() const -{ - _updateReferenceStateThermo(); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal vpb = mv + m_b_current; - pressureDerivatives(); - doublereal cpref = GasConstant * mean_X(m_cp0_R); - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - doublereal dHdT_V = (cpref + mv * dpdT_ - GasConstant - 1.0 / (2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv) * fac - +1.0/(m_b_current * sqt) * log(vpb/mv) * (-0.5 * dadt)); - return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; -} - -doublereal RedlichKwongMFTP::cv_mole() const -{ - _updateReferenceStateThermo(); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal vpb = mv + m_b_current; - doublereal cvref = GasConstant * (mean_X(m_cp0_R) - 1.0); - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - return (cvref - 1.0/(2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv)*fac - +1.0/(m_b_current * sqt) * log(vpb/mv)*(-0.5*dadt)); -} - -doublereal RedlichKwongMFTP::pressure() const -{ - _updateReferenceStateThermo(); - - // Get a copy of the private variables stored in the State object - doublereal T = temperature(); - double molarV = meanMolecularWeight() / density(); - double pp = GasConstant * T/(molarV - m_b_current) - m_a_current/(sqrt(T) * molarV * (molarV + m_b_current)); - return pp; -} - -doublereal RedlichKwongMFTP::standardConcentration(size_t k) const -{ - getStandardVolumes(m_tmpV.data()); - return 1.0 / m_tmpV[k]; -} - -void RedlichKwongMFTP::getActivityCoefficients(doublereal* ac) const -{ - doublereal mv = molarVolume(); - doublereal sqt = sqrt(temperature()); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - doublereal pres = pressure(); - - for (size_t k = 0; k < m_kk; k++) { - ac[k] = (- RT() * log(pres * mv / RT()) - + RT() * log(mv / vmb) - + RT() * b_vec_Curr_[k] / vmb - - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) - + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) - - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) - ); - } - for (size_t k = 0; k < m_kk; k++) { - ac[k] = exp(ac[k]/RT()); - } -} - -// ---- Partial Molar Properties of the Solution ----------------- - -void RedlichKwongMFTP::getChemPotentials_RT(doublereal* muRT) const -{ - getChemPotentials(muRT); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RT(); - } -} - -void RedlichKwongMFTP::getChemPotentials(doublereal* mu) const -{ - getGibbs_ref(mu); - for (size_t k = 0; k < m_kk; k++) { - double xx = std::max(SmallNumber, moleFraction(k)); - mu[k] += RT()*(log(xx)); - } - - doublereal mv = molarVolume(); - doublereal sqt = sqrt(temperature()); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - doublereal pres = pressure(); - doublereal refP = refPressure(); - - for (size_t k = 0; k < m_kk; k++) { - mu[k] += (RT() * log(pres/refP) - RT() * log(pres * mv / RT()) - + RT() * log(mv / vmb) - + RT() * b_vec_Curr_[k] / vmb - - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) - + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) - - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) - ); - } -} - -void RedlichKwongMFTP::getPartialMolarEnthalpies(doublereal* hbar) const -{ - // First we get the reference state contributions - getEnthalpy_RT_ref(hbar); - scale(hbar, hbar+m_kk, hbar, RT()); - - // We calculate dpdni_ - doublereal TKelvin = temperature(); - doublereal mv = molarVolume(); - doublereal sqt = sqrt(TKelvin); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - dpdni_[k] = RT()/vmb + RT() * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / (sqt * mv * vpb) - + m_a_current * b_vec_Curr_[k]/(sqt * mv * vpb * vpb); - } - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += 2.0 * moleFractions_[i] * TKelvin * a_coeff_vec(1,counter) - 3.0 * moleFractions_[i] * a_vec_Curr_[counter]; - } - } - - pressureDerivatives(); - doublereal fac2 = mv + TKelvin * dpdT_ / dpdV_; - for (size_t k = 0; k < m_kk; k++) { - double hE_v = (mv * dpdni_[k] - RT() - b_vec_Curr_[k]/ (m_b_current * m_b_current * sqt) * log(vpb/mv)*fac - + 1.0 / (m_b_current * sqt) * log(vpb/mv) * m_tmpV[k] - + b_vec_Curr_[k] / vpb / (m_b_current * sqt) * fac); - hbar[k] = hbar[k] + hE_v; - hbar[k] -= fac2 * dpdni_[k]; - } -} - -void RedlichKwongMFTP::getPartialMolarEntropies(doublereal* sbar) const -{ - getEntropy_R_ref(sbar); - scale(sbar, sbar+m_kk, sbar, GasConstant); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal refP = refPressure(); - - for (size_t k = 0; k < m_kk; k++) { - doublereal xx = std::max(SmallNumber, moleFraction(k)); - sbar[k] += GasConstant * (- log(xx)); - } - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); - } - } - - doublereal dadt = da_dt(); - doublereal fac = dadt - m_a_current / (2.0 * TKelvin); - doublereal vmb = mv - m_b_current; - doublereal vpb = mv + m_b_current; - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -=(GasConstant * log(GasConstant * TKelvin / (refP * mv)) - + GasConstant - + GasConstant * log(mv/vmb) - + GasConstant * b_vec_Curr_[k]/vmb - + m_pp[k]/(m_b_current * TKelvin * sqt) * log(vpb/mv) - - 2.0 * m_tmpV[k]/(m_b_current * sqt) * log(vpb/mv) - + b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) * fac - - 1.0 / (m_b_current * sqt) * b_vec_Curr_[k] / vpb * fac - ); - } - - pressureDerivatives(); - getPartialMolarVolumes(m_partialMolarVolumes.data()); - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -= -m_partialMolarVolumes[k] * dpdT_; - } -} - -void RedlichKwongMFTP::getPartialMolarIntEnergies(doublereal* ubar) const -{ - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); -} - -void RedlichKwongMFTP::getPartialMolarCp(doublereal* cpbar) const -{ - getCp_R(cpbar); - scale(cpbar, cpbar+m_kk, cpbar, GasConstant); -} - -void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); - } - } - - doublereal sqt = sqrt(temperature()); - doublereal mv = molarVolume(); - doublereal vmb = mv - m_b_current; - doublereal vpb = mv + m_b_current; - for (size_t k = 0; k < m_kk; k++) { - doublereal num = (RT() + RT() * m_b_current/ vmb + RT() * b_vec_Curr_[k] / vmb - + RT() * m_b_current * b_vec_Curr_[k] /(vmb * vmb) - - 2.0 * m_pp[k] / (sqt * vpb) - + m_a_current * b_vec_Curr_[k] / (sqt * vpb * vpb) - ); - doublereal denom = (pressure() + RT() * m_b_current/(vmb * vmb) - m_a_current / (sqt * vpb * vpb) - ); - vbar[k] = num / denom; - } -} - -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); - if (added) { - a_vec_Curr_.resize(m_kk * m_kk, 0.0); - - // Initialize a_vec and b_vec to NaN, to screen for species with - // pureFluidParameters which are undefined in the input file: - b_vec_Curr_.push_back(NAN); - a_coeff_vec.resize(2, m_kk * m_kk, NAN); - - m_pp.push_back(0.0); - m_tmpV.push_back(0.0); - m_partialMolarVolumes.push_back(0.0); - dpdni_.push_back(0.0); - } - return added; -} - -void RedlichKwongMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) -{ - if (phaseNode.hasChild("thermo")) { - XML_Node& thermoNode = phaseNode.child("thermo"); - std::string model = thermoNode["model"]; - if (model != "RedlichKwong" && model != "RedlichKwongMFTP") { - throw CanteraError("RedlichKwongMFTP::initThermoXML", - "Unknown thermo model : " + model); - } - - // Reset any coefficients which may have been set using values from - // 'critProperties.xml' as part of non-XML initialization, so that - // off-diagonal elements can be correctly initialized - a_coeff_vec.data().assign(a_coeff_vec.data().size(), NAN); - - // Go get all of the coefficients and factors in the - // activityCoefficients XML block - if (thermoNode.hasChild("activityCoefficients")) { - XML_Node& acNode = thermoNode.child("activityCoefficients"); - - // Loop through the children and read out fluid parameters. Process - // all the pureFluidParameters, first: - // Loop back through the "activityCoefficients" children and process the - // crossFluidParameters in the XML tree: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { - readXMLPureFluid(xmlACChild); - } else if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { - readXMLCrossFluid(xmlACChild); - } - } - } - // If any species exist which have undefined pureFluidParameters, - // search the database in 'critProperties.xml' to find critical - // temperature and pressure to calculate a and b. - - // Loop through all species in the CTI file - size_t iSpecies = 0; - - for (size_t i = 0; i < m_kk; i++) { - string iName = speciesName(i); - - // Get the index of the species - iSpecies = speciesIndex(iName); - - // Check if a and b are already populated (only the diagonal elements of a). - size_t counter = iSpecies + m_kk * iSpecies; - - // If not, then search the database: - if (isnan(a_coeff_vec(0, counter))) { - - vector coeffArray; - - // Search the database for the species name and calculate - // coefficients a and b, from critical properties: - // coeffArray[0] = a0, coeffArray[1] = b; - coeffArray = getCoeff(iName); - - // Check if species was found in the database of critical properties, - // and assign the results - if (!isnan(coeffArray[0])) { - //Assuming no temperature dependence (i,e a1 = 0) - setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); - } - } - } - } - - MixtureFugacityTP::initThermoXML(phaseNode, id); -} - -void RedlichKwongMFTP::initThermo() -{ - for (auto& item : m_species) { - // Read a and b coefficients 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", "Redlich-Kwong"); - double a0 = 0, a1 = 0; - if (eos["a"].isScalar()) { - a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); - } else { - auto avec = eos["a"].asVector(2); - a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); - a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); - } - double b = eos.convert("b", "m^3/kmol"); - setSpeciesCoeffs(item.first, a0, a1, b); - 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, a1 = 0; - if (item2.second.isScalar()) { - a0 = units.convert(item2.second, "Pa*m^6/kmol^2*K^0.5"); - } else { - auto avec = item2.second.asVector(2); - a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); - a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); - } - setBinaryCoeffs(item.first, item2.first, a0, a1); - } - } - } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { - // coeffs[0] = a0, coeffs[1] = b; - vector coeffs = getCoeff(item.first); - - // Check if species was found in the database of critical - // properties, and assign the results - if (!isnan(coeffs[0])) { - // Assuming no temperature dependence (i.e. a1 = 0) - setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); - } - } - } - } -} - -vector RedlichKwongMFTP::getCoeff(const std::string& iName) -{ - vector_fp spCoeff{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 specie iName to current database species - // dbName: - if (iNameLower == dbName) { - // Read from database and calculate a and b coefficients - double vParams; - double T_crit=0.; - double P_crit=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("RedlichKwongMFTP::getCoeff", - "Critical Temperature must be positive "); - } - T_crit = vParams; - } else { - throw CanteraError("RedlichKwongMFTP::getCoeff", - "Critical Temperature not in database "); - } - 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("RedlichKwongMFTP::getCoeff", - "Critical Pressure must be positive "); - } - P_crit = vParams; - } else { - throw CanteraError("RedlichKwongMFTP::getCoeff", - "Critical Pressure not in database "); - } - - //Assuming no temperature dependence - spCoeff[0] = omega_a * pow(GasConstant, 2) * pow(T_crit, 2.5) / P_crit; //coeff a - spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b - break; - } - } - return spCoeff; -} - -void RedlichKwongMFTP::readXMLPureFluid(XML_Node& pureFluidParam) -{ - string xname = pureFluidParam.name(); - if (xname != "pureFluidParameters") { - throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", - "Incorrect name for processing this routine: " + xname); - } - - double a0 = 0.0; - double a1 = 0.0; - double b = 0.0; - for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { - XML_Node& xmlChild = pureFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - string iModel = toLowerCopy(xmlChild.attrib("model")); - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - - if (iModel == "constant" && vParams.size() == 1) { - a0 = vParams[0]; - a1 = 0; - } else if (iModel == "linear_a" && vParams.size() == 2) { - a0 = vParams[0]; - a1 = vParams[1]; - } else { - throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", - "unknown model or incorrect number of parameters"); - } - - } else if (nodeName == "b_coeff") { - b = getFloatCurrent(xmlChild, "toSI"); - } - } - setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, a1, b); -} - -void RedlichKwongMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) -{ - string xname = CrossFluidParam.name(); - if (xname != "crossFluidParameters") { - throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", - "Incorrect name for processing this routine: " + xname); - } - - string iName = CrossFluidParam.attrib("species1"); - string jName = CrossFluidParam.attrib("species2"); - - size_t num = CrossFluidParam.nChildren(); - for (size_t iChild = 0; iChild < num; iChild++) { - XML_Node& xmlChild = CrossFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - string iModel = toLowerCopy(xmlChild.attrib("model")); - if (iModel == "constant" && vParams.size() == 1) { - setBinaryCoeffs(iName, jName, vParams[0], 0.0); - } else if (iModel == "linear_a") { - setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); - } else { - throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", - "unknown model ({}) or wrong number of parameters ({})", - iModel, vParams.size()); - } - } - } -} - -void RedlichKwongMFTP::setParametersFromXML(const XML_Node& thermoNode) -{ - MixtureFugacityTP::setParametersFromXML(thermoNode); - std::string model = thermoNode["model"]; -} - -doublereal RedlichKwongMFTP::sresid() const -{ - // note this agrees with tpx - doublereal rho = density(); - doublereal mmw = meanMolecularWeight(); - doublereal molarV = mmw / rho; - double hh = m_b_current / molarV; - doublereal zz = z(); - doublereal dadt = da_dt(); - doublereal T = temperature(); - doublereal sqT = sqrt(T); - doublereal fac = dadt - m_a_current / (2.0 * T); - double sresid_mol_R = log(zz*(1.0 - hh)) + log(1.0 + hh) * fac / (sqT * GasConstant * m_b_current); - return GasConstant * sresid_mol_R; -} - -doublereal RedlichKwongMFTP::hresid() const -{ - // note this agrees with tpx - doublereal rho = density(); - doublereal mmw = meanMolecularWeight(); - doublereal molarV = mmw / rho; - double hh = m_b_current / molarV; - doublereal zz = z(); - doublereal dadt = da_dt(); - doublereal T = temperature(); - doublereal sqT = sqrt(T); - doublereal fac = T * dadt - 3.0 *m_a_current / (2.0); - return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqT * m_b_current); -} - -doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGuess) const -{ - double v = m_b_current * 1.1; - double atmp; - double btmp; - calculateAB(TKelvin, atmp, btmp); - doublereal pres = std::max(psatEst(TKelvin), presGuess); - double Vroot[3]; - bool foundLiq = false; - int m = 0; - while (m < 100 && !foundLiq) { - int nsol = CubicCall(TKelvin, pres, atmp, btmp, 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; -} - -doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, int phaseRequested, doublereal rhoguess) -{ - // It's necessary to set the temperature so that m_a_current is set correctly. - setTemperature(TKelvin); - double tcrit = critTemperature(); - double mmw = meanMolecularWeight(); - if (rhoguess == -1.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 - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } - - - doublereal volguess = mmw / rhoguess; - NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); - - doublereal molarVolLast = Vroot_[0]; - if (NSolns_ >= 2) { - if (phaseRequested >= FLUID_LIQUID_0) { - molarVolLast = Vroot_[0]; - } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[2]; - } else { - if (volguess > Vroot_[1]) { - molarVolLast = Vroot_[2]; - } else { - molarVolLast = Vroot_[0]; - } - } - } else if (NSolns_ == 1) { - if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else if (NSolns_ == -1) { - if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[0]; - } else if (TKelvin > tcrit) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else { - molarVolLast = Vroot_[0]; - return -1.0; - } - return mmw / molarVolLast; -} - -doublereal RedlichKwongMFTP::densSpinodalLiquid() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); - - doublereal mmw = meanMolecularWeight(); - return mmw / (0.5 * (vv.first + vv.second)); -} - -doublereal RedlichKwongMFTP::densSpinodalGas() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); - - doublereal mmw = meanMolecularWeight(); - 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); - presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - - m_a_current / (sqt * molarVol * (molarVol + m_b_current)); - - doublereal vpb = molarVol + m_b_current; - doublereal vmb = molarVol - m_b_current; - doublereal dpdv = (- GasConstant * TKelvin / (vmb * vmb) - + m_a_current * (2 * molarVol + m_b_current) / (sqt * molarVol * molarVol * vpb * vpb)); - return dpdv; -} - -void RedlichKwongMFTP::pressureDerivatives() const -{ - doublereal TKelvin = temperature(); - doublereal mv = molarVolume(); - doublereal pres; - - dpdV_ = dpdVCalc(TKelvin, mv, pres); - doublereal sqt = sqrt(TKelvin); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - doublereal dadt = da_dt(); - doublereal fac = dadt - m_a_current/(2.0 * TKelvin); - dpdT_ = (GasConstant / vmb - fac / (sqt * mv * vpb)); -} - -void RedlichKwongMFTP::updateMixingExpressions() -{ - double temp = temperature(); - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - a_vec_Curr_[counter] = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; - } - } - } - - m_b_current = 0.0; - m_a_current = 0.0; - for (size_t i = 0; i < m_kk; i++) { - m_b_current += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - } - } - if (isnan(m_b_current)) { - // One or more species do not have specified coefficients. - fmt::memory_buffer b; - for (size_t k = 0; k < m_kk; k++) { - if (isnan(b_vec_Curr_[k])) { - if (b.size() > 0) { - format_to(b, ", {}", speciesName(k)); - } else { - format_to(b, "{}", speciesName(k)); - } - } - } - throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", - "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); - } -} - -void RedlichKwongMFTP::calculateAB(doublereal temp, doublereal& aCalc, doublereal& bCalc) const -{ - bCalc = 0.0; - aCalc = 0.0; - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - doublereal a_vec_Curr = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - } - } - } else { - for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - doublereal a_vec_Curr = a_coeff_vec(0,counter); - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - } - } - } -} - -doublereal RedlichKwongMFTP::da_dt() const -{ - doublereal dadT = 0.0; - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - dadT+= a_coeff_vec(1,counter) * moleFractions_[i] * moleFractions_[j]; - } - } - } - return dadT; -} - -void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, - doublereal& pc, doublereal& tc, doublereal& vc) const -{ - if (m_formTempParam != 0) { - a = a0_coeff; - } - if (b <= 0.0) { - tc = 1000000.; - pc = 1.0E13; - vc = omega_vc * GasConstant * tc / pc; - return; - } - if (a <= 0.0) { - tc = 0.0; - pc = 0.0; - vc = 2.0 * b; - return; - } - double tmp = a * omega_b / (b * omega_a * GasConstant); - double pp = 2./3.; - doublereal sqrttc, f, dfdt, deltatc; - - if (m_formTempParam == 0) { - tc = pow(tmp, pp); - } else { - tc = pow(tmp, pp); - for (int j = 0; j < 10; j++) { - sqrttc = sqrt(tc); - f = omega_a * b * GasConstant * tc * sqrttc / omega_b - aT_coeff * tc - a0_coeff; - dfdt = 1.5 * omega_a * b * GasConstant * sqrttc / omega_b - aT_coeff; - deltatc = - f / dfdt; - tc += deltatc; - } - if (deltatc > 0.1) { - throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); - } - } - - pc = omega_b * GasConstant * tc / b; - vc = omega_vc * GasConstant * tc / pc; -} - -int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const -{ - - // Derive the coefficients of the cubic polynomial to solve. - doublereal an = 1.0; - 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); - double pp = 2./3.; - double tc = pow(tmp, pp); - double pc = omega_b * GasConstant * tc / b; - double vc = omega_vc * GasConstant * tc / pc; - - int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, pc); - - return nSolnValues; -} - -} ->>>>>>> a933404b7... Removing dependancy of EoS specific omega_a, omega_b parameters from solveCubic -======= -//! @file RedlichKwongMFTP.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/RedlichKwongMFTP.h" -#include "cantera/thermo/ThermoFactory.h" -#include "cantera/base/stringUtils.h" -#include "cantera/base/ctml.h" - -#include - -#include - -using namespace std; -namespace bmt = boost::math::tools; - -namespace Cantera -{ - -const doublereal RedlichKwongMFTP::omega_a = 4.27480233540E-01; -const doublereal RedlichKwongMFTP::omega_b = 8.66403499650E-02; -const doublereal RedlichKwongMFTP::omega_vc = 3.33333333333333E-01; - -RedlichKwongMFTP::RedlichKwongMFTP() : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); -} - -RedlichKwongMFTP::RedlichKwongMFTP(const std::string& infile, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - initThermoFile(infile, id_); -} - -RedlichKwongMFTP::RedlichKwongMFTP(XML_Node& phaseRefRoot, const std::string& id_) : - m_formTempParam(0), - m_b_current(0.0), - m_a_current(0.0), - NSolns_(0), - dpdV_(0.0), - dpdT_(0.0) -{ - fill_n(Vroot_, 3, 0.0); - importPhase(phaseRefRoot, this); -} - -void RedlichKwongMFTP::setSpeciesCoeffs(const std::string& species, - double a0, double a1, double b) -{ - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("RedlichKwongMFTP::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } - - if (a1 != 0.0) { - m_formTempParam = 1; // expression is temperature-dependent - } - - size_t counter = k + m_kk * k; - a_coeff_vec(0, counter) = a0; - a_coeff_vec(1, counter) = a1; - - // standard mixing rule for cross-species interaction term - for (size_t j = 0; j < m_kk; j++) { - if (k == j) { - continue; - } - - // a_coeff_vec(0) is initialized to NaN to mark uninitialized species - if (isnan(a_coeff_vec(0, j + m_kk * j))) { - // The diagonal element of the jth species has not yet been defined. - continue; - } else if (isnan(a_coeff_vec(0, j + m_kk * k))) { - // Only use the mixing rules if the off-diagonal element has not already been defined by a - // user-specified crossFluidParameters entry: - double a0kj = sqrt(a_coeff_vec(0, j + m_kk * j) * a0); - double a1kj = sqrt(a_coeff_vec(1, j + m_kk * j) * a1); - a_coeff_vec(0, j + m_kk * k) = a0kj; - a_coeff_vec(1, j + m_kk * k) = a1kj; - a_coeff_vec(0, k + m_kk * j) = a0kj; - a_coeff_vec(1, k + m_kk * j) = a1kj; - } - } - a_coeff_vec.getRow(0, a_vec_Curr_.data()); - b_vec_Curr_[k] = b; -} - -void RedlichKwongMFTP::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1) -{ - size_t ki = speciesIndex(species_i); - if (ki == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_i); - } - size_t kj = speciesIndex(species_j); - if (kj == npos) { - throw CanteraError("RedlichKwongMFTP::setBinaryCoeffs", - "Unknown species '{}'.", species_j); - } - - if (a1 != 0.0) { - m_formTempParam = 1; // expression is temperature-dependent - } - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - a_coeff_vec(0, counter1) = a_coeff_vec(0, counter2) = a0; - a_coeff_vec(1, counter1) = a_coeff_vec(1, counter2) = a1; - a_vec_Curr_[counter1] = a_vec_Curr_[counter2] = a0; -} - -// ------------Molar Thermodynamic Properties ------------------------- - -doublereal RedlichKwongMFTP::cp_mole() const -{ - _updateReferenceStateThermo(); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal vpb = mv + m_b_current; - pressureDerivatives(); - doublereal cpref = GasConstant * mean_X(m_cp0_R); - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - doublereal dHdT_V = (cpref + mv * dpdT_ - GasConstant - 1.0 / (2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv) * fac - +1.0/(m_b_current * sqt) * log(vpb/mv) * (-0.5 * dadt)); - return dHdT_V - (mv + TKelvin * dpdT_ / dpdV_) * dpdT_; -} - -doublereal RedlichKwongMFTP::cv_mole() const -{ - _updateReferenceStateThermo(); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal vpb = mv + m_b_current; - doublereal cvref = GasConstant * (mean_X(m_cp0_R) - 1.0); - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - return (cvref - 1.0/(2.0 * m_b_current * TKelvin * sqt) * log(vpb/mv)*fac - +1.0/(m_b_current * sqt) * log(vpb/mv)*(-0.5*dadt)); -} - -doublereal RedlichKwongMFTP::pressure() const -{ - _updateReferenceStateThermo(); - - // Get a copy of the private variables stored in the State object - doublereal T = temperature(); - double molarV = meanMolecularWeight() / density(); - double pp = GasConstant * T/(molarV - m_b_current) - m_a_current/(sqrt(T) * molarV * (molarV + m_b_current)); - return pp; -} - -doublereal RedlichKwongMFTP::standardConcentration(size_t k) const -{ - getStandardVolumes(m_tmpV.data()); - return 1.0 / m_tmpV[k]; -} - -void RedlichKwongMFTP::getActivityCoefficients(doublereal* ac) const -{ - doublereal mv = molarVolume(); - doublereal sqt = sqrt(temperature()); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - doublereal pres = pressure(); - - for (size_t k = 0; k < m_kk; k++) { - ac[k] = (- RT() * log(pres * mv / RT()) - + RT() * log(mv / vmb) - + RT() * b_vec_Curr_[k] / vmb - - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) - + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) - - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) - ); - } - for (size_t k = 0; k < m_kk; k++) { - ac[k] = exp(ac[k]/RT()); - } -} - -// ---- Partial Molar Properties of the Solution ----------------- - -void RedlichKwongMFTP::getChemPotentials_RT(doublereal* muRT) const -{ - getChemPotentials(muRT); - for (size_t k = 0; k < m_kk; k++) { - muRT[k] *= 1.0 / RT(); - } -} - -void RedlichKwongMFTP::getChemPotentials(doublereal* mu) const -{ - getGibbs_ref(mu); - for (size_t k = 0; k < m_kk; k++) { - double xx = std::max(SmallNumber, moleFraction(k)); - mu[k] += RT()*(log(xx)); - } - - doublereal mv = molarVolume(); - doublereal sqt = sqrt(temperature()); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - doublereal pres = pressure(); - doublereal refP = refPressure(); - - for (size_t k = 0; k < m_kk; k++) { - mu[k] += (RT() * log(pres/refP) - RT() * log(pres * mv / RT()) - + RT() * log(mv / vmb) - + RT() * b_vec_Curr_[k] / vmb - - 2.0 * m_pp[k] / (m_b_current * sqt) * log(vpb/mv) - + m_a_current * b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) - - m_a_current / (m_b_current * sqt) * (b_vec_Curr_[k]/vpb) - ); - } -} - -void RedlichKwongMFTP::getPartialMolarEnthalpies(doublereal* hbar) const -{ - // First we get the reference state contributions - getEnthalpy_RT_ref(hbar); - scale(hbar, hbar+m_kk, hbar, RT()); - - // We calculate dpdni_ - doublereal TKelvin = temperature(); - doublereal mv = molarVolume(); - doublereal sqt = sqrt(TKelvin); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - dpdni_[k] = RT()/vmb + RT() * b_vec_Curr_[k] / (vmb * vmb) - 2.0 * m_pp[k] / (sqt * mv * vpb) - + m_a_current * b_vec_Curr_[k]/(sqt * mv * vpb * vpb); - } - doublereal dadt = da_dt(); - doublereal fac = TKelvin * dadt - 3.0 * m_a_current / 2.0; - - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += 2.0 * moleFractions_[i] * TKelvin * a_coeff_vec(1,counter) - 3.0 * moleFractions_[i] * a_vec_Curr_[counter]; - } - } - - pressureDerivatives(); - doublereal fac2 = mv + TKelvin * dpdT_ / dpdV_; - for (size_t k = 0; k < m_kk; k++) { - double hE_v = (mv * dpdni_[k] - RT() - b_vec_Curr_[k]/ (m_b_current * m_b_current * sqt) * log(vpb/mv)*fac - + 1.0 / (m_b_current * sqt) * log(vpb/mv) * m_tmpV[k] - + b_vec_Curr_[k] / vpb / (m_b_current * sqt) * fac); - hbar[k] = hbar[k] + hE_v; - hbar[k] -= fac2 * dpdni_[k]; - } -} - -void RedlichKwongMFTP::getPartialMolarEntropies(doublereal* sbar) const -{ - getEntropy_R_ref(sbar); - scale(sbar, sbar+m_kk, sbar, GasConstant); - doublereal TKelvin = temperature(); - doublereal sqt = sqrt(TKelvin); - doublereal mv = molarVolume(); - doublereal refP = refPressure(); - - for (size_t k = 0; k < m_kk; k++) { - doublereal xx = std::max(SmallNumber, moleFraction(k)); - sbar[k] += GasConstant * (- log(xx)); - } - for (size_t k = 0; k < m_kk; k++) { - m_pp[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); - } - } - - doublereal dadt = da_dt(); - doublereal fac = dadt - m_a_current / (2.0 * TKelvin); - doublereal vmb = mv - m_b_current; - doublereal vpb = mv + m_b_current; - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -=(GasConstant * log(GasConstant * TKelvin / (refP * mv)) - + GasConstant - + GasConstant * log(mv/vmb) - + GasConstant * b_vec_Curr_[k]/vmb - + m_pp[k]/(m_b_current * TKelvin * sqt) * log(vpb/mv) - - 2.0 * m_tmpV[k]/(m_b_current * sqt) * log(vpb/mv) - + b_vec_Curr_[k] / (m_b_current * m_b_current * sqt) * log(vpb/mv) * fac - - 1.0 / (m_b_current * sqt) * b_vec_Curr_[k] / vpb * fac - ); - } - - pressureDerivatives(); - getPartialMolarVolumes(m_partialMolarVolumes.data()); - for (size_t k = 0; k < m_kk; k++) { - sbar[k] -= -m_partialMolarVolumes[k] * dpdT_; - } -} - -void RedlichKwongMFTP::getPartialMolarIntEnergies(doublereal* ubar) const -{ - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); -} - -void RedlichKwongMFTP::getPartialMolarCp(doublereal* cpbar) const -{ - getCp_R(cpbar); - scale(cpbar, cpbar+m_kk, cpbar, GasConstant); -} - -void RedlichKwongMFTP::getPartialMolarVolumes(doublereal* 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * a_vec_Curr_[counter]; - } - } - for (size_t k = 0; k < m_kk; k++) { - m_tmpV[k] = 0.0; - for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_tmpV[k] += moleFractions_[i] * a_coeff_vec(1,counter); - } - } - - doublereal sqt = sqrt(temperature()); - doublereal mv = molarVolume(); - doublereal vmb = mv - m_b_current; - doublereal vpb = mv + m_b_current; - for (size_t k = 0; k < m_kk; k++) { - doublereal num = (RT() + RT() * m_b_current/ vmb + RT() * b_vec_Curr_[k] / vmb - + RT() * m_b_current * b_vec_Curr_[k] /(vmb * vmb) - - 2.0 * m_pp[k] / (sqt * vpb) - + m_a_current * b_vec_Curr_[k] / (sqt * vpb * vpb) - ); - doublereal denom = (pressure() + RT() * m_b_current/(vmb * vmb) - m_a_current / (sqt * vpb * vpb) - ); - vbar[k] = num / denom; - } -} - -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); - if (added) { - a_vec_Curr_.resize(m_kk * m_kk, 0.0); - - // Initialize a_vec and b_vec to NaN, to screen for species with - // pureFluidParameters which are undefined in the input file: - b_vec_Curr_.push_back(NAN); - a_coeff_vec.resize(2, m_kk * m_kk, NAN); - - m_pp.push_back(0.0); - m_tmpV.push_back(0.0); - m_partialMolarVolumes.push_back(0.0); - dpdni_.push_back(0.0); - } - return added; -} - -void RedlichKwongMFTP::initThermoXML(XML_Node& phaseNode, const std::string& id) -{ - if (phaseNode.hasChild("thermo")) { - XML_Node& thermoNode = phaseNode.child("thermo"); - std::string model = thermoNode["model"]; - if (model != "RedlichKwong" && model != "RedlichKwongMFTP") { - throw CanteraError("RedlichKwongMFTP::initThermoXML", - "Unknown thermo model : " + model); - } - - // Reset any coefficients which may have been set using values from - // 'critProperties.xml' as part of non-XML initialization, so that - // off-diagonal elements can be correctly initialized - a_coeff_vec.data().assign(a_coeff_vec.data().size(), NAN); - - // Go get all of the coefficients and factors in the - // activityCoefficients XML block - if (thermoNode.hasChild("activityCoefficients")) { - XML_Node& acNode = thermoNode.child("activityCoefficients"); - - // Loop through the children and read out fluid parameters. Process - // all the pureFluidParameters, first: - // Loop back through the "activityCoefficients" children and process the - // crossFluidParameters in the XML tree: - for (size_t i = 0; i < acNode.nChildren(); i++) { - XML_Node& xmlACChild = acNode.child(i); - if (caseInsensitiveEquals(xmlACChild.name(), "purefluidparameters")) { - readXMLPureFluid(xmlACChild); - } else if (caseInsensitiveEquals(xmlACChild.name(), "crossfluidparameters")) { - readXMLCrossFluid(xmlACChild); - } - } - } - // If any species exist which have undefined pureFluidParameters, - // search the database in 'critProperties.xml' to find critical - // temperature and pressure to calculate a and b. - - // Loop through all species in the CTI file - size_t iSpecies = 0; - - for (size_t i = 0; i < m_kk; i++) { - string iName = speciesName(i); - - // Get the index of the species - iSpecies = speciesIndex(iName); - - // Check if a and b are already populated (only the diagonal elements of a). - size_t counter = iSpecies + m_kk * iSpecies; - - // If not, then search the database: - if (isnan(a_coeff_vec(0, counter))) { - - vector coeffArray; - - // Search the database for the species name and calculate - // coefficients a and b, from critical properties: - // coeffArray[0] = a0, coeffArray[1] = b; - coeffArray = getCoeff(iName); - - // Check if species was found in the database of critical properties, - // and assign the results - if (!isnan(coeffArray[0])) { - //Assuming no temperature dependence (i,e a1 = 0) - setSpeciesCoeffs(iName, coeffArray[0], 0.0, coeffArray[1]); - } - } - } - } - - MixtureFugacityTP::initThermoXML(phaseNode, id); -} - -void RedlichKwongMFTP::initThermo() -{ - for (auto& item : m_species) { - // Read a and b coefficients 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", "Redlich-Kwong"); - double a0 = 0, a1 = 0; - if (eos["a"].isScalar()) { - a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); - } else { - auto avec = eos["a"].asVector(2); - a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); - a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); - } - double b = eos.convert("b", "m^3/kmol"); - setSpeciesCoeffs(item.first, a0, a1, b); - 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, a1 = 0; - if (item2.second.isScalar()) { - a0 = units.convert(item2.second, "Pa*m^6/kmol^2*K^0.5"); - } else { - auto avec = item2.second.asVector(2); - a0 = units.convert(avec[0], "Pa*m^6/kmol^2*K^0.5"); - a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K^0.5"); - } - setBinaryCoeffs(item.first, item2.first, a0, a1); - } - } - } 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 (isnan(a_coeff_vec(0, k + m_kk * k))) { - // coeffs[0] = a0, coeffs[1] = b; - vector coeffs = getCoeff(item.first); - - // Check if species was found in the database of critical - // properties, and assign the results - if (!isnan(coeffs[0])) { - // Assuming no temperature dependence (i.e. a1 = 0) - setSpeciesCoeffs(item.first, coeffs[0], 0.0, coeffs[1]); - } - } - } - } -} - -vector RedlichKwongMFTP::getCoeff(const std::string& iName) -{ - vector_fp spCoeff{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 specie iName to current database species - // dbName: - if (iNameLower == dbName) { - // Read from database and calculate a and b coefficients - double vParams; - double T_crit=0.; - double P_crit=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("RedlichKwongMFTP::getCoeff", - "Critical Temperature must be positive "); - } - T_crit = vParams; - } else { - throw CanteraError("RedlichKwongMFTP::getCoeff", - "Critical Temperature not in database "); - } - 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("RedlichKwongMFTP::getCoeff", - "Critical Pressure must be positive "); - } - P_crit = vParams; - } else { - throw CanteraError("RedlichKwongMFTP::getCoeff", - "Critical Pressure not in database "); - } - - //Assuming no temperature dependence - spCoeff[0] = omega_a * pow(GasConstant, 2) * pow(T_crit, 2.5) / P_crit; //coeff a - spCoeff[1] = omega_b * GasConstant * T_crit / P_crit; // coeff b - break; - } - } - return spCoeff; -} - -void RedlichKwongMFTP::readXMLPureFluid(XML_Node& pureFluidParam) -{ - string xname = pureFluidParam.name(); - if (xname != "pureFluidParameters") { - throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", - "Incorrect name for processing this routine: " + xname); - } - - double a0 = 0.0; - double a1 = 0.0; - double b = 0.0; - for (size_t iChild = 0; iChild < pureFluidParam.nChildren(); iChild++) { - XML_Node& xmlChild = pureFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - string iModel = toLowerCopy(xmlChild.attrib("model")); - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - - if (iModel == "constant" && vParams.size() == 1) { - a0 = vParams[0]; - a1 = 0; - } else if (iModel == "linear_a" && vParams.size() == 2) { - a0 = vParams[0]; - a1 = vParams[1]; - } else { - throw CanteraError("RedlichKwongMFTP::readXMLPureFluid", - "unknown model or incorrect number of parameters"); - } - - } else if (nodeName == "b_coeff") { - b = getFloatCurrent(xmlChild, "toSI"); - } - } - setSpeciesCoeffs(pureFluidParam.attrib("species"), a0, a1, b); -} - -void RedlichKwongMFTP::readXMLCrossFluid(XML_Node& CrossFluidParam) -{ - string xname = CrossFluidParam.name(); - if (xname != "crossFluidParameters") { - throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", - "Incorrect name for processing this routine: " + xname); - } - - string iName = CrossFluidParam.attrib("species1"); - string jName = CrossFluidParam.attrib("species2"); - - size_t num = CrossFluidParam.nChildren(); - for (size_t iChild = 0; iChild < num; iChild++) { - XML_Node& xmlChild = CrossFluidParam.child(iChild); - string nodeName = toLowerCopy(xmlChild.name()); - - if (nodeName == "a_coeff") { - vector_fp vParams; - getFloatArray(xmlChild, vParams, true, "Pascal-m6/kmol2", "a_coeff"); - string iModel = toLowerCopy(xmlChild.attrib("model")); - if (iModel == "constant" && vParams.size() == 1) { - setBinaryCoeffs(iName, jName, vParams[0], 0.0); - } else if (iModel == "linear_a") { - setBinaryCoeffs(iName, jName, vParams[0], vParams[1]); - } else { - throw CanteraError("RedlichKwongMFTP::readXMLCrossFluid", - "unknown model ({}) or wrong number of parameters ({})", - iModel, vParams.size()); - } - } - } -} - -void RedlichKwongMFTP::setParametersFromXML(const XML_Node& thermoNode) -{ - MixtureFugacityTP::setParametersFromXML(thermoNode); - std::string model = thermoNode["model"]; -} - -doublereal RedlichKwongMFTP::sresid() const -{ - // note this agrees with tpx - doublereal rho = density(); - doublereal mmw = meanMolecularWeight(); - doublereal molarV = mmw / rho; - double hh = m_b_current / molarV; - doublereal zz = z(); - doublereal dadt = da_dt(); - doublereal T = temperature(); - doublereal sqT = sqrt(T); - doublereal fac = dadt - m_a_current / (2.0 * T); - double sresid_mol_R = log(zz*(1.0 - hh)) + log(1.0 + hh) * fac / (sqT * GasConstant * m_b_current); - return GasConstant * sresid_mol_R; -} - -doublereal RedlichKwongMFTP::hresid() const -{ - // note this agrees with tpx - doublereal rho = density(); - doublereal mmw = meanMolecularWeight(); - doublereal molarV = mmw / rho; - double hh = m_b_current / molarV; - doublereal zz = z(); - doublereal dadt = da_dt(); - doublereal T = temperature(); - doublereal sqT = sqrt(T); - doublereal fac = T * dadt - 3.0 *m_a_current / (2.0); - return GasConstant * T * (zz - 1.0) + fac * log(1.0 + hh) / (sqT * m_b_current); -} - -doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGuess) const -{ - double v = m_b_current * 1.1; - double atmp; - double btmp; - calculateAB(TKelvin, atmp, btmp); - doublereal pres = std::max(psatEst(TKelvin), presGuess); - double Vroot[3]; - bool foundLiq = false; - int m = 0; - while (m < 100 && !foundLiq) { - int nsol = CubicCall(TKelvin, pres, atmp, btmp, 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; -} - -doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, int phaseRequested, doublereal rhoguess) -{ - // It's necessary to set the temperature so that m_a_current is set correctly. - setTemperature(TKelvin); - double tcrit = critTemperature(); - double mmw = meanMolecularWeight(); - if (rhoguess == -1.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 - rhoguess = presPa * mmw / (GasConstant * TKelvin); - } - - - doublereal volguess = mmw / rhoguess; - NSolns_ = CubicCall(TKelvin, presPa, m_a_current, m_b_current, Vroot_); - - doublereal molarVolLast = Vroot_[0]; - if (NSolns_ >= 2) { - if (phaseRequested >= FLUID_LIQUID_0) { - molarVolLast = Vroot_[0]; - } else if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[2]; - } else { - if (volguess > Vroot_[1]) { - molarVolLast = Vroot_[2]; - } else { - molarVolLast = Vroot_[0]; - } - } - } else if (NSolns_ == 1) { - if (phaseRequested == FLUID_GAS || phaseRequested == FLUID_SUPERCRIT || phaseRequested == FLUID_UNDEFINED) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else if (NSolns_ == -1) { - if (phaseRequested >= FLUID_LIQUID_0 || phaseRequested == FLUID_UNDEFINED || phaseRequested == FLUID_SUPERCRIT) { - molarVolLast = Vroot_[0]; - } else if (TKelvin > tcrit) { - molarVolLast = Vroot_[0]; - } else { - return -2.0; - } - } else { - molarVolLast = Vroot_[0]; - return -1.0; - } - return mmw / molarVolLast; -} - -doublereal RedlichKwongMFTP::densSpinodalLiquid() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); - - doublereal mmw = meanMolecularWeight(); - return mmw / (0.5 * (vv.first + vv.second)); -} - -doublereal RedlichKwongMFTP::densSpinodalGas() const -{ - double Vroot[3]; - double T = temperature(); - int nsol = CubicCall(T, pressure(), m_a_current, m_b_current, 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); - - doublereal mmw = meanMolecularWeight(); - 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); - presCalc = GasConstant * TKelvin / (molarVol - m_b_current) - - m_a_current / (sqt * molarVol * (molarVol + m_b_current)); - - doublereal vpb = molarVol + m_b_current; - doublereal vmb = molarVol - m_b_current; - doublereal dpdv = (- GasConstant * TKelvin / (vmb * vmb) - + m_a_current * (2 * molarVol + m_b_current) / (sqt * molarVol * molarVol * vpb * vpb)); - return dpdv; -} - -void RedlichKwongMFTP::pressureDerivatives() const -{ - doublereal TKelvin = temperature(); - doublereal mv = molarVolume(); - doublereal pres; - - dpdV_ = dpdVCalc(TKelvin, mv, pres); - doublereal sqt = sqrt(TKelvin); - doublereal vpb = mv + m_b_current; - doublereal vmb = mv - m_b_current; - doublereal dadt = da_dt(); - doublereal fac = dadt - m_a_current/(2.0 * TKelvin); - dpdT_ = (GasConstant / vmb - fac / (sqt * mv * vpb)); -} - -void RedlichKwongMFTP::updateMixingExpressions() -{ - double temp = temperature(); - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - a_vec_Curr_[counter] = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; - } - } - } - - m_b_current = 0.0; - m_a_current = 0.0; - for (size_t i = 0; i < m_kk; i++) { - m_b_current += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - m_a_current += a_vec_Curr_[i * m_kk + j] * moleFractions_[i] * moleFractions_[j]; - } - } - if (isnan(m_b_current)) { - // One or more species do not have specified coefficients. - fmt::memory_buffer b; - for (size_t k = 0; k < m_kk; k++) { - if (isnan(b_vec_Curr_[k])) { - if (b.size() > 0) { - format_to(b, ", {}", speciesName(k)); - } else { - format_to(b, "{}", speciesName(k)); - } - } - } - throw CanteraError("RedlichKwongMFTP::updateMixingExpressions", - "Missing Redlich-Kwong coefficients for species: {}", to_string(b)); - } -} - -void RedlichKwongMFTP::calculateAB(doublereal temp, doublereal& aCalc, doublereal& bCalc) const -{ - bCalc = 0.0; - aCalc = 0.0; - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - doublereal a_vec_Curr = a_coeff_vec(0,counter) + a_coeff_vec(1,counter) * temp; - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - } - } - } else { - for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * b_vec_Curr_[i]; - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - doublereal a_vec_Curr = a_coeff_vec(0,counter); - aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - } - } - } -} - -doublereal RedlichKwongMFTP::da_dt() const -{ - doublereal dadT = 0.0; - if (m_formTempParam == 1) { - for (size_t i = 0; i < m_kk; i++) { - for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - dadT+= a_coeff_vec(1,counter) * moleFractions_[i] * moleFractions_[j]; - } - } - } - return dadT; -} - -void RedlichKwongMFTP::calcCriticalConditions(doublereal a, doublereal b, doublereal a0_coeff, doublereal aT_coeff, - doublereal& pc, doublereal& tc, doublereal& vc) const -{ - if (m_formTempParam != 0) { - a = a0_coeff; - } - if (b <= 0.0) { - tc = 1000000.; - pc = 1.0E13; - vc = omega_vc * GasConstant * tc / pc; - return; - } - if (a <= 0.0) { - tc = 0.0; - pc = 0.0; - vc = 2.0 * b; - return; - } - double tmp = a * omega_b / (b * omega_a * GasConstant); - double pp = 2./3.; - doublereal sqrttc, f, dfdt, deltatc; - - if (m_formTempParam == 0) { - tc = pow(tmp, pp); - } else { - tc = pow(tmp, pp); - for (int j = 0; j < 10; j++) { - sqrttc = sqrt(tc); - f = omega_a * b * GasConstant * tc * sqrttc / omega_b - aT_coeff * tc - a0_coeff; - dfdt = 1.5 * omega_a * b * GasConstant * sqrttc / omega_b - aT_coeff; - deltatc = - f / dfdt; - tc += deltatc; - } - if (deltatc > 0.1) { - throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); - } - } - - pc = omega_b * GasConstant * tc / b; - vc = omega_vc * GasConstant * tc / pc; -} - int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const { @@ -4385,4 +1087,7 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl } } +<<<<<<< HEAD >>>>>>> a27b14695... Fixing indentation and white spaces +======= +>>>>>>> 3ea126da1... Replacing line endings with LF instead of CRLF From 891ce6cb276614b221b6d2720990a91d113e2af3 Mon Sep 17 00:00:00 2001 From: gkogekar Date: Tue, 16 Jun 2020 14:47:13 -0600 Subject: [PATCH 044/110] Modified Peng-Robinson to test file to read phase from thermo-models.yaml --- test/data/co2_PR_example.yaml | 2 +- test/thermo/PengRobinson_Test.cpp | 632 +++++++++++++++--------------- 2 files changed, 317 insertions(+), 317 deletions(-) diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml index cb0f817671..f4cdb248ef 100644 --- a/test/data/co2_PR_example.yaml +++ b/test/data/co2_PR_example.yaml @@ -121,4 +121,4 @@ species: reactions: - equation: CO2 + H2 <=> CO + H2O # Reaction 1 - rate-constant: {A: 1.2E+3, b: 0, Ea: 0} + rate-constant: {A: 1.2E+3, b: 0, Ea: 0} diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 1c7e00a638..23d74552fc 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -1,316 +1,316 @@ -#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/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(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] = { - -457361607.71983075, - -457350560.54839599, - -457340699.25698096, - -457331842.5539279, - -457323844.32100844, - -457316585.4752928, - -457309967.99120748, - -457303910.44199038, - -457298344.62820804 - }; - - 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 p1[6] = { - 1.7084253549322079e+002, - 1.6543121742659784e+002, - 1.6066148681014121e+002, - 1.5639259178086871e+002, - 1.525268502259365e+002, - 1.4899341020317422e+002 - }; - // Phase change between temperatures 4 & 5: - const double p2[6] = { - 7.3025772038910179e+002, - 7.0307777665949902e+002, - 6.7179381832541878e+002, - 6.3389192023192868e+002, - 5.8250166044528487e+002, - 3.8226318921022073e+002 - }; - // Supercritical; no discontinuity in rho values: - const double p3[6] = { - 7.8626284773748239e+002, - 7.6743023689871097e+002, - 7.4747463917603955e+002, - 7.2620055080831412e+002, - 7.0335270498118734e+002, - 6.7859003092723128e+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(),p1[i],1.e-8); - - test_phase->setState_TP(temp, 7388370.); - EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); - - test_phase->setState_TP(temp, 9236712.5); - EXPECT_NEAR(test_phase->density(),p3[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 99.9%, balance H2). - * 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<10; i++) - { - const double temp = 296 + i * 2; - 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); - } - } -} - -}; +#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 p1[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 p2[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 p3[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(),p1[i],1.e-8); + + test_phase->setState_TP(temp, 7388370.); + EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); + + test_phase->setState_TP(temp, 9236712.5); + EXPECT_NEAR(test_phase->density(),p3[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 99.9%, balance H2). + * 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<10; i++) + { + const double temp = 296 + i * 2; + 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); + } + } +} + +}; From f8d7ee073c10cefdf580ec06b14e8ea5ffe7390e Mon Sep 17 00:00:00 2001 From: gkogekar Date: Tue, 16 Jun 2020 15:15:22 -0600 Subject: [PATCH 045/110] clearing the merge-conflict --- test/data/co2_PR_example.cti | 167 ----------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 test/data/co2_PR_example.cti diff --git a/test/data/co2_PR_example.cti b/test/data/co2_PR_example.cti deleted file mode 100644 index f43605b7ae..0000000000 --- a/test/data/co2_PR_example.cti +++ /dev/null @@ -1,167 +0,0 @@ - -units(length ="cm", time ="s", quantity ="mol", act_energy ="cal/mol") - - -PengRobinson(name ="carbondioxide", - elements ="C O H N", - species ="""CO2 H2O H2 CO CH4 O2 N2""", - activity_coefficients = (pureFluidParameters(species="CO2", a_coeff = [3.958134E+11, 0], b_coeff = 26.6275, acentric_factor = 0.228), - pureFluidParameters(species="H2O", a_coeff = [5.998873E+11, 0], b_coeff = 18.9714, acentric_factor = 0.344), - pureFluidParameters(species="H2", a_coeff = [2.668423E+10, 0], b_coeff = 16.5478, acentric_factor = -0.22), - pureFluidParameters(species="CO", a_coeff = [1.607164E+11, 0], b_coeff = 24.6549, acentric_factor = 0.049), - pureFluidParameters(species="CH4", a_coeff = [2.496344E+11, 0], b_coeff = 26.8028, acentric_factor = 0.01), - pureFluidParameters(species="O2", a_coeff = [1.497732E+11, 0], b_coeff = 19.8281, acentric_factor = 0.022), - pureFluidParameters(species="N2", a_coeff = [1.485031E+11, 0], b_coeff = 28.0810, acentric_factor = 0.04)), - transport ="Multi", - reactions ="all", - initial_state = state(temperature = 300.0, - pressure = OneAtm, - mole_fractions = 'CO2:0.99, H2:0.01')) - -#------------------------------------------------------------------------------- -# Species data -#------------------------------------------------------------------------------- - -species(name ="H2", - atoms ="H:2", - thermo = ( - NASA([200.00, 1000.00], [2.344331120E+00, 7.980520750E-03, - -1.947815100E-05, 2.015720940E-08, -7.376117610E-12, - -9.179351730E+02, 6.830102380E-01]), - NASA([1000.00, 3500.00], [3.337279200E+00, -4.940247310E-05, - 4.994567780E-07, -1.795663940E-10, 2.002553760E-14, - -9.501589220E+02, -3.205023310E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 2.92, - well_depth = 38.00, - polar = 0.79, - rot_relax = 280.00), - note ="TPIS78" - ) - -species(name ="CO", - atoms ="C:1 O:1", - thermo = ( - NASA([200.00, 1000.00], [3.579533470E+00, -6.103536800E-04, - 1.016814330E-06, 9.070058840E-10, -9.044244990E-13, - -1.434408600E+04, 3.508409280E+00]), - NASA([1000.00, 3500.00], [2.715185610E+00, 2.062527430E-03, - -9.988257710E-07, 2.300530080E-10, -2.036477160E-14, - -1.415187240E+04, 7.818687720E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.65, - well_depth = 98.10, - polar = 1.95, - rot_relax = 1.80), - note ="TPIS79" - ) - - -species(name ="N2", - atoms ="N:2", - thermo = ( - NASA([300.00, 1000.00], [3.298677000E+00, 1.408240400E-03, - -3.963222000E-06, 5.641515000E-09, -2.444854000E-12, - -1.020899900E+03, 3.950372000E+00]), - NASA([1000.00, 5000.00], [2.926640000E+00, 1.487976800E-03, - -5.684760000E-07, 1.009703800E-10, -6.753351000E-15, - -9.227977000E+02, 5.980528000E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.62, - well_depth = 97.53, - polar = 1.76, - rot_relax = 4.00), - note ="121286" - ) - -species(name ="O2", - atoms ="O:2", - thermo = ( - NASA([200.00, 1000.00], [3.782456360E+00, -2.996734160E-03, - 9.847302010E-06, -9.681295090E-09, 3.243728370E-12, - -1.063943560E+03, 3.657675730E+00]), - NASA([1000.00, 3500.00], [3.282537840E+00, 1.483087540E-03, - -7.579666690E-07, 2.094705550E-10, -2.167177940E-14, - -1.088457720E+03, 5.453231290E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.46, - well_depth = 107.40, - polar = 1.60, - rot_relax = 3.80), - note ="TPIS89" - ) - - -species(name ="CO2", - atoms ="C:1 O:2", - thermo = ( - NASA([200.00, 1000.00], [2.356773520E+00, 8.984596770E-03, - -7.123562690E-06, 2.459190220E-09, -1.436995480E-13, - -4.837196970E+04, 9.901052220E+00]), - NASA([1000.00, 3500.00], [3.857460290E+00, 4.414370260E-03, - -2.214814040E-06, 5.234901880E-10, -4.720841640E-14, - -4.875916600E+04, 2.271638060E+00]) - ), - transport = gas_transport( - geom ="linear", - diam = 3.76, - well_depth = 244.00, - polar = 2.65, - rot_relax = 2.10), - note ="L 7/88" - ) - -species(name ="CH4", - atoms ="C:1 H:4", - thermo = ( - NASA([200.00, 1000.00], [5.149876130E+00, -1.367097880E-02, - 4.918005990E-05, -4.847430260E-08, 1.666939560E-11, - -1.024664760E+04, -4.641303760E+00]), - NASA([1000.00, 3500.00], [7.485149500E-02, 1.339094670E-02, - -5.732858090E-06, 1.222925350E-09, -1.018152300E-13, - -9.468344590E+03, 1.843731800E+01]) - ), - transport = gas_transport( - geom ="nonlinear", - diam = 3.75, - well_depth = 141.40, - polar = 2.60, - rot_relax = 13.00), - note ="L 8/88" - ) - - -species(name ="H2O", - atoms ="H:2 O:1", - thermo = ( - NASA([200.00, 1000.00], [4.198640560E+00, -2.036434100E-03, - 6.520402110E-06, -5.487970620E-09, 1.771978170E-12, - -3.029372670E+04, -8.490322080E-01]), - NASA([1000.00, 3500.00], [3.033992490E+00, 2.176918040E-03, - -1.640725180E-07, -9.704198700E-11, 1.682009920E-14, - -3.000429710E+04, 4.966770100E+00]) - ), - transport = gas_transport( - geom ="nonlinear", - diam = 2.60, - well_depth = 572.40, - dipole = 1.85, - rot_relax = 4.00), - note ="L 8/89" - ) - -#——————————————————————————————————————————— -# Reaction data -#——————————————————————————————————————————— - -# Reaction 1 -reaction("CO2 + H2 <=> CO + H2O", [1.2E+3, 0, 0]) - From 4fe68b65e8ff451977858c88cbedda0b937007dd Mon Sep 17 00:00:00 2001 From: gkogekar Date: Tue, 23 Jun 2020 13:24:34 -0600 Subject: [PATCH 046/110] Modifying the explaination for solveCubic function to be more clear and explicit. --- include/cantera/thermo/MixtureFugacityTP.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 9cda61785e..0b4afbb9cd 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -520,8 +520,9 @@ class MixtureFugacityTP : public ThermoPhase //! Solve the cubic equation of state /*! * - * Returns the number of solutions found. If it only finds the liquid - * branch solution, it will return -1 or -2 instead of 1 or 2. + * 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, From fcab6f2d92c9e97b9baf1c5b6a12c6ab75c348d8 Mon Sep 17 00:00:00 2001 From: gkogekar Date: Tue, 23 Jun 2020 15:32:59 -0600 Subject: [PATCH 047/110] Adding a python test in interface/cython test suite --- include/cantera/thermo/PengRobinson.h | 2 +- interfaces/cython/cantera/test/test_thermo.py | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index a0e2ef7664..6bd2095080 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -292,7 +292,7 @@ class PengRobinson : public MixtureFugacityTP //! Value of b in the equation of state /*! - * m_b_current is a function of the temperature and the mole fractions. + * m_b_current is a function the mole fractions and species-specific b values. */ double m_b_current; diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index 0d2193ad78..a518cd3916 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -915,7 +915,44 @@ def test_nondimensional(self): class TestPengRobinsonPhase(utilities.CanteraTest): def setup(self): - self.gas = ct.Solution('co2_PR_exampl.yaml','CO2-PR') + self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') + + def test_setSV(self): + """ + Set state in terms of (s,v) + """ + self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') + self.gas.TPX = 450, 1e5, 'CO2:1.0' + s1, v1 = self.gas.SV + self.gas.SV = s1, 2 * v1 + + self.assertNear(self.gas.s, s1) + self.assertNear(self.gas.v, 2 * v1) + + def test_setHP(self): + """ + Set state in terms of (H,p) + """ + self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') + self.gas.TPX = 450, 1e5, 'CO2:1.0' + deltaH = 1.25e5 + h1, p1 = self.gas.HP + self.gas.HP = h1 - deltaH, None + + self.assertNear(self.gas.h, h1 - deltaH) + self.assertNear(self.gas.P, p1) + + def test_energy(self): + self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') + g = self.gas + mmw = g.mean_molecular_weight + self.assertNear(g.enthalpy_mass, g.enthalpy_mole / mmw) + self.assertNear(g.int_energy_mass, g.int_energy_mole / mmw) + self.assertNear(g.gibbs_mass, g.gibbs_mole / mmw) + self.assertNear(g.entropy_mass, g.entropy_mole / mmw) + + self.assertNear(g.cv_mass, g.cv_mole / mmw) + self.assertNear(g.cp_mass, g.cp_mole / mmw) class TestInterfacePhase(utilities.CanteraTest): def setUp(self): From ffaaa77180ced363269840a64f57096da3eba633 Mon Sep 17 00:00:00 2001 From: gkogekar Date: Tue, 23 Jun 2020 20:10:56 -0600 Subject: [PATCH 048/110] Fixing error in test_thermo.py --- interfaces/cython/cantera/test/test_thermo.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index a518cd3916..e6cc14ebca 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -914,14 +914,13 @@ def test_nondimensional(self): g.gibbs_mole / (R*g.T)) class TestPengRobinsonPhase(utilities.CanteraTest): - def setup(self): + def setUp(self): self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') def test_setSV(self): """ Set state in terms of (s,v) """ - self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') self.gas.TPX = 450, 1e5, 'CO2:1.0' s1, v1 = self.gas.SV self.gas.SV = s1, 2 * v1 @@ -933,7 +932,6 @@ def test_setHP(self): """ Set state in terms of (H,p) """ - self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') self.gas.TPX = 450, 1e5, 'CO2:1.0' deltaH = 1.25e5 h1, p1 = self.gas.HP @@ -943,7 +941,6 @@ def test_setHP(self): self.assertNear(self.gas.P, p1) def test_energy(self): - self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') g = self.gas mmw = g.mean_molecular_weight self.assertNear(g.enthalpy_mass, g.enthalpy_mole / mmw) From 5d67a47c962613dc7041aa912ffc7fb5422b4b90 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sat, 27 Jun 2020 10:12:45 -0600 Subject: [PATCH 049/110] Indentation changes in the PengRobinson.cpp Co-authored-by: Ray Speth --- src/thermo/PengRobinson.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 1b83b0dccc..54b02b9ec9 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -229,13 +229,13 @@ void PengRobinson::getChemPotentials(double* mu) const double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[k]; mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + RTkelvin * log(mv / vmb) + RTkelvin * m_b_vec_Curr[k] / vmb - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* m_b_vec_Curr[k] * mv/den2 + - m_aAlpha_current * m_b_vec_Curr[k] * mv/den2 ); } } From 59e13a8cb3f5f5690546c0e4d6df6b5165f7493e Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sat, 27 Jun 2020 10:13:48 -0600 Subject: [PATCH 050/110] Update include/cantera/thermo/PengRobinson.h Co-authored-by: Ray Speth --- include/cantera/thermo/PengRobinson.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 6bd2095080..bf97ec93a6 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -194,7 +194,7 @@ class PengRobinson : public MixtureFugacityTP * * @param iName Name of the species */ - virtual std::vector getCoeff(const std::string& iName); + virtual vector_fp getCoeff(const std::string& iName); //! Set the pure fluid interaction parameters for a species /*! From 8562bb77f6aaa6827e3e2d2c0ab008f84b9526fd Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 11:31:06 -0600 Subject: [PATCH 051/110] Removing unused parameter m_formTempParam --- include/cantera/thermo/PengRobinson.h | 7 ------- src/thermo/PengRobinson.cpp | 2 -- 2 files changed, 9 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index bf97ec93a6..34cec5c4b8 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -283,13 +283,6 @@ class PengRobinson : public MixtureFugacityTP int solveCubic(double T, double pres, double a, double b, double aAlpha, double Vroot[3]) const; protected: - //! Form of the temperature parameterization - /*! - * - 0 = There is no temperature parameterization of a - * - 1 = The \f$a_{ij} \f$ parameter is a linear function of the temperature - */ - int m_formTempParam; - //! Value of b in the equation of state /*! * m_b_current is a function the mole fractions and species-specific b values. diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 54b02b9ec9..701c845ad4 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -24,7 +24,6 @@ const double PengRobinson::omega_b = 7.77960739038885E-02; const double PengRobinson::omega_vc = 3.07401308698703833E-01; PengRobinson::PengRobinson() : - m_formTempParam(0), m_b_current(0.0), m_a_current(0.0), m_aAlpha_current(0.0), @@ -36,7 +35,6 @@ PengRobinson::PengRobinson() : } PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : - m_formTempParam(0), m_b_current(0.0), m_a_current(0.0), m_aAlpha_current(0.0), From fdfcc31a1e6b5e5bf6f2d70ade5fb7e440b37e7e Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 11:44:18 -0600 Subject: [PATCH 052/110] Removed the changes for P-R EoS model in ctml_writer.py, since the CTI/XML option is not implemented for Peng-Robinson EoS model. --- interfaces/cython/cantera/ctml_writer.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/interfaces/cython/cantera/ctml_writer.py b/interfaces/cython/cantera/ctml_writer.py index 7873218d50..5e750088db 100644 --- a/interfaces/cython/cantera/ctml_writer.py +++ b/interfaces/cython/cantera/ctml_writer.py @@ -842,13 +842,12 @@ class pureFluidParameters(activityCoefficients): """ """ - def __init__(self, species = None, a_coeff = [], b_coeff = 0, acentric_factor = None): + def __init__(self, species = None, a_coeff = [], b_coeff = 0): """ """ self._species = species self._acoeff = a_coeff self._bcoeff = b_coeff - self._w_ac = acentric_factor def build(self,a): f= a.addChild("pureFluidParameters") @@ -860,9 +859,6 @@ def build(self,a): s = '%.10g\n' % self._bcoeff bc = f.addChild("b_coeff", s) bc["units"] = _ulen+'3/'+_umol - if self._w_ac: - s = '%.10g\n' % self._w_ac - cc = f.addChild("acentric_factor", s) class crossFluidParameters(activityCoefficients): From bfaae6d070ed4ef4e1b0f4034e895a9a959a1a66 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 13:05:58 -0600 Subject: [PATCH 053/110] Replacing doublereal with double in entropy_mole function declaration --- include/cantera/thermo/MixtureFugacityTP.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 0b4afbb9cd..88002a009f 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -124,7 +124,7 @@ class MixtureFugacityTP : public ThermoPhase //! @{ virtual double enthalpy_mole() const; - virtual doublereal entropy_mole() const; + virtual double entropy_mole() const; //@} /// @name Partial Molar Properties of the Solution From 71f124e01893bfe4dcd6ea772646d8515479dd48 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 13:34:11 -0600 Subject: [PATCH 054/110] Removing documentation for getChemPotential function --- include/cantera/thermo/PengRobinson.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 34cec5c4b8..c98ae7fae4 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -126,17 +126,6 @@ class PengRobinson : public MixtureFugacityTP /// @name Partial Molar Properties of the Solution //@{ - //! Get the array of non-dimensional species chemical potentials. - //! These are partial molar Gibbs free energies. - /*! - * \f$ \mu_k / \hat R T \f$. - * Units: unitless - * - * - * @param mu Output vector of non-dimensional species chemical potentials - * Length: m_kk. - */ - virtual void getChemPotentials(double* mu) const; virtual void getPartialMolarEnthalpies(double* hbar) const; virtual void getPartialMolarEntropies(double* sbar) const; From 2e46979b6597aeedfdbf53b8f821b7a17caa862b Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 13:41:44 -0600 Subject: [PATCH 055/110] Correcting the documentation for 'setSpeciesCoeff' and 'setBinaryCoeff' --- include/cantera/thermo/PengRobinson.h | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index c98ae7fae4..fa0d0cde00 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -187,20 +187,13 @@ class PengRobinson : public MixtureFugacityTP //! Set the pure fluid interaction parameters for a species /*! - * The *a* parameter for species *i* in the Peng-Robinson model is assumed - * to be a linear function of temperature: - * \f[ a = a_0 + a_1 T \f] * * @param species Name of the species - * @param a0 constant term in the expression for the "a" parameter - * of the specified species [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the expression for the - * "a" parameter of the specified species [Pa-m^6/kmol^2/K] + * @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 alpha dimensionless function of T_r and \omega - * @param omega acentric factor + * @param w acentric factor */ - void setSpeciesCoeffs(const std::string& species, double a0, double b, + void setSpeciesCoeffs(const std::string& species, double a, double b, double w); //! Set values for the interaction parameter between two species @@ -215,11 +208,10 @@ class PengRobinson : public MixtureFugacityTP * @param species_i Name of one species * @param species_j Name of the other species * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] - * @param a1 temperature-proportional term in the "a" expression - * [Pa-m^6/kmol^2/K] + * @param alpha dimensionless function of T_r and \omega */ void setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double a1); + const std::string& species_j, double a0, double alpha); protected: // Special functions inherited from MixtureFugacityTP From 61d1fe45e5cbe2b72ed88d7dc433c3d918b0a9f5 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 13:44:34 -0600 Subject: [PATCH 056/110] Removing duplication of a temp array declaration from the child classes --- include/cantera/thermo/PengRobinson.h | 3 --- include/cantera/thermo/RedlichKwongMFTP.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index fa0d0cde00..eb07a3e0ed 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -290,9 +290,6 @@ class PengRobinson : public MixtureFugacityTP double m_Vroot[3]; - //! Temporary storage - length = m_kk. - mutable vector_fp m_tmpV; - // Partial molar volumes of the species mutable vector_fp m_partialMolarVolumes; diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index 67b42cafce..b9ac71cd42 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -291,9 +291,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; From 0e81424b8b03c9f9987bbc3ee06596b81242dc09 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 13:50:25 -0600 Subject: [PATCH 057/110] Replacing tab character with spaces --- src/thermo/MixtureFugacityTP.cpp | 72 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 4d0ce9e0a9..4e5a782067 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -57,7 +57,7 @@ double MixtureFugacityTP::entropy_mole() const { _updateReferenceStateThermo(); double s_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - - std::log(pressure()/refPressure())); + - std::log(pressure()/refPressure())); double s_nonideal = sresid(); return s_ideal + s_nonideal; } @@ -490,34 +490,34 @@ doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, void MixtureFugacityTP::setToEquilState(const doublereal* mu_RT) { - double tmp, tmp2; - _updateReferenceStateThermo(); - getGibbs_RT_ref(m_tmpV.data()); - - // Within the method, we protect against inf results if the exponent is too - // high. - // - // If it is too low, we set the partial pressure to zero. This capability is - // needed by the elemental potential method. - doublereal pres = 0.0; - double m_p0 = refPressure(); - for (size_t k = 0; k < m_kk; k++) { - tmp = -m_tmpV[k] + mu_RT[k]; - if (tmp < -600.) { - m_pp[k] = 0.0; - } - else if (tmp > 500.0) { - tmp2 = tmp / 500.; - tmp2 *= tmp2; - m_pp[k] = m_p0 * exp(500.) * tmp2; - } - else { - m_pp[k] = m_p0 * exp(tmp); - } - pres += m_pp[k]; - } - // set state - setState_PX(pres, &m_pp[0]); + double tmp, tmp2; + _updateReferenceStateThermo(); + getGibbs_RT_ref(m_tmpV.data()); + + // Within the method, we protect against inf results if the exponent is too + // high. + // + // If it is too low, we set the partial pressure to zero. This capability is + // needed by the elemental potential method. + doublereal pres = 0.0; + double m_p0 = refPressure(); + for (size_t k = 0; k < m_kk; k++) { + tmp = -m_tmpV[k] + mu_RT[k]; + if (tmp < -600.) { + m_pp[k] = 0.0; + } + else if (tmp > 500.0) { + tmp2 = tmp / 500.; + tmp2 *= tmp2; + m_pp[k] = m_p0 * exp(500.) * tmp2; + } + else { + m_pp[k] = m_p0 * exp(tmp); + } + pres += m_pp[k]; + } + // set state + setState_PX(pres, &m_pp[0]); } void MixtureFugacityTP::updateMixingExpressions() @@ -901,7 +901,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, nSolnValues = 1; } - double tmp; + double tmp; // One real root -> have to determine whether gas or liquid is the root if (disc > 0.0) { double tmpD = sqrt(disc); @@ -1007,8 +1007,8 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, } if (nSolnValues == 1) { - // Determine the phase of the single root. - // nSolnValues = 1 represents the gas phase by default. + // Determine the phase of the single root. + // nSolnValues = 1 represents the gas phase by default. if (T > tc) { if (Vroot[0] < vc) { // Supercritical phase @@ -1016,15 +1016,15 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, } } else { if (Vroot[0] < xN) { - //Liquid phase + //Liquid phase nSolnValues = -1; } } } else { - // Determine if we have two distinct roots or three equal roots - // nSolnValues = 2 represents 2 equal roots by default. + // 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) + //If delta > 0, we have two distinct roots (and one repeated root) nSolnValues = -2; } } From b57740aee93abcc4bcbb67835e74c169c57c9f30 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 13:52:57 -0600 Subject: [PATCH 058/110] Correcting the documentation for 'setBinaryCoeff' function --- include/cantera/thermo/PengRobinson.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index eb07a3e0ed..634b921b6a 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -202,16 +202,14 @@ class PengRobinson : public MixtureFugacityTP * 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] * - * This function overrides the defaults with the specified parameters: - * \f[ a_{ij} = a_{ij, 0} + a_{ij, 1} T \f] * * @param species_i Name of one species * @param species_j Name of the other species - * @param a0 constant term in the "a" expression [Pa-m^6/kmol^2] + * @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 a0, double alpha); + const std::string& species_j, double a, double alpha); protected: // Special functions inherited from MixtureFugacityTP From f2cd14d90a663880579601f979db36c7a19c0ea0 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:01:35 -0600 Subject: [PATCH 059/110] Correcting documentation in PengRobinson.h file --- include/cantera/thermo/PengRobinson.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 634b921b6a..464d29438b 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -237,22 +237,22 @@ class PengRobinson : public MixtureFugacityTP */ void pressureDerivatives() const; - //! Update the a and b parameters + //! Update the a, b and alpha 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. + * 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 and the b parameters given the temperature + //! 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 temp Temperature (TKelvin) * @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; From 7d1ad7e236fbafc5f42893bef03bf7675065c9a4 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:16:37 -0600 Subject: [PATCH 060/110] Added dimensions of certain vectors in the documentation --- include/cantera/thermo/PengRobinson.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 464d29438b..a0cf620c00 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -276,13 +276,15 @@ class PengRobinson : public MixtureFugacityTP double m_aAlpha_current; // Vectors required to store a_coeff, b_coeff, alpha, kappa and other values for every species. Length = m_kk - vector_fp m_a_vec_Curr; vector_fp m_b_vec_Curr; - vector_fp m_aAlpha_vec_Curr; - vector_fp m_alpha_vec_Curr; vector_fp m_kappa_vec; mutable vector_fp m_dalphadT_vec_Curr; mutable vector_fp m_d2alphadT2; + vector_fp m_alpha_vec_Curr; + + //Matrices for Binary coefficients a_{i,j} and {a*alpha}_{i.j} are saved in a vector form. Length = m_kk * m_kk + vector_fp m_a_vec_Curr; + vector_fp m_aAlpha_vec_Curr; int m_NSolns; From 137deb6046990fea5f796ace4b32dec72853163c Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:19:16 -0600 Subject: [PATCH 061/110] Changed the function name 'pressureDerivatives' to 'calculatePressureDerivatives' --- include/cantera/thermo/PengRobinson.h | 2 +- src/thermo/PengRobinson.cpp | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index a0cf620c00..ed64ccbf25 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -235,7 +235,7 @@ class PengRobinson : public MixtureFugacityTP /*! * These are stored internally. */ - void pressureDerivatives() const; + void calculatePressureDerivatives() const; //! Update the a, b and alpha parameters /*! diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 701c845ad4..cce1482500 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -132,7 +132,7 @@ double PengRobinson::cp_mole() const double mv = molarVolume(); double vpb = mv + (1 + M_SQRT2)*m_b_current; double vmb = mv + (1 - M_SQRT2)*m_b_current; - pressureDerivatives(); + 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_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); @@ -143,7 +143,7 @@ double PengRobinson::cv_mole() const { _updateReferenceStateThermo(); double T = temperature(); - pressureDerivatives(); + calculatePressureDerivatives(); return (cp_mole() + T* m_dpdT* m_dpdT / m_dpdV); } @@ -222,12 +222,11 @@ void PengRobinson::getChemPotentials(double* mu) const } double pres = pressure(); double refP = refPressure(); - double num = 0; double den = 2 * M_SQRT2 * m_b_current * m_b_current; double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[k]; + double num = 2 * m_b_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[k]; mu[k] += (RTkelvin * log(pres/refP) - RTkelvin * log(pres * mv / RTkelvin) + RTkelvin * log(mv / vmb) @@ -270,7 +269,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const double daAlphadT = daAlpha_dT(); double fac = T * daAlphadT - m_aAlpha_current; - pressureDerivatives(); + calculatePressureDerivatives(); double fac2 = mv + T * m_dpdT / m_dpdV; double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; for (size_t k = 0; k < m_kk; k++) { @@ -319,7 +318,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const - coeff1* log(vpb2 / vmb2) / den1 - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; } - pressureDerivatives(); + calculatePressureDerivatives(); getPartialMolarVolumes(m_partialMolarVolumes.data()); for (size_t k = 0; k < m_kk; k++) { sbar[k] -= m_partialMolarVolumes[k] * m_dpdT; @@ -741,7 +740,7 @@ double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const return dpdv; } -void PengRobinson::pressureDerivatives() const +void PengRobinson::calculatePressureDerivatives() const { double T = temperature(); double mv = molarVolume(); From 157291d6901250ffb9088ad1b77ebd227f416407 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:31:35 -0600 Subject: [PATCH 062/110] Moved 'calculateAlpha' inside the 'setSpeciesCoeff' function --- src/thermo/PengRobinson.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index cce1482500..674de6346b 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -74,6 +74,9 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double b, double w) { + //Calculate alpha for the given species + calculateAlpha(species, a, b, w); + size_t k = speciesIndex(species); if (k == npos) { throw CanteraError("PengRobinson::setSpeciesCoeffs", @@ -529,7 +532,6 @@ void PengRobinson::initThermo() // unitless acentric factor: double w = eos.getDouble("w_ac",NAN); - calculateAlpha(item.first, a0, b, w); setSpeciesCoeffs(item.first, a0, b, w); if (eos.hasKey("binary-a")) { AnyMap& binary_a = eos["binary-a"].as(); @@ -559,7 +561,6 @@ void PengRobinson::initThermo() // properties, and assign the results if (!isnan(coeffs[0])) { // Assuming no temperature dependence (i.e. a1 = 0) - calculateAlpha(item.first, coeffs[0], coeffs[1], coeffs[2]); setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); } } From 46f31156f9e5c2810d70eb20b90032c81bec3d81 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:40:46 -0600 Subject: [PATCH 063/110] Changing InitThermo() to remove unused parameter a1 --- src/thermo/PengRobinson.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 674de6346b..a58f54c5e5 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -514,19 +514,14 @@ void PengRobinson::initThermo() // 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"].as(); - if (eos.getString("model", "") != "Peng-Robinson") { - throw InputFileError("PengRobinson::initThermo", eos, - "Expected species equation of state to be 'Peng-Robinson', " - "but got '{}' instead", eos.getString("model", "")); - } + auto eos = item.second->input["equation-of-state"].getMapWhere( + "model", "Peng-Robinson"); double a0 = 0, a1 = 0; if (eos["a"].isScalar()) { a0 = eos.convert("a", "Pa*m^6/kmol^2"); } else { auto avec = eos["a"].asVector(2); a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); - a1 = eos.units().convert(avec[1], "Pa*m^6/kmol^2/K"); } double b = eos.convert("b", "m^3/kmol"); // unitless acentric factor: From 21bd4e01c31118089346dd58abf582aaf86a80b7 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:45:38 -0600 Subject: [PATCH 064/110] Removing 'PengRobinsonMFTP' alias --- src/thermo/ThermoFactory.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index d7fd639590..fd733d906d 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -91,7 +91,6 @@ ThermoFactory::ThermoFactory() reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); reg("PengRobinson", []() { return new PengRobinson(); }); - addAlias("PengRobinson", "PengRobinsonMFTP"); addAlias("PengRobinson", "Peng-Robinson"); } From 131fa19b536f9105974f06777c352122e965b029 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 5 Oct 2020 14:46:12 -0600 Subject: [PATCH 065/110] Cleaning up unnessesary comments. --- src/thermo/PengRobinson.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index a58f54c5e5..747ca4d1ee 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -549,7 +549,6 @@ void PengRobinson::initThermo() // to find critical temperature and pressure to calculate a and b. size_t k = speciesIndex(item.first); if (m_a_vec_Curr[k + m_kk * k] == 0.0) { - // coeffs[0] = a0, coeffs[1] = b; vector coeffs = getCoeff(item.first); // Check if species was found in the database of critical From 5e83651932ad7be114dfb1f5d86cc0d59c9bdbc7 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 27 Oct 2020 16:18:14 -0600 Subject: [PATCH 066/110] Removing 'setToEquil' function from mixtureFugacityTP class --- include/cantera/thermo/MixtureFugacityTP.h | 1 - src/thermo/MixtureFugacityTP.cpp | 32 ---------------------- 2 files changed, 33 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 88002a009f..72526aac62 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -490,7 +490,6 @@ class MixtureFugacityTP : public ThermoPhase * @return The saturation pressure at the given temperature */ virtual doublereal satPressure(doublereal TKelvin); - virtual void setToEquilState(const doublereal* lambda_RT); virtual void getActivityConcentrations(double* c) const; protected: diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 4e5a782067..7efc0dadc1 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -488,38 +488,6 @@ doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, return densBase; } -void MixtureFugacityTP::setToEquilState(const doublereal* mu_RT) -{ - double tmp, tmp2; - _updateReferenceStateThermo(); - getGibbs_RT_ref(m_tmpV.data()); - - // Within the method, we protect against inf results if the exponent is too - // high. - // - // If it is too low, we set the partial pressure to zero. This capability is - // needed by the elemental potential method. - doublereal pres = 0.0; - double m_p0 = refPressure(); - for (size_t k = 0; k < m_kk; k++) { - tmp = -m_tmpV[k] + mu_RT[k]; - if (tmp < -600.) { - m_pp[k] = 0.0; - } - else if (tmp > 500.0) { - tmp2 = tmp / 500.; - tmp2 *= tmp2; - m_pp[k] = m_p0 * exp(500.) * tmp2; - } - else { - m_pp[k] = m_p0 * exp(tmp); - } - pres += m_pp[k]; - } - // set state - setState_PX(pres, &m_pp[0]); -} - void MixtureFugacityTP::updateMixingExpressions() { } From 91cc8dffe430c825a500d9ce961c1bcbfbdb7ada Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 27 Oct 2020 16:47:54 -0600 Subject: [PATCH 067/110] Changing operating conditions in the pengRobinson test case --- interfaces/cython/cantera/test/test_thermo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index e6cc14ebca..cee3115ee4 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -921,7 +921,7 @@ def test_setSV(self): """ Set state in terms of (s,v) """ - self.gas.TPX = 450, 1e5, 'CO2:1.0' + self.gas.TPX = 350, 20e5, 'CO2:2.0, H2O:1.0' s1, v1 = self.gas.SV self.gas.SV = s1, 2 * v1 @@ -932,7 +932,7 @@ def test_setHP(self): """ Set state in terms of (H,p) """ - self.gas.TPX = 450, 1e5, 'CO2:1.0' + self.gas.TPX = 350, 20e5, 'CO2:2.0, H2O:1.0' deltaH = 1.25e5 h1, p1 = self.gas.HP self.gas.HP = h1 - deltaH, None From 0687e6d21447a48df0d631cb32d500cd4ce65b6f Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 27 Oct 2020 16:56:56 -0600 Subject: [PATCH 068/110] Removing redundant test from PengRobinson class tests --- interfaces/cython/cantera/test/test_thermo.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index cee3115ee4..cc9109cc46 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -940,17 +940,6 @@ def test_setHP(self): self.assertNear(self.gas.h, h1 - deltaH) self.assertNear(self.gas.P, p1) - def test_energy(self): - g = self.gas - mmw = g.mean_molecular_weight - self.assertNear(g.enthalpy_mass, g.enthalpy_mole / mmw) - self.assertNear(g.int_energy_mass, g.int_energy_mole / mmw) - self.assertNear(g.gibbs_mass, g.gibbs_mole / mmw) - self.assertNear(g.entropy_mass, g.entropy_mole / mmw) - - self.assertNear(g.cv_mass, g.cv_mole / mmw) - self.assertNear(g.cp_mass, g.cp_mole / mmw) - class TestInterfacePhase(utilities.CanteraTest): def setUp(self): self.gas = ct.Solution('diamond.xml', 'gas') From 5b2e0f8e5d670810a63c39d28c3c559682f5456b Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 27 Oct 2020 17:00:34 -0600 Subject: [PATCH 069/110] Correcting the commented documentation --- test/thermo/PengRobinson_Test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 23d74552fc..e8e0983d17 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -197,7 +197,7 @@ 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 99.9%, balance H2). + /* 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 From 76bb6bfc08f4793678b601696ea37e73d1f7c727 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 28 Oct 2020 12:29:01 -0600 Subject: [PATCH 070/110] Implementation of partial internal energies --- src/thermo/PengRobinson.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 747ca4d1ee..ed3c550ec6 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -330,8 +330,14 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const void PengRobinson::getPartialMolarIntEnergies(double* ubar) const { - getIntEnergy_RT(ubar); - scale(ubar, ubar+m_kk, ubar, RT()); + // u_i = h_i - p*v_i + double* vbar; + double p = pressure(); + getPartialMolarEnthalpies(ubar); + getPartialMolarVolumes(vbar); + for (size_t k = 0; k < m_kk; k++) { + ubar[k] = ubar[k] - p*vbar[k]; + } } void PengRobinson::getPartialMolarCp(double* cpbar) const From 5d3e6dff4aa692f0710a5543986ebe31ec840912 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 28 Oct 2020 14:47:01 -0600 Subject: [PATCH 071/110] Cleaning up functions to calculate critical properties in Redlich-Kwong and Peng-Robinson classes 1. Declared calcCriticalConditions() as virtual in MixtureFugacityTP class. 2. Moved all functions to calculate critical properties to the parent (MixtureFugacityTP) class. --- include/cantera/thermo/MixtureFugacityTP.h | 10 ++ include/cantera/thermo/PengRobinson.h | 12 +-- include/cantera/thermo/RedlichKwongMFTP.h | 13 +-- src/thermo/MixtureFugacityTP.cpp | 41 +++++++++ src/thermo/PengRobinson.cpp | 63 ++++--------- src/thermo/RedlichKwongMFTP.cpp | 101 +++------------------ 6 files changed, 86 insertions(+), 154 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 72526aac62..226fea9f01 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -515,6 +515,16 @@ class MixtureFugacityTP : public ThermoPhase virtual void updateMixingExpressions(); + //@} + /// @name Critical State Properties. + //@{ + + 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 /*! diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index ed64ccbf25..9cbd94040f 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -146,15 +146,7 @@ class PengRobinson : public MixtureFugacityTP */ virtual void calculateAlpha(const std::string& species, double a, double b, double w); - //@} - /// @name Critical State Properties. - //@{ - - virtual double critTemperature() const; - virtual double critPressure() const; - virtual double critVolume() const; - virtual double critCompressibility() const; - virtual double critDensity() const; + virtual double speciesCritTemperature(double a, double b) const; public: @@ -256,7 +248,7 @@ class PengRobinson : public MixtureFugacityTP */ void calculateAB(double& aCalc, double& bCalc, double& aAlpha) const; - void calcCriticalConditions(double a, double b,double& pc, double& tc, double& vc) 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, diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index b9ac71cd42..3c18431ac5 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -121,16 +121,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 @@ -253,8 +243,7 @@ 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; //! Prepare variables and call the function to solve the cubic equation of state int CubicCall(double T, double pres, double a, double b, double Vroot[3]) const; diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 7efc0dadc1..969b17d213 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -800,6 +800,47 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const } } +double MixtureFugacityTP::critTemperature() const +{ + 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 diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index ed3c550ec6..b7dd7f925e 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -331,7 +331,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const void PengRobinson::getPartialMolarIntEnergies(double* ubar) const { // u_i = h_i - p*v_i - double* vbar; + double* vbar = 0; double p = pressure(); getPartialMolarEnthalpies(ubar); getPartialMolarVolumes(vbar); @@ -379,47 +379,19 @@ void PengRobinson::getPartialMolarVolumes(double* vbar) const double PengRobinson::speciesCritTemperature(double a, double b) const { - double pc, tc, vc; - calcCriticalConditions(a, b, pc, tc, vc); - return tc; -} - -double PengRobinson::critTemperature() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); + double tc; + if (b <= 0.0) { + tc = 1000000.; + return tc; + } + if (a <= 0.0) { + tc = 0.0; + return tc; + } + tc = a * omega_b / (b * omega_a * GasConstant); return tc; } -double PengRobinson::critPressure() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc; -} - -double PengRobinson::critVolume() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return vc; -} - -double PengRobinson::critCompressibility() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - return pc*vc/tc/GasConstant; -} - -double PengRobinson::critDensity() const -{ - double pc, tc, vc; - calcCriticalConditions(m_a_current, m_b_current, pc, tc, vc); - double mmw = meanMolecularWeight(); - return mmw / vc; -} - bool PengRobinson::addSpecies(shared_ptr spec) { bool added = MixtureFugacityTP::addSpecies(spec); @@ -858,23 +830,22 @@ double PengRobinson::d2aAlpha_dT2() const return d2aAlphadT2; } -void PengRobinson::calcCriticalConditions(double a, double b, - double& pc, double& tc, double& vc) const +void PengRobinson::calcCriticalConditions(double& pc, double& tc, double& vc) const { - if (b <= 0.0) { + if (m_b_current <= 0.0) { tc = 1000000.; pc = 1.0E13; vc = omega_vc * GasConstant * tc / pc; return; } - if (a <= 0.0) { + if (m_a_current <= 0.0) { tc = 0.0; pc = 0.0; - vc = 2.0 * b; + vc = 2.0 * m_b_current; return; } - tc = a * omega_b / (b * omega_a * GasConstant); - pc = omega_b * GasConstant * tc / b; + tc = m_a_current * omega_b / (m_b_current * omega_a * GasConstant); + pc = omega_b * GasConstant * tc / m_b_current; vc = omega_vc * GasConstant * tc / pc; } diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 81750c5d88..84b0266a40 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -377,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); @@ -1023,11 +942,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 Date: Thu, 29 Oct 2020 10:04:05 -0600 Subject: [PATCH 072/110] Defined m_a_vec_curr as the array2D type to simplify index calculations --- include/cantera/thermo/PengRobinson.h | 6 +-- src/thermo/PengRobinson.cpp | 72 ++++++++++----------------- 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 9cbd94040f..5611df07ec 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -274,9 +274,9 @@ class PengRobinson : public MixtureFugacityTP mutable vector_fp m_d2alphadT2; vector_fp m_alpha_vec_Curr; - //Matrices for Binary coefficients a_{i,j} and {a*alpha}_{i.j} are saved in a vector form. Length = m_kk * m_kk - vector_fp m_a_vec_Curr; - vector_fp m_aAlpha_vec_Curr; + //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_vec_Curr; + Array2D m_aAlpha_vec_Curr; int m_NSolns; diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index b7dd7f925e..9cdf30146e 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -83,24 +83,24 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double "Unknown species '{}'.", species); } size_t counter = k + m_kk * k; - m_a_vec_Curr[counter] = a; + m_a_vec_Curr(k,k) = a; // we store this locally because it is used below to calculate a_Alpha: double aAlpha_k = a*m_alpha_vec_Curr[k]; - m_aAlpha_vec_Curr[counter] = aAlpha_k; + m_aAlpha_vec_Curr(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_vec_Curr[j + m_kk * j] * a); + double a0kj = sqrt(m_a_vec_Curr(j,j) * a); double aAlpha_j = a*m_alpha_vec_Curr[j]; double a_Alpha = sqrt(aAlpha_j*aAlpha_k); - if (m_a_vec_Curr[j + m_kk * k] == 0) { - m_a_vec_Curr[j + m_kk * k] = a0kj; - m_aAlpha_vec_Curr[j + m_kk * k] = a_Alpha; - m_a_vec_Curr[k + m_kk * j] = a0kj; - m_aAlpha_vec_Curr[k + m_kk * j] = a_Alpha; + if (m_a_vec_Curr(j, k) == 0) { + m_a_vec_Curr(j, k) = a0kj; + m_aAlpha_vec_Curr(j, k) = a_Alpha; + m_a_vec_Curr(k, j) = a0kj; + m_aAlpha_vec_Curr(k, j) = a_Alpha; } } m_b_vec_Curr[k] = b; @@ -120,10 +120,8 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, "Unknown species '{}'.", species_j); } - size_t counter1 = ki + m_kk * kj; - size_t counter2 = kj + m_kk * ki; - m_a_vec_Curr[counter1] = m_a_vec_Curr[counter2] = a0; - m_aAlpha_vec_Curr[counter1] = m_aAlpha_vec_Curr[counter2] = a0*alpha; + m_a_vec_Curr(ki, kj) = m_a_vec_Curr(kj, ki) = a0; + m_aAlpha_vec_Curr(ki, kj) = m_aAlpha_vec_Curr(kj, ki) = a0*alpha; } // ------------Molar Thermodynamic Properties ------------------------- @@ -179,8 +177,7 @@ void PengRobinson::getActivityCoefficients(double* ac) const for (size_t k = 0; k < m_kk; k++) { m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i); } } double num = 0; @@ -219,8 +216,7 @@ void PengRobinson::getChemPotentials(double* mu) const for (size_t k = 0; k < m_kk; k++) { m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i); } } double pres = pressure(); @@ -256,8 +252,7 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const for (size_t k = 0; k < m_kk; k++) { m_pp[k] = 0.0; for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i); } } @@ -304,9 +299,8 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const m_pp[k] = 0.0; m_tmpV[k] = 0; for (size_t i = 0; i < m_kk; i++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; - m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter] *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i); + m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i) *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); } m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; } @@ -351,8 +345,7 @@ 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++) { - size_t counter = k + m_kk*i; - m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr[counter]; + m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i); } } @@ -396,11 +389,9 @@ bool PengRobinson::addSpecies(shared_ptr spec) { bool added = MixtureFugacityTP::addSpecies(spec); if (added) { - m_a_vec_Curr.resize(m_kk * m_kk, 0.0); + m_a_vec_Curr.resize(m_kk, m_kk, 0.0); m_b_vec_Curr.push_back(0.0); - m_a_vec_Curr.push_back(0.0); - m_aAlpha_vec_Curr.resize(m_kk * m_kk, 0.0); - m_aAlpha_vec_Curr.push_back(0.0); + m_aAlpha_vec_Curr.resize(m_kk, m_kk, 0.0); m_kappa_vec.push_back(0.0); m_alpha_vec_Curr.push_back(0.0); @@ -526,7 +517,7 @@ void PengRobinson::initThermo() // 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_vec_Curr[k + m_kk * k] == 0.0) { + if (m_a_vec_Curr(k, k) == 0.0) { vector coeffs = getCoeff(item.first); // Check if species was found in the database of critical @@ -733,8 +724,7 @@ void PengRobinson::updateMixingExpressions() // Update indiviual alpha for (size_t j = 0; j < m_kk; j++) { - size_t counter = j * m_kk + j; - double critTemp_j = speciesCritTemperature(m_a_vec_Curr[counter],m_b_vec_Curr[j]); + double critTemp_j = speciesCritTemperature(m_a_vec_Curr(j,j), m_b_vec_Curr[j]); sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); m_alpha_vec_Curr[j] = sqt_alpha*sqt_alpha; } @@ -742,8 +732,7 @@ void PengRobinson::updateMixingExpressions() //Update aAlpha_i, j for (size_t i = 0; i < m_kk; i++) { for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - m_aAlpha_vec_Curr[counter] = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr[counter]; + m_aAlpha_vec_Curr(i, j) = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr(i,j); } } @@ -762,10 +751,9 @@ void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) for (size_t i = 0; i < m_kk; i++) { bCalc += moleFractions_[i] * m_b_vec_Curr[i]; for (size_t j = 0; j < m_kk; j++) { - size_t counter = i * m_kk + j; - double a_vec_Curr = m_a_vec_Curr[counter]; + double a_vec_Curr = m_a_vec_Curr(i, j); aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - aAlphaCalc += m_aAlpha_vec_Curr[counter] * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += m_aAlpha_vec_Curr(i, j) * moleFractions_[i] * moleFractions_[j]; } } } @@ -775,9 +763,8 @@ double PengRobinson::daAlpha_dT() const double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; double coeff1, coeff2; for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; // Calculate first derivative of alpha for individual species - Tc = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[i]); + Tc = speciesCritTemperature(m_a_vec_Curr(i,i), m_b_vec_Curr[i]); sqtTr = sqrt(temperature() / Tc); //we need species critical temperature coeff1 = 1 / (Tc*sqtTr); coeff2 = sqtTr - 1; @@ -786,10 +773,8 @@ double PengRobinson::daAlpha_dT() const } //Calculate mixture derivative for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j * m_kk + j; - temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j])); + temp = 0.5 * sqrt((m_a_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j])); daAlphadT += moleFractions_[i] * moleFractions_[j] * temp * (m_dalphadT_vec_Curr[j] * m_alpha_vec_Curr[i] + m_dalphadT_vec_Curr[i] * m_alpha_vec_Curr[j]); } @@ -801,8 +786,7 @@ double PengRobinson::d2aAlpha_dT2() const { double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; for (size_t i = 0; i < m_kk; i++) { - size_t counter = i + m_kk * i; - double Tcrit_i = speciesCritTemperature(m_a_vec_Curr[counter], m_b_vec_Curr[i]); + double Tcrit_i = speciesCritTemperature(m_a_vec_Curr(i, i), m_b_vec_Curr[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; @@ -814,13 +798,11 @@ double PengRobinson::d2aAlpha_dT2() const //Calculate mixture derivative for (size_t i = 0; i < m_kk; i++) { - size_t counter1 = i + m_kk * i; alphai = m_alpha_vec_Curr[i]; for (size_t j = 0; j < m_kk; j++) { - size_t counter2 = j + m_kk * j; alphaj = m_alpha_vec_Curr[j]; alphaij = alphai * alphaj; - temp = 0.5 * sqrt((m_a_vec_Curr[counter1] * m_a_vec_Curr[counter2]) / (alphaij)); + temp = 0.5 * sqrt((m_a_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (alphaij)); num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); fac1 = -(0.5 / alphaij)*num*num; fac2 = alphaj * m_d2alphadT2[i] + alphai *m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; From 1e142e475cda64bdf57dd7a1af038a5565406db5 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 29 Oct 2020 11:36:13 -0600 Subject: [PATCH 073/110] Adding missing P-R coefficients for CO2 in co2_PR_example.yaml file --- test/data/co2_PR_example.yaml | 17 +++++++++++------ test/thermo/cubicSolver_Test.cpp | 6 +++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml index f4cdb248ef..fddc44d2e0 100644 --- a/test/data/co2_PR_example.yaml +++ b/test/data/co2_PR_example.yaml @@ -21,6 +21,11 @@ species: - [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 + w_ac: 0.228 - name: H2O composition: {H: 2, O: 1} thermo: @@ -34,7 +39,7 @@ species: note: L8/89 equation-of-state: model: Peng-Robinson - a: [5.998873E+11, 0] + a: 5.998873E+11 b: 18.9714 w_ac: 0.344 - name: H2 @@ -50,7 +55,7 @@ species: note: TPIS78 equation-of-state: model: Peng-Robinson - a: [2.668423E+10, 0] + a: 2.668423E+10 b: 16.5478 w_ac: -0.22 - name: CO @@ -66,7 +71,7 @@ species: note: TPIS79 equation-of-state: model: Peng-Robinson - a: [1.607164E+11, 0] + a: 1.607164E+11 b: 24.6549 w_ac: 0.049 - name: CH4 @@ -82,7 +87,7 @@ species: note: L8/88 equation-of-state: model: Peng-Robinson - a: [2.496344E+11, 0] + a: 2.496344E+11 b: 26.8028 w_ac: 0.01 - name: O2 @@ -98,7 +103,7 @@ species: note: TPIS89 equation-of-state: model: Peng-Robinson - a: [1.497732E+11, 0] + a: 1.497732E+11 b: 19.8281 w_ac: 0.022 - name: N2 @@ -114,7 +119,7 @@ species: note: '121286' equation-of-state: model: Peng-Robinson - a: [1.485031E+11, 0] + a: 1.485031E+11 b: 28.0810 w_ac: 0.04 diff --git a/test/thermo/cubicSolver_Test.cpp b/test/thermo/cubicSolver_Test.cpp index bd86ec4894..af777f8f38 100644 --- a/test/thermo/cubicSolver_Test.cpp +++ b/test/thermo/cubicSolver_Test.cpp @@ -44,9 +44,9 @@ TEST_F(cubicSolver_Test, solve_cubic) double Vroot[3]; const double expected_result[3] = { - 24.810673386441, - 0.0658516543054, - 0.0728171459395 + 24.810677442023493, + 0.065859466219402446, + 0.072816004568591469 }; //Vapor phase -> nSolnValues = 1 From d620b1a6cd8f586b83a0227ccffd6b6e5adb1ae3 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 29 Oct 2020 14:27:48 -0600 Subject: [PATCH 074/110] Redefined variable type for coefficient `a' in P-R EoS as scalar from vector --- src/thermo/PengRobinson.cpp | 9 +-------- test/data/thermo-models.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 9cdf30146e..174fc694f0 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -488,10 +488,7 @@ void PengRobinson::initThermo() double a0 = 0, a1 = 0; if (eos["a"].isScalar()) { a0 = eos.convert("a", "Pa*m^6/kmol^2"); - } else { - auto avec = eos["a"].asVector(2); - a0 = eos.units().convert(avec[0], "Pa*m^6/kmol^2"); - } + } double b = eos.convert("b", "m^3/kmol"); // unitless acentric factor: double w = eos.getDouble("w_ac",NAN); @@ -504,10 +501,6 @@ void PengRobinson::initThermo() double a0 = 0, a1 = 0; if (item2.second.isScalar()) { a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); - } else { - auto avec = item2.second.asVector(2); - a0 = units.convert(avec[0], "Pa*m^6/kmol^2"); - a1 = units.convert(avec[1], "Pa*m^6/kmol^2/K"); } setBinaryCoeffs(item.first, item2.first, a0, a1); } diff --git a/test/data/thermo-models.yaml b/test/data/thermo-models.yaml index 2e341b7444..5965e41b22 100644 --- a/test/data/thermo-models.yaml +++ b/test/data/thermo-models.yaml @@ -578,7 +578,7 @@ pr-species: equation-of-state: model: Peng-Robinson units: {length: cm, quantity: mol} - a: [2.668423E+10, 0] + a: 2.668423E+10 b: 16.5478 w_ac: -0.22 - name: H2O @@ -594,11 +594,11 @@ pr-species: equation-of-state: model: Peng-Robinson units: {length: cm, quantity: mol} - a: [5.998873E+11, 0] + a: 5.998873E+11 b: 18.9714 w_ac: 0.344 binary-a: - H2: [4 bar*cm^6/mol^2, 40 bar*cm^6/mol^2*K^-1] + H2: 4 bar*cm^6/mol^2 CO2: 7.897e7 bar*cm^6/mol^2 - name: CO2 composition: {C: 1, O: 2} @@ -613,7 +613,7 @@ pr-species: equation-of-state: model: Peng-Robinson units: {length: cm, quantity: mol} - a: [3.958134E+11, 0] + a: 3.958134E+11 b: 26.6275 w_ac: 0.228 From 6823f680a8584bab0010a7e05e0fd1e1d4d86616 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 29 Oct 2020 14:44:40 -0600 Subject: [PATCH 075/110] Reading w_ac (acentric factor) with a more useful message that will point to the source of the error accurately --- src/thermo/PengRobinson.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 174fc694f0..82e7ce235c 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -56,10 +56,7 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b // Calculate value of kappa (independent of temperature) // w is an acentric factor of species and must be specified in the CTI file - if (isnan(w)) { - throw CanteraError("PengRobinson::calculateAlpha", - "No acentric factor loaded."); - } else if (w <= 0.491) { + if (w <= 0.491) { m_kappa_vec[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { m_kappa_vec[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; @@ -491,7 +488,7 @@ void PengRobinson::initThermo() } double b = eos.convert("b", "m^3/kmol"); // unitless acentric factor: - double w = eos.getDouble("w_ac",NAN); + double w = eos["w_ac"].asDouble(); setSpeciesCoeffs(item.first, a0, b, w); if (eos.hasKey("binary-a")) { From 6a7f2b2eebfcc527cced0f166a568abccb1a90a2 Mon Sep 17 00:00:00 2001 From: decaluwe Date: Mon, 12 Oct 2020 16:00:18 -0600 Subject: [PATCH 076/110] Correcting CRLF Line endings. --- test/thermo/thermoFromYaml.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index 951bd95cda..b07f241f62 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD #include "gtest/gtest.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/thermo/Elements.h" @@ -449,6 +450,8 @@ TEST(ThermoFromYaml, BinarySolutionTabulatedThermo) EXPECT_NEAR(thermo->gibbs_mass(), -87066.246182649265, 1e-9); } ======= +======= +>>>>>>> 886e956d3... Correcting CRLF Line endings. #include "gtest/gtest.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/thermo/Elements.h" @@ -938,4 +941,7 @@ TEST(ThermoFromYaml, DeprecatedPhase) // for the test suite. EXPECT_THROW(newThermo("gri30.yaml", "gri30_mix"), CanteraError); } +<<<<<<< HEAD >>>>>>> ea38f153e... Modifying pengRobinson test from thermoFromYAML +======= +>>>>>>> 886e956d3... Correcting CRLF Line endings. From 666f285962ff4eafcbdad1a59a0e53d4b0cc2e53 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 1 Nov 2020 15:46:30 -0700 Subject: [PATCH 077/110] Rebasing with Cantera/main --- src/thermo/RedlichKwongMFTP.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 84b0266a40..f927fc68e3 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -1015,8 +1015,4 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl return nSolnValues; } -} -<<<<<<< HEAD ->>>>>>> a27b14695... Fixing indentation and white spaces -======= ->>>>>>> 3ea126da1... Replacing line endings with LF instead of CRLF +} \ No newline at end of file From 404a5f70cf7617f0b8270e23ccbb90f4bd457153 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Mon, 23 Nov 2020 12:33:53 -0700 Subject: [PATCH 078/110] Removing redundant _updateStateThermo() call from thermodynamic calculations and also rebasing with cantera/main --- src/thermo/MixtureFugacityTP.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 969b17d213..a2bdf45629 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -46,7 +46,6 @@ int MixtureFugacityTP::reportSolnBranchActual() const // ---- Molar Thermodynamic Properties --------------------------- double MixtureFugacityTP::enthalpy_mole() const { - _updateReferenceStateThermo(); double h_ideal = RT() * mean_X(m_h0_RT); double h_nonideal = hresid(); return h_ideal + h_nonideal; @@ -55,7 +54,6 @@ double MixtureFugacityTP::enthalpy_mole() const double MixtureFugacityTP::entropy_mole() const { - _updateReferenceStateThermo(); double s_ideal = GasConstant * (mean_X(m_s0_R) - sum_xlogx() - std::log(pressure()/refPressure())); double s_nonideal = sresid(); @@ -76,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++) { @@ -91,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++) { @@ -101,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++) { @@ -111,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++) { @@ -121,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; @@ -130,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(); } @@ -146,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); } @@ -164,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(); } From 4ea8da6f6147ba53666f3b648bde27f8355524e8 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 19 Mar 2021 15:15:14 -0600 Subject: [PATCH 079/110] Combining file-base and default constructors for PengRobinson class --- include/cantera/thermo/PengRobinson.h | 16 ++++++---------- src/thermo/PengRobinson.cpp | 12 +----------- src/thermo/ThermoFactory.cpp | 1 - 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 5611df07ec..95c7f9f0e2 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -19,21 +19,17 @@ namespace Cantera class PengRobinson : public MixtureFugacityTP { public: - //! @name Constructors and Duplicators - //! @{ - - //! Base constructor. - PengRobinson(); //! Construct and initialize a PengRobinson object directly from an //! input file /*! - * @param infile Name of the input file containing the phase YAML data - * to set up the object - * @param id ID of the phase in the input file. Defaults to the empty - * string. + * @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. */ - PengRobinson(const std::string& infile, const std::string& id=""); + explicit PengRobinson(const std::string& infile="", + const std::string& id=""); virtual std::string type() const { return "PengRobinson"; diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 82e7ce235c..a140804474 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -5,6 +5,7 @@ #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" @@ -23,17 +24,6 @@ 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() : - m_b_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), - m_NSolns(0), - m_dpdV(0.0), - m_dpdT(0.0) -{ - fill_n(m_Vroot, 3, 0.0); -} - PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : m_b_current(0.0), m_a_current(0.0), diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index fd733d906d..14830757b0 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -24,7 +24,6 @@ #include "cantera/thermo/PureFluidPhase.h" #include "cantera/thermo/RedlichKwongMFTP.h" #include "cantera/thermo/PengRobinson.h" -#include "cantera/thermo/ConstDensityThermo.h" #include "cantera/thermo/SurfPhase.h" #include "cantera/thermo/EdgePhase.h" #include "cantera/thermo/MetalPhase.h" From 95084285c81f09613c799e14b898876fa10a0126 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 19 Mar 2021 16:07:21 -0600 Subject: [PATCH 080/110] Fixing error for Peng-Robinson alias in ThermoFactory.cpp --- src/thermo/ThermoFactory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 14830757b0..279ee8daf5 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -89,8 +89,8 @@ ThermoFactory::ThermoFactory() addAlias("liquid-water-IAPWS95", "PureLiquidWater"); reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); - reg("PengRobinson", []() { return new PengRobinson(); }); - addAlias("PengRobinson", "Peng-Robinson"); + reg("Peng-Robinson", []() { return new PengRobinson(); }); + addAlias("Peng-Robinson", "PengRobinson"); } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) From 5e3fb4c8a586f102a371757526ebaeffe5f3338f Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 19 Mar 2021 16:28:56 -0600 Subject: [PATCH 081/110] Cleaning up 'thermoFromYaml.cpp' --- test/thermo/thermoFromYaml.cpp | 500 +-------------------------------- 1 file changed, 1 insertion(+), 499 deletions(-) diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index b07f241f62..652bff2b6b 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -1,5 +1,3 @@ -<<<<<<< HEAD -<<<<<<< HEAD #include "gtest/gtest.h" #include "cantera/thermo/ThermoFactory.h" #include "cantera/thermo/Elements.h" @@ -448,500 +446,4 @@ TEST(ThermoFromYaml, BinarySolutionTabulatedThermo) EXPECT_NEAR(thermo->entropy_mass(), 90.443481807823474, 1e-12); thermo->setMoleFractionsByName("Li[anode]: 0.55, V[anode]: 0.45"); EXPECT_NEAR(thermo->gibbs_mass(), -87066.246182649265, 1e-9); -} -======= -======= ->>>>>>> 886e956d3... Correcting CRLF Line endings. -#include "gtest/gtest.h" -#include "cantera/thermo/ThermoFactory.h" -#include "cantera/thermo/Elements.h" -#include "cantera/thermo/MolalityVPSSTP.h" -#include "cantera/thermo/IdealGasPhase.h" -#include "cantera/thermo/SurfPhase.h" -#include - -using namespace Cantera; - -namespace { - -shared_ptr newThermo(const std::string& fileName, - const std::string& phaseName) -{ - return shared_ptr(newPhase(fileName, phaseName)); -} - -} // namespace - -TEST(ThermoFromYaml, simpleIdealGas) -{ - IdealGasPhase thermo("ideal-gas.yaml", "simple"); - EXPECT_EQ(thermo.nSpecies(), (size_t) 3); - EXPECT_DOUBLE_EQ(thermo.density(), 7.0318220966379288); - EXPECT_DOUBLE_EQ(thermo.cp_mass(), 1037.7546065787594); -} - -TEST(ThermoFromYaml, failDuplicateSpecies) -{ - EXPECT_THROW(newThermo("ideal-gas.yaml", "duplicate-species"), CanteraError); -} - -TEST(ThermoFromYaml, elementOverride) -{ - auto thermo = newThermo("ideal-gas.yaml", "element-override"); - EXPECT_EQ(thermo->nElements(), (size_t) 3); - EXPECT_DOUBLE_EQ(thermo->atomicWeight(0), getElementWeight("N")); - EXPECT_DOUBLE_EQ(thermo->atomicWeight(1), getElementWeight("O")); - EXPECT_DOUBLE_EQ(thermo->atomicWeight(2), 36); -} - -TEST(ThermoFromYaml, elementFromDifferentFile) -{ - auto thermo = newThermo("ideal-gas.yaml", "element-remote"); - EXPECT_EQ(thermo->nElements(), (size_t) 3); - EXPECT_DOUBLE_EQ(thermo->atomicWeight(0), getElementWeight("N")); - EXPECT_DOUBLE_EQ(thermo->atomicWeight(1), getElementWeight("O")); - EXPECT_DOUBLE_EQ(thermo->atomicWeight(2), 38); -} - -TEST(ThermoFromYaml, speciesFromDifferentFile) -{ - IdealGasPhase thermo("ideal-gas.yaml", "species-remote"); - EXPECT_EQ(thermo.nElements(), (size_t) 2); - EXPECT_EQ(thermo.nSpecies(), (size_t) 4); - EXPECT_EQ(thermo.species(0)->composition["O"], 2); - EXPECT_EQ(thermo.species(3)->composition["O"], 1); - EXPECT_EQ(thermo.species(2)->name, "NO2"); - EXPECT_DOUBLE_EQ(thermo.moleFraction(3), 0.3); -} - -TEST(ThermoFromYaml, speciesAll) -{ - auto thermo = newThermo("ideal-gas.yaml", "species-all"); - EXPECT_EQ(thermo->nElements(), (size_t) 3); - EXPECT_EQ(thermo->nSpecies(), (size_t) 6); - EXPECT_EQ(thermo->species(1)->name, "NO"); - EXPECT_EQ(thermo->species(2)->name, "N2"); -} - -TEST(ThermoFromYaml, StoichSubstance1) -{ - auto thermo = newThermo("thermo-models.yaml", "NaCl(s)"); - EXPECT_EQ(thermo->type(), "StoichSubstance"); - EXPECT_EQ(thermo->nSpecies(), (size_t) 1); - EXPECT_EQ(thermo->nElements(), (size_t) 2); - EXPECT_DOUBLE_EQ(thermo->density(), 2165.0); - EXPECT_DOUBLE_EQ(thermo->cp_mass(), 864.88371960557095); // Regression test based on XML -} - -TEST(ThermoFromYaml, StoichSubstance2) -{ - auto thermo = newThermo("thermo-models.yaml", "KCl(s)"); - EXPECT_EQ(thermo->type(), "StoichSubstance"); - EXPECT_EQ(thermo->nSpecies(), (size_t) 1); - EXPECT_EQ(thermo->nElements(), (size_t) 2); - EXPECT_NEAR(thermo->density(), 1980, 0.1); -} - -TEST(ThermoFromYaml, SurfPhase) -{ - auto thermo = newThermo("surface-phases.yaml", "Pt-surf"); - EXPECT_EQ(thermo->type(), "Surf"); - EXPECT_EQ(thermo->nSpecies(), (size_t) 3); - auto surf = std::dynamic_pointer_cast(thermo); - EXPECT_DOUBLE_EQ(surf->siteDensity(), 2.7063e-8); - vector_fp cov(surf->nSpecies()); - surf->getCoverages(cov.data()); - EXPECT_DOUBLE_EQ(cov[surf->speciesIndex("Pt(s)")], 0.5); - EXPECT_DOUBLE_EQ(cov[surf->speciesIndex("H(s)")], 0.4); -} - -TEST(ThermoFromYaml, EdgePhase) -{ - auto thermo = newThermo("surface-phases.yaml", "TPB"); - EXPECT_EQ(thermo->type(), "Edge"); - EXPECT_EQ(thermo->nSpecies(), (size_t) 1); - auto edge = std::dynamic_pointer_cast(thermo); - EXPECT_DOUBLE_EQ(edge->siteDensity(), 5e-18); -} - -TEST(ThermoFromYaml, WaterSSTP) -{ - auto thermo = newThermo("thermo-models.yaml", "liquid-water"); - EXPECT_EQ(thermo->nSpecies(), (size_t) 1); - thermo->setState_TP(350, 2*OneAtm); - // Regression tests based on XML - EXPECT_NEAR(thermo->density(), 973.7736331, 1e-6); - EXPECT_NEAR(thermo->enthalpy_mass(), -15649685.52296013, 1e-6); -} - -//! @todo Remove after Cantera 2.5 - class FixedChemPotSSTP is deprecated -TEST(ThermoFromYaml, FixedChemPot) -{ - suppress_deprecation_warnings(); - auto thermo = newThermo("thermo-models.yaml", "Li-fixed"); - EXPECT_EQ(thermo->nSpecies(), (size_t) 1); - double mu; - thermo->getChemPotentials(&mu); - EXPECT_DOUBLE_EQ(mu, -2.3e7); - make_deprecation_warnings_fatal(); -} - -TEST(ThermoFromYaml, Margules) -{ - auto thermo = newThermo("thermo-models.yaml", "molten-salt-Margules"); - EXPECT_EQ(thermo->type(), "Margules"); - - // Regression test based on LiKCl_liquid.xml - EXPECT_NEAR(thermo->density(), 2041.9831422315351, 1e-9); - EXPECT_NEAR(thermo->gibbs_mass(), -9683614.0890585743, 1e-5); - EXPECT_NEAR(thermo->cp_mole(), 67478.48085733457, 1e-8); -} - -TEST(ThermoFromYaml, IdealMolalSoln) -{ - auto thermo = newThermo("thermo-models.yaml", "ideal-molal-aqueous"); - EXPECT_EQ(thermo->type(), "IdealMolalSoln"); - - EXPECT_NEAR(thermo->enthalpy_mole(), 0.013282, 1e-6); - EXPECT_NEAR(thermo->gibbs_mole(), -3.8986e7, 1e3); - EXPECT_NEAR(thermo->density(), 12.058, 1e-3); -} - -TEST(ThermoFromYaml, DebyeHuckel_bdot_ak) -{ - auto thermo = newThermo("thermo-models.yaml", "debye-huckel-B-dot-ak"); - - // Regression test based on XML input file - EXPECT_EQ(thermo->type(), "DebyeHuckel"); - EXPECT_NEAR(thermo->density(), 60.296, 1e-2); - EXPECT_NEAR(thermo->cp_mass(), 1.58216e5, 1e0); - EXPECT_NEAR(thermo->entropy_mass(), 4.04233e3, 1e-2); - - vector_fp actcoeff(thermo->nSpecies()); - vector_fp mu_ss(thermo->nSpecies()); - auto& molphase = dynamic_cast(*thermo); - molphase.getMolalityActivityCoefficients(actcoeff.data()); - thermo->getStandardChemPotentials(mu_ss.data()); - double act_ref[] = {0.849231, 1.18392, 0.990068, 1.69245, 1.09349, 1.0}; - double mu_ss_ref[] = {-3.06816e+08, -2.57956e+08, -1.84117e+08, 0.0, - -2.26855e+08, -4.3292e+08}; - for (size_t k = 0; k < thermo->nSpecies(); k++) { - EXPECT_NEAR(actcoeff[k], act_ref[k], 1e-5); - EXPECT_NEAR(mu_ss[k], mu_ss_ref[k], 1e3); - } -} - -TEST(ThermoFromYaml, DebyeHuckel_beta_ij) -{ - auto thermo = newThermo("thermo-models.yaml", "debye-huckel-beta_ij"); - - // Regression test based on XML input file - EXPECT_EQ(thermo->type(), "DebyeHuckel"); - EXPECT_NEAR(thermo->density(), 122.262, 1e-3); - EXPECT_NEAR(thermo->cp_mass(), 81263.5, 1e-1); - EXPECT_NEAR(thermo->entropy_mass(), 4022.35, 1e-2); - - vector_fp actcoeff(thermo->nSpecies()); - vector_fp mu_ss(thermo->nSpecies()); - auto& molphase = dynamic_cast(*thermo); - molphase.getMolalityActivityCoefficients(actcoeff.data()); - thermo->getStandardChemPotentials(mu_ss.data()); - double act_ref[] = {0.959912, 1.16955, 1.16955, 2.40275, 0.681552, 1.0}; - double mu_ss_ref[] = {-3.06816e+08, -2.57956e+08, -1.84117e+08, 0, - -2.26855e+08, -4.3292e+08}; - for (size_t k = 0; k < thermo->nSpecies(); k++) { - EXPECT_NEAR(actcoeff[k], act_ref[k], 1e-5); - EXPECT_NEAR(mu_ss[k], mu_ss_ref[k], 1e3); - } -} - -TEST(ThermoFromYaml, IonsFromNeutral) -{ - auto thermo = newThermo("thermo-models.yaml", "ions-from-neutral-molecule"); - ASSERT_EQ((int) thermo->nSpecies(), 2); - vector_fp mu(thermo->nSpecies()); - thermo->getChemPotentials(mu.data()); - - // Values for regression testing only -- same as "fromScratch" test - EXPECT_NEAR(thermo->density(), 1984.2507319669949, 1e-6); - EXPECT_NEAR(thermo->enthalpy_mass(), -14738312.44316336, 1e-6); - EXPECT_NEAR(mu[0], -4.66404010e+08, 1e1); - EXPECT_NEAR(mu[1], -2.88157316e+06, 1e-1); -} - -TEST(ThermoFromYaml, IonsFromNeutral_fromString) -{ - // A little different because we can't re-read the input file to get the - // phase definition for the neutral phase - std::ifstream infile("../data/thermo-models.yaml"); - std::stringstream buffer; - buffer << infile.rdbuf(); - AnyMap input = AnyMap::fromYamlString(buffer.str()); - auto thermo = newPhase( - input["phases"].getMapWhere("name", "ions-from-neutral-molecule"), - input); - ASSERT_EQ((int) thermo->nSpecies(), 2); - vector_fp mu(thermo->nSpecies()); - thermo->getChemPotentials(mu.data()); - - // Values for regression testing only -- same as "fromScratch" test - EXPECT_NEAR(thermo->density(), 1984.2507319669949, 1e-6); - EXPECT_NEAR(thermo->enthalpy_mass(), -14738312.44316336, 1e-6); - EXPECT_NEAR(mu[0], -4.66404010e+08, 1e1); - EXPECT_NEAR(mu[1], -2.88157316e+06, 1e-1); -} - -//! @todo Remove after Cantera 2.5 - "gas" mode of IdealSolnGasVPSS is -//! deprecated -TEST(ThermoFromYaml, IdealSolnGas_gas) -{ - suppress_deprecation_warnings(); - auto thermo = newThermo("thermo-models.yaml", "IdealSolnGas-gas"); - make_deprecation_warnings_fatal(); - thermo->equilibrate("HP"); - EXPECT_NEAR(thermo->temperature(), 479.929, 1e-3); // based on h2o2.cti - EXPECT_NEAR(thermo->moleFraction("H2O"), 0.01, 1e-4); - EXPECT_NEAR(thermo->moleFraction("H2"), 0.0, 1e-4); -} - -TEST(ThermoFromYaml, IdealSolnGas_liquid) -{ - auto thermo = newThermo("thermo-models.yaml", "IdealSolnGas-liquid"); - thermo->setState_TP(300, OneAtm); - EXPECT_NEAR(thermo->density(), 505.42393940, 2e-8); - EXPECT_NEAR(thermo->gibbs_mole(), -7801634.1184443515, 2e-8); - thermo->setState_TP(400, 2*OneAtm); - EXPECT_NEAR(thermo->density(), 495.06986080, 2e-8); - EXPECT_NEAR(thermo->molarVolume(), 0.014018223587243668, 2e-12); - thermo->setState_TP(500, 2*OneAtm); - EXPECT_NEAR(thermo->density(), 484.66590, 2e-8); - EXPECT_NEAR(thermo->enthalpy_mass(), 1236701.0904197122, 2e-8); - EXPECT_NEAR(thermo->entropy_mole(), 49848.488477407751, 2e-8); -} - -TEST(ThermoFromYaml, RedlichKister) -{ - auto thermo = newThermo("thermo-models.yaml", "Redlich-Kister-LiC6"); - vector_fp chemPotentials(2); - vector_fp dlnActCoeffdx(2); - thermo->setState_TP(298.15, OneAtm); - thermo->setMoleFractionsByName("Li(C6): 0.6375, V(C6): 0.3625"); - thermo->getChemPotentials(chemPotentials.data()); - thermo->getdlnActCoeffdlnX_diag(dlnActCoeffdx.data()); - EXPECT_NEAR(chemPotentials[0], -1.2618554573674981e+007, 1e-6); - EXPECT_NEAR(dlnActCoeffdx[0], 0.200612, 1e-6); - - thermo->setMoleFractionsByName("Li(C6): 0.8625, V(C6): 0.1375"); - thermo->getChemPotentials(chemPotentials.data()); - thermo->getdlnActCoeffdlnX_diag(dlnActCoeffdx.data()); - EXPECT_NEAR(chemPotentials[0], -1.179299486233677e+07, 1e-6); - EXPECT_NEAR(dlnActCoeffdx[0], -0.309379, 1e-6); -} - -TEST(ThermoFromYaml, MaskellSolidSoln) -{ - auto thermo = newThermo("thermo-models.yaml", "MaskellSolidSoln"); - vector_fp chemPotentials(2); - thermo->getChemPotentials(chemPotentials.data()); - EXPECT_NEAR(chemPotentials[0], -4.989677789060059e6, 1e-6); - EXPECT_NEAR(chemPotentials[1], 4.989677789060059e6 + 1000, 1e-6); -} - -TEST(ThermoFromYaml, HMWSoln) -{ - auto thermo = newThermo("thermo-models.yaml", "HMW-NaCl-electrolyte"); - size_t N = thermo->nSpecies(); - auto HMW = dynamic_cast(thermo.get()); - vector_fp acMol(N), mf(N), activities(N), moll(N), mu0(N); - thermo->getMoleFractions(mf.data()); - HMW->getMolalities(moll.data()); - HMW->getMolalityActivityCoefficients(acMol.data()); - thermo->getActivities(activities.data()); - thermo->getStandardChemPotentials(mu0.data()); - - double acMolRef[] = {0.9341, 1.0191, 3.9637, 1.0191, 0.4660}; - double mfRef[] = {0.8198, 0.0901, 0.0000, 0.0901, 0.0000}; - double activitiesRef[] = {0.7658, 6.2164, 0.0000, 6.2164, 0.0000}; - double mollRef[] = {55.5093, 6.0997, 0.0000, 6.0997, 0.0000}; - double mu0Ref[] = {-317.175792, -186.014569, 0.0017225, -441.615456, -322.000432}; // kJ/gmol - - for (size_t k = 0 ; k < N; k++) { - EXPECT_NEAR(acMol[k], acMolRef[k], 2e-4); - EXPECT_NEAR(mf[k], mfRef[k], 2e-4); - EXPECT_NEAR(activities[k], activitiesRef[k], 2e-4); - EXPECT_NEAR(moll[k], mollRef[k], 2e-4); - EXPECT_NEAR(mu0[k]/1e6, mu0Ref[k], 2e-6); - } - EXPECT_EQ("liquid", HMW->phaseOfMatter()); -} - -TEST(ThermoFromYaml, HMWSoln_HKFT) -{ - auto thermo = newThermo("thermo-models.yaml", "HMW-NaCl-HKFT"); - double mvRef[] = {0.01815224, 0.00157182, 0.01954605, 0.00173137, -0.0020266}; - double hRef[] = {-2.84097587e+08, -2.38159643e+08, -1.68846908e+08, - 3.59728865e+06, -2.29291570e+08}; - double acoeffRef[] = {0.922403480, 1.21859875, 1.21859855, 5.08171133, - 0.5983205}; - - // Regression test based on HMWSoln.fromScratch_HKFT - size_t N = thermo->nSpecies(); - vector_fp mv(N), h(N), acoeff(N); - thermo->getPartialMolarVolumes(mv.data()); - thermo->getPartialMolarEnthalpies(h.data()); - thermo->getActivityCoefficients(acoeff.data()); - for (size_t k = 0; k < N; k++) { - EXPECT_NEAR(mv[k], mvRef[k], 2e-8); - EXPECT_NEAR(h[k], hRef[k], 2e0); - EXPECT_NEAR(acoeff[k], acoeffRef[k], 2e-8); - } -} - -TEST(ThermoFromYaml, RedlichKwong_CO2) -{ - auto thermo = newThermo("thermo-models.yaml", "CO2-RK"); - EXPECT_NEAR(thermo->density(), 892.404657616, 1e-8); - EXPECT_NEAR(thermo->enthalpy_mass(), -9199911.5290408, 1e-6); - EXPECT_NEAR(thermo->cp_mass(), 2219.940330064, 1e-8); - - thermo->setState_TPX(350, 180*OneAtm, "CO2:0.6, H2O:0.02, H2:0.38"); - EXPECT_NEAR(thermo->density(), 181.564971902, 1e-8); - EXPECT_NEAR(thermo->enthalpy_mass(), -8873033.2793978, 1e-6); - 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"); - thermo->setState_TP(70, 2*OneAtm); - EXPECT_NEAR(thermo->density(), 841.0420151, 1e-6); - EXPECT_NEAR(thermo->gibbs_mole(), -17654454.0912211, 1e-6); -} - -TEST(ThermoFromYaml, PureFluid_CO2) -{ - auto thermo = newThermo("thermo-models.yaml", "CO2-purefluid"); - EXPECT_NEAR(thermo->vaporFraction(), 0.1, 1e-6); - EXPECT_NEAR(thermo->density(), 513.27928388, 1e-6); -} - -TEST(ThermoFromYaml, PureFluid_Unknown) -{ - AnyMap root = AnyMap::fromYamlString( - "phases:\n" - "- name: unknown-purefluid\n" - " species: [N2]\n" - " thermo: pure-fluid\n" - " pure-fluid-name: unknown-purefluid\n" - ); - AnyMap& phase = root["phases"].getMapWhere("name", "unknown-purefluid"); - EXPECT_THROW(newPhase(phase, root), CanteraError); -} - -TEST(ThermoFromYaml, ConstDensityThermo) -{ - suppress_deprecation_warnings(); - auto thermo = newThermo("thermo-models.yaml", "const-density"); - EXPECT_DOUBLE_EQ(thermo->density(), 700.0); - make_deprecation_warnings_fatal(); -} - -TEST(ThermoFromYaml, IdealSolidSolnPhase) -{ - auto thermo = newThermo("thermo-models.yaml", "IdealSolidSolnPhase"); - - // Regression test following IdealSolidSolnPhase.fromScratch - EXPECT_NEAR(thermo->density(), 10.1787080, 1e-6); - EXPECT_NEAR(thermo->enthalpy_mass(), -15642788.8547624, 1e-4); - EXPECT_NEAR(thermo->gibbs_mole(), -313642312.7114608, 1e-4); -} - -TEST(ThermoFromYaml, Lattice) -{ - auto thermo = newThermo("thermo-models.yaml", "Li7Si3_and_interstitials"); - - // Regression test based on modified version of Li7Si3_ls.xml - EXPECT_NEAR(thermo->enthalpy_mass(), -2077955.0584538165, 1e-6); - double mu_ref[] = {-4.62717474e+08, -4.64248485e+07, 1.16370186e+05}; - double vol_ref[] = {0.095564748201438871, 0.2, 0.09557086}; - vector_fp mu(thermo->nSpecies()); - vector_fp vol(thermo->nSpecies()); - thermo->getChemPotentials(mu.data()); - thermo->getPartialMolarVolumes(vol.data()); - - for (size_t k = 0; k < thermo->nSpecies(); k++) { - EXPECT_NEAR(mu[k], mu_ref[k], 1e-7*fabs(mu_ref[k])); - EXPECT_NEAR(vol[k], vol_ref[k], 1e-7); - } -} - -TEST(ThermoFromYaml, Lattice_fromString) -{ - // A little different because we can't re-read the input file to get the - // phase definition for the neutral phase - std::ifstream infile("../data/thermo-models.yaml"); - std::stringstream buffer; - buffer << infile.rdbuf(); - AnyMap input = AnyMap::fromYamlString(buffer.str()); - auto thermo = newPhase( - input["phases"].getMapWhere("name", "Li7Si3_and_interstitials"), - input); - - // Regression test based on modified version of Li7Si3_ls.xml - EXPECT_NEAR(thermo->enthalpy_mass(), -2077955.0584538165, 1e-6); - double mu_ref[] = {-4.62717474e+08, -4.64248485e+07, 1.16370186e+05}; - double vol_ref[] = {0.095564748201438871, 0.2, 0.09557086}; - vector_fp mu(thermo->nSpecies()); - vector_fp vol(thermo->nSpecies()); - thermo->getChemPotentials(mu.data()); - thermo->getPartialMolarVolumes(vol.data()); - - for (size_t k = 0; k < thermo->nSpecies(); k++) { - EXPECT_NEAR(mu[k], mu_ref[k], 1e-7*fabs(mu_ref[k])); - EXPECT_NEAR(vol[k], vol_ref[k], 1e-7); - } -} - -TEST(ThermoFromYaml, Metal) -{ - auto thermo = newThermo("thermo-models.yaml", "Metal"); - EXPECT_DOUBLE_EQ(thermo->density(), 9.0); - EXPECT_DOUBLE_EQ(thermo->gibbs_mass(), 0.0); -} - -TEST(ThermoFromYaml, BinarySolutionTabulatedThermo) -{ - auto thermo = newThermo("thermo-models.yaml", "graphite-anode"); - EXPECT_NEAR(thermo->density(), 5031.7, 1e-5); - EXPECT_NEAR(thermo->enthalpy_mass(), -32501.245047302145, 1e-9); - EXPECT_NEAR(thermo->entropy_mass(), 90.443481807823474, 1e-12); - thermo->setMoleFractionsByName("Li[anode]: 0.55, V[anode]: 0.45"); - EXPECT_NEAR(thermo->gibbs_mass(), -87066.246182649265, 1e-9); -} - -TEST(ThermoFromYaml, DeprecatedPhase) -{ - // The deprecation warning in this file is turned into an - // error by make_deprecation_warnings_fatal() called in main() - // for the test suite. - EXPECT_THROW(newThermo("gri30.yaml", "gri30_mix"), CanteraError); -} -<<<<<<< HEAD ->>>>>>> ea38f153e... Modifying pengRobinson test from thermoFromYAML -======= ->>>>>>> 886e956d3... Correcting CRLF Line endings. +} \ No newline at end of file From 7fee130d5ddef4cf763aec5b23597cb072ddf094 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sat, 20 Mar 2021 08:47:12 -0600 Subject: [PATCH 082/110] Updating PengRobinson test from thermoFromYaml.cpp --- test/thermo/thermoFromYaml.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index 652bff2b6b..73a41e57cc 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -338,13 +338,13 @@ 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.2135196672034, 1e-8); + 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(), 3065.022259252295, 1e-8); - EXPECT_NEAR(thermo->cv_mole(), 32221.352497580105, 1e-8); + EXPECT_NEAR(thermo->cp_mass(), 3072.2397990253207, 1e-8); + EXPECT_NEAR(thermo->cv_mole(), 32420.064214752296, 1e-8); } TEST(ThermoFromYaml, PureFluid_nitrogen) From 52e221214174ebf5d5288dcacdc4f63e951b7ab8 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 10:18:57 -0600 Subject: [PATCH 083/110] Update include/cantera/thermo/PengRobinson.h Co-authored-by: Ray Speth --- include/cantera/thermo/PengRobinson.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 95c7f9f0e2..ed7805c3e3 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -81,10 +81,7 @@ class PengRobinson : public MixtureFugacityTP * \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; // @} From 67f22f6a25a8f30da6106f9194f5551507178beb Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 10:20:05 -0600 Subject: [PATCH 084/110] Update include/cantera/thermo/PengRobinson.h Co-authored-by: Ray Speth --- include/cantera/thermo/PengRobinson.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index ed7805c3e3..6fd1d039be 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -130,7 +130,7 @@ class PengRobinson : public MixtureFugacityTP /*! * The temperature dependent parameter in P-R EoS is calculated as * \f$ \alpha = [1 + \kappa(1 - \sqrt{T/T_{crit}})]^2 \f$ - * kappa is a function calulated based on the acentric factor. + * kappa is a function calculated based on the acentric factor. * Units: unitless * * @param a species-specific coefficients used in P-R EoS From 4e0774318ced4093cb8e8a7b5d37f212f8b706d9 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 11:54:14 -0600 Subject: [PATCH 085/110] Moving declaration of the protected variable m_pp from MixtureFugacity class to the PengRobinson class --- include/cantera/thermo/MixtureFugacityTP.h | 3 --- include/cantera/thermo/PengRobinson.h | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 226fea9f01..19349d0183 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -574,9 +574,6 @@ class MixtureFugacityTP : public ThermoPhase //! Temporary storage for dimensionless reference state entropies mutable vector_fp m_s0_R; -public: - //! Temporary storage - length = m_kk. - mutable vector_fp m_pp; }; } diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 6fd1d039be..e46d784796 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -275,6 +275,9 @@ class PengRobinson : public MixtureFugacityTP 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; From 6b14db50603aa5fd8be0b2fbbdfe94f1e779ca07 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 12:28:09 -0600 Subject: [PATCH 086/110] Cleaning up whitespaces, typos and redundant variables --- include/cantera/thermo/PengRobinson.h | 5 ---- include/cantera/thermo/RedlichKwongMFTP.h | 28 ++++++++++++++++++++--- src/thermo/MixtureFugacityTP.cpp | 8 +++---- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index e46d784796..251c0f394e 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -86,8 +86,6 @@ class PengRobinson : public MixtureFugacityTP // @} -public: - //! Returns the standard concentration \f$ C^0_k \f$, which is used to //! normalize the generalized concentration. /*! @@ -142,7 +140,6 @@ class PengRobinson : public MixtureFugacityTP virtual double speciesCritTemperature(double a, double b) const; -public: //@} //! @name Initialization Methods - For Internal use /*! @@ -172,7 +169,6 @@ class PengRobinson : public MixtureFugacityTP //! 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] @@ -187,7 +183,6 @@ class PengRobinson : public MixtureFugacityTP * 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] diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index 3c18431ac5..d92d700030 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -46,11 +46,9 @@ class RedlichKwongMFTP : public MixtureFugacityTP } //! @name Molar Thermodynamic properties - //! @{ - + //! @{ virtual doublereal cp_mole() const; virtual doublereal cv_mole() const; - //! @} //! @name Mechanical Properties //! @{ @@ -69,6 +67,30 @@ 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: //! normalize the generalized concentration. diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index a2bdf45629..193890e41a 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -827,8 +827,8 @@ void MixtureFugacityTP::calcCriticalConditions(double& pc, double& tc, double& v } 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 + 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) { @@ -954,7 +954,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::solveCubic", "Inconsistency in cubic solver : solver is poorly conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -962,7 +962,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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", "Inconsistancy in cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::solveCubic", "Inconsistency in cubic solver : solver is poorly conditioned."); } delta = -delta; Vroot[0] = xN + delta; From c766a9d46669b99c5493695dc7ae099b94734fb3 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 12:41:11 -0600 Subject: [PATCH 087/110] Wrapping up some error messages to keep the line length below 80 characters --- src/thermo/MixtureFugacityTP.cpp | 38 +++++++++++++++++++++----------- src/thermo/RedlichKwongMFTP.cpp | 3 ++- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index 193890e41a..ae4b11a54b 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -254,10 +254,12 @@ void MixtureFugacityTP::setPressure(doublereal p) setDensity(rho); iState_ = phaseState(true); } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + throw CanteraError("MixtureFugacityTP::setPressure", + "neg rho"); } } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + throw CanteraError("MixtureFugacityTP::setPressure", + "neg rho"); } } } else if (forcedState_ == FLUID_GAS) { @@ -268,10 +270,12 @@ void MixtureFugacityTP::setPressure(doublereal p) setDensity(rho); iState_ = phaseState(true); if (iState_ >= FLUID_LIQUID_0) { - throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); + throw CanteraError("MixtureFugacityTP::setPressure", + "wrong state"); } } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + throw CanteraError("MixtureFugacityTP::setPressure", + "neg rho"); } } } else if (forcedState_ > FLUID_LIQUID_0) { @@ -281,10 +285,12 @@ void MixtureFugacityTP::setPressure(doublereal p) setDensity(rho); iState_ = phaseState(true); if (iState_ == FLUID_GAS) { - throw CanteraError("MixtureFugacityTP::setPressure", "wrong state"); + throw CanteraError("MixtureFugacityTP::setPressure", + "wrong state"); } } else { - throw CanteraError("MixtureFugacityTP::setPressure", "neg rho"); + throw CanteraError("MixtureFugacityTP::setPressure", + "neg rho"); } } } @@ -466,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; } @@ -780,7 +787,8 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const } doublereal pref = refPressure(); if (pref <= 0.0) { - throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", "neg ref pressure"); + throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", + "neg ref pressure"); } } } @@ -832,7 +840,8 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, { fill_n(Vroot, 3, 0.0); if (T <= 0.0) { - throw CanteraError("MixtureFugacityTP::solveCubic", "negative temperature T = {}", T); + throw CanteraError("MixtureFugacityTP::solveCubic", + "negative temperature T = {}", T); } // Derive the center of the cubic, x_N @@ -878,7 +887,8 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, //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"); + throw CanteraError("MixtureFugacityTP::solveCubic", + "value of yN and h are too high, unrealistic roots may be obtained"); } disc = 0.0; } @@ -934,7 +944,7 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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 = {}):" + writelog("MixtureFugacityTP::solveCubic(T ={}, p ={}):" " WARNING roots have merged: {}, {}\n", T, pres, Vroot[i], Vroot[j]); } @@ -954,7 +964,8 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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 cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::solveCubic", + "Inconsistency in solver: solver is ill-conditioned."); } Vroot[1] = xN + delta; Vroot[0] = xN - 2.0*delta; // liquid phase root @@ -962,7 +973,8 @@ int MixtureFugacityTP::solveCubic(double T, double pres, double a, double b, 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 cubic solver : solver is poorly conditioned."); + throw CanteraError("MixtureFugacityTP::solveCubic", + "Inconsistency in solver: solver is ill-conditioned."); } delta = -delta; Vroot[0] = xN + delta; diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index f927fc68e3..f015c4828d 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -986,7 +986,8 @@ void RedlichKwongMFTP::calcCriticalConditions(doublereal& pc, doublereal& tc, do tc += deltatc; } if (deltatc > 0.1) { - throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", "didn't converge"); + throw CanteraError("RedlichKwongMFTP::calcCriticalConditions", + "didn't converge"); } } From fb7386fa8feab26e21b85850f236816217f3d5a3 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 14:02:41 -0600 Subject: [PATCH 088/110] Cleaning up whitespaces and some comments --- src/thermo/PengRobinson.cpp | 63 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index a140804474..471b091809 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -45,7 +45,7 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b } // Calculate value of kappa (independent of temperature) - // w is an acentric factor of species and must be specified in the CTI file + // w is an acentric factor of species if (w <= 0.491) { m_kappa_vec[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { @@ -123,7 +123,7 @@ double PengRobinson::cp_mole() const 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_current) * log(vpb / vmb) * T *d2aAlpha_dT2(); + + 1.0 / (2.0 * M_SQRT2 * m_b_current) * log(vpb / vmb) * T * d2aAlpha_dT2(); return dHdT_V - (mv + T * m_dpdT / m_dpdV) * m_dpdT; } @@ -132,7 +132,7 @@ double PengRobinson::cv_mole() const _updateReferenceStateThermo(); double T = temperature(); calculatePressureDerivatives(); - return (cp_mole() + T* m_dpdT* m_dpdT / m_dpdV); + return (cp_mole() + T * m_dpdT * m_dpdT / m_dpdV); } double PengRobinson::pressure() const @@ -169,14 +169,14 @@ void PengRobinson::getActivityCoefficients(double* ac) const } double num = 0; double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + double den2 = m_b_current * (mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); double RTkelvin = RT(); for (size_t k = 0; k < m_kk; k++) { - num = 2 * m_b_current * m_pp[k] - m_aAlpha_current* m_b_vec_Curr[k]; - ac[k] = (-RTkelvin *log(pres*mv/ RTkelvin) + RTkelvin * log(mv / vmb) + num = 2 * m_b_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[k]; + ac[k] = (-RTkelvin * log(pres * mv/ RTkelvin) + RTkelvin * log(mv / vmb) + RTkelvin * m_b_vec_Curr[k] / vmb - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current* m_b_vec_Curr[k] * mv/den2 + - m_aAlpha_current * m_b_vec_Curr[k] * mv/den2 ); } for (size_t k = 0; k < m_kk; k++) { @@ -192,7 +192,7 @@ void PengRobinson::getChemPotentials(double* mu) const double RTkelvin = RT(); for (size_t k = 0; k < m_kk; k++) { double xx = std::max(SmallNumber, moleFraction(k)); - mu[k] += RTkelvin *(log(xx)); + mu[k] += RTkelvin * (log(xx)); } double mv = molarVolume(); @@ -209,7 +209,7 @@ void PengRobinson::getChemPotentials(double* mu) const double pres = pressure(); double refP = refPressure(); double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current*(mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + double den2 = m_b_current * (mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); for (size_t k = 0; k < m_kk; k++) { double num = 2 * m_b_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[k]; @@ -244,10 +244,10 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const } double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; - double den2 = den*den; + double den2 = den * den; double RTkelvin = RT(); for (size_t k = 0; k < m_kk; k++) { - m_dpdni[k] = RTkelvin /vmb + RTkelvin * m_b_vec_Curr[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + m_dpdni[k] = RTkelvin / vmb + RTkelvin * m_b_vec_Curr[k] / (vmb * vmb) - 2.0 * m_pp[k] / den + 2 * vmb * m_aAlpha_current * m_b_vec_Curr[k] / den2; } @@ -256,10 +256,10 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const calculatePressureDerivatives(); double fac2 = mv + T * m_dpdT / m_dpdV; - double fac3 = 2 * M_SQRT2 * m_b_current *m_b_current; + double fac3 = 2 * M_SQRT2 * m_b_current * m_b_current; for (size_t k = 0; k < m_kk; k++) { - double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2)*fac - + (mv * m_b_vec_Curr[k]) /(m_b_current*den) * fac; + double hE_v = mv * m_dpdni[k] - RTkelvin + (2 * m_b_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2) * fac + + (mv * m_b_vec_Curr[k]) /(m_b_current * den) * fac; hbar[k] = hbar[k] + hE_v; hbar[k] -= fac2 * m_dpdni[k]; } @@ -287,7 +287,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const m_tmpV[k] = 0; for (size_t i = 0; i < m_kk; i++) { m_pp[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i); - m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i) *(m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); + m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i) * (m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[i]); } m_pp[k] = m_pp[k] * m_dalphadT_vec_Curr[k] / m_alpha_vec_Curr[k]; } @@ -299,7 +299,7 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const + GasConstant + GasConstant * log(mv / vmb) + GasConstant * m_b_vec_Curr[k] / vmb - - coeff1* log(vpb2 / vmb2) / den1 + - coeff1 * log(vpb2 / vmb2) / den1 - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; } calculatePressureDerivatives(); @@ -351,7 +351,7 @@ void PengRobinson::getPartialMolarVolumes(double* vbar) const ); double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) + m_aAlpha_current/fac - - 2 * mv* vpb *m_aAlpha_current / fac2 + - 2 * mv* vpb * m_aAlpha_current / fac2 ); vbar[k] = num / denom; } @@ -455,7 +455,7 @@ vector PengRobinson::getCoeff(const std::string& iName) } //Assuming no temperature dependence - spCoeff[0] = omega_a * (GasConstant* GasConstant) * (T_crit* T_crit) / P_crit; //coeff a + 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; @@ -503,7 +503,6 @@ void PengRobinson::initThermo() // Check if species was found in the database of critical // properties, and assign the results if (!isnan(coeffs[0])) { - // Assuming no temperature dependence (i.e. a1 = 0) setSpeciesCoeffs(item.first, coeffs[0], coeffs[1], coeffs[2]); } } @@ -517,8 +516,8 @@ double PengRobinson::sresid() const double hh = m_b_current / molarV; double zz = z(); double alpha_1 = daAlpha_dT(); - double vpb = molarV + (1.0 + M_SQRT2) *m_b_current; - double vmb = molarV + (1.0 - M_SQRT2) *m_b_current; + double vpb = molarV + (1.0 + M_SQRT2) * m_b_current; + double vmb = molarV + (1.0 - M_SQRT2) * m_b_current; double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); double sresid_mol_R = log(zz*(1.0 - hh)) + fac * log(vpb / vmb) / GasConstant; return GasConstant * sresid_mol_R; @@ -530,10 +529,10 @@ double PengRobinson::hresid() const double zz = z(); double aAlpha_1 = daAlpha_dT(); double T = temperature(); - double vpb = molarV + (1 + M_SQRT2) *m_b_current; - double vmb = molarV + (1 - M_SQRT2) *m_b_current; + double vpb = molarV + (1 + M_SQRT2) * m_b_current; + double vmb = molarV + (1 - M_SQRT2) * m_b_current; double fac = 1 / (2.0 * M_SQRT2 * m_b_current); - return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) *(T * aAlpha_1 - m_aAlpha_current); + return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) * (T * aAlpha_1 - m_aAlpha_current); } double PengRobinson::liquidVolEst(double T, double& presGuess) const @@ -676,11 +675,11 @@ double PengRobinson::pressureCalc(double T, double molarVol) const double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const { double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current/ den; + presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; double vpb = molarVol + m_b_current; double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * T / (vmb * vmb) + 2 *m_aAlpha_current * vpb / (den*den); + double dpdv = -GasConstant * T / (vmb * vmb) + 2 * m_aAlpha_current * vpb / (den*den); return dpdv; } @@ -749,7 +748,7 @@ double PengRobinson::daAlpha_dT() const coeff1 = 1 / (Tc*sqtTr); coeff2 = sqtTr - 1; k = m_kappa_vec[i]; - m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); + m_dalphadT_vec_Curr[i] = coeff1 * (k*k*coeff2 - k); } //Calculate mixture derivative for (size_t i = 0; i < m_kk; i++) { @@ -772,8 +771,8 @@ double PengRobinson::d2aAlpha_dT2() const double coeff2 = sqt_Tr - 1; // Calculate first and second derivatives of alpha for individual species double k = m_kappa_vec[i]; - m_dalphadT_vec_Curr[i] = coeff1 *(k* k*coeff2 - k); - m_d2alphadT2[i] = (k*k + k) * coeff1 / (2 * sqt_Tr*sqt_Tr); + m_dalphadT_vec_Curr[i] = coeff1 * (k*k*coeff2 - k); + m_d2alphadT2[i] = (k*k + k) * coeff1 / (2*sqt_Tr*sqt_Tr); } //Calculate mixture derivative @@ -784,9 +783,9 @@ double PengRobinson::d2aAlpha_dT2() const alphaij = alphai * alphaj; temp = 0.5 * sqrt((m_a_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (alphaij)); num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); - fac1 = -(0.5 / alphaij)*num*num; - fac2 = alphaj * m_d2alphadT2[i] + alphai *m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; - d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp *(fac1 + fac2); + fac1 = -(0.5 / alphaij) * num * num; + fac2 = alphaj * m_d2alphadT2[i] + alphai * m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; + d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp * (fac1 + fac2); } } return d2aAlphadT2; From ec17b30c1accfb6e12dade1a33c1de77638e01ad Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 14:54:54 -0600 Subject: [PATCH 089/110] Cleaning up some more indentations errors, typos and comments --- include/cantera/thermo/PengRobinson.h | 3 +-- src/thermo/PengRobinson.cpp | 35 +++++++++------------------ src/thermo/RedlichKwongMFTP.cpp | 2 +- test/thermo/PengRobinson_Test.cpp | 12 ++++----- test/thermo/thermoFromYaml.cpp | 2 +- 5 files changed, 21 insertions(+), 33 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 251c0f394e..c571aee904 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -90,8 +90,7 @@ class PengRobinson : public MixtureFugacityTP //! normalize the generalized concentration. /*! * This is defined as the concentration by which the generalized - * concentration is normalized to produce the activity. In many cases, this - * quantity will be the same for all species in a phase. + * 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$. diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 471b091809..c5787a1a23 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -69,7 +69,6 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double throw CanteraError("PengRobinson::setSpeciesCoeffs", "Unknown species '{}'.", species); } - size_t counter = k + m_kk * k; m_a_vec_Curr(k,k) = a; // we store this locally because it is used below to calculate a_Alpha: double aAlpha_k = a*m_alpha_vec_Curr[k]; @@ -155,7 +154,6 @@ double PengRobinson::standardConcentration(size_t k) const void PengRobinson::getActivityCoefficients(double* ac) const { double mv = molarVolume(); - //double T = temperature(); double vpb2 = mv + (1 + M_SQRT2)*m_b_current; double vmb2 = mv + (1 - M_SQRT2)*m_b_current; double vmb = mv - m_b_current; @@ -454,7 +452,6 @@ vector PengRobinson::getCoeff(const std::string& iName) } - //Assuming no temperature dependence 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 @@ -547,7 +544,7 @@ double PengRobinson::liquidVolEst(double T, double& presGuess) const bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { - int nsol = solveCubic(T, pres, atmp, btmp, aAlphatmp, Vroot); + int nsol = solveCubic(T, pres, atmp, btmp, aAlphatmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -698,13 +695,11 @@ void PengRobinson::calculatePressureDerivatives() const void PengRobinson::updateMixingExpressions() { double temp = temperature(); - //Update aAlpha_i - double sqt_alpha; - // Update indiviual alpha + // Update individual alpha for (size_t j = 0; j < m_kk; j++) { double critTemp_j = speciesCritTemperature(m_a_vec_Curr(j,j), m_b_vec_Curr[j]); - sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); + double sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); m_alpha_vec_Curr[j] = sqt_alpha*sqt_alpha; } @@ -714,11 +709,6 @@ void PengRobinson::updateMixingExpressions() m_aAlpha_vec_Curr(i, j) = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr(i,j); } } - - m_b_current = 0.0; - m_a_current = 0.0; - m_aAlpha_current = 0.0; - calculateAB(m_a_current,m_b_current,m_aAlpha_current); } @@ -739,8 +729,7 @@ void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) double PengRobinson::daAlpha_dT() const { - double daAlphadT = 0.0, temp, k, Tc = 0.0, sqtTr = 0.0; - double coeff1, coeff2; + 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_vec_Curr(i,i), m_b_vec_Curr[i]); @@ -763,7 +752,6 @@ double PengRobinson::daAlpha_dT() const double PengRobinson::d2aAlpha_dT2() const { - double temp, fac1, fac2, alphaij, alphai, alphaj, d2aAlphadT2 = 0.0, num; for (size_t i = 0; i < m_kk; i++) { double Tcrit_i = speciesCritTemperature(m_a_vec_Curr(i, i), m_b_vec_Curr[i]); double sqt_Tr = sqrt(temperature() / Tcrit_i); //we need species critical temperature @@ -776,15 +764,16 @@ double PengRobinson::d2aAlpha_dT2() const } //Calculate mixture derivative + double d2aAlphadT2 = 0.0; for (size_t i = 0; i < m_kk; i++) { - alphai = m_alpha_vec_Curr[i]; + double alphai = m_alpha_vec_Curr[i]; for (size_t j = 0; j < m_kk; j++) { - alphaj = m_alpha_vec_Curr[j]; - alphaij = alphai * alphaj; - temp = 0.5 * sqrt((m_a_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (alphaij)); - num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); - fac1 = -(0.5 / alphaij) * num * num; - fac2 = alphaj * m_d2alphadT2[i] + alphai * m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; + double alphaj = m_alpha_vec_Curr[j]; + double alphaij = alphai * alphaj; + double temp = 0.5 * sqrt((m_a_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (alphaij)); + double num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * alphaj); + double fac1 = -(0.5 / alphaij) * num * num; + double fac2 = alphaj * m_d2alphadT2[i] + alphai * m_d2alphadT2[j] + 2. * m_dalphadT_vec_Curr[i] * m_dalphadT_vec_Curr[j]; d2aAlphadT2 += moleFractions_[i] * moleFractions_[j] * temp * (fac1 + fac2); } } diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index f015c4828d..40e845229d 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -1016,4 +1016,4 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl return nSolnValues; } -} \ No newline at end of file +} diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index e8e0983d17..9085f1cb7a 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -151,7 +151,7 @@ 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 p1[6] = { + const double rho1[6] = { 6.6603507723749249e+002, 1.6824762614489907e+002, 1.6248354709581241e+002, @@ -160,7 +160,7 @@ TEST_F(PengRobinson_Test, setTP) 1.4902908974486667e+002 }; // Phase change between temperatures 4 & 5: - const double p2[6] = { + const double rho2[6] = { 7.5732259810273172e+002, 7.2766981078381912e+002, 6.935475475396446e+002, @@ -169,7 +169,7 @@ TEST_F(PengRobinson_Test, setTP) 3.9966973266966875e+002 }; // Supercritical; no discontinuity in rho values: - const double p3[6] = { + const double rho3[6] = { 8.0601205067780199e+002, 7.8427655940884574e+002, 7.6105347579146576e+002, @@ -183,13 +183,13 @@ TEST_F(PengRobinson_Test, setTP) const double temp = 294 + i*2; set_r(0.999); test_phase->setState_TP(temp, 5542027.5); - EXPECT_NEAR(test_phase->density(),p1[i],1.e-8); + EXPECT_NEAR(test_phase->density(),rho1[i],1.e-8); test_phase->setState_TP(temp, 7388370.); - EXPECT_NEAR(test_phase->density(),p2[i],1.e-8); + EXPECT_NEAR(test_phase->density(),rho2[i],1.e-8); test_phase->setState_TP(temp, 9236712.5); - EXPECT_NEAR(test_phase->density(),p3[i],1.e-8); + EXPECT_NEAR(test_phase->density(),rho3[i],1.e-8); } } diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index 73a41e57cc..6ba3e8701e 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -446,4 +446,4 @@ TEST(ThermoFromYaml, BinarySolutionTabulatedThermo) EXPECT_NEAR(thermo->entropy_mass(), 90.443481807823474, 1e-12); thermo->setMoleFractionsByName("Li[anode]: 0.55, V[anode]: 0.45"); EXPECT_NEAR(thermo->gibbs_mass(), -87066.246182649265, 1e-9); -} \ No newline at end of file +} From 1f5dbe79346aca7a82a5bb50d22efeec27f2e6cd Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 14:22:21 -0600 Subject: [PATCH 090/110] Update src/thermo/PengRobinson.cpp Co-authored-by: Ray Speth --- src/thermo/PengRobinson.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index c5787a1a23..baf4665c86 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -357,17 +357,13 @@ void PengRobinson::getPartialMolarVolumes(double* vbar) const double PengRobinson::speciesCritTemperature(double a, double b) const { - double tc; if (b <= 0.0) { - tc = 1000000.; - return tc; - } - if (a <= 0.0) { - tc = 0.0; - return tc; + return 1000000.; + } else if (a <= 0.0) { + return 0.0; + } else { + return a * omega_b / (b * omega_a * GasConstant); } - tc = a * omega_b / (b * omega_a * GasConstant); - return tc; } bool PengRobinson::addSpecies(shared_ptr spec) From 1539bd1c9438e9c1515513815106137ff1ff659e Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Sun, 21 Mar 2021 15:17:18 -0600 Subject: [PATCH 091/110] Removing mistakenly added functions from RedlichKwongMFTP class. --- include/cantera/thermo/RedlichKwongMFTP.h | 25 +---------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index d92d700030..bf8807a846 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -67,32 +67,9 @@ 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: + //! 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 From 8f0adc61fba1d2749c3dd6f8602f8f3cfc496f24 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 23 Mar 2021 10:45:02 -0600 Subject: [PATCH 092/110] Adding documentation for certain functions --- include/cantera/thermo/PengRobinson.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index c571aee904..9bb9538514 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -134,9 +134,17 @@ class PengRobinson : public MixtureFugacityTP * @param b species-specific coefficients used in P-R EoS * @param w the acentric factor */ - virtual void calculateAlpha(const std::string& species, double a, double b, double w); + //! 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; //@} @@ -205,7 +213,16 @@ class PengRobinson : public MixtureFugacityTP // 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: From d17641c88a245c3b787ad304df99f3ee6b22acf6 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 23 Mar 2021 11:34:23 -0600 Subject: [PATCH 093/110] Declaring omega_a, omega_b values as private --- include/cantera/thermo/PengRobinson.h | 2 +- include/cantera/thermo/RedlichKwongMFTP.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 9bb9538514..b29a06341f 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -313,7 +313,7 @@ class PengRobinson : public MixtureFugacityTP */ mutable vector_fp m_dpdni; -public: +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. diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index bf8807a846..24faf6cebd 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -303,7 +303,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 From a0dd0c0016f1844235b0744a9b65da80bb3fe2f3 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 23 Mar 2021 11:45:58 -0600 Subject: [PATCH 094/110] Renaming 'cubicSolve' to 'solveCubic' --- include/cantera/thermo/RedlichKwongMFTP.h | 2 +- src/thermo/RedlichKwongMFTP.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index 24faf6cebd..08a11c62cb 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -245,7 +245,7 @@ class RedlichKwongMFTP : public MixtureFugacityTP void calcCriticalConditions(doublereal& pc, doublereal& tc, doublereal& vc) const; //! Prepare variables and call the function to solve the cubic equation of state - int CubicCall(double T, double pres, double a, double b, double Vroot[3]) const; + int solveCubic(double T, double pres, double a, double b, double Vroot[3]) const; protected: //! Form of the temperature parameterization diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 40e845229d..51b4d389ba 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -711,7 +711,7 @@ doublereal RedlichKwongMFTP::liquidVolEst(doublereal TKelvin, doublereal& presGu bool foundLiq = false; int m = 0; while (m < 100 && !foundLiq) { - int nsol = CubicCall(TKelvin, pres, atmp, btmp, Vroot); + int nsol = solveCubic(TKelvin, pres, atmp, btmp, Vroot); if (nsol == 1 || nsol == 2) { double pc = critPressure(); if (pres > pc) { @@ -750,7 +750,7 @@ doublereal RedlichKwongMFTP::densityCalc(doublereal TKelvin, doublereal presPa, doublereal volguess = mmw / rhoguess; - NSolns_ = CubicCall(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) { @@ -790,7 +790,7 @@ doublereal RedlichKwongMFTP::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); - int nsol = CubicCall(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(); } @@ -812,7 +812,7 @@ doublereal RedlichKwongMFTP::densSpinodalGas() const { double Vroot[3]; double T = temperature(); - int nsol = CubicCall(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(); } @@ -995,7 +995,7 @@ void RedlichKwongMFTP::calcCriticalConditions(doublereal& pc, doublereal& tc, do vc = omega_vc * GasConstant * tc / pc; } -int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, double Vroot[3]) const +int RedlichKwongMFTP::solveCubic(double T, double pres, double a, double b, double Vroot[3]) const { // Derive the coefficients of the cubic polynomial to solve. @@ -1011,7 +1011,7 @@ int RedlichKwongMFTP::CubicCall(double T, double pres, double a, double b, doubl double pc = omega_b * GasConstant * tc / b; double vc = omega_vc * GasConstant * tc / pc; - int nSolnValues = solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, pc); + int nSolnValues = MixtureFugacityTP::solveCubic(T, pres, a, b, a, Vroot, an, bn, cn, dn, tc, pc); return nSolnValues; } From 2478999b8fdf4a8d1ff85c0fc89a6d4a968d3bd7 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 23 Mar 2021 14:02:35 -0600 Subject: [PATCH 095/110] Merging 'calculateAlpha' function in 'setSpeciesCoeffs'. --- include/cantera/thermo/PengRobinson.h | 13 ------------- src/thermo/PengRobinson.cpp | 18 +++--------------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index b29a06341f..ab5bde9150 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -123,19 +123,6 @@ class PengRobinson : public MixtureFugacityTP virtual void getPartialMolarCp(double* cpbar) const; virtual void getPartialMolarVolumes(double* vbar) const; - //! Calculate the temperature dependent interaction parameter \f$\alpha\f$ needed for P-R EoS - /*! - * The temperature dependent parameter in P-R EoS is calculated as - * \f$ \alpha = [1 + \kappa(1 - \sqrt{T/T_{crit}})]^2 \f$ - * kappa is a function calculated based on the acentric factor. - * Units: unitless - * - * @param a species-specific coefficients used in P-R EoS - * @param b species-specific coefficients used in P-R EoS - * @param w the acentric factor - */ - virtual void calculateAlpha(const std::string& species, double a, double b, double w); - //! Calculate species-specific critical temperature /*! * The temperature dependent parameter in P-R EoS is calculated as diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index baf4665c86..f0bc7d58f9 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -36,11 +36,11 @@ PengRobinson::PengRobinson(const std::string& infile, const std::string& id_) : initThermoFile(infile, id_); } -void PengRobinson::calculateAlpha(const std::string& species, double a, double b, double w) -{ +void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double b, double w) +{ size_t k = speciesIndex(species); if (k == npos) { - throw CanteraError("PengRobinson::calculateAlpha", + throw CanteraError("PengRobinson::setSpeciesCoeffs", "Unknown species '{}'.", species); } @@ -57,20 +57,8 @@ void PengRobinson::calculateAlpha(const std::string& species, double a, double b double sqt_T_r = sqrt(temperature() / critTemp); double sqt_alpha = 1 + m_kappa_vec[k] * (1 - sqt_T_r); m_alpha_vec_Curr[k] = sqt_alpha*sqt_alpha; -} - -void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double b, double w) -{ - //Calculate alpha for the given species - calculateAlpha(species, a, b, w); - size_t k = speciesIndex(species); - if (k == npos) { - throw CanteraError("PengRobinson::setSpeciesCoeffs", - "Unknown species '{}'.", species); - } m_a_vec_Curr(k,k) = a; - // we store this locally because it is used below to calculate a_Alpha: double aAlpha_k = a*m_alpha_vec_Curr[k]; m_aAlpha_vec_Curr(k,k) = aAlpha_k; From 95f9a65ec14c4b615037aa1e6e42c2d040e3eb30 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 23 Mar 2021 15:00:07 -0600 Subject: [PATCH 096/110] Removing python test for PengRobinson class --- interfaces/cython/cantera/test/test_thermo.py | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/interfaces/cython/cantera/test/test_thermo.py b/interfaces/cython/cantera/test/test_thermo.py index cc9109cc46..51a63eaccc 100644 --- a/interfaces/cython/cantera/test/test_thermo.py +++ b/interfaces/cython/cantera/test/test_thermo.py @@ -913,33 +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 TestPengRobinsonPhase(utilities.CanteraTest): - def setUp(self): - self.gas = ct.Solution('co2_PR_example.yaml','CO2-PR') - - def test_setSV(self): - """ - Set state in terms of (s,v) - """ - self.gas.TPX = 350, 20e5, 'CO2:2.0, H2O:1.0' - s1, v1 = self.gas.SV - self.gas.SV = s1, 2 * v1 - - self.assertNear(self.gas.s, s1) - self.assertNear(self.gas.v, 2 * v1) - - def test_setHP(self): - """ - Set state in terms of (H,p) - """ - self.gas.TPX = 350, 20e5, 'CO2:2.0, H2O:1.0' - deltaH = 1.25e5 - h1, p1 = self.gas.HP - self.gas.HP = h1 - deltaH, None - - self.assertNear(self.gas.h, h1 - deltaH) - self.assertNear(self.gas.P, p1) - class TestInterfacePhase(utilities.CanteraTest): def setUp(self): self.gas = ct.Solution('diamond.xml', 'gas') From dee71c11e5de0f84e9265aff0273418895d6121b Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Tue, 23 Mar 2021 15:21:15 -0600 Subject: [PATCH 097/110] Added documentation for the 'solvecubic' function --- include/cantera/thermo/MixtureFugacityTP.h | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index 19349d0183..e71878c4fc 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -528,16 +528,30 @@ class MixtureFugacityTP : public ThermoPhase //! 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, + * + * 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) - */ - int solveCubic(double T, double pres, double a, double b, + * + * @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 + * @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; From aef53e5545961b27bcf3775888ffb040cf0e5076 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 26 Mar 2021 13:37:14 -0600 Subject: [PATCH 098/110] Renaming _curr vector variables --- include/cantera/thermo/PengRobinson.h | 22 +-- src/thermo/PengRobinson.cpp | 260 +++++++++++++------------- 2 files changed, 141 insertions(+), 141 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index ab5bde9150..5f19faec9b 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -247,27 +247,27 @@ class PengRobinson : public MixtureFugacityTP protected: //! Value of b in the equation of state /*! - * m_b_current is a function the mole fractions and species-specific b values. + * m_b is a function the mole fractions and species-specific b values. */ - double m_b_current; + double m_b; //! Value of a and alpha in the equation of state /*! - * m_aAlpha_current is a function of the temperature and the mole fractions. m_a_current depends only on the mole fractions. + * m_aAlpha_mix is a function of the temperature and the mole fractions. m_a depends only on the mole fractions. */ - double m_a_current; - double m_aAlpha_current; + 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_vec_Curr; - vector_fp m_kappa_vec; - mutable vector_fp m_dalphadT_vec_Curr; + vector_fp m_b_coeffs; + vector_fp m_kappa; + mutable vector_fp m_dalphadT; mutable vector_fp m_d2alphadT2; - vector_fp m_alpha_vec_Curr; + 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_vec_Curr; - Array2D m_aAlpha_vec_Curr; + Array2D m_a_coeffs; + Array2D m_aAlpha_binary; int m_NSolns; diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index f0bc7d58f9..a7086564de 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -25,9 +25,9 @@ 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_current(0.0), - m_a_current(0.0), - m_aAlpha_current(0.0), + m_b(0.0), + m_a(0.0), + m_aAlpha_mix(0.0), m_NSolns(0), m_dpdV(0.0), m_dpdT(0.0) @@ -47,37 +47,37 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double // Calculate value of kappa (independent of temperature) // w is an acentric factor of species if (w <= 0.491) { - m_kappa_vec[k] = 0.37464 + 1.54226*w - 0.26992*w*w; + m_kappa[k] = 0.37464 + 1.54226*w - 0.26992*w*w; } else { - m_kappa_vec[k] = 0.374642 + 1.487503*w - 0.164423*w*w + 0.016666*w*w*w; + 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_vec[k] * (1 - sqt_T_r); - m_alpha_vec_Curr[k] = sqt_alpha*sqt_alpha; + double sqt_alpha = 1 + m_kappa[k] * (1 - sqt_T_r); + m_alpha[k] = sqt_alpha*sqt_alpha; - m_a_vec_Curr(k,k) = a; - double aAlpha_k = a*m_alpha_vec_Curr[k]; - m_aAlpha_vec_Curr(k,k) = aAlpha_k; + 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_vec_Curr(j,j) * a); - double aAlpha_j = a*m_alpha_vec_Curr[j]; + 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_vec_Curr(j, k) == 0) { - m_a_vec_Curr(j, k) = a0kj; - m_aAlpha_vec_Curr(j, k) = a_Alpha; - m_a_vec_Curr(k, j) = a0kj; - m_aAlpha_vec_Curr(k, j) = a_Alpha; + 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_vec_Curr[k] = b; + m_b_coeffs[k] = b; } void PengRobinson::setBinaryCoeffs(const std::string& species_i, @@ -94,8 +94,8 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, "Unknown species '{}'.", species_j); } - m_a_vec_Curr(ki, kj) = m_a_vec_Curr(kj, ki) = a0; - m_aAlpha_vec_Curr(ki, kj) = m_aAlpha_vec_Curr(kj, ki) = a0*alpha; + m_a_coeffs(ki, kj) = m_a_coeffs(kj, ki) = a0; + m_aAlpha_binary(ki, kj) = m_aAlpha_binary(kj, ki) = a0*alpha; } // ------------Molar Thermodynamic Properties ------------------------- @@ -105,12 +105,12 @@ double PengRobinson::cp_mole() const _updateReferenceStateThermo(); double T = temperature(); double mv = molarVolume(); - double vpb = mv + (1 + M_SQRT2)*m_b_current; - double vmb = mv + (1 - M_SQRT2)*m_b_current; + 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_current) * log(vpb / vmb) * T * d2aAlpha_dT2(); + + 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; } @@ -128,8 +128,8 @@ double PengRobinson::pressure() const // 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_current - m_b_current * m_b_current; - double pp = GasConstant * T / (mv - m_b_current) - m_aAlpha_current / denom; + 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; } @@ -142,27 +142,27 @@ double PengRobinson::standardConcentration(size_t k) const void PengRobinson::getActivityCoefficients(double* ac) const { double mv = molarVolume(); - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; - double vmb = mv - m_b_current; + 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_vec_Curr(k, i); + m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i); } } double num = 0; - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current * (mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + 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_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[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_vec_Curr[k] / vmb + + RTkelvin * m_b_coeffs[k] / vmb - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current * m_b_vec_Curr[k] * mv/den2 + - m_aAlpha_mix * m_b_coeffs[k] * mv/den2 ); } for (size_t k = 0; k < m_kk; k++) { @@ -182,29 +182,29 @@ void PengRobinson::getChemPotentials(double* mu) const } double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + 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_vec_Curr(k, i); + m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i); } } double pres = pressure(); double refP = refPressure(); - double den = 2 * M_SQRT2 * m_b_current * m_b_current; - double den2 = m_b_current * (mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current); + 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_current * m_pp[k] - m_aAlpha_current * m_b_vec_Curr[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_vec_Curr[k] / vmb + + RTkelvin * m_b_coeffs[k] / vmb - (num /den) * log(vpb2/vmb2) - - m_aAlpha_current * m_b_vec_Curr[k] * mv/den2 + - m_aAlpha_mix * m_b_coeffs[k] * mv/den2 ); } } @@ -218,34 +218,34 @@ void PengRobinson::getPartialMolarEnthalpies(double* hbar) const // We calculate m_dpdni double T = temperature(); double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + 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_vec_Curr(k, i); + m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i); } } - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + 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_vec_Curr[k] / (vmb * vmb) - 2.0 * m_pp[k] / den - + 2 * vmb * m_aAlpha_current * m_b_vec_Curr[k] / den2; + 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_current; + double fac = T * daAlphadT - m_aAlpha_mix; calculatePressureDerivatives(); double fac2 = mv + T * m_dpdT / m_dpdV; - double fac3 = 2 * M_SQRT2 * m_b_current * m_b_current; + 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_current - m_b_vec_Curr[k]) / fac3 * log(vpb2 / vmb2) * fac - + (mv * m_b_vec_Curr[k]) /(m_b_current * den) * fac; + 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]; } @@ -257,14 +257,14 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const scale(sbar, sbar+m_kk, sbar, GasConstant); double T = temperature(); double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb2 = mv + (1 + M_SQRT2)*m_b_current; - double vmb2 = mv + (1 - M_SQRT2)*m_b_current; + 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_current * m_b_current; - double den2 = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + 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 @@ -272,21 +272,21 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const 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_vec_Curr(k, i); - m_tmpV[k] += moleFractions_[i] * m_aAlpha_vec_Curr(k, i) * (m_dalphadT_vec_Curr[i] / m_alpha_vec_Curr[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_vec_Curr[k] / m_alpha_vec_Curr[k]; + m_pp[k] = m_pp[k] * m_dalphadT[k] / m_alpha[k]; } for (size_t k = 0; k < m_kk; k++) { - coeff1 = m_b_current * (m_pp[k] + m_tmpV[k]) - daAlphadT * m_b_vec_Curr[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_vec_Curr[k] / vmb + + GasConstant * m_b_coeffs[k] / vmb - coeff1 * log(vpb2 / vmb2) / den1 - - m_b_vec_Curr[k] * mv * daAlphadT / den2 / m_b_current; + - m_b_coeffs[k] * mv * daAlphadT / den2 / m_b; } calculatePressureDerivatives(); getPartialMolarVolumes(m_partialMolarVolumes.data()); @@ -318,26 +318,26 @@ 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_vec_Curr(k, i); + m_pp[k] += moleFractions_[i] * m_aAlpha_binary(k, i); } } double mv = molarVolume(); - double vmb = mv - m_b_current; - double vpb = mv + m_b_current; - double fac = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + 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_current/ vmb + RTkelvin * m_b_vec_Curr[k] / vmb - + RTkelvin * m_b_current * m_b_vec_Curr[k] /(vmb * vmb) + 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_current * m_b_vec_Curr[k] / fac2 + + 2 * mv * vmb * m_aAlpha_mix * m_b_coeffs[k] / fac2 ); - double denom = (pressure() + RTkelvin * m_b_current / (vmb * vmb) - + m_aAlpha_current/fac - - 2 * mv* vpb * m_aAlpha_current / fac2 + double denom = (pressure() + RTkelvin * m_b / (vmb * vmb) + + m_aAlpha_mix/fac + - 2 * mv* vpb * m_aAlpha_mix / fac2 ); vbar[k] = num / denom; } @@ -358,13 +358,13 @@ bool PengRobinson::addSpecies(shared_ptr spec) { bool added = MixtureFugacityTP::addSpecies(spec); if (added) { - m_a_vec_Curr.resize(m_kk, m_kk, 0.0); - m_b_vec_Curr.push_back(0.0); - m_aAlpha_vec_Curr.resize(m_kk, m_kk, 0.0); - m_kappa_vec.push_back(0.0); + 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_vec_Curr.push_back(0.0); - m_dalphadT_vec_Curr.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); @@ -478,7 +478,7 @@ void PengRobinson::initThermo() // 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_vec_Curr(k, k) == 0.0) { + if (m_a_coeffs(k, k) == 0.0) { vector coeffs = getCoeff(item.first); // Check if species was found in the database of critical @@ -494,12 +494,12 @@ void PengRobinson::initThermo() double PengRobinson::sresid() const { double molarV = molarVolume(); - double hh = m_b_current / molarV; + double hh = m_b / molarV; double zz = z(); double alpha_1 = daAlpha_dT(); - double vpb = molarV + (1.0 + M_SQRT2) * m_b_current; - double vmb = molarV + (1.0 - M_SQRT2) * m_b_current; - double fac = alpha_1 / (2.0 * M_SQRT2 * m_b_current); + 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; } @@ -510,15 +510,15 @@ double PengRobinson::hresid() const double zz = z(); double aAlpha_1 = daAlpha_dT(); double T = temperature(); - double vpb = molarV + (1 + M_SQRT2) * m_b_current; - double vmb = molarV + (1 - M_SQRT2) * m_b_current; - double fac = 1 / (2.0 * M_SQRT2 * m_b_current); - return GasConstant * T * (zz - 1.0) + fac * log(vpb / vmb) * (T * aAlpha_1 - m_aAlpha_current); + 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_current * 1.1; + double v = m_b * 1.1; double atmp; double btmp; double aAlphatmp; @@ -551,7 +551,7 @@ double PengRobinson::liquidVolEst(double T, double& presGuess) const double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, double rhoGuess) { - // It's necessary to set the temperature so that m_aAlpha_current is set correctly. + // It's necessary to set the temperature so that m_aAlpha_mix is set correctly. setTemperature(T); double tcrit = critTemperature(); double mmw = meanMolecularWeight(); @@ -566,7 +566,7 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do } double volGuess = mmw / rhoGuess; - m_NSolns = solveCubic(T, presPa, m_a_current, m_b_current, m_aAlpha_current, m_Vroot); + m_NSolns = solveCubic(T, presPa, m_a, m_b, m_aAlpha_mix, m_Vroot); double molarVolLast = m_Vroot[0]; if (m_NSolns >= 2) { @@ -606,7 +606,7 @@ double PengRobinson::densSpinodalLiquid() const { double Vroot[3]; double T = temperature(); - int nsol = solveCubic(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); + int nsol = solveCubic(T, pressure(), m_a, m_b, m_aAlpha_mix, Vroot); if (nsol != 3) { return critDensity(); } @@ -628,7 +628,7 @@ double PengRobinson::densSpinodalGas() const { double Vroot[3]; double T = temperature(); - int nsol = solveCubic(T, pressure(), m_a_current, m_b_current, m_aAlpha_current, Vroot); + int nsol = solveCubic(T, pressure(), m_a, m_b, m_aAlpha_mix, Vroot); if (nsol != 3) { return critDensity(); } @@ -648,19 +648,19 @@ double PengRobinson::densSpinodalGas() const double PengRobinson::pressureCalc(double T, double molarVol) const { - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - double pres = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; + double den = molarVol * molarVol + 2 * molarVol * m_b - m_b * m_b; + double pres = GasConstant * T / (molarVol - m_b) - m_aAlpha_mix / den; return pres; } double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const { - double den = molarVol * molarVol + 2 * molarVol * m_b_current - m_b_current * m_b_current; - presCalc = GasConstant * T / (molarVol - m_b_current) - m_aAlpha_current / den; + 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_current; - double vmb = molarVol - m_b_current; - double dpdv = -GasConstant * T / (vmb * vmb) + 2 * m_aAlpha_current * vpb / (den*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; } @@ -671,8 +671,8 @@ void PengRobinson::calculatePressureDerivatives() const double pres; m_dpdV = dpdVCalc(T, mv, pres); - double vmb = mv - m_b_current; - double den = mv * mv + 2 * mv * m_b_current - m_b_current * m_b_current; + double vmb = mv - m_b; + double den = mv * mv + 2 * mv * m_b - m_b * m_b; m_dpdT = (GasConstant / vmb - daAlpha_dT() / den); } @@ -682,18 +682,18 @@ void PengRobinson::updateMixingExpressions() // Update individual alpha for (size_t j = 0; j < m_kk; j++) { - double critTemp_j = speciesCritTemperature(m_a_vec_Curr(j,j), m_b_vec_Curr[j]); - double sqt_alpha = 1 + m_kappa_vec[j] * (1 - sqrt(temp / critTemp_j)); - m_alpha_vec_Curr[j] = sqt_alpha*sqt_alpha; + 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_vec_Curr(i, j) = sqrt(m_alpha_vec_Curr[i] * m_alpha_vec_Curr[j]) * m_a_vec_Curr(i,j); + m_aAlpha_binary(i, j) = sqrt(m_alpha[i] * m_alpha[j]) * m_a_coeffs(i,j); } } - calculateAB(m_a_current,m_b_current,m_aAlpha_current); + calculateAB(m_a,m_b,m_aAlpha_mix); } void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) const @@ -702,11 +702,11 @@ void PengRobinson::calculateAB(double& aCalc, double& bCalc, double& aAlphaCalc) aCalc = 0.0; aAlphaCalc = 0.0; for (size_t i = 0; i < m_kk; i++) { - bCalc += moleFractions_[i] * m_b_vec_Curr[i]; + bCalc += moleFractions_[i] * m_b_coeffs[i]; for (size_t j = 0; j < m_kk; j++) { - double a_vec_Curr = m_a_vec_Curr(i, j); + double a_vec_Curr = m_a_coeffs(i, j); aCalc += a_vec_Curr * moleFractions_[i] * moleFractions_[j]; - aAlphaCalc += m_aAlpha_vec_Curr(i, j) * moleFractions_[i] * moleFractions_[j]; + aAlphaCalc += m_aAlpha_binary(i, j) * moleFractions_[i] * moleFractions_[j]; } } } @@ -716,19 +716,19 @@ 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_vec_Curr(i,i), m_b_vec_Curr[i]); + 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_vec[i]; - m_dalphadT_vec_Curr[i] = coeff1 * (k*k*coeff2 - k); + 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_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (m_alpha_vec_Curr[i] * m_alpha_vec_Curr[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_vec_Curr[j] * m_alpha_vec_Curr[i] + m_dalphadT_vec_Curr[i] * m_alpha_vec_Curr[j]); + * (m_dalphadT[j] * m_alpha[i] + m_dalphadT[i] * m_alpha[j]); } } return daAlphadT; @@ -737,27 +737,27 @@ double PengRobinson::daAlpha_dT() const double PengRobinson::d2aAlpha_dT2() const { for (size_t i = 0; i < m_kk; i++) { - double Tcrit_i = speciesCritTemperature(m_a_vec_Curr(i, i), m_b_vec_Curr[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_vec[i]; - m_dalphadT_vec_Curr[i] = coeff1 * (k*k*coeff2 - k); + 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_vec_Curr[i]; + double alphai = m_alpha[i]; for (size_t j = 0; j < m_kk; j++) { - double alphaj = m_alpha_vec_Curr[j]; + double alphaj = m_alpha[j]; double alphaij = alphai * alphaj; - double temp = 0.5 * sqrt((m_a_vec_Curr(i, i) * m_a_vec_Curr(j, j)) / (alphaij)); - double num = (m_dalphadT_vec_Curr[j] * alphai + m_dalphadT_vec_Curr[i] * 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_vec_Curr[i] * m_dalphadT_vec_Curr[j]; + 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); } } @@ -766,20 +766,20 @@ double PengRobinson::d2aAlpha_dT2() const void PengRobinson::calcCriticalConditions(double& pc, double& tc, double& vc) const { - if (m_b_current <= 0.0) { + if (m_b <= 0.0) { tc = 1000000.; pc = 1.0E13; vc = omega_vc * GasConstant * tc / pc; return; } - if (m_a_current <= 0.0) { + if (m_a <= 0.0) { tc = 0.0; pc = 0.0; - vc = 2.0 * m_b_current; + vc = 2.0 * m_b; return; } - tc = m_a_current * omega_b / (m_b_current * omega_a * GasConstant); - pc = omega_b * GasConstant * tc / m_b_current; + tc = m_a * omega_b / (m_b * omega_a * GasConstant); + pc = omega_b * GasConstant * tc / m_b; vc = omega_vc * GasConstant * tc / pc; } From f934e503c83796ac3784fc54451730c9296ee21c Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 26 Mar 2021 13:38:53 -0600 Subject: [PATCH 099/110] Removing the unused function 'pressureCalc()' --- include/cantera/thermo/PengRobinson.h | 1 - include/cantera/thermo/RedlichKwongMFTP.h | 1 - src/thermo/PengRobinson.cpp | 7 ------- src/thermo/RedlichKwongMFTP.cpp | 8 -------- 4 files changed, 17 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index 5f19faec9b..bff58ea46f 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -195,7 +195,6 @@ class PengRobinson : public MixtureFugacityTP virtual double densSpinodalLiquid() const; virtual double densSpinodalGas() const; - virtual double pressureCalc(double TKelvin, double molarVol) const; virtual double dpdVCalc(double TKelvin, double molarVol, double& presCalc) const; // Special functions not inherited from MixtureFugacityTP diff --git a/include/cantera/thermo/RedlichKwongMFTP.h b/include/cantera/thermo/RedlichKwongMFTP.h index 08a11c62cb..f28b58bb13 100644 --- a/include/cantera/thermo/RedlichKwongMFTP.h +++ b/include/cantera/thermo/RedlichKwongMFTP.h @@ -210,7 +210,6 @@ class RedlichKwongMFTP : public MixtureFugacityTP 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 diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index a7086564de..d9cccd338d 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -646,13 +646,6 @@ double PengRobinson::densSpinodalGas() const return mmw / (0.5 * (vv.first + vv.second)); } -double PengRobinson::pressureCalc(double T, double molarVol) const -{ - double den = molarVol * molarVol + 2 * molarVol * m_b - m_b * m_b; - double pres = GasConstant * T / (molarVol - m_b) - m_aAlpha_mix / den; - return pres; -} - double PengRobinson::dpdVCalc(double T, double molarVol, double& presCalc) const { double den = molarVol * molarVol + 2 * molarVol * m_b - m_b * m_b; diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 51b4d389ba..b31b44586d 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -830,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); From e835d172db19b96b06d0708acd6bab84d9b5898e Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 26 Mar 2021 13:42:03 -0600 Subject: [PATCH 100/110] Fixing error in 'getPartialMolarIntEnergies' function --- src/thermo/PengRobinson.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index d9cccd338d..872e5f6b7b 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -298,12 +298,11 @@ void PengRobinson::getPartialMolarEntropies(double* sbar) const void PengRobinson::getPartialMolarIntEnergies(double* ubar) const { // u_i = h_i - p*v_i - double* vbar = 0; double p = pressure(); getPartialMolarEnthalpies(ubar); - getPartialMolarVolumes(vbar); + getPartialMolarVolumes(m_tmpV.data()); for (size_t k = 0; k < m_kk; k++) { - ubar[k] = ubar[k] - p*vbar[k]; + ubar[k] = ubar[k] - p*m_tmpV[k]; } } From 2ea6496b7e315c98abf0e5a3c8e0e9ae862f073a Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 26 Mar 2021 14:20:46 -0600 Subject: [PATCH 101/110] Fixing indentations and removing redundant statements --- include/cantera/thermo/MixtureFugacityTP.h | 3 +- src/thermo/PengRobinson.cpp | 127 ++++++++++----------- src/thermo/ThermoFactory.cpp | 1 - 3 files changed, 64 insertions(+), 67 deletions(-) diff --git a/include/cantera/thermo/MixtureFugacityTP.h b/include/cantera/thermo/MixtureFugacityTP.h index e71878c4fc..518c133e40 100644 --- a/include/cantera/thermo/MixtureFugacityTP.h +++ b/include/cantera/thermo/MixtureFugacityTP.h @@ -541,7 +541,7 @@ class MixtureFugacityTP : public ThermoPhase * @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 + * @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 @@ -557,7 +557,6 @@ class MixtureFugacityTP : public ThermoPhase //@} -protected: //! Storage for the current values of the mole fractions of the species /*! * This vector is kept up-to-date when some the setState functions are called. diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 872e5f6b7b..2c020e0645 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -376,72 +376,71 @@ bool PengRobinson::addSpecies(shared_ptr spec) 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; + 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 (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 (vParams <= 0.0) { //Assuming that Pc and Tc are non zero. + throw CanteraError("PengRobinson::getCoeff", + "Critical Temperature must be positive"); } - 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; - + 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); } - - 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; + 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; + } + return spCoeff; } void PengRobinson::initThermo() @@ -556,9 +555,9 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do double mmw = meanMolecularWeight(); if (rhoGuess == -1.0) { if (phaseRequested >= FLUID_LIQUID_0) { - double lqvol = liquidVolEst(T, presPa); - rhoGuess = mmw / lqvol; - } + 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); diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 279ee8daf5..9faeff0f4a 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -90,7 +90,6 @@ ThermoFactory::ThermoFactory() reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); reg("Peng-Robinson", []() { return new PengRobinson(); }); - addAlias("Peng-Robinson", "PengRobinson"); } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) From ffafe405b35c7d9cd25bd40502aba8b9aa504099 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 25 Mar 2021 17:51:58 -0600 Subject: [PATCH 102/110] Modifying comment Co-authored-by: Bryan W. Weber --- src/thermo/MixtureFugacityTP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/thermo/MixtureFugacityTP.cpp b/src/thermo/MixtureFugacityTP.cpp index ae4b11a54b..46d1fa435d 100644 --- a/src/thermo/MixtureFugacityTP.cpp +++ b/src/thermo/MixtureFugacityTP.cpp @@ -788,7 +788,7 @@ void MixtureFugacityTP::_updateReferenceStateThermo() const doublereal pref = refPressure(); if (pref <= 0.0) { throw CanteraError("MixtureFugacityTP::_updateReferenceStateThermo", - "neg ref pressure"); + "negative reference pressure"); } } } From 228d8025a199428e4727ec6eaca4be1871a8e76b Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 25 Mar 2021 17:55:09 -0600 Subject: [PATCH 103/110] Fixing indentation Co-authored-by: Bryan W. Weber --- include/cantera/thermo/PengRobinson.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index bff58ea46f..c95412c4ec 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -29,7 +29,7 @@ class PengRobinson : public MixtureFugacityTP * first phase definition in the input file will be used. */ explicit PengRobinson(const std::string& infile="", - const std::string& id=""); + const std::string& id=""); virtual std::string type() const { return "PengRobinson"; From 97895552ed8cb4bd0b3a44274411a6717d339b6a Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 14 Apr 2021 17:20:02 -0600 Subject: [PATCH 104/110] Renaming w_ac to acentric_factor --- src/thermo/PengRobinson.cpp | 2 +- test/data/co2_PR_example.yaml | 14 +++++++------- test/data/thermo-models.yaml | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 2c020e0645..35dcf94a4b 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -457,7 +457,7 @@ void PengRobinson::initThermo() } double b = eos.convert("b", "m^3/kmol"); // unitless acentric factor: - double w = eos["w_ac"].asDouble(); + double w = eos["acentric_factor"].asDouble(); setSpeciesCoeffs(item.first, a0, b, w); if (eos.hasKey("binary-a")) { diff --git a/test/data/co2_PR_example.yaml b/test/data/co2_PR_example.yaml index fddc44d2e0..4f67f4e03a 100644 --- a/test/data/co2_PR_example.yaml +++ b/test/data/co2_PR_example.yaml @@ -25,7 +25,7 @@ species: model: Peng-Robinson a: 3.958134E+11 b: 26.6275 - w_ac: 0.228 + acentric_factor: 0.228 - name: H2O composition: {H: 2, O: 1} thermo: @@ -41,7 +41,7 @@ species: model: Peng-Robinson a: 5.998873E+11 b: 18.9714 - w_ac: 0.344 + acentric_factor: 0.344 - name: H2 composition: {H: 2} thermo: @@ -57,7 +57,7 @@ species: model: Peng-Robinson a: 2.668423E+10 b: 16.5478 - w_ac: -0.22 + acentric_factor: -0.22 - name: CO composition: {C: 1, O: 1} thermo: @@ -73,7 +73,7 @@ species: model: Peng-Robinson a: 1.607164E+11 b: 24.6549 - w_ac: 0.049 + acentric_factor: 0.049 - name: CH4 composition: {C: 1, H: 4} thermo: @@ -89,7 +89,7 @@ species: model: Peng-Robinson a: 2.496344E+11 b: 26.8028 - w_ac: 0.01 + acentric_factor: 0.01 - name: O2 composition: {O: 2} thermo: @@ -105,7 +105,7 @@ species: model: Peng-Robinson a: 1.497732E+11 b: 19.8281 - w_ac: 0.022 + acentric_factor: 0.022 - name: N2 composition: {N: 2} thermo: @@ -121,7 +121,7 @@ species: model: Peng-Robinson a: 1.485031E+11 b: 28.0810 - w_ac: 0.04 + acentric_factor: 0.04 reactions: diff --git a/test/data/thermo-models.yaml b/test/data/thermo-models.yaml index 5965e41b22..87ebe7cae6 100644 --- a/test/data/thermo-models.yaml +++ b/test/data/thermo-models.yaml @@ -580,7 +580,7 @@ pr-species: units: {length: cm, quantity: mol} a: 2.668423E+10 b: 16.5478 - w_ac: -0.22 + acentric_factor: -0.22 - name: H2O composition: {H: 2, O: 1} thermo: @@ -596,7 +596,7 @@ pr-species: units: {length: cm, quantity: mol} a: 5.998873E+11 b: 18.9714 - w_ac: 0.344 + acentric_factor: 0.344 binary-a: H2: 4 bar*cm^6/mol^2 CO2: 7.897e7 bar*cm^6/mol^2 @@ -615,7 +615,7 @@ pr-species: units: {length: cm, quantity: mol} a: 3.958134E+11 b: 26.6275 - w_ac: 0.228 + acentric_factor: 0.228 HMW-species: From c5d40617ccc2022d985cfbd93f2060a828a403bb Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Wed, 14 Apr 2021 17:38:54 -0600 Subject: [PATCH 105/110] Modifying 'getPressure' test to cover a wider range of temperatures --- test/thermo/PengRobinson_Test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 9085f1cb7a..8114517f73 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -216,9 +216,9 @@ TEST_F(PengRobinson_Test, getPressure) //Calculate kappa value kappa = 0.37464 + 1.54226*acc_factor - 0.26992*acc_factor*acc_factor; - for (int i = 0; i<10; i++) + for (int i = 0; i < 15; i++) { - const double temp = 296 + i * 2; + const double temp = 296 + i * 50; set_r(1.0); test_phase->setState_TR(temp, rho); const double Tcrit = test_phase->critTemperature(); From 88a10c037dbdaeb5f1148d059f32061f5c42ee33 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 15 Apr 2021 14:13:41 -0600 Subject: [PATCH 106/110] Adding a test to validate P-R EoS with CoopProp --- test/thermo/PengRobinson_Test.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/thermo/PengRobinson_Test.cpp b/test/thermo/PengRobinson_Test.cpp index 8114517f73..c0c1a3031d 100644 --- a/test/thermo/PengRobinson_Test.cpp +++ b/test/thermo/PengRobinson_Test.cpp @@ -313,4 +313,32 @@ TEST_F(PengRobinson_Test, cpValidate) } } +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); + } +} }; From d0f66ce944763b7f02a7bb2b163f8fa9b100f546 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 15 Apr 2021 15:14:47 -0600 Subject: [PATCH 107/110] Modifying the cubic solver test --- test/thermo/cubicSolver_Test.cpp | 46 ++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/test/thermo/cubicSolver_Test.cpp b/test/thermo/cubicSolver_Test.cpp index af777f8f38..404b34f53e 100644 --- a/test/thermo/cubicSolver_Test.cpp +++ b/test/thermo/cubicSolver_Test.cpp @@ -12,6 +12,14 @@ class cubicSolver_Test : public testing::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; }; @@ -32,21 +40,22 @@ TEST_F(cubicSolver_Test, solve_cubic) // 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; + double temp, pres, alpha, rho, p; int nSolnValues; double Vroot[3]; const double expected_result[3] = { - 24.810677442023493, - 0.065859466219402446, - 0.072816004568591469 + 24.809417072270659, + 0.063638901847459045, + 0.10521518550521104 }; //Vapor phase -> nSolnValues = 1 @@ -59,6 +68,13 @@ TEST_F(cubicSolver_Test, solve_cubic) 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; @@ -69,13 +85,27 @@ TEST_F(cubicSolver_Test, solve_cubic) EXPECT_NEAR(expected_result[1], Vroot[0], 1.e-6); EXPECT_NEAR(nSolnValues, -1, 1.e-6); - //Near critical point -> nSolnValues = -1 - + // 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, -1, 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); } }; From f892c201dc02e35e8eb29179931963eee6416470 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Thu, 15 Apr 2021 16:45:18 -0600 Subject: [PATCH 108/110] Adding a function to calculate partial molar specific heats --- src/thermo/PengRobinson.cpp | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 35dcf94a4b..803646f96c 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -308,8 +308,54 @@ void PengRobinson::getPartialMolarIntEnergies(double* ubar) const 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 From a9e8c4d01f6785a8da6d7e47c77ba3693018c001 Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 16 Apr 2021 09:16:41 -0600 Subject: [PATCH 109/110] Fixing indentation --- src/thermo/PengRobinson.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 803646f96c..6ab0dc8ea3 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -604,9 +604,9 @@ double PengRobinson::densityCalc(double T, double presPa, int phaseRequested, do 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); + } else { + // Assume the Gas phase initial guess, if nothing is specified to the routine + rhoGuess = presPa * mmw / (GasConstant * T); } double volGuess = mmw / rhoGuess; From 5a7a9381d054315619709f67051956c375ebd7ef Mon Sep 17 00:00:00 2001 From: Gandhali Kogekar Date: Fri, 16 Apr 2021 10:02:50 -0600 Subject: [PATCH 110/110] Fixing an error in 'setBinaryCoeffs()' --- include/cantera/thermo/PengRobinson.h | 2 +- src/thermo/PengRobinson.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/cantera/thermo/PengRobinson.h b/include/cantera/thermo/PengRobinson.h index c95412c4ec..ba7577d125 100644 --- a/include/cantera/thermo/PengRobinson.h +++ b/include/cantera/thermo/PengRobinson.h @@ -183,7 +183,7 @@ class PengRobinson : public MixtureFugacityTP * @param alpha dimensionless function of T_r and \omega */ void setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a, double alpha); + const std::string& species_j, double a); protected: // Special functions inherited from MixtureFugacityTP diff --git a/src/thermo/PengRobinson.cpp b/src/thermo/PengRobinson.cpp index 6ab0dc8ea3..f4032cdb20 100644 --- a/src/thermo/PengRobinson.cpp +++ b/src/thermo/PengRobinson.cpp @@ -81,7 +81,7 @@ void PengRobinson::setSpeciesCoeffs(const std::string& species, double a, double } void PengRobinson::setBinaryCoeffs(const std::string& species_i, - const std::string& species_j, double a0, double alpha) + const std::string& species_j, double a0) { size_t ki = speciesIndex(species_i); if (ki == npos) { @@ -95,7 +95,9 @@ void PengRobinson::setBinaryCoeffs(const std::string& species_i, } m_a_coeffs(ki, kj) = m_a_coeffs(kj, ki) = a0; - m_aAlpha_binary(ki, kj) = m_aAlpha_binary(kj, ki) = a0*alpha; + // 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 ------------------------- @@ -497,7 +499,7 @@ void PengRobinson::initThermo() if (item.second->input.hasKey("equation-of-state")) { auto eos = item.second->input["equation-of-state"].getMapWhere( "model", "Peng-Robinson"); - double a0 = 0, a1 = 0; + double a0 = 0; if (eos["a"].isScalar()) { a0 = eos.convert("a", "Pa*m^6/kmol^2"); } @@ -510,11 +512,11 @@ void PengRobinson::initThermo() AnyMap& binary_a = eos["binary-a"].as(); const UnitSystem& units = binary_a.units(); for (auto& item2 : binary_a) { - double a0 = 0, a1 = 0; + double a0 = 0; if (item2.second.isScalar()) { a0 = units.convert(item2.second, "Pa*m^6/kmol^2"); } - setBinaryCoeffs(item.first, item2.first, a0, a1); + setBinaryCoeffs(item.first, item2.first, a0); } } } else {