cantera/src/thermo/WaterPropsIAPWS.cpp

634 lines
19 KiB
C++

/**
* @file WaterPropsIAPWS.cpp
* Definitions for a class for calculating the equation of state of water
* from the IAPWS 1995 Formulation based on the steam tables thermodynamic
* basis (See class \link Cantera::WaterPropsIAPWS WaterPropsIAPWS\endlink).
*/
/*
* Copyright (2006) Sandia Corporation. Under the terms of
* Contract DE-AC04-94AL85000 with Sandia Corporation, the
* U.S. Government retains certain rights in this software.
*/
#include "cantera/thermo/WaterPropsIAPWS.h"
#include "cantera/base/ctexceptions.h"
#include "cantera/base/stringUtils.h"
namespace Cantera
{
// Critical Point values of water in mks units
//! Critical Temperature value (kelvin)
const doublereal T_c = 647.096;
//! Critical Pressure (Pascals)
static const doublereal P_c = 22.064E6;
//! Value of the Density at the critical point (kg m-3)
const doublereal Rho_c = 322.;
//! Molecular Weight of water that is consistent with the paper (kg kmol-1)
static const doublereal M_water = 18.015268;
//! Gas constant that is quoted in the paper
/*
* Note, this is the Rgas value quoted in the paper. For consistency
* we have to use that value and not the updated value
*
* The Ratio of R/M = 0.46151805 kJ kg-1 K-1 , which is Eqn. (6.3) in the paper.
*/
static const doublereal Rgas = 8.314371E3; // Joules kmol-1 K-1
// Base constructor
WaterPropsIAPWS::WaterPropsIAPWS() :
tau(-1.0),
delta(-1.0),
iState(-30000)
{
}
WaterPropsIAPWS::WaterPropsIAPWS(const WaterPropsIAPWS& b) :
tau(b.tau),
delta(b.delta),
iState(b.iState)
{
m_phi.tdpolycalc(tau, delta);
}
WaterPropsIAPWS& WaterPropsIAPWS::operator=(const WaterPropsIAPWS& b)
{
if (this == &b) {
return *this;
}
tau = b.tau;
delta = b.delta;
iState = b.iState;
m_phi.tdpolycalc(tau, delta);
return *this;
}
void WaterPropsIAPWS::calcDim(doublereal temperature, doublereal rho)
{
tau = T_c / temperature;
delta = rho / Rho_c;
// Determine the internal state
if (temperature > T_c) {
iState = WATER_SUPERCRIT;
} else {
if (delta < 1.0) {
iState = WATER_GAS;
} else {
iState = WATER_LIQUID;
}
}
}
doublereal WaterPropsIAPWS::helmholtzFE() const
{
doublereal retn = m_phi.phi(tau, delta);
doublereal temperature = T_c/tau;
doublereal RT = Rgas * temperature;
return retn * RT;
}
doublereal WaterPropsIAPWS::pressure() const
{
doublereal retn = m_phi.pressureM_rhoRT(tau, delta);
doublereal rho = delta * Rho_c;
doublereal temperature = T_c / tau;
return retn * rho * Rgas * temperature/M_water;
}
doublereal WaterPropsIAPWS::density(doublereal temperature, doublereal pressure,
int phase, doublereal rhoguess)
{
doublereal deltaGuess = 0.0;
if (rhoguess == -1.0) {
if (phase != -1) {
if (temperature > T_c) {
rhoguess = pressure * M_water / (Rgas * temperature);
} else {
if (phase == WATER_GAS || phase == WATER_SUPERCRIT) {
rhoguess = pressure * M_water / (Rgas * temperature);
} else if (phase == WATER_LIQUID) {
// Provide a guess about the liquid density that is
// relatively high -> convergence from above seems robust.
rhoguess = 1000.;
} else if (phase == WATER_UNSTABLELIQUID || phase == WATER_UNSTABLEGAS) {
throw CanteraError("WaterPropsIAPWS::density",
"Unstable Branch finder is untested");
} else {
throw CanteraError("WaterPropsIAPWS::density",
"unknown state: {}", phase);
}
}
} else {
// Assume the Gas phase initial guess, if nothing is specified to
// the routine
rhoguess = pressure * M_water / (Rgas * temperature);
}
}
doublereal p_red = pressure * M_water / (Rgas * temperature * Rho_c);
deltaGuess = rhoguess / Rho_c;
setState_TR(temperature, rhoguess);
doublereal delta_retn = m_phi.dfind(p_red, tau, deltaGuess);
doublereal density_retn;
if (delta_retn >0.0) {
delta = delta_retn;
// Dimensionalize the density before returning
density_retn = delta_retn * Rho_c;
// Set the internal state -> this may be a duplication. However, let's
// just be sure.
setState_TR(temperature, density_retn);
} else {
density_retn = -1.0;
}
return density_retn;
}
doublereal WaterPropsIAPWS::density_const(doublereal pressure,
int phase, doublereal rhoguess) const
{
doublereal temperature = T_c / tau;
doublereal deltaGuess = 0.0;
doublereal deltaSave = delta;
if (rhoguess == -1.0) {
if (phase != -1) {
if (temperature > T_c) {
rhoguess = pressure * M_water / (Rgas * temperature);
} else {
if (phase == WATER_GAS || phase == WATER_SUPERCRIT) {
rhoguess = pressure * M_water / (Rgas * temperature);
} else if (phase == WATER_LIQUID) {
// Provide a guess about the liquid density that is
// relatively high -> convergence from above seems robust.
rhoguess = 1000.;
} else if (phase == WATER_UNSTABLELIQUID || phase == WATER_UNSTABLEGAS) {
throw CanteraError("WaterPropsIAPWS::density",
"Unstable Branch finder is untested");
} else {
throw CanteraError("WaterPropsIAPWS::density",
"unknown state: {}", phase);
}
}
} else {
// Assume the Gas phase initial guess, if nothing is specified to
// the routine
rhoguess = pressure * M_water / (Rgas * temperature);
}
}
doublereal p_red = pressure * M_water / (Rgas * temperature * Rho_c);
deltaGuess = rhoguess / Rho_c;
delta = deltaGuess;
m_phi.tdpolycalc(tau, delta);
doublereal delta_retn = m_phi.dfind(p_red, tau, deltaGuess);
doublereal density_retn;
if (delta_retn > 0.0) {
delta = delta_retn;
// Dimensionalize the density before returning
density_retn = delta_retn * Rho_c;
} else {
density_retn = -1.0;
}
delta = deltaSave;
m_phi.tdpolycalc(tau, delta);
return density_retn;
}
doublereal WaterPropsIAPWS::density() const
{
return delta * Rho_c;
}
doublereal WaterPropsIAPWS::temperature() const
{
return T_c / tau;
}
doublereal WaterPropsIAPWS::psat_est(doublereal temperature) const
{
// Formula and constants from: "NBS/NRC Steam Tables: Thermodynamic and
// Transport Properties and Computer Programs for Vapor and Liquid States of
// Water in SI Units". L. Haar, J. S. Gallagher, G. S. Kell. Hemisphere
// Publishing. 1984.
static const doublereal A[8] = {
-7.8889166E0,
2.5514255E0,
-6.716169E0,
33.2239495E0,
-105.38479E0,
174.35319E0,
-148.39348E0,
48.631602E0
};
doublereal ps;
if (temperature < 314.) {
doublereal pl = 6.3573118E0 - 8858.843E0 / temperature
+ 607.56335E0 * pow(temperature, -0.6);
ps = 0.1 * exp(pl);
} else {
doublereal v = temperature / 647.25;
doublereal w = fabs(1.0-v);
doublereal b = 0.0;
for (int i = 0; i < 8; i++) {
doublereal z = i + 1;
b += A[i] * pow(w, ((z+1.0)/2.0));
}
doublereal q = b / v;
ps = 22.093*exp(q);
}
// Original correlation was in cgs. Convert to mks
ps *= 1.0E6;
return ps;
}
doublereal WaterPropsIAPWS::isothermalCompressibility() const
{
doublereal dpdrho_val = dpdrho();
doublereal dens = delta * Rho_c;
return 1.0 / (dens * dpdrho_val);
}
doublereal WaterPropsIAPWS::dpdrho() const
{
doublereal retn = m_phi.dimdpdrho(tau, delta);
doublereal temperature = T_c/tau;
return retn * Rgas * temperature / M_water;
}
doublereal WaterPropsIAPWS::coeffPresExp() const
{
return m_phi.dimdpdT(tau, delta);
}
doublereal WaterPropsIAPWS::coeffThermExp() const
{
doublereal kappa = isothermalCompressibility();
doublereal beta = coeffPresExp();
doublereal dens = delta * Rho_c;
return kappa * dens * Rgas * beta / M_water;
}
doublereal WaterPropsIAPWS::Gibbs() const
{
doublereal gRT = m_phi.gibbs_RT();
doublereal temperature = T_c/tau;
return gRT * Rgas * temperature;
}
void WaterPropsIAPWS::corr(doublereal temperature, doublereal pressure,
doublereal& densLiq, doublereal& densGas, doublereal& delGRT)
{
densLiq = density(temperature, pressure, WATER_LIQUID, densLiq);
if (densLiq <= 0.0) {
throw CanteraError("WaterPropsIAPWS::corr",
"Error occurred trying to find liquid density at (T,P) = {} {}",
temperature, pressure);
}
setState_TR(temperature, densLiq);
doublereal gibbsLiqRT = m_phi.gibbs_RT();
densGas = density(temperature, pressure, WATER_GAS, densGas);
if (densGas <= 0.0) {
throw CanteraError("WaterPropsIAPWS::corr",
"Error occurred trying to find gas density at (T,P) = {} {}",
temperature, pressure);
}
setState_TR(temperature, densGas);
doublereal gibbsGasRT = m_phi.gibbs_RT();
delGRT = gibbsLiqRT - gibbsGasRT;
}
void WaterPropsIAPWS::corr1(doublereal temperature, doublereal pressure,
doublereal& densLiq, doublereal& densGas, doublereal& pcorr)
{
densLiq = density(temperature, pressure, WATER_LIQUID, densLiq);
if (densLiq <= 0.0) {
throw CanteraError("WaterPropsIAPWS::corr1",
"Error occurred trying to find liquid density at (T,P) = {} {}",
temperature, pressure);
}
setState_TR(temperature, densLiq);
doublereal prL = m_phi.phiR();
densGas = density(temperature, pressure, WATER_GAS, densGas);
if (densGas <= 0.0) {
throw CanteraError("WaterPropsIAPWS::corr1",
"Error occurred trying to find gas density at (T,P) = {} {}",
temperature, pressure);
}
setState_TR(temperature, densGas);
doublereal prG = m_phi.phiR();
doublereal rhs = (prL - prG) + log(densLiq/densGas);
rhs /= (1.0/densGas - 1.0/densLiq);
pcorr = rhs * Rgas * temperature / M_water;
}
doublereal WaterPropsIAPWS::psat(doublereal temperature, int waterState)
{
static int method = 1;
doublereal densLiq = -1.0, densGas = -1.0, delGRT = 0.0;
doublereal dp, pcorr;
if (temperature >= T_c) {
densGas = density(temperature, P_c, WATER_SUPERCRIT);
setState_TR(temperature, densGas);
return P_c;
}
doublereal p = psat_est(temperature);
for (int i = 0; i < 30; i++) {
if (method == 1) {
corr(temperature, p, densLiq, densGas, delGRT);
doublereal delV = M_water * (1.0/densLiq - 1.0/densGas);
dp = - delGRT * Rgas * temperature / delV;
} else {
corr1(temperature, p, densLiq, densGas, pcorr);
dp = pcorr - p;
}
p += dp;
if ((method == 1) && delGRT < 1.0E-8) {
break;
} else {
if (fabs(dp/p) < 1.0E-9) {
break;
}
}
}
// Put the fluid in the desired end condition
if (waterState == WATER_LIQUID) {
setState_TR(temperature, densLiq);
} else if (waterState == WATER_GAS) {
setState_TR(temperature, densGas);
} else {
throw CanteraError("WaterPropsIAPWS::psat",
"unknown water state input: {}", waterState);
}
return p;
}
int WaterPropsIAPWS::phaseState(bool checkState) const
{
if (checkState) {
if (tau <= 1.0) {
iState = WATER_SUPERCRIT;
} else {
doublereal T = T_c / tau;
doublereal rho = delta * Rho_c;
doublereal rhoMidAtm = 0.5 * (OneAtm * M_water / (Rgas * 373.15) + 1.0E3);
doublereal rhoMid = Rho_c + (T - T_c) * (Rho_c - rhoMidAtm) / (T_c - 373.15);
int iStateGuess = WATER_LIQUID;
if (rho < rhoMid) {
iStateGuess = WATER_GAS;
}
doublereal kappa = isothermalCompressibility();
if (kappa >= 0.0) {
iState = iStateGuess;
} else {
// When we are here we are between the spinodal curves
doublereal rhoDel = rho * 1.000001;
doublereal deltaSave = delta;
doublereal deltaDel = rhoDel / Rho_c;
delta = deltaDel;
m_phi.tdpolycalc(tau, deltaDel);
doublereal kappaDel = isothermalCompressibility();
doublereal d2rhodp2 = (rhoDel * kappaDel - rho * kappa) / (rhoDel - rho);
if (d2rhodp2 > 0.0) {
iState = WATER_UNSTABLELIQUID;
} else {
iState = WATER_UNSTABLEGAS;
}
delta = deltaSave;
m_phi.tdpolycalc(tau, delta);
}
}
}
return iState;
}
doublereal WaterPropsIAPWS::densSpinodalWater() const
{
doublereal temperature = T_c/tau;
doublereal delta_save = delta;
// return the critical density if we are above or even just a little below
// the critical temperature. We just don't want to worry about the critical
// point at this juncture.
if (temperature >= T_c - 0.001) {
return Rho_c;
}
doublereal p = psat_est(temperature);
doublereal rho_low = 0.0;
doublereal rho_high = 1000;
doublereal densSatLiq = density_const(p, WATER_LIQUID);
doublereal dens_old = densSatLiq;
delta = dens_old / Rho_c;
m_phi.tdpolycalc(tau, delta);
doublereal dpdrho_old = dpdrho();
if (dpdrho_old > 0.0) {
rho_high = std::min(dens_old, rho_high);
} else {
rho_low = std::max(rho_low, dens_old);
}
doublereal dens_new = densSatLiq* (1.0001);
delta = dens_new / Rho_c;
m_phi.tdpolycalc(tau, delta);
doublereal dpdrho_new = dpdrho();
if (dpdrho_new > 0.0) {
rho_high = std::min(dens_new, rho_high);
} else {
rho_low = std::max(rho_low, dens_new);
}
bool conv = false;
for (int it = 0; it < 50; it++) {
doublereal slope = (dpdrho_new - dpdrho_old)/(dens_new - dens_old);
if (slope >= 0.0) {
slope = std::max(slope, dpdrho_new *5.0/ dens_new);
} else {
slope = -dpdrho_new;
// shouldn't be here for liquid spinodal
}
doublereal delta_rho = - dpdrho_new / slope;
if (delta_rho > 0.0) {
delta_rho = std::min(delta_rho, dens_new * 0.1);
} else {
delta_rho = std::max(delta_rho, - dens_new * 0.1);
}
doublereal dens_est = dens_new + delta_rho;
if (dens_est < rho_low) {
dens_est = 0.5 * (rho_low + dens_new);
}
if (dens_est > rho_high) {
dens_est = 0.5 * (rho_high + dens_new);
}
dens_old = dens_new;
dpdrho_old = dpdrho_new;
dens_new = dens_est;
delta = dens_new / Rho_c;
m_phi.tdpolycalc(tau, delta);
dpdrho_new = dpdrho();
if (dpdrho_new > 0.0) {
rho_high = std::min(dens_new, rho_high);
} else if (dpdrho_new < 0.0) {
rho_low = std::max(rho_low, dens_new);
} else {
conv = true;
break;
}
if (fabs(dpdrho_new) < 1.0E-5) {
conv = true;
break;
}
}
if (!conv) {
throw CanteraError("WaterPropsIAPWS::densSpinodalWater()",
"convergence failure");
}
// Restore the original delta
delta = delta_save;
m_phi.tdpolycalc(tau, delta);
return dens_new;
}
doublereal WaterPropsIAPWS::densSpinodalSteam() const
{
doublereal temperature = T_c/tau;
doublereal delta_save = delta;
// return the critical density if we are above or even just a little below
// the critical temperature. We just don't want to worry about the critical
// point at this juncture.
if (temperature >= T_c - 0.001) {
return Rho_c;
}
doublereal p = psat_est(temperature);
doublereal rho_low = 0.0;
doublereal rho_high = 1000;
doublereal densSatGas = density_const(p, WATER_GAS);
doublereal dens_old = densSatGas;
delta = dens_old / Rho_c;
m_phi.tdpolycalc(tau, delta);
doublereal dpdrho_old = dpdrho();
if (dpdrho_old < 0.0) {
rho_high = std::min(dens_old, rho_high);
} else {
rho_low = std::max(rho_low, dens_old);
}
doublereal dens_new = densSatGas * (0.99);
delta = dens_new / Rho_c;
m_phi.tdpolycalc(tau, delta);
doublereal dpdrho_new = dpdrho();
if (dpdrho_new < 0.0) {
rho_high = std::min(dens_new, rho_high);
} else {
rho_low = std::max(rho_low, dens_new);
}
bool conv = false;
for (int it = 0; it < 50; it++) {
doublereal slope = (dpdrho_new - dpdrho_old)/(dens_new - dens_old);
if (slope >= 0.0) {
slope = dpdrho_new;
// shouldn't be here for gas spinodal
} else {
slope = std::min(slope, dpdrho_new *5.0 / dens_new);
}
doublereal delta_rho = - dpdrho_new / slope;
if (delta_rho > 0.0) {
delta_rho = std::min(delta_rho, dens_new * 0.1);
} else {
delta_rho = std::max(delta_rho, - dens_new * 0.1);
}
doublereal dens_est = dens_new + delta_rho;
if (dens_est < rho_low) {
dens_est = 0.5 * (rho_low + dens_new);
}
if (dens_est > rho_high) {
dens_est = 0.5 * (rho_high + dens_new);
}
dens_old = dens_new;
dpdrho_old = dpdrho_new;
dens_new = dens_est;
delta = dens_new / Rho_c;
m_phi.tdpolycalc(tau, delta);
dpdrho_new = dpdrho();
if (dpdrho_new < 0.0) {
rho_high = std::min(dens_new, rho_high);
} else if (dpdrho_new > 0.0) {
rho_low = std::max(rho_low, dens_new);
} else {
conv = true;
break;
}
if (fabs(dpdrho_new) < 1.0E-5) {
conv = true;
break;
}
}
if (!conv) {
throw CanteraError("WaterPropsIAPWS::densSpinodalSteam()",
"convergence failure");
}
// Restore the original delta
delta = delta_save;
m_phi.tdpolycalc(tau, delta);
return dens_new;
}
void WaterPropsIAPWS::setState_TR(doublereal temperature, doublereal rho)
{
calcDim(temperature, rho);
m_phi.tdpolycalc(tau, delta);
}
doublereal WaterPropsIAPWS::enthalpy() const
{
doublereal temperature = T_c/tau;
doublereal hRT = m_phi.enthalpy_RT();
return hRT * Rgas * temperature;
}
doublereal WaterPropsIAPWS::intEnergy() const
{
doublereal temperature = T_c / tau;
doublereal uRT = m_phi.intEnergy_RT();
return uRT * Rgas * temperature;
}
doublereal WaterPropsIAPWS::entropy() const
{
doublereal sR = m_phi.entropy_R();
return sR * Rgas;
}
doublereal WaterPropsIAPWS::cv() const
{
doublereal cvR = m_phi.cv_R();
return cvR * Rgas;
}
doublereal WaterPropsIAPWS::cp() const
{
doublereal cpR = m_phi.cp_R();
return cpR * Rgas;
}
doublereal WaterPropsIAPWS::molarVolume() const
{
doublereal rho = delta * Rho_c;
return M_water / rho;
}
}