cantera/src/thermo/DebyeHuckel.cpp

2658 lines
80 KiB
C++

/**
* @file DebyeHuckel.cpp
* Declarations for the %DebyeHuckel ThermoPhase object, which models dilute
* electrolyte solutions
* (see \ref thermoprops and \link Cantera::DebyeHuckel DebyeHuckel \endlink).
*
* Class %DebyeHuckel represents a dilute liquid electrolyte phase which
* obeys the Debye Huckel formulation for nonideality.
*/
/*
* Copywrite (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/DebyeHuckel.h"
#include "cantera/thermo/ThermoFactory.h"
#include "cantera/thermo/WaterProps.h"
#include "cantera/thermo/PDSS_Water.h"
#include <cstring>
#include <cstdlib>
using namespace std;
using namespace ctml;
namespace Cantera
{
/*
* Default constructor
*/
DebyeHuckel::DebyeHuckel() :
MolalityVPSSTP(),
m_formDH(DHFORM_DILUTE_LIMIT),
m_formGC(2),
m_IionicMolality(0.0),
m_maxIionicStrength(30.0),
m_useHelgesonFixedForm(false),
m_IionicMolalityStoich(0.0),
m_form_A_Debye(A_DEBYE_CONST),
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m
m_waterSS(0),
m_densWaterSS(1000.),
m_waterProps(0)
{
m_npActCoeff.resize(3);
m_npActCoeff[0] = 0.1127;
m_npActCoeff[1] = -0.01049;
m_npActCoeff[2] = 1.545E-3;
}
/*
* Working constructors
*
* The two constructors below are the normal way
* the phase initializes itself. They are shells that call
* the routine initThermo(), with a reference to the
* XML database to get the info for the phase.
*/
DebyeHuckel::DebyeHuckel(std::string inputFile, std::string id) :
MolalityVPSSTP(),
m_formDH(DHFORM_DILUTE_LIMIT),
m_formGC(2),
m_IionicMolality(0.0),
m_maxIionicStrength(30.0),
m_useHelgesonFixedForm(false),
m_IionicMolalityStoich(0.0),
m_form_A_Debye(A_DEBYE_CONST),
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m
m_waterSS(0),
m_densWaterSS(1000.),
m_waterProps(0)
{
m_npActCoeff.resize(3);
m_npActCoeff[0] = 0.1127;
m_npActCoeff[1] = -0.01049;
m_npActCoeff[2] = 1.545E-3;
constructPhaseFile(inputFile, id);
}
DebyeHuckel::DebyeHuckel(XML_Node& phaseRoot, std::string id) :
MolalityVPSSTP(),
m_formDH(DHFORM_DILUTE_LIMIT),
m_formGC(2),
m_IionicMolality(0.0),
m_maxIionicStrength(3.0),
m_useHelgesonFixedForm(false),
m_IionicMolalityStoich(0.0),
m_form_A_Debye(A_DEBYE_CONST),
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m
m_waterSS(0),
m_densWaterSS(1000.),
m_waterProps(0)
{
m_npActCoeff.resize(3);
m_npActCoeff[0] = 0.1127;
m_npActCoeff[1] = -0.01049;
m_npActCoeff[2] = 1.545E-3;
constructPhaseXML(phaseRoot, id);
}
/*
* Copy Constructor:
*
* Note this stuff will not work until the underlying phase
* has a working copy constructor
*/
DebyeHuckel::DebyeHuckel(const DebyeHuckel& b) :
MolalityVPSSTP(),
m_formDH(DHFORM_DILUTE_LIMIT),
m_formGC(2),
m_IionicMolality(0.0),
m_maxIionicStrength(30.0),
m_useHelgesonFixedForm(false),
m_IionicMolalityStoich(0.0),
m_form_A_Debye(A_DEBYE_CONST),
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m
m_waterSS(0),
m_densWaterSS(1000.),
m_waterProps(0)
{
/*
* Use the assignment operator to do the brunt
* of the work for the copy construtor.
*/
*this = b;
}
/*
* operator=()
*
* Note this stuff will not work until the underlying phase
* has a working assignment operator
*/
DebyeHuckel& DebyeHuckel::
operator=(const DebyeHuckel& b)
{
if (&b != this) {
MolalityVPSSTP::operator=(b);
m_formDH = b.m_formDH;
m_formGC = b.m_formGC;
m_Aionic = b.m_Aionic;
m_npActCoeff = b.m_npActCoeff;
m_IionicMolality = b.m_IionicMolality;
m_maxIionicStrength = b.m_maxIionicStrength;
m_useHelgesonFixedForm= b.m_useHelgesonFixedForm;
m_IionicMolalityStoich= b.m_IionicMolalityStoich;
m_form_A_Debye = b.m_form_A_Debye;
m_A_Debye = b.m_A_Debye;
m_B_Debye = b.m_B_Debye;
m_B_Dot = b.m_B_Dot;
m_npActCoeff = b.m_npActCoeff;
// This is an internal shallow copy of the PDSS_Water pointer
m_waterSS = dynamic_cast<PDSS_Water*>(providePDSS(0)) ;
if (!m_waterSS) {
throw CanteraError("DebyHuckel::operator=()", "Dynamic cast to waterPDSS failed");
}
m_densWaterSS = b.m_densWaterSS;
if (m_waterProps) {
delete m_waterProps;
m_waterProps = 0;
}
if (b.m_waterProps) {
m_waterProps = new WaterProps(m_waterSS);
}
m_expg0_RT = b.m_expg0_RT;
m_pe = b.m_pe;
m_pp = b.m_pp;
m_tmpV = b.m_tmpV;
m_speciesCharge_Stoich= b.m_speciesCharge_Stoich;
m_Beta_ij = b.m_Beta_ij;
m_lnActCoeffMolal = b.m_lnActCoeffMolal;
m_d2lnActCoeffMolaldT2= b.m_d2lnActCoeffMolaldT2;
}
return *this;
}
/*
* ~DebyeHuckel(): (virtual)
*
* Destructor for DebyeHuckel. Release objects that
* it owns.
*/
DebyeHuckel::~DebyeHuckel()
{
if (m_waterProps) {
delete m_waterProps;
m_waterProps = 0;
}
}
/*
* duplMyselfAsThermoPhase():
*
* This routine operates at the ThermoPhase level to
* duplicate the current object. It uses the copy constructor
* defined above.
*/
ThermoPhase* DebyeHuckel::duplMyselfAsThermoPhase() const
{
DebyeHuckel* mtp = new DebyeHuckel(*this);
return (ThermoPhase*) mtp;
}
/*
* Equation of state type flag. The base class returns
* zero. Subclasses should define this to return a unique
* non-zero value. Constants defined for this purpose are
* listed in mix_defs.h.
*/
int DebyeHuckel::eosType() const
{
int res;
switch (m_formGC) {
case 0:
res = cDebyeHuckel0;
break;
case 1:
res = cDebyeHuckel1;
break;
case 2:
res = cDebyeHuckel2;
break;
default:
throw CanteraError("eosType", "Unknown type");
break;
}
return res;
}
//
// -------- Molar Thermodynamic Properties of the Solution ---------------
//
/*
* Molar enthalpy of the solution. Units: J/kmol.
*/
doublereal DebyeHuckel::enthalpy_mole() const
{
getPartialMolarEnthalpies(DATA_PTR(m_tmpV));
return mean_X(DATA_PTR(m_tmpV));
}
/*
* Molar internal energy of the solution. Units: J/kmol.
*
* This is calculated from the soln enthalpy and then
* subtracting pV.
*/
doublereal DebyeHuckel::intEnergy_mole() const
{
double hh = enthalpy_mole();
double pres = pressure();
double molarV = 1.0/molarDensity();
double uu = hh - pres * molarV;
return uu;
}
/*
* Molar soln entropy at constant pressure. Units: J/kmol/K.
*
* This is calculated from the partial molar entropies.
*/
doublereal DebyeHuckel::entropy_mole() const
{
getPartialMolarEntropies(DATA_PTR(m_tmpV));
return mean_X(DATA_PTR(m_tmpV));
}
// Molar Gibbs function. Units: J/kmol.
doublereal DebyeHuckel::gibbs_mole() const
{
getChemPotentials(DATA_PTR(m_tmpV));
return mean_X(DATA_PTR(m_tmpV));
}
/*
* Molar heat capacity at constant pressure. Units: J/kmol/K.
*
* Returns the solution heat capacition at constant pressure.
* This is calculated from the partial molar heat capacities.
*/
doublereal DebyeHuckel::cp_mole() const
{
getPartialMolarCp(DATA_PTR(m_tmpV));
double val = mean_X(DATA_PTR(m_tmpV));
return val;
}
/// Molar heat capacity at constant volume. Units: J/kmol/K.
doublereal DebyeHuckel::cv_mole() const
{
//getPartialMolarCv(m_tmpV.begin());
//return mean_X(m_tmpV.begin());
err("not implemented");
return 0.0;
}
//
// ------- Mechanical Equation of State Properties ------------------------
//
/*
* Pressure. Units: Pa.
* For this incompressible system, we return the internally storred
* independent value of the pressure.
*/
doublereal DebyeHuckel::pressure() const
{
return m_Pcurrent;
}
void DebyeHuckel::setPressure(doublereal p)
{
setState_TP(temperature(), p);
}
void DebyeHuckel::setState_TP(doublereal t, doublereal p)
{
State::setTemperature(t);
/*
* Store the current pressure
*/
m_Pcurrent = p;
/*
* update the standard state thermo
* -> This involves calling the water function and setting the pressure
*/
_updateStandardStateThermo();
/*
* Calculate all of the other standard volumes
* -> note these are constant for now
*/
calcDensity();
}
/*
* 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.
*
*/
void DebyeHuckel::calcDensity()
{
if (m_waterSS) {
/*
* Store the internal density of the water SS.
* Note, we would have to do this for all other
* species if they had pressure dependent properties.
*/
m_densWaterSS = m_waterSS->density();
}
double* vbar = &m_pp[0];
getPartialMolarVolumes(vbar);
double* x = &m_tmpV[0];
getMoleFractions(x);
doublereal vtotal = 0.0;
for (size_t i = 0; i < m_kk; i++) {
vtotal += vbar[i] * x[i];
}
doublereal dd = meanMolecularWeight() / vtotal;
State::setDensity(dd);
}
/*
* The isothermal compressibility. Units: 1/Pa.
* The isothermal compressibility is defined as
* \f[
* \kappa_T = -\frac{1}{v}\left(\frac{\partial v}{\partial P}\right)_T
* \f]
*
* It's equal to zero for this model, since the molar volume
* doesn't change with pressure or temperature.
*/
doublereal DebyeHuckel::isothermalCompressibility() const
{
throw CanteraError("DebyeHuckel::isothermalCompressibility",
"unimplemented");
return 0.0;
}
/*
* The thermal expansion coefficient. Units: 1/K.
* The thermal expansion coefficient is defined as
*
* \f[
* \beta = \frac{1}{v}\left(\frac{\partial v}{\partial T}\right)_P
* \f]
*
* It's equal to zero for this model, since the molar volume
* doesn't change with pressure or temperature.
*/
doublereal DebyeHuckel::thermalExpansionCoeff() const
{
throw CanteraError("DebyeHuckel::thermalExpansionCoeff",
"unimplemented");
return 0.0;
}
/*
* Overwritten setDensity() function is necessary because the
* density is not an indendent variable.
*
* This function will now throw an error condition
*
* @internal May have to adjust the strategy here to make
* the eos for these materials slightly compressible, in order
* to create a condition where the density is a function of
* the pressure.
*
* This function will now throw an error condition.
*
* NOTE: This is an overwritten function from the State.h
* class
*/
void DebyeHuckel::setDensity(doublereal rho)
{
double dens = density();
if (rho != dens) {
throw CanteraError("Idea;MolalSoln::setDensity",
"Density is not an independent variable");
}
}
/*
* Overwritten setMolarDensity() function is necessary because the
* density is not an indendent variable.
*
* This function will now throw an error condition.
*
* NOTE: This is a virtual function, overwritten function from the State.h
* class
*/
void DebyeHuckel::setMolarDensity(const doublereal conc)
{
double concI = molarDensity();
if (conc != concI) {
throw CanteraError("Idea;MolalSoln::setMolarDensity",
"molarDensity/density is not an independent variable");
}
}
/*
* Overwritten setTemperature(double) from State.h. This
* function sets the temperature, and makes sure that
* the value propagates to underlying objects.
*/
void DebyeHuckel::setTemperature(const doublereal temp)
{
setState_TP(temp, m_Pcurrent);
}
//
// ------- Activities and Activity Concentrations
//
/*
* This method returns an array of generalized concentrations
* \f$ C_k\f$ that are defined such that
* \f$ a_k = C_k / C^0_k, \f$ where \f$ C^0_k \f$
* is a standard concentration
* defined below. These generalized concentrations are used
* by kinetics manager classes to compute the forward and
* reverse rates of elementary reactions.
*
* @param c Array of generalized concentrations. The
* units depend upon the implementation of the
* reaction rate expressions within the phase.
*/
void DebyeHuckel::getActivityConcentrations(doublereal* c) const
{
double c_solvent = standardConcentration();
getActivities(c);
for (size_t k = 0; k < m_kk; k++) {
c[k] *= c_solvent;
}
}
/*
* The standard concentration \f$ C^0_k \f$ used to normalize
* the generalized concentration. In many cases, this quantity
* will be the same for all species in a phase - for example,
* for an ideal gas \f$ C^0_k = P/\hat R T \f$. For this
* reason, this method returns a single value, instead of an
* array. However, for phases in which the standard
* concentration is species-specific (e.g. surface species of
* different sizes), this method may be called with an
* optional parameter indicating the species.
*
* For the time being we will use the concentration of pure
* solvent for the the standard concentration of all species.
* This has the effect of making reaction rates
* based on the molality of species proportional to the
* molality of the species.
*/
doublereal DebyeHuckel::standardConcentration(size_t k) const
{
double mvSolvent = m_speciesSize[m_indexSolvent];
return 1.0 / mvSolvent;
}
/*
* Returns the natural logarithm of the standard
* concentration of the kth species
*/
doublereal DebyeHuckel::logStandardConc(size_t k) const
{
double c_solvent = standardConcentration(k);
return log(c_solvent);
}
/*
* Returns the units of the standard and general concentrations
* Note they have the same units, as their divisor is
* defined to be equal to the activity of the kth species
* in the solution, which is unitless.
*
* This routine is used in print out applications where the
* units are needed. Usually, MKS units are assumed throughout
* the program and in the XML input files.
*
* On return uA contains the powers of the units (MKS assumed)
* of the standard concentrations and generalized concentrations
* for the kth species.
*
* uA[0] = kmol units - default = 1
* uA[1] = m units - default = -nDim(), the number of spatial
* dimensions in the Phase class.
* uA[2] = kg units - default = 0;
* uA[3] = Pa(pressure) units - default = 0;
* uA[4] = Temperature units - default = 0;
* uA[5] = time units - default = 0
*/
void DebyeHuckel::getUnitsStandardConc(double* uA, int k, int sizeUA) const
{
for (int i = 0; i < sizeUA; i++) {
if (i == 0) {
uA[0] = 1.0;
}
if (i == 1) {
uA[1] = -int(nDim());
}
if (i == 2) {
uA[2] = 0.0;
}
if (i == 3) {
uA[3] = 0.0;
}
if (i == 4) {
uA[4] = 0.0;
}
if (i == 5) {
uA[5] = 0.0;
}
}
}
/*
* Get the array of non-dimensional activities at
* the current solution temperature, pressure, and
* solution concentration.
* (note solvent activity coefficient is on the molar scale).
*
*/
void DebyeHuckel::getActivities(doublereal* ac) const
{
_updateStandardStateThermo();
/*
* Update the molality array, m_molalities()
* This requires an update due to mole fractions
*/
s_update_lnMolalityActCoeff();
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
ac[k] = m_molalities[k] * exp(m_lnActCoeffMolal[k]);
}
}
double xmolSolvent = moleFraction(m_indexSolvent);
ac[m_indexSolvent] =
exp(m_lnActCoeffMolal[m_indexSolvent]) * xmolSolvent;
}
/*
* getMolalityActivityCoefficients() (virtual, const)
*
* Get the array of non-dimensional Molality based
* activity coefficients at
* the current solution temperature, pressure, and
* solution concentration.
* (note solvent activity coefficient is on the molar scale).
*
* Note, most of the work is done in an internal private routine
*/
void DebyeHuckel::
getMolalityActivityCoefficients(doublereal* acMolality) const
{
_updateStandardStateThermo();
A_Debye_TP(-1.0, -1.0);
s_update_lnMolalityActCoeff();
copy(m_lnActCoeffMolal.begin(), m_lnActCoeffMolal.end(), acMolality);
for (size_t k = 0; k < m_kk; k++) {
acMolality[k] = exp(acMolality[k]);
}
}
//
// ------ Partial Molar Properties of the Solution -----------------
//
/*
* Get the species chemical potentials. Units: J/kmol.
*
* This function returns a vector of chemical potentials of the
* species in solution.
*
* \f[
* \mu_k = \mu^{o}_k(T,P) + R T ln(m_k)
* \f]
*
* \f[
* \mu_solvent = \mu^{o}_solvent(T,P) +
* R T ((X_solvent - 1.0) / X_solvent)
* \f]
*/
void DebyeHuckel::getChemPotentials(doublereal* mu) const
{
double xx;
const double xxSmall = 1.0E-150;
/*
* First get the standard chemical potentials in
* molar form.
* -> this requires updates of standard state as a function
* of T and P
*/
getStandardChemPotentials(mu);
/*
* Update the activity coefficients
* This also updates the internal molality array.
*/
s_update_lnMolalityActCoeff();
doublereal RT = GasConstant * temperature();
double xmolSolvent = moleFraction(m_indexSolvent);
for (size_t k = 0; k < m_kk; k++) {
if (m_indexSolvent != k) {
xx = std::max(m_molalities[k], xxSmall);
mu[k] += RT * (log(xx) + m_lnActCoeffMolal[k]);
}
}
xx = std::max(xmolSolvent, xxSmall);
mu[m_indexSolvent] +=
RT * (log(xx) + m_lnActCoeffMolal[m_indexSolvent]);
}
/*
* Returns an array of partial molar enthalpies for the species
* in the mixture.
* Units (J/kmol)
*
* We calculate this quantity partially from the relation and
* partially by calling the standard state enthalpy function.
*
* hbar_i = - T**2 * d(chemPot_i/T)/dT
*
* We calculate
*/
void DebyeHuckel::getPartialMolarEnthalpies(doublereal* hbar) const
{
/*
* Get the nondimensional standard state enthalpies
*/
getEnthalpy_RT(hbar);
/*
* Dimensionalize it.
*/
double T = temperature();
double RT = GasConstant * T;
for (size_t k = 0; k < m_kk; k++) {
hbar[k] *= RT;
}
/*
* Check to see whether activity coefficients are temperature
* dependent. If they are, then calculate the their temperature
* derivatives and add them into the result.
*/
double dAdT = dA_DebyedT_TP();
if (dAdT != 0.0) {
/*
* Update the activity coefficients, This also update the
* internally storred molalities.
*/
s_update_lnMolalityActCoeff();
s_update_dlnMolalityActCoeff_dT();
double RTT = GasConstant * T * T;
for (size_t k = 0; k < m_kk; k++) {
hbar[k] -= RTT * m_dlnActCoeffMolaldT[k];
}
}
}
/*
*
* getPartialMolarEntropies() (virtual, const)
*
* Returns an array of partial molar entropies of the species in the
* solution. Units: J/kmol.
*
* Maxwell's equations provide an insight in how to calculate this
* (p.215 Smith and Van Ness)
*
* d(chemPot_i)/dT = -sbar_i
*
* For this phase, the partial molar entropies are equal to the
* SS species entropies plus the ideal solution contribution.following
* contribution:
* \f[
* \bar s_k(T,P) = \hat s^0_k(T) - R log(M0 * molality[k])
* \f]
* \f[
* \bar s_solvent(T,P) = \hat s^0_solvent(T)
* - R ((xmolSolvent - 1.0) / xmolSolvent)
* \f]
*
* The reference-state pure-species entropies,\f$ \hat s^0_k(T) \f$,
* at the reference pressure, \f$ P_{ref} \f$, are computed by the
* species thermodynamic
* property manager. They are polynomial functions of temperature.
* @see SpeciesThermo
*/
void DebyeHuckel::
getPartialMolarEntropies(doublereal* sbar) const
{
/*
* Get the standard state entropies at the temperature
* and pressure of the solution.
*/
getEntropy_R(sbar);
/*
* Dimensionalize the entropies
*/
doublereal R = GasConstant;
for (size_t k = 0; k < m_kk; k++) {
sbar[k] *= R;
}
/*
* Update the activity coefficients, This also update the
* internally storred molalities.
*/
s_update_lnMolalityActCoeff();
/*
* First we will add in the obvious dependence on the T
* term out front of the log activity term
*/
doublereal mm;
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
mm = std::max(SmallNumber, m_molalities[k]);
sbar[k] -= R * (log(mm) + m_lnActCoeffMolal[k]);
}
}
double xmolSolvent = moleFraction(m_indexSolvent);
mm = std::max(SmallNumber, xmolSolvent);
sbar[m_indexSolvent] -= R *(log(mm) + m_lnActCoeffMolal[m_indexSolvent]);
/*
* Check to see whether activity coefficients are temperature
* dependent. If they are, then calculate the their temperature
* derivatives and add them into the result.
*/
double dAdT = dA_DebyedT_TP();
if (dAdT != 0.0) {
s_update_dlnMolalityActCoeff_dT();
double RT = R * temperature();
for (size_t k = 0; k < m_kk; k++) {
sbar[k] -= RT * m_dlnActCoeffMolaldT[k];
}
}
}
/*
* getPartialMolarVolumes() (virtual, const)
*
* returns an array of partial molar volumes of the species
* in the solution. Units: m^3 kmol-1.
*
* For this solution, the partial molar volumes are normally
* equal to theconstant species molar volumes, except
* when the activity coefficients depend on pressure.
*
* The general relation is
*
* vbar_i = d(chemPot_i)/dP at const T, n
*
* = V0_i + d(Gex)/dP)_T,M
*
* = V0_i + RT d(lnActCoeffi)dP _T,M
*
*/
void DebyeHuckel::getPartialMolarVolumes(doublereal* vbar) const
{
getStandardVolumes(vbar);
/*
* Update the derivatives wrt the activity coefficients.
*/
s_update_lnMolalityActCoeff();
s_update_dlnMolalityActCoeff_dP();
double T = temperature();
double RT = GasConstant * T;
for (size_t k = 0; k < m_kk; k++) {
vbar[k] += RT * m_dlnActCoeffMolaldP[k];
}
}
/*
* Partial molar heat capacity of the solution:
* The kth partial molar heat capacity is equal to
* the temperature derivative of the partial molar
* enthalpy of the kth species in the solution at constant
* P and composition (p. 220 Smith and Van Ness).
*
* Cp = -T d2(chemPot_i)/dT2
*/
void DebyeHuckel::getPartialMolarCp(doublereal* cpbar) const
{
/*
* Get the nondimensional gibbs standard state of the
* species at the T and P of the solution.
*/
getCp_R(cpbar);
for (size_t k = 0; k < m_kk; k++) {
cpbar[k] *= GasConstant;
}
/*
* Check to see whether activity coefficients are temperature
* dependent. If they are, then calculate the their temperature
* derivatives and add them into the result.
*/
double dAdT = dA_DebyedT_TP();
if (dAdT != 0.0) {
/*
* Update the activity coefficients, This also update the
* internally storred molalities.
*/
s_update_lnMolalityActCoeff();
s_update_dlnMolalityActCoeff_dT();
s_update_d2lnMolalityActCoeff_dT2();
double T = temperature();
double RT = GasConstant * T;
double RTT = RT * T;
for (size_t k = 0; k < m_kk; k++) {
cpbar[k] -= (2.0 * RT * m_dlnActCoeffMolaldT[k] +
RTT * m_d2lnActCoeffMolaldT2[k]);
}
}
}
/*
* -------------- Utilities -------------------------------
*/
/*
* Initialization routine for a DebyeHuckel phase.
*
* This is a virtual routine. This routine will call initThermo()
* for the parent class as well.
*/
void DebyeHuckel::initThermo()
{
MolalityVPSSTP::initThermo();
initLengths();
}
/*
* constructPhaseFile
*
* Initialization of a Debye-Huckel phase using an
* xml file.
*
* This routine is a precursor to initThermo(XML_Node*)
* routine, which does most of the work.
*
* @param infile XML file containing the description of the
* phase
*
* @param id Optional parameter identifying the name of the
* phase. If none is given, the first XML
* phase element will be used.
*/
void DebyeHuckel::constructPhaseFile(std::string inputFile, std::string id)
{
if (inputFile.size() == 0) {
throw CanteraError("DebyeHuckel::initThermo",
"input file is null");
}
std::string path = findInputFile(inputFile);
ifstream fin(path.c_str());
if (!fin) {
throw CanteraError("DebyeHuckel::initThermo","could not open "
+path+" for reading.");
}
/*
* The phase object automatically constructs an XML object.
* Use this object to store information.
*/
XML_Node& phaseNode_XML = xml();
XML_Node* fxml = new XML_Node();
fxml->build(fin);
XML_Node* fxml_phase = findXMLPhase(fxml, id);
if (!fxml_phase) {
throw CanteraError("DebyeHuckel::initThermo",
"ERROR: Can not find phase named " +
id + " in file named " + inputFile);
}
fxml_phase->copy(&phaseNode_XML);
constructPhaseXML(*fxml_phase, id);
delete fxml;
}
//! Utility function to assign an integer value from a string for the ElectrolyteSpeciesType field.
/*!
* @param estString input string that will be interpreted
*/
static int interp_est(std::string estString)
{
const char* cc = estString.c_str();
string lc = lowercase(estString);
const char* ccl = lc.c_str();
if (!strcmp(ccl, "solvent")) {
return cEST_solvent;
} else if (!strcmp(ccl, "chargedspecies")) {
return cEST_chargedSpecies;
} else if (!strcmp(ccl, "weakacidassociated")) {
return cEST_weakAcidAssociated;
} else if (!strcmp(ccl, "strongacidassociated")) {
return cEST_strongAcidAssociated;
} else if (!strcmp(ccl, "polarneutral")) {
return cEST_polarNeutral;
} else if (!strcmp(ccl, "nonpolarneutral")) {
return cEST_nonpolarNeutral;
}
int retn, rval;
if ((retn = sscanf(cc, "%d", &rval)) != 1) {
return -1;
}
return rval;
}
/*
* Import and initialize a DebyeHuckel phase
* specification in an XML tree into the current object.
* Here we read an XML description of the phase.
* We import descriptions of the elements that make up the
* species in a phase.
* We import information about the species, including their
* reference state thermodynamic polynomials. We then freeze
* the state of the species.
*
* Then, we read the species molar volumes from the xml
* tree to finish the initialization.
*
* @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.
*/
void DebyeHuckel::constructPhaseXML(XML_Node& phaseNode, std::string id)
{
if (id.size() > 0) {
std::string idp = phaseNode.id();
if (idp != id) {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"phasenode and Id are incompatible");
}
}
/*
* Find the Thermo XML node
*/
if (!phaseNode.hasChild("thermo")) {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"no thermo XML node");
}
XML_Node& thermoNode = phaseNode.child("thermo");
/*
* Possibly change the form of the standard concentrations
*/
if (thermoNode.hasChild("standardConc")) {
XML_Node& scNode = thermoNode.child("standardConc");
m_formGC = 2;
std::string formString = scNode.attrib("model");
if (formString != "") {
if (formString == "unity") {
m_formGC = 0;
printf("exit standardConc = unity not done\n");
exit(EXIT_FAILURE);
} else if (formString == "molar_volume") {
m_formGC = 1;
printf("exit standardConc = molar_volume not done\n");
exit(EXIT_FAILURE);
} else if (formString == "solvent_volume") {
m_formGC = 2;
} else {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"Unknown standardConc model: " + formString);
}
}
}
/*
* Get the Name of the Solvent:
* <solvent> solventName </solvent>
*/
std::string solventName = "";
if (thermoNode.hasChild("solvent")) {
XML_Node& scNode = thermoNode.child("solvent");
vector<std::string> nameSolventa;
getStringArray(scNode, nameSolventa);
int nsp = static_cast<int>(nameSolventa.size());
if (nsp != 1) {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"badly formed solvent XML node");
}
solventName = nameSolventa[0];
}
/*
* Determine the form of the Debye-Huckel model,
* m_formDH. We will use this information to size arrays below.
*/
if (thermoNode.hasChild("activityCoefficients")) {
XML_Node& scNode = thermoNode.child("activityCoefficients");
m_formDH = DHFORM_DILUTE_LIMIT;
std::string formString = scNode.attrib("model");
if (formString != "") {
if (formString == "Dilute_limit") {
m_formDH = DHFORM_DILUTE_LIMIT;
} else if (formString == "Bdot_with_variable_a") {
m_formDH = DHFORM_BDOT_AK ;
} else if (formString == "Bdot_with_common_a") {
m_formDH = DHFORM_BDOT_ACOMMON;
} else if (formString == "Beta_ij") {
m_formDH = DHFORM_BETAIJ;
} else if (formString == "Pitzer_with_Beta_ij") {
m_formDH = DHFORM_PITZER_BETAIJ;
} else {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"Unknown standardConc model: " + formString);
}
}
} else {
/*
* If there is no XML node named "activityCoefficients", assume
* that we are doing the extreme dilute limit assumption
*/
m_formDH = DHFORM_DILUTE_LIMIT;
}
/*
* Call the Cantera importPhase() function. This will import
* all of the species into the phase. This will also handle
* all of the solvent and solute standard states
*/
bool m_ok = importPhase(phaseNode, this);
if (!m_ok) {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"importPhase failed ");
}
}
/*
* Process the XML file after species are set up.
*
* This gets called from importPhase(). It processes the XML file
* after the species are set up. This is the main routine for
* reading in activity coefficient parameters.
*
* @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.
*/
void DebyeHuckel::
initThermoXML(XML_Node& phaseNode, std::string id)
{
std::string stemp;
/*
* Find the Thermo XML node
*/
if (!phaseNode.hasChild("thermo")) {
throw CanteraError("HMWSoln::initThermoXML",
"no thermo XML node");
}
XML_Node& thermoNode = phaseNode.child("thermo");
/*
* Possibly change the form of the standard concentrations
*/
if (thermoNode.hasChild("standardConc")) {
XML_Node& scNode = thermoNode.child("standardConc");
m_formGC = 2;
std::string formString = scNode.attrib("model");
if (formString != "") {
if (formString == "unity") {
m_formGC = 0;
printf("exit standardConc = unity not done\n");
exit(EXIT_FAILURE);
} else if (formString == "molar_volume") {
m_formGC = 1;
printf("exit standardConc = molar_volume not done\n");
exit(EXIT_FAILURE);
} else if (formString == "solvent_volume") {
m_formGC = 2;
} else {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"Unknown standardConc model: " + formString);
}
}
}
/*
* Reconcile the solvent name and index.
*/
/*
* Get the Name of the Solvent:
* <solvent> solventName </solvent>
*/
std::string solventName = "";
if (thermoNode.hasChild("solvent")) {
XML_Node& scNode = thermoNode.child("solvent");
vector<std::string> nameSolventa;
getStringArray(scNode, nameSolventa);
int nsp = static_cast<int>(nameSolventa.size());
if (nsp != 1) {
throw CanteraError("DebyeHuckel::initThermoXML",
"badly formed solvent XML node");
}
solventName = nameSolventa[0];
}
for (size_t k = 0; k < m_kk; k++) {
std::string sname = speciesName(k);
if (solventName == sname) {
m_indexSolvent = k;
break;
}
}
if (m_indexSolvent == npos) {
cout << "DebyeHuckel::initThermoXML: Solvent Name not found"
<< endl;
throw CanteraError("DebyeHuckel::initThermoXML",
"Solvent name not found");
}
if (m_indexSolvent != 0) {
throw CanteraError("DebyeHuckel::initThermoXML",
"Solvent " + solventName +
" should be first species");
}
/*
* Determine the form of the Debye-Huckel model,
* m_formDH. We will use this information to size arrays below.
*/
if (thermoNode.hasChild("activityCoefficients")) {
XML_Node& scNode = thermoNode.child("activityCoefficients");
m_formDH = DHFORM_DILUTE_LIMIT;
std::string formString = scNode.attrib("model");
if (formString != "") {
if (formString == "Dilute_limit") {
m_formDH = DHFORM_DILUTE_LIMIT;
} else if (formString == "Bdot_with_variable_a") {
m_formDH = DHFORM_BDOT_AK ;
} else if (formString == "Bdot_with_common_a") {
m_formDH = DHFORM_BDOT_ACOMMON;
} else if (formString == "Beta_ij") {
m_formDH = DHFORM_BETAIJ;
} else if (formString == "Pitzer_with_Beta_ij") {
m_formDH = DHFORM_PITZER_BETAIJ;
} else {
throw CanteraError("DebyeHuckel::constructPhaseXML",
"Unknown standardConc model: " + formString);
}
}
} else {
/*
* If there is no XML node named "activityCoefficients", assume
* that we are doing the extreme dilute limit assumption
*/
m_formDH = DHFORM_DILUTE_LIMIT;
}
/*
* Initialize all of the lengths of arrays in the object
* now that we know what species are in the phase.
*/
initThermo();
/*
* Now go get the specification of the standard states for
* species in the solution. This includes the molar volumes
* data blocks for incompressible species.
*/
XML_Node& speciesList = phaseNode.child("speciesArray");
XML_Node* speciesDB =
get_XML_NameID("speciesData", speciesList["datasrc"],
&phaseNode.root());
const vector<string>&sss = speciesNames();
for (size_t k = 0; k < m_kk; k++) {
XML_Node* s = speciesDB->findByAttr("name", sss[k]);
if (!s) {
throw CanteraError("DebyeHuckel::initThermoXML",
"Species Data Base " + sss[k] + " not found");
}
XML_Node* ss = s->findByName("standardState");
if (!ss) {
throw CanteraError("DebyeHuckel::initThermoXML",
"Species " + sss[k] +
" standardState XML block not found");
}
std::string modelStringa = ss->attrib("model");
if (modelStringa == "") {
throw CanteraError("DebyeHuckel::initThermoXML",
"Species " + sss[k] +
" standardState XML block model attribute not found");
}
std::string modelString = lowercase(modelStringa);
if (k == 0) {
if (modelString == "wateriapws" || modelString == "real_water" ||
modelString == "waterpdss") {
/*
* Initialize the water standard state model
*/
m_waterSS = dynamic_cast<PDSS_Water*>(providePDSS(0)) ;
if (!m_waterSS) {
throw CanteraError("HMWSoln::installThermoXML",
"Dynamic cast to PDSS_Water failed");
}
/*
* Fill in the molar volume of water (m3/kmol)
* at standard conditions to fill in the m_speciesSize entry
* with something reasonable.
*/
m_waterSS->setState_TP(300., OneAtm);
double dens = m_waterSS->density();
double mw = m_waterSS->molecularWeight();
m_speciesSize[0] = mw / dens;
#ifdef DEBUG_MODE_NOT
cout << "Solvent species " << sss[k] << " has volume " <<
m_speciesSize[k] << endl;
#endif
} else if (modelString == "constant_incompressible") {
m_speciesSize[k] = getFloat(*ss, "molarVolume", "toSi");
#ifdef DEBUG_MODE_NOT
cout << "species " << sss[k] << " has volume " <<
m_speciesSize[k] << endl;
#endif
} else {
throw CanteraError("DebyeHuckel::initThermoXML",
"Solvent SS Model \"" + modelStringa +
"\" is not known");
}
} else {
if (modelString != "constant_incompressible") {
throw CanteraError("DebyeHuckel::initThermoXML",
"Solute SS Model \"" + modelStringa +
"\" is not known");
}
m_speciesSize[k] = getFloat(*ss, "molarVolume", "toSI");
#ifdef DEBUG_MODE_NOT
cout << "species " << sss[k] << " has volume " <<
m_speciesSize[k] << endl;
#endif
}
}
/*
* Go get all of the coefficients and factors in the
* activityCoefficients XML block
*/
XML_Node* acNodePtr = 0;
if (thermoNode.hasChild("activityCoefficients")) {
XML_Node& acNode = thermoNode.child("activityCoefficients");
acNodePtr = &acNode;
/*
* Look for parameters for A_Debye
*/
if (acNode.hasChild("A_Debye")) {
XML_Node* ss = acNode.findByName("A_Debye");
string modelStringa = ss->attrib("model");
string modelString = lowercase(modelStringa);
if (modelString != "") {
if (modelString == "water") {
m_form_A_Debye = A_DEBYE_WATER;
} else {
throw CanteraError("DebyeHuckel::initThermoXML",
"A_Debye Model \"" + modelStringa +
"\" is not known");
}
} else {
m_A_Debye = getFloat(acNode, "A_Debye");
#ifdef DEBUG_HKM_NOT
cout << "A_Debye = " << m_A_Debye << endl;
#endif
}
}
/*
* Initialize the water property calculator. It will share
* the internal eos water calculator.
*/
if (m_form_A_Debye == A_DEBYE_WATER) {
if (m_waterProps) {
delete m_waterProps;
}
m_waterProps = new WaterProps(m_waterSS);
}
/*
* Look for parameters for B_Debye
*/
if (acNode.hasChild("B_Debye")) {
m_B_Debye = getFloat(acNode, "B_Debye");
#ifdef DEBUG_HKM_NOT
cout << "B_Debye = " << m_B_Debye << endl;
#endif
}
/*
* Look for parameters for B_dot
*/
if (acNode.hasChild("B_dot")) {
if (m_formDH == DHFORM_BETAIJ ||
m_formDH == DHFORM_DILUTE_LIMIT ||
m_formDH == DHFORM_PITZER_BETAIJ) {
throw CanteraError("DebyeHuckel:init",
"B_dot entry in the wrong DH form");
}
double bdot_common = getFloat(acNode, "B_dot");
#ifdef DEBUG_HKM_NOT
cout << "B_dot = " << bdot_common << endl;
#endif
/*
* Set B_dot parameters for charged species
*/
for (size_t k = 0; k < m_kk; k++) {
double z_k = charge(k);
if (fabs(z_k) > 0.0001) {
m_B_Dot[k] = bdot_common;
} else {
m_B_Dot[k] = 0.0;
}
}
}
/*
* Look for Parameters for the Maximum Ionic Strength
*/
if (acNode.hasChild("maxIonicStrength")) {
m_maxIionicStrength = getFloat(acNode, "maxIonicStrength");
#ifdef DEBUG_HKM_NOT
cout << "m_maxIionicStrength = "
<<m_maxIionicStrength << endl;
#endif
}
/*
* Look for Helgeson Parameters
*/
if (acNode.hasChild("UseHelgesonFixedForm")) {
m_useHelgesonFixedForm = true;
} else {
m_useHelgesonFixedForm = false;
}
/*
* Look for parameters for the Ionic radius
*/
if (acNode.hasChild("ionicRadius")) {
XML_Node& irNode = acNode.child("ionicRadius");
std::string Aunits = "";
double Afactor = 1.0;
if (irNode.hasAttrib("units")) {
std::string Aunits = irNode.attrib("units");
Afactor = toSI(Aunits);
}
if (irNode.hasAttrib("default")) {
std::string ads = irNode.attrib("default");
double ad = fpValue(ads);
for (size_t k = 0; k < m_kk; k++) {
m_Aionic[k] = ad * Afactor;
}
}
/*
* If the Debye-Huckel form is BDOT_AK, we can
* have separate values for the denominator's ionic
* size. -> That's how the activity coefficient is
* parameterized. In this case only do we allow the
* code to read in these parameters.
*/
if (m_formDH == DHFORM_BDOT_AK) {
/*
* Define a string-string map, and interpret the
* value of the xml element as binary pairs separated
* by colons, e.g.:
* Na+:3.0
* Cl-:4.0
* H+:9.0
* OH-:3.5
* Read them into the map.
*/
map<string, string> m;
getMap(irNode, m);
/*
* Iterate over the map pairs, interpreting the
* first string as a species in the current phase.
* If no match is made, silently ignore the
* lack of agreement (HKM -> may be changed in the
* future).
*/
map<std::string,std::string>::const_iterator _b = m.begin();
for (; _b != m.end(); ++_b) {
size_t kk = speciesIndex(_b->first);
m_Aionic[kk] = fpValue(_b->second) * Afactor;
}
}
}
/*
* Get the matrix of coefficients for the Beta
* binary interaction parameters. We assume here that
* this matrix is symmetric, so that we only have to
* input 1/2 of the values.
*/
if (acNode.hasChild("DHBetaMatrix")) {
if (m_formDH == DHFORM_BETAIJ ||
m_formDH == DHFORM_PITZER_BETAIJ) {
XML_Node& irNode = acNode.child("DHBetaMatrix");
const vector<string>& sn = speciesNames();
getMatrixValues(irNode, sn, sn, m_Beta_ij, true, true);
} else {
throw CanteraError("DebyeHuckel::initThermoXML:",
"DHBetaMatrix found for wrong type");
}
}
/*
* Fill in parameters for the calculation of the
* stoichiometric Ionic Strength
*
* The default is that stoich charge is the same as the
* regular charge.
*/
m_speciesCharge_Stoich.resize(m_kk, 0.0);
for (size_t k = 0; k < m_kk; k++) {
m_speciesCharge_Stoich[k] = m_speciesCharge[k];
}
/*
* First look at the species database.
* -> Look for the subelement "stoichIsMods"
* in each of the species SS databases.
*/
std::vector<const XML_Node*> xspecies= speciesData();
std::string kname, jname;
size_t jj = xspecies.size();
for (size_t k = 0; k < m_kk; k++) {
size_t jmap = -1;
kname = speciesName(k);
for (size_t j = 0; j < jj; j++) {
const XML_Node& sp = *xspecies[j];
jname = sp["name"];
if (jname == kname) {
jmap = j;
break;
}
}
if (jmap != npos) {
const XML_Node& sp = *xspecies[jmap];
if (sp.hasChild("stoichIsMods")) {
double val = getFloat(sp, "stoichIsMods");
m_speciesCharge_Stoich[k] = val;
}
}
}
/*
* Now look at the activity coefficient database
*/
if (acNodePtr) {
if (acNodePtr->hasChild("stoichIsMods")) {
XML_Node& sIsNode = acNodePtr->child("stoichIsMods");
map<std::string, std::string> msIs;
getMap(sIsNode, msIs);
map<std::string,std::string>::const_iterator _b = msIs.begin();
for (; _b != msIs.end(); ++_b) {
size_t kk = speciesIndex(_b->first);
double val = fpValue(_b->second);
m_speciesCharge_Stoich[kk] = val;
}
}
}
}
/*
* Fill in the vector specifying the electrolyte species
* type
*
* First fill in default values. Everthing is either
* a charge species, a nonpolar neutral, or the solvent.
*/
for (size_t k = 0; k < m_kk; k++) {
if (fabs(m_speciesCharge[k]) > 0.0001) {
m_electrolyteSpeciesType[k] = cEST_chargedSpecies;
if (fabs(m_speciesCharge_Stoich[k] - m_speciesCharge[k])
> 0.0001) {
m_electrolyteSpeciesType[k] = cEST_weakAcidAssociated;
}
} else if (fabs(m_speciesCharge_Stoich[k]) > 0.0001) {
m_electrolyteSpeciesType[k] = cEST_weakAcidAssociated;
} else {
m_electrolyteSpeciesType[k] = cEST_nonpolarNeutral;
}
}
m_electrolyteSpeciesType[m_indexSolvent] = cEST_solvent;
/*
* First look at the species database.
* -> Look for the subelement "stoichIsMods"
* in each of the species SS databases.
*/
std::vector<const XML_Node*> xspecies= speciesData();
const XML_Node* spPtr = 0;
std::string kname;
for (size_t k = 0; k < m_kk; k++) {
kname = speciesName(k);
spPtr = xspecies[k];
if (!spPtr) {
if (spPtr->hasChild("electrolyteSpeciesType")) {
std::string est = getChildValue(*spPtr, "electrolyteSpeciesType");
if ((m_electrolyteSpeciesType[k] = interp_est(est)) == -1) {
throw CanteraError("DebyeHuckel:initThermoXML",
"Bad electrolyte type: " + est);
}
}
}
}
/*
* Then look at the phase thermo specification
*/
if (acNodePtr) {
if (acNodePtr->hasChild("electrolyteSpeciesType")) {
XML_Node& ESTNode = acNodePtr->child("electrolyteSpeciesType");
map<std::string, std::string> msEST;
getMap(ESTNode, msEST);
map<std::string,std::string>::const_iterator _b = msEST.begin();
for (; _b != msEST.end(); ++_b) {
size_t kk = speciesIndex(_b->first);
std::string est = _b->second;
if ((m_electrolyteSpeciesType[kk] = interp_est(est)) == -1) {
throw CanteraError("DebyeHuckel:initThermoXML",
"Bad electrolyte type: " + est);
}
}
}
}
/*
* Lastly set the state
*/
if (phaseNode.hasChild("state")) {
XML_Node& stateNode = phaseNode.child("state");
setStateFromXML(stateNode);
}
}
/*
* @internal
* Set equation of state parameters. The number and meaning of
* these depends on the subclass.
* @param n number of parameters
* @param c array of \i n coefficients
*
*/
void DebyeHuckel::setParameters(int n, doublereal* const c)
{
}
void DebyeHuckel::getParameters(int& n, doublereal* const c) const
{
}
/*
* Set equation of state parameter values from XML
* entries. This method is called by function importPhase in
* file importCTML.cpp when processing a phase definition in
* an input file. It should be overloaded in subclasses to set
* any parameters that are specific to that particular phase
* model.
*
* @param eosdata An XML_Node object corresponding to
* the "thermo" entry for this phase in the input file.
*
* HKM -> Right now, the parameters are set elsewhere (initThermoXML)
* It just didn't seem to fit.
*/
void DebyeHuckel::setParametersFromXML(const XML_Node& eosdata)
{
}
/*
* Report the molar volume of species k
*
* units - \f$ m^3 kmol^-1 \f$
*/
// double DebyeHuckel::speciesMolarVolume(int k) const {
// return m_speciesSize[k];
//}
/*
* A_Debye_TP() (virtual)
*
* Returns the A_Debye parameter as a function of temperature
* and pressure.
*
* The default is to assume that it is constant, given
* in the initialization process and storred in the
* member double, m_A_Debye
*/
double DebyeHuckel::A_Debye_TP(double tempArg, double presArg) const
{
double T = temperature();
double A;
if (tempArg != -1.0) {
T = tempArg;
}
double P = pressure();
if (presArg != -1.0) {
P = presArg;
}
switch (m_form_A_Debye) {
case A_DEBYE_CONST:
A = m_A_Debye;
break;
case A_DEBYE_WATER:
A = m_waterProps->ADebye(T, P, 0);
m_A_Debye = A;
break;
default:
printf("shouldn't be here\n");
exit(EXIT_FAILURE);
}
return A;
}
/*
* dA_DebyedT_TP() (virtual)
*
* Returns the derivative of the A_Debye parameter with
* respect to temperature as a function of temperature
* and pressure.
*
* units = A_Debye has units of sqrt(gmol kg-1).
* Temp has units of Kelvin.
*/
double DebyeHuckel::dA_DebyedT_TP(double tempArg, double presArg) const
{
double T = temperature();
if (tempArg != -1.0) {
T = tempArg;
}
double P = pressure();
if (presArg != -1.0) {
P = presArg;
}
double dAdT;
switch (m_form_A_Debye) {
case A_DEBYE_CONST:
dAdT = 0.0;
break;
case A_DEBYE_WATER:
dAdT = m_waterProps->ADebye(T, P, 1);
break;
default:
printf("shouldn't be here\n");
exit(EXIT_FAILURE);
}
return dAdT;
}
/*
* d2A_DebyedT2_TP() (virtual)
*
* Returns the 2nd derivative of the A_Debye parameter with
* respect to temperature as a function of temperature
* and pressure.
*
* units = A_Debye has units of sqrt(gmol kg-1).
* Temp has units of Kelvin.
*/
double DebyeHuckel::d2A_DebyedT2_TP(double tempArg, double presArg) const
{
double T = temperature();
if (tempArg != -1.0) {
T = tempArg;
}
double P = pressure();
if (presArg != -1.0) {
P = presArg;
}
double d2AdT2;
switch (m_form_A_Debye) {
case A_DEBYE_CONST:
d2AdT2 = 0.0;
break;
case A_DEBYE_WATER:
d2AdT2 = m_waterProps->ADebye(T, P, 2);
break;
default:
printf("shouldn't be here\n");
exit(EXIT_FAILURE);
}
return d2AdT2;
}
/*
* dA_DebyedP_TP() (virtual)
*
* Returns the derivative of the A_Debye parameter with
* respect to pressure, as a function of temperature
* and pressure.
*
* units = A_Debye has units of sqrt(gmol kg-1).
* Pressure has units of pascals.
*/
double DebyeHuckel::dA_DebyedP_TP(double tempArg, double presArg) const
{
double T = temperature();
if (tempArg != -1.0) {
T = tempArg;
}
double P = pressure();
if (presArg != -1.0) {
P = presArg;
}
double dAdP;
switch (m_form_A_Debye) {
case A_DEBYE_CONST:
dAdP = 0.0;
break;
case A_DEBYE_WATER:
dAdP = m_waterProps->ADebye(T, P, 3);
break;
default:
printf("shouldn't be here\n");
exit(EXIT_FAILURE);
}
return dAdP;
}
/*
* ----------- Critical State Properties --------------------------
*/
/*
* ---------- Other Property Functions
*/
double DebyeHuckel::AionicRadius(int k) const
{
return m_Aionic[k];
}
/*
* ------------ Private and Restricted Functions ------------------
*/
/*
* Bail out of functions with an error exit if they are not
* implemented.
*/
doublereal DebyeHuckel::err(std::string msg) const
{
throw CanteraError("DebyeHuckel",
"Unfinished func called: " + msg);
return 0.0;
}
/*
* initLengths():
*
* This internal function adjusts the lengths of arrays based on
* the number of species
*/
void DebyeHuckel::initLengths()
{
m_kk = nSpecies();
/*
* Obtain the limits of the temperature from the species
* thermo handler's limits.
*/
m_electrolyteSpeciesType.resize(m_kk, cEST_polarNeutral);
m_speciesSize.resize(m_kk);
m_Aionic.resize(m_kk, 0.0);
m_lnActCoeffMolal.resize(m_kk, 0.0);
m_dlnActCoeffMolaldT.resize(m_kk, 0.0);
m_d2lnActCoeffMolaldT2.resize(m_kk, 0.0);
m_dlnActCoeffMolaldP.resize(m_kk, 0.0);
m_B_Dot.resize(m_kk, 0.0);
m_expg0_RT.resize(m_kk, 0.0);
m_pe.resize(m_kk, 0.0);
m_pp.resize(m_kk, 0.0);
m_tmpV.resize(m_kk, 0.0);
if (m_formDH == DHFORM_BETAIJ ||
m_formDH == DHFORM_PITZER_BETAIJ) {
m_Beta_ij.resize(m_kk, m_kk, 0.0);
}
}
/*
* nonpolarActCoeff() (private)
*
* Static function that implements the non-polar species
* salt-out modifications.
* Returns the calculated activity coefficients.
*/
double DebyeHuckel::_nonpolarActCoeff(double IionicMolality) const
{
double I2 = IionicMolality * IionicMolality;
double l10actCoeff =
m_npActCoeff[0] * IionicMolality +
m_npActCoeff[1] * I2 +
m_npActCoeff[2] * I2 * IionicMolality;
return pow(10.0 , l10actCoeff);
}
/**
* _osmoticCoeffHelgesonFixedForm()
*
* Formula for the osmotic coefficient that occurs in
* the GWB. It is originally from Helgeson for a variable
* NaCl brine. It's to be used with extreme caution.
*/
double DebyeHuckel::
_osmoticCoeffHelgesonFixedForm() const
{
const double a0 = 1.454;
const double b0 = 0.02236;
const double c0 = 9.380E-3;
const double d0 = -5.362E-4;
double Is = m_IionicMolalityStoich;
if (Is <= 0.0) {
return 0.0;
}
double Is2 = Is * Is;
double bhat = 1.0 + a0 * sqrt(Is);
double func = bhat - 2.0 * log(bhat) - 1.0/bhat;
double v1 = m_A_Debye / (a0 * a0 * a0 * Is) * func;
double oc = 1.0 - v1 + b0 * Is / 2.0 + 2.0 * c0 * Is2 / 3.0
+ 3.0 * d0 * Is2 * Is / 4.0;
return oc;
}
/*
* _activityWaterHelgesonFixedForm()
*
* Formula for the log of the activity of the water
* solvent that occurs in
* the GWB. It is originally from Helgeson for a variable
* NaCl brine. It's to be used with extreme caution.
*/
double DebyeHuckel::
_lnactivityWaterHelgesonFixedForm() const
{
/*
* Update the internally storred vector of molalities
*/
calcMolalities();
double oc = _osmoticCoeffHelgesonFixedForm();
double sum = 0.0;
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
sum += std::max(m_molalities[k], 0.0);
}
}
if (sum > 2.0 * m_maxIionicStrength) {
sum = 2.0 * m_maxIionicStrength;
};
double lac = - m_Mnaught * sum * oc;
return lac;
}
/*
* s_update_lnMolalityActCoeff():
*
* Using internally stored values, this function calculates
* the activity coefficients for all species.
*
* The ln(activity_solvent) is first calculated for the
* solvent. Then the molar based activity coefficient
* is calculated and returned.
*
* ( Note this is the main routine for implementing the
* activity coefficient formulation.)
*/
void DebyeHuckel::s_update_lnMolalityActCoeff() const
{
double z_k, zs_k1, zs_k2;
/*
* Update the internally storred vector of molalities
*/
calcMolalities();
/*
* Calculate the apparent (real) ionic strength.
*
* Note this is not the stoichiometric ionic strengh,
* where reactions of ions forming neutral salts
* are ignorred in calculating the ionic strength.
*/
m_IionicMolality = 0.0;
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_IionicMolality += m_molalities[k] * z_k * z_k;
}
m_IionicMolality /= 2.0;
if (m_IionicMolality > m_maxIionicStrength) {
m_IionicMolality = m_maxIionicStrength;
}
/*
* Calculate the stoichiometric ionic charge
*/
m_IionicMolalityStoich = 0.0;
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
zs_k1 = m_speciesCharge_Stoich[k];
if (z_k == zs_k1) {
m_IionicMolalityStoich += m_molalities[k] * z_k * z_k;
} else {
zs_k2 = z_k - zs_k1;
m_IionicMolalityStoich
+= m_molalities[k] * (zs_k1 * zs_k1 + zs_k2 * zs_k2);
}
}
m_IionicMolalityStoich /= 2.0;
if (m_IionicMolalityStoich > m_maxIionicStrength) {
m_IionicMolalityStoich = m_maxIionicStrength;
}
/*
* Possibly update the storred value of the
* Debye-Huckel parameter A_Debye
* This parameter appears on the top of the activity
* coefficient expression.
* It depends on T (and P), as it depends explicity
* on the temperature. Also, the dielectric constant
* is usually a fairly strong function of T, also.
*/
m_A_Debye = A_Debye_TP();
/*
* Calculate a safe value for the mole fraction
* of the solvent
*/
double xmolSolvent = moleFraction(m_indexSolvent);
xmolSolvent = std::max(8.689E-3, xmolSolvent);
int est;
double ac_nonPolar = 1.0;
double numTmp = m_A_Debye * sqrt(m_IionicMolality);
double denomTmp = m_B_Debye * sqrt(m_IionicMolality);
double coeff;
double lnActivitySolvent = 0.0;
double tmp;
double tmpLn;
double y, yp1, sigma;
switch (m_formDH) {
case DHFORM_DILUTE_LIMIT:
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_lnActCoeffMolal[k] = - z_k * z_k * numTmp;
}
lnActivitySolvent =
(xmolSolvent - 1.0)/xmolSolvent +
2.0 / 3.0 * m_A_Debye * m_Mnaught *
m_IionicMolality * sqrt(m_IionicMolality);
break;
case DHFORM_BDOT_AK:
ac_nonPolar = _nonpolarActCoeff(m_IionicMolality);
for (size_t k = 0; k < m_kk; k++) {
est = m_electrolyteSpeciesType[k];
if (est == cEST_nonpolarNeutral) {
m_lnActCoeffMolal[k] = log(ac_nonPolar);
} else {
z_k = m_speciesCharge[k];
m_lnActCoeffMolal[k] =
- z_k * z_k * numTmp / (1.0 + denomTmp * m_Aionic[k])
+ log(10.0) * m_B_Dot[k] * m_IionicMolality;
}
}
lnActivitySolvent = (xmolSolvent - 1.0)/xmolSolvent;
coeff = 2.0 / 3.0 * m_A_Debye * m_Mnaught
* sqrt(m_IionicMolality);
tmp = 0.0;
if (denomTmp > 0.0) {
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent || m_Aionic[k] != 0.0) {
y = denomTmp * m_Aionic[k];
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
z_k = m_speciesCharge[k];
tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
}
}
}
lnActivitySolvent += coeff * tmp;
tmp = 0.0;
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
if ((k != m_indexSolvent) && (z_k != 0.0)) {
tmp += m_B_Dot[k] * m_molalities[k];
}
}
lnActivitySolvent -=
m_Mnaught * log(10.0) * m_IionicMolality * tmp / 2.0;
/*
* Special section to implement the Helgeson fixed form
* for the water brine activity coefficient.
*/
if (m_useHelgesonFixedForm) {
lnActivitySolvent = _lnactivityWaterHelgesonFixedForm();
}
break;
case DHFORM_BDOT_ACOMMON:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_lnActCoeffMolal[k] =
- z_k * z_k * numTmp / (1.0 + denomTmp)
+ log(10.0) * m_B_Dot[k] * m_IionicMolality;
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
} else {
sigma = 0.0;
}
lnActivitySolvent =
(xmolSolvent - 1.0)/xmolSolvent +
2.0 /3.0 * m_A_Debye * m_Mnaught *
m_IionicMolality * sqrt(m_IionicMolality) * sigma;
tmp = 0.0;
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
if ((k != m_indexSolvent) && (z_k != 0.0)) {
tmp += m_B_Dot[k] * m_molalities[k];
}
}
lnActivitySolvent -=
m_Mnaught * log(10.0) * m_IionicMolality * tmp / 2.0;
break;
case DHFORM_BETAIJ:
denomTmp = m_B_Debye * m_Aionic[0];
denomTmp *= sqrt(m_IionicMolality);
lnActivitySolvent =
(xmolSolvent - 1.0)/xmolSolvent;
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_lnActCoeffMolal[k] =
- z_k * z_k * numTmp / (1.0 + denomTmp);
for (size_t j = 0; j < m_kk; j++) {
double beta = m_Beta_ij.value(k, j);
#ifdef DEBUG_HKM_NOT
if (beta != 0.0) {
printf("b: k = %d, j = %d, betakj = %g\n",
k, j, beta);
}
#endif
m_lnActCoeffMolal[k] += 2.0 * m_molalities[j] * beta;
}
}
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 -2.0*log(yp1));
} else {
sigma = 0.0;
}
lnActivitySolvent =
(xmolSolvent - 1.0)/xmolSolvent +
2.0 /3.0 * m_A_Debye * m_Mnaught *
m_IionicMolality * sqrt(m_IionicMolality) * sigma;
tmp = 0.0;
for (size_t k = 0; k < m_kk; k++) {
for (size_t j = 0; j < m_kk; j++) {
tmp +=
m_Beta_ij.value(k, j) * m_molalities[k] * m_molalities[j];
}
}
lnActivitySolvent -= m_Mnaught * tmp;
break;
case DHFORM_PITZER_BETAIJ:
denomTmp = m_B_Debye * sqrt(m_IionicMolality);
denomTmp *= m_Aionic[0];
numTmp = m_A_Debye * sqrt(m_IionicMolality);
tmpLn = log(1.0 + denomTmp);
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_lnActCoeffMolal[k] =
- z_k * z_k * numTmp / 3.0 / (1.0 + denomTmp);
m_lnActCoeffMolal[k] +=
- 2.0 * z_k * z_k * m_A_Debye * tmpLn /
(3.0 * m_B_Debye * m_Aionic[0]);
for (size_t j = 0; j < m_kk; j++) {
m_lnActCoeffMolal[k] += 2.0 * m_molalities[j] *
m_Beta_ij.value(k, j);
}
}
}
sigma = 1.0 / (1.0 + denomTmp);
lnActivitySolvent =
(xmolSolvent - 1.0)/xmolSolvent +
2.0 /3.0 * m_A_Debye * m_Mnaught *
m_IionicMolality * sqrt(m_IionicMolality) * sigma;
tmp = 0.0;
for (size_t k = 0; k < m_kk; k++) {
for (size_t j = 0; j < m_kk; j++) {
tmp +=
m_Beta_ij.value(k, j) * m_molalities[k] * m_molalities[j];
}
}
lnActivitySolvent -= m_Mnaught * tmp;
break;
default:
printf("ERROR\n");
exit(EXIT_FAILURE);
}
/*
* Above, we calculated the ln(activitySolvent). Translate that
* into the molar-based activity coefficient by dividing by
* the solvent mole fraction. Solvents are not on the molality
* scale.
*/
xmolSolvent = moleFraction(m_indexSolvent);
m_lnActCoeffMolal[m_indexSolvent] =
lnActivitySolvent - log(xmolSolvent);
}
/*
* s_update_dMolalityActCoeff_dT() (private, const )
*
* Using internally stored values, this function calculates
* the temperature derivative of the logarithm of the
* activity coefficient for all species in the mechanism.
*
* We assume that the activity coefficients are current.
*
* solvent activity coefficient is on the molality
* scale. It's derivative is too.
*/
void DebyeHuckel::s_update_dlnMolalityActCoeff_dT() const
{
double z_k, coeff, tmp, y, yp1, sigma, tmpLn;
// First we store dAdT explicitly here
double dAdT = dA_DebyedT_TP();
if (dAdT == 0.0) {
for (size_t k = 0; k < m_kk; k++) {
m_dlnActCoeffMolaldT[k] = 0.0;
}
return;
}
/*
* Calculate a safe value for the mole fraction
* of the solvent
*/
double xmolSolvent = moleFraction(m_indexSolvent);
xmolSolvent = std::max(8.689E-3, xmolSolvent);
double sqrtI = sqrt(m_IionicMolality);
double numdAdTTmp = dAdT * sqrtI;
double denomTmp = m_B_Debye * sqrtI;
double d_lnActivitySolvent_dT = 0;
switch (m_formDH) {
case DHFORM_DILUTE_LIMIT:
for (size_t k = 1; k < m_kk; k++) {
m_dlnActCoeffMolaldT[k] =
m_lnActCoeffMolal[k] * dAdT / m_A_Debye;
}
d_lnActivitySolvent_dT = 2.0 / 3.0 * dAdT * m_Mnaught *
m_IionicMolality * sqrt(m_IionicMolality);
m_dlnActCoeffMolaldT[m_indexSolvent] = d_lnActivitySolvent_dT;
break;
case DHFORM_BDOT_AK:
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldT[k] =
- z_k * z_k * numdAdTTmp / (1.0 + denomTmp * m_Aionic[k]);
}
m_dlnActCoeffMolaldT[m_indexSolvent] = 0.0;
coeff = 2.0 / 3.0 * dAdT * m_Mnaught * sqrtI;
tmp = 0.0;
if (denomTmp > 0.0) {
for (size_t k = 0; k < m_kk; k++) {
y = denomTmp * m_Aionic[k];
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
z_k = m_speciesCharge[k];
tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
}
}
m_dlnActCoeffMolaldT[m_indexSolvent] += coeff * tmp;
break;
case DHFORM_BDOT_ACOMMON:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldT[k] =
- z_k * z_k * numdAdTTmp / (1.0 + denomTmp);
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
} else {
sigma = 0.0;
}
m_dlnActCoeffMolaldT[m_indexSolvent] =
2.0 /3.0 * dAdT * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
case DHFORM_BETAIJ:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldT[k] =
- z_k * z_k * numdAdTTmp / (1.0 + denomTmp);
}
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
} else {
sigma = 0.0;
}
m_dlnActCoeffMolaldT[m_indexSolvent] =
2.0 /3.0 * dAdT * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
case DHFORM_PITZER_BETAIJ:
denomTmp *= m_Aionic[0];
tmpLn = log(1.0 + denomTmp);
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldT[k] =
- z_k * z_k * numdAdTTmp / (1.0 + denomTmp)
- 2.0 * z_k * z_k * dAdT * tmpLn
/ (m_B_Debye * m_Aionic[0]);
m_dlnActCoeffMolaldT[k] /= 3.0;
}
}
sigma = 1.0 / (1.0 + denomTmp);
m_dlnActCoeffMolaldT[m_indexSolvent] =
2.0 /3.0 * dAdT * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
default:
printf("ERROR\n");
exit(EXIT_FAILURE);
break;
}
}
/*
* s_update_d2lnMolalityActCoeff_dT2() (private, const )
*
* Using internally stored values, this function calculates
* the temperature 2nd derivative of the logarithm of the
* activity coefficient
* for all species in the mechanism.
*
* We assume that the activity coefficients are current.
*
* solvent activity coefficient is on the molality
* scale. It's derivatives are too.
*/
void DebyeHuckel::s_update_d2lnMolalityActCoeff_dT2() const
{
double z_k, coeff, tmp, y, yp1, sigma, tmpLn;
double dAdT = dA_DebyedT_TP();
double d2AdT2 = d2A_DebyedT2_TP();
if (d2AdT2 == 0.0 && dAdT == 0.0) {
for (size_t k = 0; k < m_kk; k++) {
m_d2lnActCoeffMolaldT2[k] = 0.0;
}
return;
}
/*
* Calculate a safe value for the mole fraction
* of the solvent
*/
double xmolSolvent = moleFraction(m_indexSolvent);
xmolSolvent = std::max(8.689E-3, xmolSolvent);
double sqrtI = sqrt(m_IionicMolality);
double numd2AdT2Tmp = d2AdT2 * sqrtI;
double denomTmp = m_B_Debye * sqrtI;
switch (m_formDH) {
case DHFORM_DILUTE_LIMIT:
for (size_t k = 0; k < m_kk; k++) {
m_d2lnActCoeffMolaldT2[k] =
m_lnActCoeffMolal[k] * d2AdT2 / m_A_Debye;
}
break;
case DHFORM_BDOT_AK:
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_d2lnActCoeffMolaldT2[k] =
- z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp * m_Aionic[k]);
}
m_d2lnActCoeffMolaldT2[m_indexSolvent] = 0.0;
coeff = 2.0 / 3.0 * d2AdT2 * m_Mnaught * sqrtI;
tmp = 0.0;
if (denomTmp > 0.0) {
for (size_t k = 0; k < m_kk; k++) {
y = denomTmp * m_Aionic[k];
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
z_k = m_speciesCharge[k];
tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
}
}
m_d2lnActCoeffMolaldT2[m_indexSolvent] += coeff * tmp;
break;
case DHFORM_BDOT_ACOMMON:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_d2lnActCoeffMolaldT2[k] =
- z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp);
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
} else {
sigma = 0.0;
}
m_d2lnActCoeffMolaldT2[m_indexSolvent] =
2.0 /3.0 * d2AdT2 * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
case DHFORM_BETAIJ:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_d2lnActCoeffMolaldT2[k] =
- z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp);
}
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 -2.0*log(yp1));
} else {
sigma = 0.0;
}
m_d2lnActCoeffMolaldT2[m_indexSolvent] =
2.0 /3.0 * d2AdT2 * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
case DHFORM_PITZER_BETAIJ:
denomTmp *= m_Aionic[0];
tmpLn = log(1.0 + denomTmp);
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_d2lnActCoeffMolaldT2[k] =
- z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp)
- 2.0 * z_k * z_k * d2AdT2 * tmpLn
/ (m_B_Debye * m_Aionic[0]);
m_d2lnActCoeffMolaldT2[k] /= 3.0;
}
}
sigma = 1.0 / (1.0 + denomTmp);
m_d2lnActCoeffMolaldT2[m_indexSolvent] =
2.0 /3.0 * d2AdT2 * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
default:
printf("ERROR\n");
exit(EXIT_FAILURE);
break;
}
}
/*
* s_update_dlnMolalityActCoeff_dP() (private, const )
*
* Using internally stored values, this function calculates
* the pressure derivative of the logarithm of the
* activity coefficient for all species in the mechanism.
*
* We assume that the activity coefficients, molalities,
* and A_Debye are current.
*
* solvent activity coefficient is on the molality
* scale. It's derivatives are too.
*/
void DebyeHuckel::s_update_dlnMolalityActCoeff_dP() const
{
double z_k, coeff, tmp, y, yp1, sigma, tmpLn;
int est;
double dAdP = dA_DebyedP_TP();
if (dAdP == 0.0) {
for (size_t k = 0; k < m_kk; k++) {
m_dlnActCoeffMolaldP[k] = 0.0;
}
return;
}
/*
* Calculate a safe value for the mole fraction
* of the solvent
*/
double xmolSolvent = moleFraction(m_indexSolvent);
xmolSolvent = std::max(8.689E-3, xmolSolvent);
double sqrtI = sqrt(m_IionicMolality);
double numdAdPTmp = dAdP * sqrtI;
double denomTmp = m_B_Debye * sqrtI;
switch (m_formDH) {
case DHFORM_DILUTE_LIMIT:
for (size_t k = 0; k < m_kk; k++) {
m_dlnActCoeffMolaldP[k] =
m_lnActCoeffMolal[k] * dAdP / m_A_Debye;
}
break;
case DHFORM_BDOT_AK:
for (size_t k = 0; k < m_kk; k++) {
est = m_electrolyteSpeciesType[k];
if (est == cEST_nonpolarNeutral) {
m_lnActCoeffMolal[k] = 0.0;
} else {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldP[k] =
- z_k * z_k * numdAdPTmp / (1.0 + denomTmp * m_Aionic[k]);
}
}
m_dlnActCoeffMolaldP[m_indexSolvent] = 0.0;
coeff = 2.0 / 3.0 * dAdP * m_Mnaught * sqrtI;
tmp = 0.0;
if (denomTmp > 0.0) {
for (size_t k = 0; k < m_kk; k++) {
y = denomTmp * m_Aionic[k];
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
z_k = m_speciesCharge[k];
tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
}
}
m_dlnActCoeffMolaldP[m_indexSolvent] += coeff * tmp;
break;
case DHFORM_BDOT_ACOMMON:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldP[k] =
- z_k * z_k * numdAdPTmp / (1.0 + denomTmp);
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
} else {
sigma = 0.0;
}
m_dlnActCoeffMolaldP[m_indexSolvent] =
2.0 /3.0 * dAdP * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
case DHFORM_BETAIJ:
denomTmp *= m_Aionic[0];
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldP[k] =
- z_k * z_k * numdAdPTmp / (1.0 + denomTmp);
}
}
if (denomTmp > 0.0) {
y = denomTmp;
yp1 = y + 1.0;
sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
} else {
sigma = 0.0;
}
m_dlnActCoeffMolaldP[m_indexSolvent] =
2.0 /3.0 * dAdP * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
case DHFORM_PITZER_BETAIJ:
denomTmp *= m_Aionic[0];
tmpLn = log(1.0 + denomTmp);
for (size_t k = 0; k < m_kk; k++) {
if (k != m_indexSolvent) {
z_k = m_speciesCharge[k];
m_dlnActCoeffMolaldP[k] =
- z_k * z_k * numdAdPTmp / (1.0 + denomTmp)
- 2.0 * z_k * z_k * dAdP * tmpLn
/ (m_B_Debye * m_Aionic[0]);
m_dlnActCoeffMolaldP[k] /= 3.0;
}
}
sigma = 1.0 / (1.0 + denomTmp);
m_dlnActCoeffMolaldP[m_indexSolvent] =
2.0 /3.0 * dAdP * m_Mnaught *
m_IionicMolality * sqrtI * sigma;
break;
default:
printf("ERROR\n");
exit(EXIT_FAILURE);
break;
}
}
/*
* Updates the standard state thermodynamic functions at the current T and P of the solution.
*
* @internal
*
* This function gets called for every call to functions in this
* class. It checks to see whether the temperature or pressure has changed and
* thus the ss thermodynamics functions for all of the species
* must be recalculated.
*/
// void DebyeHuckel::_updateStandardStateThermo() const {
// doublereal tnow = temperature();
// doublereal pnow = m_Pcurrent;
// if (m_waterSS) {
// m_waterSS->setTempPressure(tnow, pnow);
// }
// m_VPSS_ptr->setState_TP(tnow, pnow);
// VPStandardStateTP::updateStandardStateThermo();
//}
}