/** * @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). * */ /* * Copyright (2005) Sandia Corporation. Under the terms of * Contract DE-AC04-94AL85000 with Sandia Corporation, the * U.S. Government retains certain rights in this software. */ /* * $Author: hkmoffa $ * $Date: 2010-01-17 12:08:00 -0700 (Sun, 17 Jan 2010) $ * $Revision: 388 $ */ // turn off warnings under Windows #ifdef WIN32 #pragma warning(disable:4786) #pragma warning(disable:4503) #endif #include "cantera/thermo/MixtureFugacityTP.h" #include "cantera/thermo/VPSSMgr.h" #include "cantera/thermo/PDSS.h" using namespace std; namespace Cantera { //==================================================================================================================== /* * Default constructor */ MixtureFugacityTP::MixtureFugacityTP() : ThermoPhase(), m_Pcurrent(-1.0), moleFractions_(0), iState_(FLUID_GAS), forcedState_(FLUID_UNDEFINED), m_Tlast_ref(-1.0), m_logc0(0.0), m_h0_RT(0), m_cp0_R(0), m_g0_RT(0), m_s0_R(0) { } //==================================================================================================================== /* * Copy Constructor: * * Note this stuff will not work until the underlying phase * has a working copy constructor. * * The copy constructor just calls the assignment operator * to do the heavy lifting. */ MixtureFugacityTP::MixtureFugacityTP(const MixtureFugacityTP& b) : ThermoPhase(), m_Pcurrent(-1.0), moleFractions_(0), iState_(FLUID_GAS), forcedState_(FLUID_UNDEFINED), m_Tlast_ref(-1.0), m_logc0(0.0), m_h0_RT(0), m_cp0_R(0), m_g0_RT(0), m_s0_R(0) { MixtureFugacityTP::operator=(b); } //==================================================================================================================== /* * operator=() * * Note this stuff will not work until the underlying phase * has a working assignment operator */ MixtureFugacityTP& MixtureFugacityTP::operator=(const MixtureFugacityTP& b) { if (&b != this) { /* * Mostly, this is a passthrough to the underlying * assignment operator for the ThermoPhase parent object. */ ThermoPhase::operator=(b); /* * However, we have to handle data that we own. */ m_Pcurrent = b.m_Pcurrent; moleFractions_ = b.moleFractions_; iState_ = b.iState_; forcedState_ = b.forcedState_; m_Tlast_ref = b.m_Tlast_ref; m_logc0 = b.m_logc0; m_h0_RT = b.m_h0_RT; m_cp0_R = b.m_cp0_R; m_g0_RT = b.m_g0_RT; m_s0_R = b.m_s0_R; /* * The VPSSMgr object contains shallow pointers. Whenever you have shallow * pointers, they have to be fixed up to point to the correct objects refering * back to this ThermoPhase's properties. */ //m_VPSS_ptr->initAllPtrs(this, m_spthermo); /* * The PDSS objects contains shallow pointers. Whenever you have shallow * pointers, they have to be fixed up to point to the correct objects refering * back to this ThermoPhase's properties. This function also sets m_VPSS_ptr * so it occurs after m_VPSS_ptr is set. */ /* * Ok, the VPSSMgr object is ready for business. * We need to resync the temperature and the pressure of the new standard states * with what is stored in this object. */ // m_VPSS_ptr->setState_TP(m_Tlast_ss, m_Plast_ss); } return *this; } //==================================================================================================================== /* * ~MixtureFugacityTP(): (virtual) * */ MixtureFugacityTP::~MixtureFugacityTP() { } /* * Duplication function. * This calls the copy constructor for this object. */ ThermoPhase* MixtureFugacityTP::duplMyselfAsThermoPhase() const { MixtureFugacityTP* vptp = new MixtureFugacityTP(*this); return (ThermoPhase*) vptp; } //==================================================================================================================== // This method returns the convention used in specification // of the standard state, of which there are currently two, // temperature based, and variable pressure based. /* * Currently, there are two standard state conventions: * - Temperature-based activities * cSS_CONVENTION_TEMPERATURE 0 * - default * * - Variable Pressure and Temperature -based activities * cSS_CONVENTION_VPSS 1 */ int MixtureFugacityTP::standardStateConvention() const { return cSS_CONVENTION_TEMPERATURE; } //==================================================================================================================== // 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. */ void MixtureFugacityTP::setForcedSolutionBranch(int solnBranch) { forcedState_ = 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. */ int MixtureFugacityTP::forcedSolutionBranch() const { return forcedState_; } //==================================================================================================================== // 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. */ int MixtureFugacityTP::reportSolnBranchActual() const { return iState_; } //==================================================================================================================== /* * ------------Molar Thermodynamic Properties ------------------------- */ //==================================================================================================================== doublereal MixtureFugacityTP::err(std::string msg) const { throw CanteraError("MixtureFugacityTP","Base class method " +msg+" called. Equation of state type: "+int2str(eosType())); return 0; } //==================================================================================================================== /* * ---- 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. */ void MixtureFugacityTP::getChemPotentials_RT(doublereal* muRT) const { getChemPotentials(muRT); doublereal invRT = 1.0 / _RT(); for (size_t k = 0; k < m_kk; k++) { muRT[k] *= invRT; } } //==================================================================================================================== /* * ----- 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); doublereal RT = _RT(); double tmp = log(pressure() /m_spthermo->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); } //================================================================================================ #ifdef H298MODIFY_CAPABILITY // Modify the value of the 298 K Heat of Formation of one species in the phase (J kmol-1) /* * The 298K heat of formation is defined as the enthalpy change to create the standard state * of the species from its constituent elements in their standard states at 298 K and 1 bar. * * @param k Species k * @param Hf298New Specify the new value of the Heat of Formation at 298K and 1 bar */ void MixtureFugacityTP::modifyOneHf298SS(const int k, const doublereal Hf298New) { m_spthermo->modifyOneHf298(k, Hf298New); m_Tlast_ref += 0.0001234; } #endif //==================================================================================================================== /* * Get the array of nondimensional entropy functions for the * standard state species * at the current T and P of the solution. */ void MixtureFugacityTP::getEntropy_R(doublereal* sr) const { _updateReferenceStateThermo(); copy(m_s0_R.begin(), m_s0_R.end(), sr); double tmp = log(pressure() /m_spthermo->refPressure()); for (size_t k = 0; k < m_kk; k++) { sr[k] -= tmp; } } //==================================================================================================================== /* * Get the nondimensional gibbs function for the species * standard states at the current T and P of the solution. */ void MixtureFugacityTP::getGibbs_RT(doublereal* grt) const { _updateReferenceStateThermo(); copy(m_g0_RT.begin(), m_g0_RT.end(), grt); double tmp = log(pressure() /m_spthermo->refPressure()); for (size_t k = 0; k < m_kk; k++) { grt[k] += tmp; } } //==================================================================================================================== /* * get the pure Gibbs free energies of each species assuming * it is in its standard state. This is the same as * getStandardChemPotentials(). */ void MixtureFugacityTP::getPureGibbs(doublereal* g) const { _updateReferenceStateThermo(); scale(m_g0_RT.begin(), m_g0_RT.end(), g, _RT()); double tmp = log(pressure() /m_spthermo->refPressure()); tmp *= _RT(); for (size_t k = 0; k < m_kk; k++) { g[k] += tmp; } } //==================================================================================================================== /* * Returns the vector of nondimensional * internal Energies of the standard state at the current temperature * and pressure of the solution for each species. */ void MixtureFugacityTP::getIntEnergy_RT(doublereal* urt) const { _updateReferenceStateThermo(); copy(m_h0_RT.begin(), m_h0_RT.end(), urt); doublereal p = pressure(); doublereal tmp = p / _RT(); doublereal v0 = _RT() / p; for (size_t i = 0; i < m_kk; i++) { urt[i] -= tmp * v0; } } //==================================================================================================================== /* * Get the nondimensional heat capacity at constant pressure * function for the species * standard states at the current T and P of the solution. */ void MixtureFugacityTP::getCp_R(doublereal* cpr) const { _updateReferenceStateThermo(); copy(m_cp0_R.begin(), m_cp0_R.end(), cpr); } //==================================================================================================================== /* * Get the molar volumes of the species standard states at the current * T and P of the solution. * units = m^3 / kmol * * @param vol Output vector containing the standard state volumes. * Length: m_kk. */ void MixtureFugacityTP::getStandardVolumes(doublereal* vol) const { _updateReferenceStateThermo(); doublereal v0 = _RT() / pressure(); for (size_t i = 0; i < m_kk; i++) { vol[i]= v0; } } //==================================================================================================================== /* * ----- Thermodynamic Values for the Species Reference States ---- */ /* * Returns the vector of nondimensional enthalpies of the * reference state at the current temperature of the solution and * the reference pressure for the species. */ void MixtureFugacityTP::getEnthalpy_RT_ref(doublereal* hrt) const { _updateReferenceStateThermo(); copy(m_h0_RT.begin(), m_h0_RT.end(), hrt); } //==================================================================================================================== /* * Returns the vector of nondimensional * enthalpies of the reference state at the current temperature * of the solution and the reference pressure for the species. */ void MixtureFugacityTP::getGibbs_RT_ref(doublereal* grt) const { _updateReferenceStateThermo(); copy(m_g0_RT.begin(), m_g0_RT.end(), grt); } //==================================================================================================================== /* * Returns the vector of the * gibbs function of the reference state at the current temperature * of the solution and the reference pressure for the species. * units = J/kmol * * This is filled in here so that derived classes don't have to * take care of it. */ void MixtureFugacityTP::getGibbs_ref(doublereal* g) const { const array_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; } //==================================================================================================================== /* * Returns the vector of nondimensional * entropies of the reference state at the current temperature * of the solution and the reference pressure for the species. */ void MixtureFugacityTP::getEntropy_R_ref(doublereal* er) const { _updateReferenceStateThermo(); copy(m_s0_R.begin(), m_s0_R.end(), er); return; } //==================================================================================================================== /* * Returns the vector of nondimensional * constant pressure heat capacities of the reference state * at the current temperature of the solution * and reference pressure for the species. */ void MixtureFugacityTP::getCp_R_ref(doublereal* cpr) const { _updateReferenceStateThermo(); copy(m_cp0_R.begin(), m_cp0_R.end(), cpr); } //==================================================================================================================== /* * Get the molar volumes of the species reference states at the current * T and reference pressure of the solution. * * units = m^3 / kmol */ void MixtureFugacityTP::getStandardVolumes_ref(doublereal* vol) const { _updateReferenceStateThermo(); double pp = refPressure(); doublereal v0 = _RT() / pp; for (size_t i = 0; i < m_kk; i++) { vol[i]= v0; } } //==================================================================================================================== // Set the initial state of the phase to the conditions specified in the state XML element. /* * * This method sets the temperature, pressure, and mole fraction vector to a set default value. * We modify the default behavior here so that TP is evaluated at the same time. * * @param state AN XML_Node object corresponding to * the "state" entry for this phase in the * input file. */ void MixtureFugacityTP::setStateFromXML(const XML_Node& state) { int doTP = 0; string comp = ctml::getChildValue(state,"moleFractions"); if (comp != "") { // not overloaded in current object -> phase state is not calculated. setMoleFractionsByName(comp); doTP = 1; } else { comp = ctml::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 = ctml::getFloat(state, "temperature", "temperature"); doTP = 1; } if (state.hasChild("pressure")) { double p = ctml::getFloat(state, "pressure", "pressure"); setState_TP(t, p); } else if (state.hasChild("density")) { double rho = ctml::getFloat(state, "density", "density"); setState_TR(t, rho); } else if (doTP) { double rho = State::density(); setState_TR(t, rho); } } //==================================================================================================================== /* * Perform initializations after all species have been * added. */ void MixtureFugacityTP::initThermo() { initLengths(); ThermoPhase::initThermo(); } //==================================================================================================================== /* * Initialize the internal lengths. * (this is not a virtual function) */ void MixtureFugacityTP::initLengths() { m_kk = nSpecies(); moleFractions_.resize(m_kk, 0.0); moleFractions_[0] = 1.0; m_h0_RT.resize(m_kk, 0.0); m_cp0_R.resize(m_kk, 0.0); m_g0_RT.resize(m_kk, 0.0); m_s0_R.resize(m_kk, 0.0); } //==================================================================================================================== void MixtureFugacityTP::setTemperature(const doublereal temp) { _updateReferenceStateThermo(); setState_TR(temperature(), density()); } //==================================================================================================================== void MixtureFugacityTP::setPressure(doublereal p) { setState_TP(temperature(), p); // double chemPot[5], mf[5]; // getMoleFractions(mf); // getChemPotentials(chemPot); // for (int i = 0; i < m_kk; i++) { // printf(" MixFug:setPres: mu(%d = %g) = %18.8g\n", i, mf[i], chemPot[i]); // } } //==================================================================================================================== void MixtureFugacityTP::setMassFractions(const doublereal* const y) { State::setMassFractions(y); getMoleFractions(DATA_PTR(moleFractions_)); } //==================================================================================================================== void MixtureFugacityTP::setMassFractions_NoNorm(const doublereal* const y) { State::setMassFractions_NoNorm(y); getMoleFractions(DATA_PTR(moleFractions_)); } //==================================================================================================================== void MixtureFugacityTP::setMoleFractions(const doublereal* const x) { State::setMoleFractions(x); getMoleFractions(DATA_PTR(moleFractions_)); } //==================================================================================================================== void MixtureFugacityTP::setMoleFractions_NoNorm(const doublereal* const x) { State::setMoleFractions_NoNorm(x); getMoleFractions(DATA_PTR(moleFractions_)); } //==================================================================================================================== void MixtureFugacityTP::setConcentrations(const doublereal* const c) { State::setConcentrations(c); getMoleFractions(DATA_PTR(moleFractions_)); } //==================================================================================================================== void MixtureFugacityTP::setMoleFractions_NoState(const doublereal* const x) { State::setMoleFractions(x); getMoleFractions(DATA_PTR(moleFractions_)); updateMixingExpressions(); } //==================================================================================================================== void MixtureFugacityTP::calcDensity() { err("MixtureFugacityTP::calcDensity() called, but EOS for phase is not known"); } //==================================================================================================================== void MixtureFugacityTP::setState_TP(doublereal t, doublereal pres) { /* * 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(DATA_PTR(moleFractions_)); State::setTemperature(t); _updateReferenceStateThermo(); // Depends on the mole fractions and the temperature updateMixingExpressions(); // setPressure(pres); m_Pcurrent = pres; // double mmw = meanMolecularWeight(); if (forcedState_ == FLUID_UNDEFINED) { double rhoNow = State::density(); double rho = densityCalc(t, pres, iState_, rhoNow); if (rho > 0.0) { State::setDensity(rho); m_Pcurrent = pres; iState_ = phaseState(true); } else { if (rho < -1.5) { rho = densityCalc(t, pres, FLUID_UNDEFINED , rhoNow); if (rho > 0.0) { State::setDensity(rho); m_Pcurrent = pres; iState_ = phaseState(true); } else { throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho"); } } else { throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho"); } } } else if (forcedState_ == FLUID_GAS) { // Normal density calculation if (iState_ < FLUID_LIQUID_0) { double rhoNow = State::density(); double rho = densityCalc(t, pres, iState_, rhoNow); if (rho > 0.0) { State::setDensity(rho); m_Pcurrent = pres; iState_ = phaseState(true); if (iState_ >= FLUID_LIQUID_0) { throw CanteraError("MixtureFugacityTP::setState_TP()", "wrong state"); } } else { throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho"); } } } else if (forcedState_ > FLUID_LIQUID_0) { if (iState_ >= FLUID_LIQUID_0) { double rhoNow = State::density(); double rho = densityCalc(t, pres, iState_, rhoNow); if (rho > 0.0) { State::setDensity(rho); m_Pcurrent = pres; iState_ = phaseState(true); if (iState_ == FLUID_GAS) { throw CanteraError("MixtureFugacityTP::setState_TP()", "wrong state"); } } else { throw CanteraError("MixtureFugacityTP::setState_TP()", "neg rho"); } } } //setTemperature(t); //setPressure(pres); //calcDensity(); } //==================================================================================================================== // Set the internally stored temperature (K) and density (kg/m^3) /* * This overrides the default behavior. In addition to just storing the state in the object, we need to do * an equation of state calculation and figure out what phase state we are in. * * @param t Temperature in kelvin * @param rho Density (kg/m^3) */ void MixtureFugacityTP::setState_TR(doublereal T, doublereal rho) { getMoleFractions(DATA_PTR(moleFractions_)); State::setTemperature(T); _updateReferenceStateThermo(); State::setDensity(rho); doublereal mv = molarVolume(); // depends on mole fraction and temperature updateMixingExpressions(); m_Pcurrent = pressureCalc(T, mv); iState_ = phaseState(true); // printf("setState_TR: state at T = %g, rho = %g, mv = %g, P = %20.13g, iState = %d\n", T, rho, mv, m_Pcurrent, iState_); } //==================================================================================================================== // Set the temperature (K), pressure (Pa), and mole fractions. /* * Note, the mole fractions are set first before the pressure is set. * Setting the pressure may involve the solution of a nonlinear equation. * * @param t Temperature (K) * @param p Pressure (Pa) * @param x Vector of mole fractions. * Length is equal to m_kk. */ void MixtureFugacityTP::setState_TPX(doublereal t, doublereal p, const doublereal* x) { setMoleFractions_NoState(x); setState_TP(t,p); } //==================================================================================================================== /* * Import and initialize a ThermoPhase object * * param phaseNode This object must be the phase node of a * complete XML tree * description of the phase, including all of the * species data. In other words while "phase" must * point to an XML phase object, it must have * sibling nodes "speciesData" that describe * the species in the phase. * param id ID of the phase. If nonnull, a check is done * to see if phaseNode is pointing to the phase * with the correct id. * * This routine initializes the lengths in the current object and * then calls the parent routine. */ void MixtureFugacityTP::initThermoXML(XML_Node& phaseNode, std::string id) { MixtureFugacityTP::initLengths(); //m_VPSS_ptr->initThermo(); // m_VPSS_ptr->initThermoXML(phaseNode, id); ThermoPhase::initThermoXML(phaseNode, id); } //==================================================================================================================== doublereal MixtureFugacityTP::z() const { doublereal p = pressure(); doublereal rho = density(); doublereal mmw = meanMolecularWeight(); doublereal molarV = mmw / rho; doublereal rt = _RT(); doublereal zz = p * molarV / rt; return zz; } //==================================================================================================================== doublereal MixtureFugacityTP::sresid() const { throw CanteraError("MixtureFugacityTP::sresid()", "Base Class: not implemented"); return 0.0; } //==================================================================================================================== doublereal MixtureFugacityTP::hresid() const { throw CanteraError("MixtureFugacityTP::hresid()", "Base Class: not implemented"); return 0.0; } //==================================================================================================================== doublereal MixtureFugacityTP::psatEst(doublereal TKelvin) const { doublereal tcrit = critTemperature(); doublereal pcrit = critPressure(); doublereal tt = tcrit/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 CanteraError("MixtureFugacityTP::liquidVolEst()", "unimplemented"); return 0.0; } //==================================================================================================================== /* * Calculates the density given the temperature and the pressure, * and a guess at the density. Note, below T_c, this is a * multivalued function. This function assumes that the phase is on one side of the vapor dome * or the other. It does not allow for crosses of the vapor dome. * * parameters: * temperature: Kelvin * pressure : Pressure in Pascals (Newton/m**2) * phase : guessed phase of water * : -1: no guessed phase * rhoguess : guessed density of the water * * -1.0 no guessed density * * If a problem is encountered, a negative 1 is returned. * * @TODO make this a const function */ doublereal MixtureFugacityTP::densityCalc(doublereal TKelvin, doublereal presPa, int phase, doublereal rhoguess) { double tcrit = critTemperature(); doublereal mmw = meanMolecularWeight(); // double pcrit = critPressure(); // doublereal deltaGuess = 0.0; 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; doublereal pcheck = 1.0E-30 + 1.0E-8 * presPa; doublereal presBase, dpdVBase, delMV; 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 */ dpdVBase = dpdVCalc(TKelvin, molarVolBase, presBase); /* * If dpdV is positve, 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("", "confused"); } /* * TODO Spawn a calculation for the value of the spinodal point that is * very accurate. Answer the question as to wethera 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) < pcheck) { conv = true; break; } /* * Dampen and crop the update */ doublereal dpdV = dpdVBase; if (n < 10) { dpdV = dpdVBase * 1.5; } // if (dpdV > -0.001) dpdV = -0.001; /* * 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 */ delMV = - (presBase - presPa) / dpdV; if (!gasSide || delMV < 0.0) { if (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) { if (-delMV > 0.5 * (molarVolBase - molarVolSpinodal)) { delMV = - 0.5 * (molarVolBase - molarVolSpinodal); } } } else { if (delMV > 0.0) { if (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 didnot converge"); } else { densBase = mmw / molarVolBase; } return densBase; } //==================================================================================================================== void MixtureFugacityTP::updateMixingExpressions() { } //==================================================================================================================== MixtureFugacityTP::spinodalFunc::spinodalFunc(MixtureFugacityTP* tp) : ResidEval(), m_tp(tp) { } //==================================================================================================================== int MixtureFugacityTP::spinodalFunc::evalSS(const doublereal t, const doublereal* const y, doublereal* const r) { int status = 0; doublereal molarVol = y[0]; doublereal tt = m_tp->temperature(); doublereal pp; doublereal val = m_tp->dpdVCalc(tt, molarVol, pp); r[0] = val; return status; } //==================================================================================================================== // Utility routine in the calculation of the saturation pressure /* * Private routine * * @param TKelvin temperature (kelvin) * @param pres pressure (Pascal) * @param densLiq Output density of liquid * @param densGas output density of gas * @param delGRT output delGRT * * @return Returns zero if both the gas and the liquid states are found for a given pressure. */ 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) { // throw Cantera::CanteraError("MixtureFugacityTP::corr0", // "Error occurred trying to find liquid density at (T,P) = " // + Cantera::fp2str(TKelvin) + " " + Cantera::fp2str(pres)); 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) { //throw Cantera::CanteraError("MixtureFugacityTP::corr0", // "Error occurred trying to find gas density at (T,P) = " // + Cantera::fp2str(TKelvin) + " " + Cantera::fp2str(pres)); if (retn == -1) { throw Cantera::CanteraError("MixtureFugacityTP::corr0", "Error occurred trying to find gas density at (T,P) = " + Cantera::fp2str(TKelvin) + " " + Cantera::fp2str(pres)); } retn = -2; } else { densGasGuess = densGas; setState_TR(TKelvin, densGas); gasGRT = gibbs_mole() / _RT(); } // delGRT = gibbsLiqRT - gibbsGasRT; return retn; } //==================================================================================================================== // 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 paramters 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 MixtureFugacityTP::phaseState(bool checkState) const { int state = iState_; if (checkState) { double t = temperature(); double tcrit = critTemperature(); double rhocrit = critDensity(); if (t >= tcrit) { state = FLUID_SUPERCRIT; return state; } 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; } //==================================================================================================================== // Return the value of the density at the liquid spinodal point (on the liquid side) // for the current temperature. /* * @return returns the density with units of kg m-3 */ doublereal MixtureFugacityTP::densSpinodalLiquid() const { throw CanteraError("", "unimplmented"); return 0.0; } //==================================================================================================================== // Return the value of the density at the gas spinodal point (on the gas side) // for the current temperature. /* * @return returns the density with units of kg m-3 */ doublereal MixtureFugacityTP::densSpinodalGas() const { throw CanteraError("", "unimplmented"); return 0.0; } //==================================================================================================================== // Calculate the saturation pressure at the current mixture content for the given temperature /* * This is a non-const routine that is public. * * 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) * * * * @param TKelvin (input) Temperature (Kelvin) * @param molarVolGas (return) Molar volume of the gas * @param molarVolLiquid (return) Molar volume of the liquid * * @return Returns the saturation pressure at the given temperature * * @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. * */ doublereal MixtureFugacityTP::calculatePsat(doublereal TKelvin, doublereal& molarVolGas, doublereal& molarVolLiquid) { // we need this because this is a non-const routine that is public setTemperature(TKelvin); double tcrit = critTemperature(); double RhoLiquid, RhoGas; double RhoLiquidGood, RhoGasGood; double densSave = density(); double tempSave = temperature(); double pres; doublereal mw = meanMolecularWeight(); if (TKelvin < tcrit) { pres = psatEst(TKelvin); // trial value = Psat from correlation int i; doublereal volLiquid = liquidVolEst(TKelvin, pres); RhoLiquidGood = mw / volLiquid; RhoGasGood = pres * mw / (GasConstant * TKelvin); doublereal delGRT, liqGRT, gasGRT; int stab; doublereal presLast = pres; #ifdef DDDD double pVec[100]; int n = 0; for (int i = 0; i < 50; i++) { pVec[n++] = 3.40E6 + 0.01E5 * i; } for (int i = 0; i < 50; i++) { stab = corr0(TKelvin, pVec[i], RhoLiquid, RhoGas, liqGRT, gasGRT); printf("p = %g, T = %g, stab = %d, Rl = %g Rg = %g, Gl = %g, Gg = %g\n", pVec[i], TKelvin, stab, RhoLiquid, RhoGas,liqGRT, gasGRT); } #endif /* * First part of the calculation involves finding a pressure at which the * gas and the liquid state coexists. */ doublereal presLiquid; 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) { if (presGas == presLiquid) { pres = presGas; goto startIteration; } pres = 0.5 * (presLiquid + presGas); bool goodLiq; bool goodGas; for (int i = 0; i < 50; i++) { doublereal densLiquid = densityCalc(TKelvin, pres, FLUID_LIQUID_0, RhoLiquidGood); if (densLiquid <= 0.0) { goodLiq = false; } else { goodLiq = true; RhoLiquidGood = densLiquid; presLiquid = pres; } doublereal 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) { printf("error coundn't find a starting pressure\n"); return (0.0); } if (presGas != presLiquid) { printf("error coundn't find a starting pressure\n"); return (0.0); } startIteration: pres = presGas; presLast = pres; RhoGas = RhoGasGood; RhoLiquid = RhoLiquidGood; /* * Now that we have found a good pressure we can proceed with the algorithm. */ for (i = 0; i < 20; i++) { 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); RhoGas = density(); molarVolGas = mw / RhoGas; molarVolLiquid = molarVolGas; setState_TR(tempSave, densSave); } return pres; } //==================================================================================================================== // Calculate the pressure given the temperature and the molar volume doublereal MixtureFugacityTP::pressureCalc(doublereal TKelvin, doublereal molarVol) const { throw CanteraError("MixtureFugacityTP::pressureCalc", "unimplemented"); return 0.0; } //==================================================================================================================== // Calculate the pressure given the temperature and the molar volume doublereal MixtureFugacityTP::dpdVCalc(doublereal TKelvin, doublereal molarVol, doublereal& presCalc) const { throw CanteraError("MixtureFugacityTP::dpdVCalc", "unimplemented"); return 0.0; } //==================================================================================================================== /* * void _updateStandardStateThermo() (protected, virtual, const) * * If m_useTmpStandardStateStorage is true, * This function must be called for every call to functions in this * class that need standard state properties. * Child classes may require that it be called even if m_useTmpStandardStateStorage * is not true. * It checks to see whether the temperature has changed and * thus the ss thermodynamics functions for all of the species * must be recalculated. * * This */ 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_ref != Tnow) { m_spthermo->update(Tnow, &m_cp0_R[0], &m_h0_RT[0], &m_s0_R[0]); m_Tlast_ref = 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"); } m_logc0 = log(pref/(GasConstant * Tnow)); } } //==================================================================================================================== }