4296 lines
163 KiB
C++
4296 lines
163 KiB
C++
/**
|
|
* @file HMWSoln.cpp
|
|
* Definitions for the HMWSoln ThermoPhase object, which
|
|
* models concentrated electrolyte solutions
|
|
* (see \ref thermoprops and \link Cantera::HMWSoln HMWSoln \endlink) .
|
|
*
|
|
* Class HMWSoln represents a concentrated liquid electrolyte phase which obeys
|
|
* the Pitzer formulation for nonideality using molality-based standard states.
|
|
*
|
|
* This version of the code was modified to have the binary Beta2 Pitzer
|
|
* parameter consistent with the temperature expansions used for Beta0,
|
|
* Beta1, and Cphi.(CFJC, SNL)
|
|
*/
|
|
|
|
// This file is part of Cantera. See License.txt in the top-level directory or
|
|
// at http://www.cantera.org/license.txt for license and copyright information.
|
|
|
|
#include "cantera/thermo/HMWSoln.h"
|
|
#include "cantera/thermo/ThermoFactory.h"
|
|
#include "cantera/thermo/PDSS_Water.h"
|
|
#include "cantera/thermo/electrolytes.h"
|
|
#include "cantera/base/stringUtils.h"
|
|
#include "cantera/base/ctml.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace Cantera
|
|
{
|
|
|
|
HMWSoln::HMWSoln() :
|
|
m_formPitzerTemp(PITZER_TEMP_CONSTANT),
|
|
m_IionicMolality(0.0),
|
|
m_maxIionicStrength(100.0),
|
|
m_TempPitzerRef(298.15),
|
|
m_form_A_Debye(A_DEBYE_CONST),
|
|
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
|
|
m_waterSS(0),
|
|
m_molalitiesAreCropped(false),
|
|
IMS_X_o_cutoff_(0.2),
|
|
IMS_cCut_(0.05),
|
|
IMS_slopegCut_(0.0),
|
|
IMS_dfCut_(0.0),
|
|
IMS_efCut_(0.0),
|
|
IMS_afCut_(0.0),
|
|
IMS_bfCut_(0.0),
|
|
IMS_dgCut_(0.0),
|
|
IMS_egCut_(0.0),
|
|
IMS_agCut_(0.0),
|
|
IMS_bgCut_(0.0),
|
|
MC_X_o_cutoff_(0.0),
|
|
MC_dpCut_(0.0),
|
|
MC_epCut_(0.0),
|
|
MC_apCut_(0.0),
|
|
MC_bpCut_(0.0),
|
|
MC_cpCut_(0.0),
|
|
CROP_ln_gamma_o_min(-6.0),
|
|
CROP_ln_gamma_o_max(3.0),
|
|
CROP_ln_gamma_k_min(-5.0),
|
|
CROP_ln_gamma_k_max(15.0),
|
|
m_last_is(-1.0)
|
|
{
|
|
}
|
|
|
|
HMWSoln::~HMWSoln()
|
|
{
|
|
}
|
|
|
|
HMWSoln::HMWSoln(const std::string& inputFile, const std::string& id_) :
|
|
m_formPitzerTemp(PITZER_TEMP_CONSTANT),
|
|
m_IionicMolality(0.0),
|
|
m_maxIionicStrength(100.0),
|
|
m_TempPitzerRef(298.15),
|
|
m_form_A_Debye(A_DEBYE_CONST),
|
|
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
|
|
m_waterSS(0),
|
|
m_molalitiesAreCropped(false),
|
|
IMS_X_o_cutoff_(0.2),
|
|
IMS_cCut_(0.05),
|
|
IMS_slopegCut_(0.0),
|
|
IMS_dfCut_(0.0),
|
|
IMS_efCut_(0.0),
|
|
IMS_afCut_(0.0),
|
|
IMS_bfCut_(0.0),
|
|
IMS_dgCut_(0.0),
|
|
IMS_egCut_(0.0),
|
|
IMS_agCut_(0.0),
|
|
IMS_bgCut_(0.0),
|
|
MC_X_o_cutoff_(0.0),
|
|
MC_dpCut_(0.0),
|
|
MC_epCut_(0.0),
|
|
MC_apCut_(0.0),
|
|
MC_bpCut_(0.0),
|
|
MC_cpCut_(0.0),
|
|
CROP_ln_gamma_o_min(-6.0),
|
|
CROP_ln_gamma_o_max(3.0),
|
|
CROP_ln_gamma_k_min(-5.0),
|
|
CROP_ln_gamma_k_max(15.0),
|
|
m_last_is(-1.0)
|
|
{
|
|
initThermoFile(inputFile, id_);
|
|
}
|
|
|
|
HMWSoln::HMWSoln(XML_Node& phaseRoot, const std::string& id_) :
|
|
m_formPitzerTemp(PITZER_TEMP_CONSTANT),
|
|
m_IionicMolality(0.0),
|
|
m_maxIionicStrength(100.0),
|
|
m_TempPitzerRef(298.15),
|
|
m_form_A_Debye(A_DEBYE_CONST),
|
|
m_A_Debye(1.172576), // units = sqrt(kg/gmol)
|
|
m_waterSS(0),
|
|
m_molalitiesAreCropped(false),
|
|
IMS_X_o_cutoff_(0.2),
|
|
IMS_cCut_(0.05),
|
|
IMS_slopegCut_(0.0),
|
|
IMS_dfCut_(0.0),
|
|
IMS_efCut_(0.0),
|
|
IMS_afCut_(0.0),
|
|
IMS_bfCut_(0.0),
|
|
IMS_dgCut_(0.0),
|
|
IMS_egCut_(0.0),
|
|
IMS_agCut_(0.0),
|
|
IMS_bgCut_(0.0),
|
|
MC_X_o_cutoff_(0.0),
|
|
MC_dpCut_(0.0),
|
|
MC_epCut_(0.0),
|
|
MC_apCut_(0.0),
|
|
MC_bpCut_(0.0),
|
|
MC_cpCut_(0.0),
|
|
CROP_ln_gamma_o_min(-6.0),
|
|
CROP_ln_gamma_o_max(3.0),
|
|
CROP_ln_gamma_k_min(-5.0),
|
|
CROP_ln_gamma_k_max(15.0),
|
|
m_last_is(-1.0)
|
|
{
|
|
importPhase(phaseRoot, this);
|
|
}
|
|
|
|
// -------- Molar Thermodynamic Properties of the Solution ---------------
|
|
|
|
doublereal HMWSoln::enthalpy_mole() const
|
|
{
|
|
getPartialMolarEnthalpies(m_tmpV.data());
|
|
return mean_X(m_tmpV);
|
|
}
|
|
|
|
doublereal HMWSoln::relative_enthalpy() const
|
|
{
|
|
getPartialMolarEnthalpies(m_tmpV.data());
|
|
double hbar = mean_X(m_tmpV);
|
|
getEnthalpy_RT(m_gamma_tmp.data());
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_gamma_tmp[k] *= RT();
|
|
}
|
|
double h0bar = mean_X(m_gamma_tmp);
|
|
return hbar - h0bar;
|
|
}
|
|
|
|
doublereal HMWSoln::relative_molal_enthalpy() const
|
|
{
|
|
double L = relative_enthalpy();
|
|
getMoleFractions(m_tmpV.data());
|
|
double xanion = 0.0;
|
|
size_t kcation = npos;
|
|
double xcation = 0.0;
|
|
size_t kanion = npos;
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
if (m_tmpV[k] > xanion) {
|
|
xanion = m_tmpV[k];
|
|
kanion = k;
|
|
}
|
|
} else if (charge(k) < 0.0) {
|
|
if (m_tmpV[k] > xcation) {
|
|
xcation = m_tmpV[k];
|
|
kcation = k;
|
|
}
|
|
}
|
|
}
|
|
if (kcation == npos || kanion == npos) {
|
|
return L;
|
|
}
|
|
double xuse = xcation;
|
|
double factor = 1;
|
|
if (xanion < xcation) {
|
|
xuse = xanion;
|
|
if (charge(kcation) != 1.0) {
|
|
factor = charge(kcation);
|
|
}
|
|
} else {
|
|
if (charge(kanion) != 1.0) {
|
|
factor = charge(kanion);
|
|
}
|
|
}
|
|
xuse = xuse / factor;
|
|
return L / xuse;
|
|
}
|
|
|
|
doublereal HMWSoln::entropy_mole() const
|
|
{
|
|
getPartialMolarEntropies(m_tmpV.data());
|
|
return mean_X(m_tmpV);
|
|
}
|
|
|
|
doublereal HMWSoln::gibbs_mole() const
|
|
{
|
|
getChemPotentials(m_tmpV.data());
|
|
return mean_X(m_tmpV);
|
|
}
|
|
|
|
doublereal HMWSoln::cp_mole() const
|
|
{
|
|
getPartialMolarCp(m_tmpV.data());
|
|
return mean_X(m_tmpV);
|
|
}
|
|
|
|
doublereal HMWSoln::cv_mole() const
|
|
{
|
|
double kappa_t = isothermalCompressibility();
|
|
double beta = thermalExpansionCoeff();
|
|
double cp = cp_mole();
|
|
double tt = temperature();
|
|
double molarV = molarVolume();
|
|
return cp - beta * beta * tt * molarV / kappa_t;
|
|
}
|
|
|
|
// ------- Mechanical Equation of State Properties ------------------------
|
|
|
|
void HMWSoln::calcDensity()
|
|
{
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
if(cached.validate(temperature(), pressure(), stateMFNumber())) {
|
|
return;
|
|
}
|
|
|
|
// Calculate all of the other standard volumes. Note these are constant for
|
|
// now
|
|
getPartialMolarVolumes(m_tmpV.data());
|
|
double dd = meanMolecularWeight() / mean_X(m_tmpV);
|
|
Phase::setDensity(dd);
|
|
}
|
|
|
|
void HMWSoln::setDensity(const doublereal rho)
|
|
{
|
|
double dens_old = density();
|
|
if (rho != dens_old) {
|
|
throw CanteraError("HMWSoln::setDensity",
|
|
"Density is not an independent variable");
|
|
}
|
|
}
|
|
|
|
void HMWSoln::setMolarDensity(const doublereal rho)
|
|
{
|
|
throw CanteraError("HMWSoln::setMolarDensity",
|
|
"Density is not an independent variable");
|
|
}
|
|
|
|
// ------- Activities and Activity Concentrations
|
|
|
|
void HMWSoln::getActivityConcentrations(doublereal* c) const
|
|
{
|
|
double cs_solvent = standardConcentration();
|
|
getActivities(c);
|
|
c[0] *= cs_solvent;
|
|
if (m_kk > 1) {
|
|
double cs_solute = standardConcentration(1);
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
c[k] *= cs_solute;
|
|
}
|
|
}
|
|
}
|
|
|
|
doublereal HMWSoln::standardConcentration(size_t k) const
|
|
{
|
|
getStandardVolumes(m_tmpV.data());
|
|
double mvSolvent = m_tmpV[0];
|
|
if (k > 0) {
|
|
return m_Mnaught / mvSolvent;
|
|
}
|
|
return 1.0 / mvSolvent;
|
|
}
|
|
|
|
void HMWSoln::getActivities(doublereal* ac) const
|
|
{
|
|
updateStandardStateThermo();
|
|
|
|
// Update the molality array, m_molalities(). This requires an update due to
|
|
// mole fractions
|
|
s_update_lnMolalityActCoeff();
|
|
|
|
// Now calculate the array of activities.
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
ac[k] = m_molalities[k] * exp(m_lnActCoeffMolal_Scaled[k]);
|
|
}
|
|
double xmolSolvent = moleFraction(0);
|
|
ac[0] = exp(m_lnActCoeffMolal_Scaled[0]) * xmolSolvent;
|
|
}
|
|
|
|
void HMWSoln::getUnscaledMolalityActivityCoefficients(doublereal* acMolality) const
|
|
{
|
|
updateStandardStateThermo();
|
|
A_Debye_TP(-1.0, -1.0);
|
|
s_update_lnMolalityActCoeff();
|
|
std::copy(m_lnActCoeffMolal_Unscaled.begin(), m_lnActCoeffMolal_Unscaled.end(), acMolality);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
acMolality[k] = exp(acMolality[k]);
|
|
}
|
|
}
|
|
|
|
// ------ Partial Molar Properties of the Solution -----------------
|
|
|
|
void HMWSoln::getChemPotentials(doublereal* mu) const
|
|
{
|
|
double xx;
|
|
|
|
// 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();
|
|
double xmolSolvent = moleFraction(0);
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
xx = std::max(m_molalities[k], SmallNumber);
|
|
mu[k] += RT() * (log(xx) + m_lnActCoeffMolal_Scaled[k]);
|
|
}
|
|
xx = std::max(xmolSolvent, SmallNumber);
|
|
mu[0] += RT() * (log(xx) + m_lnActCoeffMolal_Scaled[0]);
|
|
}
|
|
|
|
void HMWSoln::getPartialMolarEnthalpies(doublereal* hbar) const
|
|
{
|
|
// Get the nondimensional standard state enthalpies
|
|
getEnthalpy_RT(hbar);
|
|
|
|
// dimensionalize it.
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
hbar[k] *= RT();
|
|
}
|
|
|
|
// Update the activity coefficients, This also update the internally stored
|
|
// molalities.
|
|
s_update_lnMolalityActCoeff();
|
|
s_update_dlnMolalityActCoeff_dT();
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
hbar[k] -= RT() * temperature() * m_dlnActCoeffMolaldT_Scaled[k];
|
|
}
|
|
}
|
|
|
|
void HMWSoln::getPartialMolarEntropies(doublereal* sbar) const
|
|
{
|
|
// Get the standard state entropies at the temperature and pressure of the
|
|
// solution.
|
|
getEntropy_R(sbar);
|
|
|
|
// Dimensionalize the entropies
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
sbar[k] *= GasConstant;
|
|
}
|
|
|
|
// Update the activity coefficients, This also update the internally stored
|
|
// 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 = 1; k < m_kk; k++) {
|
|
mm = std::max(SmallNumber, m_molalities[k]);
|
|
sbar[k] -= GasConstant * (log(mm) + m_lnActCoeffMolal_Scaled[k]);
|
|
}
|
|
double xmolSolvent = moleFraction(0);
|
|
mm = std::max(SmallNumber, xmolSolvent);
|
|
sbar[0] -= GasConstant *(log(mm) + m_lnActCoeffMolal_Scaled[0]);
|
|
|
|
// Check to see whether activity coefficients are temperature dependent. If
|
|
// they are, then calculate the their temperature derivatives and add them
|
|
// into the result.
|
|
s_update_dlnMolalityActCoeff_dT();
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
sbar[k] -= RT() * m_dlnActCoeffMolaldT_Scaled[k];
|
|
}
|
|
}
|
|
|
|
void HMWSoln::getPartialMolarVolumes(doublereal* vbar) const
|
|
{
|
|
// Get the standard state values in m^3 kmol-1
|
|
getStandardVolumes(vbar);
|
|
|
|
// Update the derivatives wrt the activity coefficients.
|
|
s_update_lnMolalityActCoeff();
|
|
s_update_dlnMolalityActCoeff_dP();
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
vbar[k] += RT() * m_dlnActCoeffMolaldP_Scaled[k];
|
|
}
|
|
}
|
|
|
|
void HMWSoln::getPartialMolarCp(doublereal* cpbar) const
|
|
{
|
|
getCp_R(cpbar);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
cpbar[k] *= GasConstant;
|
|
}
|
|
|
|
// Update the activity coefficients, This also update the internally stored
|
|
// molalities.
|
|
s_update_lnMolalityActCoeff();
|
|
s_update_dlnMolalityActCoeff_dT();
|
|
s_update_d2lnMolalityActCoeff_dT2();
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
cpbar[k] -= (2.0 * RT() * m_dlnActCoeffMolaldT_Scaled[k] +
|
|
RT() * temperature() * m_d2lnActCoeffMolaldT2_Scaled[k]);
|
|
}
|
|
}
|
|
|
|
// -------------- Utilities -------------------------------
|
|
|
|
doublereal HMWSoln::satPressure(doublereal t) {
|
|
double p_old = pressure();
|
|
double t_old = temperature();
|
|
double pres = m_waterSS->satPressure(t);
|
|
|
|
// Set the underlying object back to its original state.
|
|
m_waterSS->setState_TP(t_old, p_old);
|
|
return pres;
|
|
}
|
|
|
|
static void check_nParams(const std::string& method, size_t nParams,
|
|
size_t m_formPitzerTemp)
|
|
{
|
|
if (m_formPitzerTemp == PITZER_TEMP_CONSTANT && nParams != 1) {
|
|
throw CanteraError(method, "'constant' temperature model requires one"
|
|
" coefficient for each of parameter, but {} were given", nParams);
|
|
} else if (m_formPitzerTemp == PITZER_TEMP_LINEAR && nParams != 2) {
|
|
throw CanteraError(method, "'linear' temperature model requires two"
|
|
" coefficients for each parameter, but {} were given", nParams);
|
|
}
|
|
if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1 && nParams != 5) {
|
|
throw CanteraError(method, "'complex' temperature model requires five"
|
|
" coefficients for each parameter, but {} were given", nParams);
|
|
}
|
|
}
|
|
|
|
void HMWSoln::setBinarySalt(const std::string& sp1, const std::string& sp2,
|
|
size_t nParams, double* beta0, double* beta1, double* beta2,
|
|
double* Cphi, double alpha1, double alpha2)
|
|
{
|
|
size_t k1 = speciesIndex(sp1);
|
|
size_t k2 = speciesIndex(sp2);
|
|
if (k1 == npos) {
|
|
throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' not found", sp1);
|
|
} else if (k2 == npos) {
|
|
throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' not found", sp2);
|
|
}
|
|
if (charge(k1) < 0 && charge(k2) > 0) {
|
|
std::swap(k1, k2);
|
|
} else if (charge(k1) * charge(k2) >= 0) {
|
|
throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' and '{}' "
|
|
"do not have opposite charges ({}, {})", sp1, sp2,
|
|
charge(k1), charge(k2));
|
|
}
|
|
check_nParams("HMWSoln::setBinarySalt", nParams, m_formPitzerTemp);
|
|
|
|
size_t c = m_CounterIJ[k1 * m_kk + k2];
|
|
m_Beta0MX_ij[c] = beta0[0];
|
|
m_Beta1MX_ij[c] = beta1[0];
|
|
m_Beta2MX_ij[c] = beta2[0];
|
|
m_CphiMX_ij[c] = Cphi[0];
|
|
for (size_t n = 0; n < nParams; n++) {
|
|
m_Beta0MX_ij_coeff(n, c) = beta0[n];
|
|
m_Beta1MX_ij_coeff(n, c) = beta1[n];
|
|
m_Beta2MX_ij_coeff(n, c) = beta2[n];
|
|
m_CphiMX_ij_coeff(n, c) = Cphi[n];
|
|
}
|
|
m_Alpha1MX_ij[c] = alpha1;
|
|
m_Alpha2MX_ij[c] = alpha2;
|
|
}
|
|
|
|
void HMWSoln::setTheta(const std::string& sp1, const std::string& sp2,
|
|
size_t nParams, double* theta)
|
|
{
|
|
size_t k1 = speciesIndex(sp1);
|
|
size_t k2 = speciesIndex(sp2);
|
|
if (k1 == npos) {
|
|
throw CanteraError("HMWSoln::setTheta", "Species '{}' not found", sp1);
|
|
} else if (k2 == npos) {
|
|
throw CanteraError("HMWSoln::setTheta", "Species '{}' not found", sp2);
|
|
}
|
|
if (charge(k1) * charge(k2) <= 0) {
|
|
throw CanteraError("HMWSoln::setTheta", "Species '{}' and '{}' "
|
|
"should both have the same (non-zero) charge ({}, {})", sp1, sp2,
|
|
charge(k1), charge(k2));
|
|
}
|
|
check_nParams("HMWSoln::setTheta", nParams, m_formPitzerTemp);
|
|
size_t c = m_CounterIJ[k1 * m_kk + k2];
|
|
m_Theta_ij[c] = theta[0];
|
|
for (size_t n = 0; n < nParams; n++) {
|
|
m_Theta_ij_coeff(n, c) = theta[n];
|
|
}
|
|
}
|
|
|
|
void HMWSoln::setPsi(const std::string& sp1, const std::string& sp2,
|
|
const std::string& sp3, size_t nParams, double* psi)
|
|
{
|
|
size_t k1 = speciesIndex(sp1);
|
|
size_t k2 = speciesIndex(sp2);
|
|
size_t k3 = speciesIndex(sp3);
|
|
if (k1 == npos) {
|
|
throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp1);
|
|
} else if (k2 == npos) {
|
|
throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp2);
|
|
} else if (k3 == npos) {
|
|
throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp3);
|
|
}
|
|
|
|
if (!charge(k1) || !charge(k2) || !charge(k3) ||
|
|
std::abs(sign(charge(k1) + sign(charge(k2)) + sign(charge(k3)))) != 1) {
|
|
throw CanteraError("HMWSoln::setPsi", "All species must be ions and"
|
|
" must include at least one cation and one anion, but given species"
|
|
" (charges) were: {} ({}), {} ({}), and {} ({}).",
|
|
sp1, charge(k1), sp2, charge(k2), sp3, charge(k3));
|
|
}
|
|
check_nParams("HMWSoln::setPsi", nParams, m_formPitzerTemp);
|
|
auto cc = {k1*m_kk*m_kk + k2*m_kk + k3,
|
|
k1*m_kk*m_kk + k3*m_kk + k2,
|
|
k2*m_kk*m_kk + k1*m_kk + k3,
|
|
k2*m_kk*m_kk + k3*m_kk + k1,
|
|
k3*m_kk*m_kk + k2*m_kk + k1,
|
|
k3*m_kk*m_kk + k1*m_kk + k2};
|
|
for (auto c : cc) {
|
|
for (size_t n = 0; n < nParams; n++) {
|
|
m_Psi_ijk_coeff(n, c) = psi[n];
|
|
}
|
|
m_Psi_ijk[c] = psi[0];
|
|
}
|
|
}
|
|
|
|
void HMWSoln::setLambda(const std::string& sp1, const std::string& sp2,
|
|
size_t nParams, double* lambda)
|
|
{
|
|
size_t k1 = speciesIndex(sp1);
|
|
size_t k2 = speciesIndex(sp2);
|
|
if (k1 == npos) {
|
|
throw CanteraError("HMWSoln::setLambda", "Species '{}' not found", sp1);
|
|
} else if (k2 == npos) {
|
|
throw CanteraError("HMWSoln::setLambda", "Species '{}' not found", sp2);
|
|
}
|
|
|
|
if (charge(k1) != 0 && charge(k2) != 0) {
|
|
throw CanteraError("HMWSoln::setLambda", "Expected at least one neutral"
|
|
" species, but given species (charges) were: {} ({}) and {} ({}).",
|
|
sp1, charge(k1), sp2, charge(k2));
|
|
}
|
|
if (charge(k1) != 0) {
|
|
std::swap(k1, k2);
|
|
}
|
|
check_nParams("HMWSoln::setLambda", nParams, m_formPitzerTemp);
|
|
size_t c = k1*m_kk + k2;
|
|
for (size_t n = 0; n < nParams; n++) {
|
|
m_Lambda_nj_coeff(n, c) = lambda[n];
|
|
}
|
|
m_Lambda_nj(k1, k2) = lambda[0];
|
|
}
|
|
|
|
void HMWSoln::setMunnn(const std::string& sp, size_t nParams, double* munnn)
|
|
{
|
|
size_t k = speciesIndex(sp);
|
|
if (k == npos) {
|
|
throw CanteraError("HMWSoln::setMunnn", "Species '{}' not found", sp);
|
|
}
|
|
|
|
if (charge(k) != 0) {
|
|
throw CanteraError("HMWSoln::setMunnn", "Expected a neutral species,"
|
|
" got {} ({}).", sp, charge(k));
|
|
}
|
|
check_nParams("HMWSoln::setMunnn", nParams, m_formPitzerTemp);
|
|
for (size_t n = 0; n < nParams; n++) {
|
|
m_Mu_nnn_coeff(n, k) = munnn[n];
|
|
}
|
|
m_Mu_nnn[k] = munnn[0];
|
|
}
|
|
|
|
void HMWSoln::setZeta(const std::string& sp1, const std::string& sp2,
|
|
const std::string& sp3, size_t nParams, double* psi)
|
|
{
|
|
size_t k1 = speciesIndex(sp1);
|
|
size_t k2 = speciesIndex(sp2);
|
|
size_t k3 = speciesIndex(sp3);
|
|
if (k1 == npos) {
|
|
throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp1);
|
|
} else if (k2 == npos) {
|
|
throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp2);
|
|
} else if (k3 == npos) {
|
|
throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp3);
|
|
}
|
|
|
|
if (charge(k1)*charge(k2)*charge(k3) != 0 ||
|
|
sign(charge(k1)) + sign(charge(k2)) + sign(charge(k3)) != 0) {
|
|
throw CanteraError("HMWSoln::setZeta", "Requires one neutral species, "
|
|
"one cation, and one anion, but given species (charges) were: "
|
|
"{} ({}), {} ({}), and {} ({}).",
|
|
sp1, charge(k1), sp2, charge(k2), sp3, charge(k3));
|
|
}
|
|
|
|
//! Make k1 the neutral species
|
|
if (charge(k2) == 0) {
|
|
std::swap(k1, k2);
|
|
} else if (charge(k3) == 0) {
|
|
std::swap(k1, k3);
|
|
}
|
|
|
|
// Make k2 the cation
|
|
if (charge(k3) > 0) {
|
|
std::swap(k2, k3);
|
|
}
|
|
|
|
check_nParams("HMWSoln::setZeta", nParams, m_formPitzerTemp);
|
|
// In contrast to setPsi, there are no duplicate entries
|
|
size_t c = k1 * m_kk *m_kk + k2 * m_kk + k3;
|
|
for (size_t n = 0; n < nParams; n++) {
|
|
m_Psi_ijk_coeff(n, c) = psi[n];
|
|
}
|
|
m_Psi_ijk[c] = psi[0];
|
|
}
|
|
|
|
void HMWSoln::setPitzerTempModel(const std::string& model)
|
|
{
|
|
if (caseInsensitiveEquals(model, "constant") || caseInsensitiveEquals(model, "default")) {
|
|
m_formPitzerTemp = PITZER_TEMP_CONSTANT;
|
|
} else if (caseInsensitiveEquals(model, "linear")) {
|
|
m_formPitzerTemp = PITZER_TEMP_LINEAR;
|
|
} else if (caseInsensitiveEquals(model, "complex") || caseInsensitiveEquals(model, "complex1")) {
|
|
m_formPitzerTemp = PITZER_TEMP_COMPLEX1;
|
|
} else {
|
|
throw CanteraError("HMWSoln::setPitzerTempModel",
|
|
"Unknown Pitzer ActivityCoeff Temp model: {}", model);
|
|
}
|
|
}
|
|
|
|
void HMWSoln::setA_Debye(double A)
|
|
{
|
|
if (A < 0) {
|
|
m_form_A_Debye = A_DEBYE_WATER;
|
|
} else {
|
|
m_form_A_Debye = A_DEBYE_CONST;
|
|
m_A_Debye = A;
|
|
}
|
|
}
|
|
|
|
void HMWSoln::setCroppingCoefficients(double ln_gamma_k_min,
|
|
double ln_gamma_k_max, double ln_gamma_o_min, double ln_gamma_o_max)
|
|
{
|
|
CROP_ln_gamma_k_min = ln_gamma_k_min;
|
|
CROP_ln_gamma_k_max = ln_gamma_k_max;
|
|
CROP_ln_gamma_o_min = ln_gamma_o_min;
|
|
CROP_ln_gamma_o_max = ln_gamma_o_max;
|
|
}
|
|
|
|
vector_fp getSizedVector(const AnyMap& item, const std::string& key, size_t nCoeffs)
|
|
{
|
|
vector_fp v;
|
|
if (item[key].is<double>()) {
|
|
// Allow a single value to be given directly, rather than as a list of
|
|
// one item
|
|
v.push_back(item[key].asDouble());
|
|
} else {
|
|
v = item[key].asVector<double>(1, nCoeffs);
|
|
}
|
|
if (v.size() == 1 && nCoeffs == 5) {
|
|
// Adapt constant-temperature data to be compatible with the "complex"
|
|
// temperature model
|
|
v.resize(5, 0.0);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void HMWSoln::initThermo()
|
|
{
|
|
MolalityVPSSTP::initThermo();
|
|
if (m_input.hasKey("activity-data")) {
|
|
auto& actData = m_input["activity-data"].as<AnyMap>();
|
|
setPitzerTempModel(actData["temperature-model"].asString());
|
|
initLengths();
|
|
size_t nCoeffs = 1;
|
|
if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
|
|
nCoeffs = 2;
|
|
} else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
nCoeffs = 5;
|
|
}
|
|
if (actData.hasKey("A_Debye")) {
|
|
if (actData["A_Debye"].is<string>()
|
|
&& actData["A_Debye"].asString() == "variable") {
|
|
setA_Debye(-1);
|
|
} else {
|
|
setA_Debye(actData.convert("A_Debye", "kg^0.5/gmol^0.5"));
|
|
}
|
|
}
|
|
if (actData.hasKey("max-ionic-strength")) {
|
|
setMaxIonicStrength(actData["max-ionic-strength"].asDouble());
|
|
}
|
|
if (actData.hasKey("interactions")) {
|
|
for (auto& item : actData["interactions"].asVector<AnyMap>()) {
|
|
auto& species = item["species"].asVector<string>(1, 3);
|
|
size_t nsp = species.size();
|
|
double q0 = charge(speciesIndex(species[0]));
|
|
double q1 = (nsp > 1) ? charge(speciesIndex(species[1])) : 0;
|
|
double q2 = (nsp == 3) ? charge(speciesIndex(species[2])) : 0;
|
|
if (nsp == 2 && q0 * q1 < 0) {
|
|
// Two species with opposite charges - binary salt
|
|
vector_fp beta0 = getSizedVector(item, "beta0", nCoeffs);
|
|
vector_fp beta1 = getSizedVector(item, "beta1", nCoeffs);
|
|
vector_fp beta2 = getSizedVector(item, "beta2", nCoeffs);
|
|
vector_fp Cphi = getSizedVector(item, "Cphi", nCoeffs);
|
|
if (beta0.size() != beta1.size() || beta0.size() != beta2.size()
|
|
|| beta0.size() != Cphi.size()) {
|
|
throw CanteraError("HMWSoln::initThermo", "Inconsistent"
|
|
" binary salt array sizes ({}, {}, {}, {})",
|
|
beta0.size(), beta1.size(), beta2.size(), Cphi.size());
|
|
}
|
|
double alpha1 = item["alpha1"].asDouble();
|
|
double alpha2 = item.getDouble("alpha2", 0.0);
|
|
setBinarySalt(species[0], species[1], beta0.size(),
|
|
beta0.data(), beta1.data(), beta2.data(), Cphi.data(),
|
|
alpha1, alpha2);
|
|
} else if (nsp == 2 && q0 * q1 > 0) {
|
|
// Two species with like charges - "theta" interaction
|
|
vector_fp theta = getSizedVector(item, "theta", nCoeffs);
|
|
setTheta(species[0], species[1], theta.size(), theta.data());
|
|
} else if (nsp == 2 && q0 * q1 == 0) {
|
|
// Two species, including at least one neutral
|
|
vector_fp lambda = getSizedVector(item, "lambda", nCoeffs);
|
|
setLambda(species[0], species[1], lambda.size(), lambda.data());
|
|
} else if (nsp == 3 && q0 * q1 * q2 != 0) {
|
|
// Three charged species - "psi" interaction
|
|
vector_fp psi = getSizedVector(item, "psi", nCoeffs);
|
|
setPsi(species[0], species[1], species[2],
|
|
psi.size(), psi.data());
|
|
} else if (nsp == 3 && q0 * q1 * q2 == 0) {
|
|
// Three species, including one neutral
|
|
vector_fp zeta = getSizedVector(item, "zeta", nCoeffs);
|
|
setZeta(species[0], species[1], species[2],
|
|
zeta.size(), zeta.data());
|
|
} else if (nsp == 1) {
|
|
// single species (should be neutral)
|
|
vector_fp mu = getSizedVector(item, "mu", nCoeffs);
|
|
setMunnn(species[0], mu.size(), mu.data());
|
|
}
|
|
}
|
|
}
|
|
if (actData.hasKey("cropping-coefficients")) {
|
|
auto& crop = actData["cropping-coefficients"].as<AnyMap>();
|
|
setCroppingCoefficients(
|
|
crop.getDouble("ln_gamma_k_min", -5.0),
|
|
crop.getDouble("ln_gamma_k_max", 15.0),
|
|
crop.getDouble("ln_gamma_o_min", -6.0),
|
|
crop.getDouble("ln_gamma_o_max", 3.0));
|
|
}
|
|
} else {
|
|
initLengths();
|
|
}
|
|
|
|
for (int i = 0; i < 17; i++) {
|
|
elambda[i] = 0.0;
|
|
elambda1[i] = 0.0;
|
|
}
|
|
|
|
// Store a local pointer to the water standard state model.
|
|
m_waterSS = providePDSS(0);
|
|
|
|
// Initialize the water property calculator. It will share the internal eos
|
|
// water calculator.
|
|
m_waterProps.reset(new WaterProps(dynamic_cast<PDSS_Water*>(m_waterSS)));
|
|
|
|
// Lastly calculate the charge balance and then add stuff until the charges
|
|
// compensate
|
|
vector_fp mf(m_kk, 0.0);
|
|
getMoleFractions(mf.data());
|
|
bool notDone = true;
|
|
|
|
while (notDone) {
|
|
double sum = 0.0;
|
|
size_t kMaxC = npos;
|
|
double MaxC = 0.0;
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
sum += mf[k] * charge(k);
|
|
if (fabs(mf[k] * charge(k)) > MaxC) {
|
|
kMaxC = k;
|
|
}
|
|
}
|
|
size_t kHp = speciesIndex("H+");
|
|
size_t kOHm = speciesIndex("OH-");
|
|
|
|
if (fabs(sum) > 1.0E-30) {
|
|
if (kHp != npos) {
|
|
if (mf[kHp] > sum * 1.1) {
|
|
mf[kHp] -= sum;
|
|
mf[0] += sum;
|
|
notDone = false;
|
|
} else {
|
|
if (sum > 0.0) {
|
|
mf[kHp] *= 0.5;
|
|
mf[0] += mf[kHp];
|
|
sum -= mf[kHp];
|
|
}
|
|
}
|
|
}
|
|
if (notDone) {
|
|
if (kOHm != npos) {
|
|
if (mf[kOHm] > -sum * 1.1) {
|
|
mf[kOHm] += sum;
|
|
mf[0] -= sum;
|
|
notDone = false;
|
|
} else {
|
|
if (sum < 0.0) {
|
|
mf[kOHm] *= 0.5;
|
|
mf[0] += mf[kOHm];
|
|
sum += mf[kOHm];
|
|
}
|
|
}
|
|
}
|
|
if (notDone && kMaxC != npos) {
|
|
if (mf[kMaxC] > (1.1 * sum / charge(kMaxC))) {
|
|
mf[kMaxC] -= sum / charge(kMaxC);
|
|
mf[0] += sum / charge(kMaxC);
|
|
} else {
|
|
mf[kMaxC] *= 0.5;
|
|
mf[0] += mf[kMaxC];
|
|
notDone = true;
|
|
}
|
|
}
|
|
}
|
|
setMoleFractions(mf.data());
|
|
} else {
|
|
notDone = false;
|
|
}
|
|
}
|
|
|
|
calcIMSCutoffParams_();
|
|
calcMCCutoffParams_();
|
|
setMoleFSolventMin(1.0E-5);
|
|
}
|
|
|
|
void HMWSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_)
|
|
{
|
|
if (id_.size() > 0) {
|
|
string idp = phaseNode.id();
|
|
if (idp != id_) {
|
|
throw CanteraError("HMWSoln::initThermoXML",
|
|
"phasenode and Id are incompatible");
|
|
}
|
|
}
|
|
|
|
// Find the Thermo XML node
|
|
if (!phaseNode.hasChild("thermo")) {
|
|
throw CanteraError("HMWSoln::initThermoXML",
|
|
"no thermo XML node");
|
|
}
|
|
XML_Node& thermoNode = phaseNode.child("thermo");
|
|
|
|
// Determine the form of the Pitzer model, We will use this information to
|
|
// size arrays below.
|
|
if (thermoNode.hasChild("activityCoefficients")) {
|
|
XML_Node& scNode = thermoNode.child("activityCoefficients");
|
|
|
|
// Determine the form of the temperature dependence of the Pitzer
|
|
// activity coefficient model.
|
|
string formString = scNode.attrib("TempModel");
|
|
if (formString != "") {
|
|
setPitzerTempModel(formString);
|
|
}
|
|
|
|
// Determine the reference temperature of the Pitzer activity
|
|
// coefficient model's temperature dependence formulation: defaults to
|
|
// 25C
|
|
formString = scNode.attrib("TempReference");
|
|
if (formString != "") {
|
|
setPitzerRefTemperature(fpValueCheck(formString));
|
|
}
|
|
}
|
|
|
|
// Initialize all of the lengths of arrays in the object
|
|
// now that we know what species are in the phase.
|
|
initLengths();
|
|
|
|
// Go get all of the coefficients and factors in the activityCoefficients
|
|
// XML block
|
|
if (thermoNode.hasChild("activityCoefficients")) {
|
|
XML_Node& acNode = thermoNode.child("activityCoefficients");
|
|
|
|
// Look for parameters for A_Debye
|
|
if (acNode.hasChild("A_Debye")) {
|
|
XML_Node& ADebye = acNode.child("A_Debye");
|
|
if (caseInsensitiveEquals(ADebye["model"], "water")) {
|
|
setA_Debye(-1);
|
|
} else {
|
|
setA_Debye(getFloat(acNode, "A_Debye"));
|
|
}
|
|
}
|
|
|
|
// Look for Parameters for the Maximum Ionic Strength
|
|
if (acNode.hasChild("maxIonicStrength")) {
|
|
setMaxIonicStrength(getFloat(acNode, "maxIonicStrength"));
|
|
}
|
|
|
|
for (const auto& xmlACChild : acNode.children()) {
|
|
string nodeName = xmlACChild->name();
|
|
|
|
// Process any of the XML fields that make up the Pitzer Database.
|
|
// Entries will be ignored if any of the species in the entry aren't
|
|
// in the solution.
|
|
if (caseInsensitiveEquals(nodeName, "binarysaltparameters")) {
|
|
readXMLBinarySalt(*xmlACChild);
|
|
} else if (caseInsensitiveEquals(nodeName, "thetaanion")) {
|
|
readXMLTheta(*xmlACChild);
|
|
} else if (caseInsensitiveEquals(nodeName, "thetacation")) {
|
|
readXMLTheta(*xmlACChild);
|
|
} else if (caseInsensitiveEquals(nodeName, "psicommonanion")) {
|
|
readXMLPsi(*xmlACChild);
|
|
} else if (caseInsensitiveEquals(nodeName, "psicommoncation")) {
|
|
readXMLPsi(*xmlACChild);
|
|
} else if (caseInsensitiveEquals(nodeName, "lambdaneutral")) {
|
|
readXMLLambdaNeutral(*xmlACChild);
|
|
} else if (caseInsensitiveEquals(nodeName, "zetacation")) {
|
|
readXMLZetaCation(*xmlACChild);
|
|
}
|
|
}
|
|
|
|
// Go look up the optional Cropping parameters
|
|
if (acNode.hasChild("croppingCoefficients")) {
|
|
XML_Node& cropNode = acNode.child("croppingCoefficients");
|
|
setCroppingCoefficients(
|
|
getFloat(cropNode.child("ln_gamma_k_min"), "pureSolventValue"),
|
|
getFloat(cropNode.child("ln_gamma_k_max"), "pureSolventValue"),
|
|
getFloat(cropNode.child("ln_gamma_o_min"), "pureSolventValue"),
|
|
getFloat(cropNode.child("ln_gamma_o_max"), "pureSolventValue"));
|
|
}
|
|
}
|
|
|
|
MolalityVPSSTP::initThermoXML(phaseNode, id_);
|
|
}
|
|
|
|
double HMWSoln::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;
|
|
}
|
|
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
if(cached.validate(T, P)) {
|
|
return m_A_Debye;
|
|
}
|
|
|
|
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:
|
|
throw CanteraError("HMWSoln::A_Debye_TP", "shouldn't be here");
|
|
}
|
|
return A;
|
|
}
|
|
|
|
double HMWSoln::dA_DebyedT_TP(double tempArg, double presArg) const
|
|
{
|
|
doublereal T = temperature();
|
|
if (tempArg != -1.0) {
|
|
T = tempArg;
|
|
}
|
|
doublereal P = pressure();
|
|
if (presArg != -1.0) {
|
|
P = presArg;
|
|
}
|
|
doublereal 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:
|
|
throw CanteraError("HMWSoln::dA_DebyedT_TP", "shouldn't be here");
|
|
}
|
|
return dAdT;
|
|
}
|
|
|
|
double HMWSoln::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;
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
switch (m_form_A_Debye) {
|
|
case A_DEBYE_CONST:
|
|
dAdP = 0.0;
|
|
break;
|
|
case A_DEBYE_WATER:
|
|
if(cached.validate(T, P)) {
|
|
dAdP = cached.value;
|
|
} else {
|
|
dAdP = m_waterProps->ADebye(T, P, 3);
|
|
cached.value = dAdP;
|
|
}
|
|
break;
|
|
default:
|
|
throw CanteraError("HMWSoln::dA_DebyedP_TP", "shouldn't be here");
|
|
}
|
|
return dAdP;
|
|
}
|
|
|
|
double HMWSoln::ADebye_L(double tempArg, double presArg) const
|
|
{
|
|
double dAdT = dA_DebyedT_TP();
|
|
double dAphidT = dAdT /3.0;
|
|
double T = temperature();
|
|
if (tempArg != -1.0) {
|
|
T = tempArg;
|
|
}
|
|
return dAphidT * (4.0 * GasConstant * T * T);
|
|
}
|
|
|
|
double HMWSoln::ADebye_V(double tempArg, double presArg) const
|
|
{
|
|
double dAdP = dA_DebyedP_TP();
|
|
double dAphidP = dAdP /3.0;
|
|
double T = temperature();
|
|
if (tempArg != -1.0) {
|
|
T = tempArg;
|
|
}
|
|
return - dAphidP * (4.0 * GasConstant * T);
|
|
}
|
|
|
|
double HMWSoln::ADebye_J(double tempArg, double presArg) const
|
|
{
|
|
double T = temperature();
|
|
if (tempArg != -1.0) {
|
|
T = tempArg;
|
|
}
|
|
double A_L = ADebye_L(T, presArg);
|
|
double d2 = d2A_DebyedT2_TP(T, presArg);
|
|
double d2Aphi = d2 / 3.0;
|
|
return 2.0 * A_L / T + 4.0 * GasConstant * T * T *d2Aphi;
|
|
}
|
|
|
|
double HMWSoln::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:
|
|
throw CanteraError("HMWSoln::d2A_DebyedT2_TP", "shouldn't be here");
|
|
}
|
|
return d2AdT2;
|
|
}
|
|
|
|
// ---------- Other Property Functions
|
|
|
|
// ------------ Private and Restricted Functions ------------------
|
|
|
|
void HMWSoln::initLengths()
|
|
{
|
|
m_tmpV.resize(m_kk, 0.0);
|
|
m_molalitiesCropped.resize(m_kk, 0.0);
|
|
|
|
size_t maxCounterIJlen = 1 + (m_kk-1) * (m_kk-2) / 2;
|
|
|
|
// Figure out the size of the temperature coefficient arrays
|
|
int TCoeffLength = 1;
|
|
if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
|
|
TCoeffLength = 2;
|
|
} else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
TCoeffLength = 5;
|
|
}
|
|
|
|
m_Beta0MX_ij.resize(maxCounterIJlen, 0.0);
|
|
m_Beta0MX_ij_L.resize(maxCounterIJlen, 0.0);
|
|
m_Beta0MX_ij_LL.resize(maxCounterIJlen, 0.0);
|
|
m_Beta0MX_ij_P.resize(maxCounterIJlen, 0.0);
|
|
m_Beta0MX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
|
|
|
|
m_Beta1MX_ij.resize(maxCounterIJlen, 0.0);
|
|
m_Beta1MX_ij_L.resize(maxCounterIJlen, 0.0);
|
|
m_Beta1MX_ij_LL.resize(maxCounterIJlen, 0.0);
|
|
m_Beta1MX_ij_P.resize(maxCounterIJlen, 0.0);
|
|
m_Beta1MX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
|
|
|
|
m_Beta2MX_ij.resize(maxCounterIJlen, 0.0);
|
|
m_Beta2MX_ij_L.resize(maxCounterIJlen, 0.0);
|
|
m_Beta2MX_ij_LL.resize(maxCounterIJlen, 0.0);
|
|
m_Beta2MX_ij_P.resize(maxCounterIJlen, 0.0);
|
|
m_Beta2MX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
|
|
|
|
m_CphiMX_ij.resize(maxCounterIJlen, 0.0);
|
|
m_CphiMX_ij_L.resize(maxCounterIJlen, 0.0);
|
|
m_CphiMX_ij_LL.resize(maxCounterIJlen, 0.0);
|
|
m_CphiMX_ij_P.resize(maxCounterIJlen, 0.0);
|
|
m_CphiMX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
|
|
|
|
m_Alpha1MX_ij.resize(maxCounterIJlen, 2.0);
|
|
m_Alpha2MX_ij.resize(maxCounterIJlen, 12.0);
|
|
m_Theta_ij.resize(maxCounterIJlen, 0.0);
|
|
m_Theta_ij_L.resize(maxCounterIJlen, 0.0);
|
|
m_Theta_ij_LL.resize(maxCounterIJlen, 0.0);
|
|
m_Theta_ij_P.resize(maxCounterIJlen, 0.0);
|
|
m_Theta_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
|
|
|
|
size_t n = m_kk*m_kk*m_kk;
|
|
m_Psi_ijk.resize(n, 0.0);
|
|
m_Psi_ijk_L.resize(n, 0.0);
|
|
m_Psi_ijk_LL.resize(n, 0.0);
|
|
m_Psi_ijk_P.resize(n, 0.0);
|
|
m_Psi_ijk_coeff.resize(TCoeffLength, n, 0.0);
|
|
|
|
m_Lambda_nj.resize(m_kk, m_kk, 0.0);
|
|
m_Lambda_nj_L.resize(m_kk, m_kk, 0.0);
|
|
m_Lambda_nj_LL.resize(m_kk, m_kk, 0.0);
|
|
m_Lambda_nj_P.resize(m_kk, m_kk, 0.0);
|
|
m_Lambda_nj_coeff.resize(TCoeffLength, m_kk * m_kk, 0.0);
|
|
|
|
m_Mu_nnn.resize(m_kk, 0.0);
|
|
m_Mu_nnn_L.resize(m_kk, 0.0);
|
|
m_Mu_nnn_LL.resize(m_kk, 0.0);
|
|
m_Mu_nnn_P.resize(m_kk, 0.0);
|
|
m_Mu_nnn_coeff.resize(TCoeffLength, m_kk, 0.0);
|
|
|
|
m_lnActCoeffMolal_Scaled.resize(m_kk, 0.0);
|
|
m_dlnActCoeffMolaldT_Scaled.resize(m_kk, 0.0);
|
|
m_d2lnActCoeffMolaldT2_Scaled.resize(m_kk, 0.0);
|
|
m_dlnActCoeffMolaldP_Scaled.resize(m_kk, 0.0);
|
|
m_lnActCoeffMolal_Unscaled.resize(m_kk, 0.0);
|
|
m_dlnActCoeffMolaldT_Unscaled.resize(m_kk, 0.0);
|
|
m_d2lnActCoeffMolaldT2_Unscaled.resize(m_kk, 0.0);
|
|
m_dlnActCoeffMolaldP_Unscaled.resize(m_kk, 0.0);
|
|
|
|
m_CounterIJ.resize(m_kk*m_kk, 0);
|
|
m_gfunc_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_g2func_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_hfunc_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_h2func_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_BMX_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_BMX_IJ_L.resize(maxCounterIJlen, 0.0);
|
|
m_BMX_IJ_LL.resize(maxCounterIJlen, 0.0);
|
|
m_BMX_IJ_P.resize(maxCounterIJlen, 0.0);
|
|
m_BprimeMX_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_BprimeMX_IJ_L.resize(maxCounterIJlen, 0.0);
|
|
m_BprimeMX_IJ_LL.resize(maxCounterIJlen, 0.0);
|
|
m_BprimeMX_IJ_P.resize(maxCounterIJlen, 0.0);
|
|
m_BphiMX_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_BphiMX_IJ_L.resize(maxCounterIJlen, 0.0);
|
|
m_BphiMX_IJ_LL.resize(maxCounterIJlen, 0.0);
|
|
m_BphiMX_IJ_P.resize(maxCounterIJlen, 0.0);
|
|
m_Phi_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_Phi_IJ_L.resize(maxCounterIJlen, 0.0);
|
|
m_Phi_IJ_LL.resize(maxCounterIJlen, 0.0);
|
|
m_Phi_IJ_P.resize(maxCounterIJlen, 0.0);
|
|
m_Phiprime_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_PhiPhi_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_PhiPhi_IJ_L.resize(maxCounterIJlen, 0.0);
|
|
m_PhiPhi_IJ_LL.resize(maxCounterIJlen, 0.0);
|
|
m_PhiPhi_IJ_P.resize(maxCounterIJlen, 0.0);
|
|
m_CMX_IJ.resize(maxCounterIJlen, 0.0);
|
|
m_CMX_IJ_L.resize(maxCounterIJlen, 0.0);
|
|
m_CMX_IJ_LL.resize(maxCounterIJlen, 0.0);
|
|
m_CMX_IJ_P.resize(maxCounterIJlen, 0.0);
|
|
|
|
m_gamma_tmp.resize(m_kk, 0.0);
|
|
IMS_lnActCoeffMolal_.resize(m_kk, 0.0);
|
|
CROP_speciesCropped_.resize(m_kk, 0);
|
|
|
|
counterIJ_setup();
|
|
}
|
|
|
|
void HMWSoln::s_update_lnMolalityActCoeff() const
|
|
{
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
|
|
return;
|
|
}
|
|
|
|
// Calculate the molalities. Currently, the molalities may not be current
|
|
// with respect to the contents of the State objects' data.
|
|
calcMolalities();
|
|
|
|
// Calculate a cropped set of molalities that will be used in all activity
|
|
// coefficient calculations.
|
|
calcMolalitiesCropped();
|
|
|
|
// Update the temperature dependence of the pitzer coefficients and their
|
|
// derivatives
|
|
s_updatePitzer_CoeffWRTemp();
|
|
|
|
// Calculate the IMS cutoff factors
|
|
s_updateIMS_lnMolalityActCoeff();
|
|
|
|
// Now do the main calculation.
|
|
s_updatePitzer_lnMolalityActCoeff();
|
|
|
|
double xmolSolvent = moleFraction(0);
|
|
double xx = std::max(m_xmolSolventMIN, xmolSolvent);
|
|
double lnActCoeffMolal0 = - log(xx) + (xx - 1.0)/xx;
|
|
double lnxs = log(xx);
|
|
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
CROP_speciesCropped_[k] = 0;
|
|
m_lnActCoeffMolal_Unscaled[k] += IMS_lnActCoeffMolal_[k];
|
|
if (m_lnActCoeffMolal_Unscaled[k] > (CROP_ln_gamma_k_max- 2.5 *lnxs)) {
|
|
CROP_speciesCropped_[k] = 2;
|
|
m_lnActCoeffMolal_Unscaled[k] = CROP_ln_gamma_k_max - 2.5 * lnxs;
|
|
}
|
|
if (m_lnActCoeffMolal_Unscaled[k] < (CROP_ln_gamma_k_min - 2.5 *lnxs)) {
|
|
// -1.0 and -1.5 caused multiple solutions
|
|
CROP_speciesCropped_[k] = 2;
|
|
m_lnActCoeffMolal_Unscaled[k] = CROP_ln_gamma_k_min - 2.5 * lnxs;
|
|
}
|
|
}
|
|
CROP_speciesCropped_[0] = 0;
|
|
m_lnActCoeffMolal_Unscaled[0] += (IMS_lnActCoeffMolal_[0] - lnActCoeffMolal0);
|
|
if (m_lnActCoeffMolal_Unscaled[0] < CROP_ln_gamma_o_min) {
|
|
CROP_speciesCropped_[0] = 2;
|
|
m_lnActCoeffMolal_Unscaled[0] = CROP_ln_gamma_o_min;
|
|
}
|
|
if (m_lnActCoeffMolal_Unscaled[0] > CROP_ln_gamma_o_max) {
|
|
CROP_speciesCropped_[0] = 2;
|
|
// -0.5 caused multiple solutions
|
|
m_lnActCoeffMolal_Unscaled[0] = CROP_ln_gamma_o_max;
|
|
}
|
|
if (m_lnActCoeffMolal_Unscaled[0] > CROP_ln_gamma_o_max - 0.5 * lnxs) {
|
|
CROP_speciesCropped_[0] = 2;
|
|
m_lnActCoeffMolal_Unscaled[0] = CROP_ln_gamma_o_max - 0.5 * lnxs;
|
|
}
|
|
|
|
// Now do the pH Scaling
|
|
s_updateScaling_pHScaling();
|
|
}
|
|
|
|
void HMWSoln::calcMolalitiesCropped() const
|
|
{
|
|
doublereal Imax = 0.0;
|
|
m_molalitiesAreCropped = false;
|
|
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_molalitiesCropped[k] = m_molalities[k];
|
|
Imax = std::max(m_molalities[k] * charge(k) * charge(k), Imax);
|
|
}
|
|
|
|
int cropMethod = 1;
|
|
if (cropMethod == 0) {
|
|
// Quick return
|
|
if (Imax < m_maxIionicStrength) {
|
|
return;
|
|
}
|
|
|
|
m_molalitiesAreCropped = true;
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
double charge_i = charge(i);
|
|
double abs_charge_i = fabs(charge_i);
|
|
if (charge_i == 0.0) {
|
|
continue;
|
|
}
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
double charge_j = charge(j);
|
|
double abs_charge_j = fabs(charge_j);
|
|
|
|
// Only loop over oppositely charge species
|
|
if (charge_i * charge_j < 0) {
|
|
double Iac_max = m_maxIionicStrength;
|
|
|
|
if (m_molalitiesCropped[i] > m_molalitiesCropped[j]) {
|
|
Imax = m_molalitiesCropped[i] * abs_charge_i * abs_charge_i;
|
|
if (Imax > Iac_max) {
|
|
m_molalitiesCropped[i] = Iac_max / (abs_charge_i * abs_charge_i);
|
|
}
|
|
Imax = m_molalitiesCropped[j] * fabs(abs_charge_j * abs_charge_i);
|
|
if (Imax > Iac_max) {
|
|
m_molalitiesCropped[j] = Iac_max / (abs_charge_j * abs_charge_i);
|
|
}
|
|
} else {
|
|
Imax = m_molalitiesCropped[j] * abs_charge_j * abs_charge_j;
|
|
if (Imax > Iac_max) {
|
|
m_molalitiesCropped[j] = Iac_max / (abs_charge_j * abs_charge_j);
|
|
}
|
|
Imax = m_molalitiesCropped[i] * abs_charge_j * abs_charge_i;
|
|
if (Imax > Iac_max) {
|
|
m_molalitiesCropped[i] = Iac_max / (abs_charge_j * abs_charge_i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do this loop 10 times until we have achieved charge neutrality in the
|
|
// cropped molalities
|
|
for (int times = 0; times< 10; times++) {
|
|
double anion_charge = 0.0;
|
|
double cation_charge = 0.0;
|
|
size_t anion_contrib_max_i = npos;
|
|
double anion_contrib_max = -1.0;
|
|
size_t cation_contrib_max_i = npos;
|
|
double cation_contrib_max = -1.0;
|
|
for (size_t i = 0; i < m_kk; i++) {
|
|
double charge_i = charge(i);
|
|
if (charge_i < 0.0) {
|
|
double anion_contrib = - m_molalitiesCropped[i] * charge_i;
|
|
anion_charge += anion_contrib;
|
|
if (anion_contrib > anion_contrib_max) {
|
|
anion_contrib_max = anion_contrib;
|
|
anion_contrib_max_i = i;
|
|
}
|
|
} else if (charge_i > 0.0) {
|
|
double cation_contrib = m_molalitiesCropped[i] * charge_i;
|
|
cation_charge += cation_contrib;
|
|
if (cation_contrib > cation_contrib_max) {
|
|
cation_contrib_max = cation_contrib;
|
|
cation_contrib_max_i = i;
|
|
}
|
|
}
|
|
}
|
|
double total_charge = cation_charge - anion_charge;
|
|
if (total_charge > 1.0E-8) {
|
|
double desiredCrop = total_charge/charge(cation_contrib_max_i);
|
|
double maxCrop = 0.66 * m_molalitiesCropped[cation_contrib_max_i];
|
|
if (desiredCrop < maxCrop) {
|
|
m_molalitiesCropped[cation_contrib_max_i] -= desiredCrop;
|
|
break;
|
|
} else {
|
|
m_molalitiesCropped[cation_contrib_max_i] -= maxCrop;
|
|
}
|
|
} else if (total_charge < -1.0E-8) {
|
|
double desiredCrop = total_charge/charge(anion_contrib_max_i);
|
|
double maxCrop = 0.66 * m_molalitiesCropped[anion_contrib_max_i];
|
|
if (desiredCrop < maxCrop) {
|
|
m_molalitiesCropped[anion_contrib_max_i] -= desiredCrop;
|
|
break;
|
|
} else {
|
|
m_molalitiesCropped[anion_contrib_max_i] -= maxCrop;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cropMethod == 1) {
|
|
double* molF = m_gamma_tmp.data();
|
|
getMoleFractions(molF);
|
|
double xmolSolvent = molF[0];
|
|
if (xmolSolvent >= MC_X_o_cutoff_) {
|
|
return;
|
|
}
|
|
|
|
m_molalitiesAreCropped = true;
|
|
double poly = MC_apCut_ + MC_bpCut_ * xmolSolvent + MC_dpCut_* xmolSolvent * xmolSolvent;
|
|
double p = xmolSolvent + MC_epCut_ + exp(- xmolSolvent/ MC_cpCut_) * poly;
|
|
double denomInv = 1.0/ (m_Mnaught * p);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_molalitiesCropped[k] = molF[k] * denomInv;
|
|
}
|
|
|
|
// Do a further check to see if the Ionic strength is below a max value
|
|
// Reduce the molalities to enforce this. Note, this algorithm preserves
|
|
// the charge neutrality of the solution after cropping.
|
|
double Itmp = 0.0;
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
Itmp += m_molalitiesCropped[k] * charge(k) * charge(k);
|
|
}
|
|
if (Itmp > m_maxIionicStrength) {
|
|
double ratio = Itmp / m_maxIionicStrength;
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
if (charge(k) != 0.0) {
|
|
m_molalitiesCropped[k] *= ratio;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HMWSoln::counterIJ_setup() const
|
|
{
|
|
m_CounterIJ.resize(m_kk * m_kk);
|
|
int counter = 0;
|
|
for (size_t i = 0; i < m_kk; i++) {
|
|
size_t n = i;
|
|
size_t nc = m_kk * i;
|
|
m_CounterIJ[n] = 0;
|
|
m_CounterIJ[nc] = 0;
|
|
}
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
size_t n = m_kk * i + i;
|
|
m_CounterIJ[n] = 0;
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
n = m_kk * j + i;
|
|
size_t nc = m_kk * i + j;
|
|
counter++;
|
|
m_CounterIJ[n] = counter;
|
|
m_CounterIJ[nc] = counter;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HMWSoln::readXMLBinarySalt(XML_Node& BinSalt)
|
|
{
|
|
if (BinSalt.name() != "binarySaltParameters") {
|
|
throw CanteraError("HMWSoln::readXMLBinarySalt",
|
|
"Incorrect name for processing this routine: " + BinSalt.name());
|
|
}
|
|
|
|
string iName = BinSalt.attrib("cation");
|
|
if (iName == "") {
|
|
throw CanteraError("HMWSoln::readXMLBinarySalt", "no cation attrib");
|
|
}
|
|
string jName = BinSalt.attrib("anion");
|
|
if (jName == "") {
|
|
throw CanteraError("HMWSoln::readXMLBinarySalt", "no anion attrib");
|
|
}
|
|
|
|
// Find the index of the species in the current phase. It's not an error to
|
|
// not find the species
|
|
if (speciesIndex(iName) == npos || speciesIndex(jName) == npos) {
|
|
return;
|
|
}
|
|
|
|
vector_fp beta0, beta1, beta2, Cphi;
|
|
getFloatArray(BinSalt, beta0, false, "", "beta0");
|
|
getFloatArray(BinSalt, beta1, false, "", "beta1");
|
|
getFloatArray(BinSalt, beta2, false, "", "beta2");
|
|
getFloatArray(BinSalt, Cphi, false, "", "Cphi");
|
|
if (beta0.size() != beta1.size() || beta0.size() != beta2.size() ||
|
|
beta0.size() != Cphi.size()) {
|
|
throw CanteraError("HMWSoln::readXMLBinarySalt", "Inconsistent"
|
|
" array sizes ({}, {}, {}, {})", beta0.size(), beta1.size(),
|
|
beta2.size(), Cphi.size());
|
|
}
|
|
if (beta0.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
beta0.resize(5, 0.0);
|
|
beta1.resize(5, 0.0);
|
|
beta2.resize(5, 0.0);
|
|
Cphi.resize(5, 0.0);
|
|
}
|
|
double alpha1 = getFloat(BinSalt, "Alpha1");
|
|
double alpha2 = 0.0;
|
|
getOptionalFloat(BinSalt, "Alpha2", alpha2);
|
|
setBinarySalt(iName, jName, beta0.size(), beta0.data(), beta1.data(),
|
|
beta2.data(), Cphi.data(), alpha1, alpha2);
|
|
}
|
|
|
|
void HMWSoln::readXMLTheta(XML_Node& node)
|
|
{
|
|
string ispName, jspName;
|
|
if (node.name() == "thetaAnion") {
|
|
ispName = node.attrib("anion1");
|
|
if (ispName == "") {
|
|
throw CanteraError("HMWSoln::readXMLTheta", "no anion1 attrib");
|
|
}
|
|
jspName = node.attrib("anion2");
|
|
if (jspName == "") {
|
|
throw CanteraError("HMWSoln::readXMLTheta", "no anion2 attrib");
|
|
}
|
|
} else if (node.name() == "thetaCation") {
|
|
ispName = node.attrib("cation1");
|
|
if (ispName == "") {
|
|
throw CanteraError("HMWSoln::readXMLTheta", "no cation1 attrib");
|
|
}
|
|
jspName = node.attrib("cation2");
|
|
if (jspName == "") {
|
|
throw CanteraError("HMWSoln::readXMLTheta", "no cation2 attrib");
|
|
}
|
|
} else {
|
|
throw CanteraError("HMWSoln::readXMLTheta",
|
|
"Incorrect name for processing this routine: " + node.name());
|
|
}
|
|
|
|
// Find the index of the species in the current phase. It's not an error to
|
|
// not find the species
|
|
if (speciesIndex(ispName) == npos || speciesIndex(jspName) == npos) {
|
|
return;
|
|
}
|
|
|
|
vector_fp theta;
|
|
getFloatArray(node, theta, false, "", "theta");
|
|
if (theta.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
theta.resize(5, 0.0);
|
|
}
|
|
setTheta(ispName, jspName, theta.size(), theta.data());
|
|
}
|
|
|
|
void HMWSoln::readXMLPsi(XML_Node& node)
|
|
{
|
|
string iName, jName, kName;
|
|
if (node.name() == "psiCommonCation") {
|
|
kName = node.attrib("cation");
|
|
if (kName == "") {
|
|
throw CanteraError("HMWSoln::readXMLPsi", "no cation attrib");
|
|
}
|
|
iName = node.attrib("anion1");
|
|
if (iName == "") {
|
|
throw CanteraError("HMWSoln::readXMLPsi", "no anion1 attrib");
|
|
}
|
|
jName = node.attrib("anion2");
|
|
if (jName == "") {
|
|
throw CanteraError("HMWSoln::readXMLPsi", "no anion2 attrib");
|
|
}
|
|
} else if (node.name() == "psiCommonAnion") {
|
|
kName = node.attrib("anion");
|
|
if (kName == "") {
|
|
throw CanteraError("HMWSoln::readXMLPsi", "no anion attrib");
|
|
}
|
|
iName = node.attrib("cation1");
|
|
if (iName == "") {
|
|
throw CanteraError("HMWSoln::readXMLPsi", "no cation1 attrib");
|
|
}
|
|
jName = node.attrib("cation2");
|
|
if (jName == "") {
|
|
throw CanteraError("HMWSoln::readXMLPsi", "no cation2 attrib");
|
|
}
|
|
} else {
|
|
throw CanteraError("HMWSoln::readXMLPsi",
|
|
"Incorrect name for processing this routine: " + node.name());
|
|
}
|
|
|
|
// Find the index of the species in the current phase. It's not an error to
|
|
// not find the species
|
|
if (speciesIndex(iName) == npos || speciesIndex(jName) == npos ||
|
|
speciesIndex(kName) == npos) {
|
|
return;
|
|
}
|
|
|
|
vector_fp psi;
|
|
getFloatArray(node, psi, false, "", "psi");
|
|
if (psi.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
psi.resize(5, 0.0);
|
|
}
|
|
setPsi(iName, jName, kName, psi.size(), psi.data());
|
|
}
|
|
|
|
void HMWSoln::readXMLLambdaNeutral(XML_Node& node)
|
|
{
|
|
vector_fp vParams;
|
|
if (node.name() != "lambdaNeutral") {
|
|
throw CanteraError("HMWSoln::readXMLLambdaNeutral",
|
|
"Incorrect name for processing this routine: " + node.name());
|
|
}
|
|
string iName = node.attrib("species1");
|
|
if (iName == "") {
|
|
throw CanteraError("HMWSoln::readXMLLambdaNeutral", "no species1 attrib");
|
|
}
|
|
string jName = node.attrib("species2");
|
|
if (jName == "") {
|
|
throw CanteraError("HMWSoln::readXMLLambdaNeutral", "no species2 attrib");
|
|
}
|
|
|
|
// Find the index of the species in the current phase. It's not an error to
|
|
// not find the species
|
|
if (speciesIndex(iName) == npos || speciesIndex(jName) == npos) {
|
|
return;
|
|
}
|
|
|
|
vector_fp lambda;
|
|
getFloatArray(node, lambda, false, "", "lambda");
|
|
if (lambda.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
lambda.resize(5, 0.0);
|
|
}
|
|
setLambda(iName, jName, lambda.size(), lambda.data());
|
|
}
|
|
|
|
void HMWSoln::readXMLMunnnNeutral(XML_Node& node)
|
|
{
|
|
if (node.name() != "MunnnNeutral") {
|
|
throw CanteraError("HMWSoln::readXMLMunnnNeutral",
|
|
"Incorrect name for processing this routine: " + node.name());
|
|
}
|
|
string iName = node.attrib("species1");
|
|
if (iName == "") {
|
|
throw CanteraError("HMWSoln::readXMLMunnnNeutral", "no species1 attrib");
|
|
}
|
|
|
|
// Find the index of the species in the current phase. It's not an error to
|
|
// not find the species
|
|
if (speciesIndex(iName) == npos) {
|
|
return;
|
|
}
|
|
|
|
vector_fp munnn;
|
|
getFloatArray(node, munnn, false, "", "munnn");
|
|
if (munnn.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
munnn.resize(5, 0.0);
|
|
}
|
|
setMunnn(iName, munnn.size(), munnn.data());
|
|
}
|
|
|
|
void HMWSoln::readXMLZetaCation(const XML_Node& node)
|
|
{
|
|
if (node.name() != "zetaCation") {
|
|
throw CanteraError("HMWSoln::readXMLZetaCation",
|
|
"Incorrect name for processing this routine: " + node.name());
|
|
}
|
|
|
|
string iName = node.attrib("neutral");
|
|
if (iName == "") {
|
|
throw CanteraError("HMWSoln::readXMLZetaCation", "no neutral attrib");
|
|
}
|
|
string jName = node.attrib("cation1");
|
|
if (jName == "") {
|
|
throw CanteraError("HMWSoln::readXMLZetaCation", "no cation1 attrib");
|
|
}
|
|
string kName = node.attrib("anion1");
|
|
if (kName == "") {
|
|
throw CanteraError("HMWSoln::readXMLZetaCation", "no anion1 attrib");
|
|
}
|
|
|
|
// Find the index of the species in the current phase. It's not an error to
|
|
// not find the species
|
|
if (speciesIndex(iName) == npos || speciesIndex(jName) == npos ||
|
|
speciesIndex(kName) == npos) {
|
|
return;
|
|
}
|
|
|
|
vector_fp zeta;
|
|
getFloatArray(node, zeta, false, "", "zeta");
|
|
if (zeta.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
zeta.resize(5, 0.0);
|
|
}
|
|
setZeta(iName, jName, kName, zeta.size(), zeta.data());
|
|
}
|
|
|
|
void HMWSoln::calcIMSCutoffParams_()
|
|
{
|
|
double IMS_gamma_o_min_ = 1.0E-5; // value at the zero solvent point
|
|
double IMS_gamma_k_min_ = 10.0; // minimum at the zero solvent point
|
|
double IMS_slopefCut_ = 0.6; // slope of the f function at the zero solvent point
|
|
|
|
IMS_afCut_ = 1.0 / (std::exp(1.0) * IMS_gamma_k_min_);
|
|
IMS_efCut_ = 0.0;
|
|
bool converged = false;
|
|
double oldV = 0.0;
|
|
for (int its = 0; its < 100 && !converged; its++) {
|
|
oldV = IMS_efCut_;
|
|
IMS_afCut_ = 1.0 / (std::exp(1.0) * IMS_gamma_k_min_) -IMS_efCut_;
|
|
IMS_bfCut_ = IMS_afCut_ / IMS_cCut_ + IMS_slopefCut_ - 1.0;
|
|
IMS_dfCut_ = ((- IMS_afCut_/IMS_cCut_ + IMS_bfCut_ - IMS_bfCut_*IMS_X_o_cutoff_/IMS_cCut_)
|
|
/
|
|
(IMS_X_o_cutoff_*IMS_X_o_cutoff_/IMS_cCut_ - 2.0 * IMS_X_o_cutoff_));
|
|
double tmp = IMS_afCut_ + IMS_X_o_cutoff_*(IMS_bfCut_ + IMS_dfCut_ *IMS_X_o_cutoff_);
|
|
double eterm = std::exp(-IMS_X_o_cutoff_/IMS_cCut_);
|
|
IMS_efCut_ = - eterm * tmp;
|
|
if (fabs(IMS_efCut_ - oldV) < 1.0E-14) {
|
|
converged = true;
|
|
}
|
|
}
|
|
if (!converged) {
|
|
throw CanteraError("HMWSoln::calcIMSCutoffParams_()",
|
|
" failed to converge on the f polynomial");
|
|
}
|
|
converged = false;
|
|
double f_0 = IMS_afCut_ + IMS_efCut_;
|
|
double f_prime_0 = 1.0 - IMS_afCut_ / IMS_cCut_ + IMS_bfCut_;
|
|
IMS_egCut_ = 0.0;
|
|
for (int its = 0; its < 100 && !converged; its++) {
|
|
oldV = IMS_egCut_;
|
|
double lng_0 = -log(IMS_gamma_o_min_) - f_prime_0 / f_0;
|
|
IMS_agCut_ = exp(lng_0) - IMS_egCut_;
|
|
IMS_bgCut_ = IMS_agCut_ / IMS_cCut_ + IMS_slopegCut_ - 1.0;
|
|
IMS_dgCut_ = ((- IMS_agCut_/IMS_cCut_ + IMS_bgCut_ - IMS_bgCut_*IMS_X_o_cutoff_/IMS_cCut_)
|
|
/
|
|
(IMS_X_o_cutoff_*IMS_X_o_cutoff_/IMS_cCut_ - 2.0 * IMS_X_o_cutoff_));
|
|
double tmp = IMS_agCut_ + IMS_X_o_cutoff_*(IMS_bgCut_ + IMS_dgCut_ *IMS_X_o_cutoff_);
|
|
double eterm = std::exp(-IMS_X_o_cutoff_/IMS_cCut_);
|
|
IMS_egCut_ = - eterm * tmp;
|
|
if (fabs(IMS_egCut_ - oldV) < 1.0E-14) {
|
|
converged = true;
|
|
}
|
|
}
|
|
if (!converged) {
|
|
throw CanteraError("HMWSoln::calcIMSCutoffParams_()",
|
|
" failed to converge on the g polynomial");
|
|
}
|
|
}
|
|
|
|
void HMWSoln::calcMCCutoffParams_()
|
|
{
|
|
double MC_X_o_min_ = 0.35; // value at the zero solvent point
|
|
MC_X_o_cutoff_ = 0.6;
|
|
double MC_slopepCut_ = 0.02; // slope of the p function at the zero solvent point
|
|
MC_cpCut_ = 0.25;
|
|
|
|
// Initial starting values
|
|
MC_apCut_ = MC_X_o_min_;
|
|
MC_epCut_ = 0.0;
|
|
bool converged = false;
|
|
double oldV = 0.0;
|
|
double damp = 0.5;
|
|
for (int its = 0; its < 500 && !converged; its++) {
|
|
oldV = MC_epCut_;
|
|
MC_apCut_ = damp *(MC_X_o_min_ - MC_epCut_) + (1-damp) * MC_apCut_;
|
|
double MC_bpCutNew = MC_apCut_ / MC_cpCut_ + MC_slopepCut_ - 1.0;
|
|
MC_bpCut_ = damp * MC_bpCutNew + (1-damp) * MC_bpCut_;
|
|
double MC_dpCutNew = ((- MC_apCut_/MC_cpCut_ + MC_bpCut_ - MC_bpCut_ * MC_X_o_cutoff_/MC_cpCut_)
|
|
/
|
|
(MC_X_o_cutoff_ * MC_X_o_cutoff_/MC_cpCut_ - 2.0 * MC_X_o_cutoff_));
|
|
MC_dpCut_ = damp * MC_dpCutNew + (1-damp) * MC_dpCut_;
|
|
double tmp = MC_apCut_ + MC_X_o_cutoff_*(MC_bpCut_ + MC_dpCut_ * MC_X_o_cutoff_);
|
|
double eterm = std::exp(- MC_X_o_cutoff_ / MC_cpCut_);
|
|
MC_epCut_ = - eterm * tmp;
|
|
double diff = MC_epCut_ - oldV;
|
|
if (fabs(diff) < 1.0E-14) {
|
|
converged = true;
|
|
}
|
|
}
|
|
if (!converged) {
|
|
throw CanteraError("HMWSoln::calcMCCutoffParams_()",
|
|
" failed to converge on the p polynomial");
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updatePitzer_CoeffWRTemp(int doDerivs) const
|
|
{
|
|
double T = temperature();
|
|
const double twoT = 2.0 * T;
|
|
const double invT = 1.0 / T;
|
|
const double invT2 = invT * invT;
|
|
const double twoinvT3 = 2.0 * invT * invT2;
|
|
double tinv = 0.0, tln = 0.0, tlin = 0.0, tquad = 0.0;
|
|
if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
|
|
tlin = T - m_TempPitzerRef;
|
|
} else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
|
|
tlin = T - m_TempPitzerRef;
|
|
tquad = T * T - m_TempPitzerRef * m_TempPitzerRef;
|
|
tln = log(T/ m_TempPitzerRef);
|
|
tinv = 1.0/T - 1.0/m_TempPitzerRef;
|
|
}
|
|
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
const double* beta0MX_coeff = m_Beta0MX_ij_coeff.ptrColumn(counterIJ);
|
|
const double* beta1MX_coeff = m_Beta1MX_ij_coeff.ptrColumn(counterIJ);
|
|
const double* beta2MX_coeff = m_Beta2MX_ij_coeff.ptrColumn(counterIJ);
|
|
const double* CphiMX_coeff = m_CphiMX_ij_coeff.ptrColumn(counterIJ);
|
|
const double* Theta_coeff = m_Theta_ij_coeff.ptrColumn(counterIJ);
|
|
|
|
switch (m_formPitzerTemp) {
|
|
case PITZER_TEMP_CONSTANT:
|
|
break;
|
|
case PITZER_TEMP_LINEAR:
|
|
|
|
m_Beta0MX_ij[counterIJ] = beta0MX_coeff[0]
|
|
+ beta0MX_coeff[1]*tlin;
|
|
m_Beta0MX_ij_L[counterIJ] = beta0MX_coeff[1];
|
|
m_Beta0MX_ij_LL[counterIJ] = 0.0;
|
|
m_Beta1MX_ij[counterIJ] = beta1MX_coeff[0]
|
|
+ beta1MX_coeff[1]*tlin;
|
|
m_Beta1MX_ij_L[counterIJ] = beta1MX_coeff[1];
|
|
m_Beta1MX_ij_LL[counterIJ] = 0.0;
|
|
m_Beta2MX_ij[counterIJ] = beta2MX_coeff[0]
|
|
+ beta2MX_coeff[1]*tlin;
|
|
m_Beta2MX_ij_L[counterIJ] = beta2MX_coeff[1];
|
|
m_Beta2MX_ij_LL[counterIJ] = 0.0;
|
|
m_CphiMX_ij[counterIJ] = CphiMX_coeff[0]
|
|
+ CphiMX_coeff[1]*tlin;
|
|
m_CphiMX_ij_L[counterIJ] = CphiMX_coeff[1];
|
|
m_CphiMX_ij_LL[counterIJ] = 0.0;
|
|
m_Theta_ij[counterIJ] = Theta_coeff[0] + Theta_coeff[1]*tlin;
|
|
m_Theta_ij_L[counterIJ] = Theta_coeff[1];
|
|
m_Theta_ij_LL[counterIJ] = 0.0;
|
|
break;
|
|
|
|
case PITZER_TEMP_COMPLEX1:
|
|
m_Beta0MX_ij[counterIJ] = beta0MX_coeff[0]
|
|
+ beta0MX_coeff[1]*tlin
|
|
+ beta0MX_coeff[2]*tquad
|
|
+ beta0MX_coeff[3]*tinv
|
|
+ beta0MX_coeff[4]*tln;
|
|
m_Beta1MX_ij[counterIJ] = beta1MX_coeff[0]
|
|
+ beta1MX_coeff[1]*tlin
|
|
+ beta1MX_coeff[2]*tquad
|
|
+ beta1MX_coeff[3]*tinv
|
|
+ beta1MX_coeff[4]*tln;
|
|
m_Beta2MX_ij[counterIJ] = beta2MX_coeff[0]
|
|
+ beta2MX_coeff[1]*tlin
|
|
+ beta2MX_coeff[2]*tquad
|
|
+ beta2MX_coeff[3]*tinv
|
|
+ beta2MX_coeff[4]*tln;
|
|
m_CphiMX_ij[counterIJ] = CphiMX_coeff[0]
|
|
+ CphiMX_coeff[1]*tlin
|
|
+ CphiMX_coeff[2]*tquad
|
|
+ CphiMX_coeff[3]*tinv
|
|
+ CphiMX_coeff[4]*tln;
|
|
m_Theta_ij[counterIJ] = Theta_coeff[0]
|
|
+ Theta_coeff[1]*tlin
|
|
+ Theta_coeff[2]*tquad
|
|
+ Theta_coeff[3]*tinv
|
|
+ Theta_coeff[4]*tln;
|
|
m_Beta0MX_ij_L[counterIJ] = beta0MX_coeff[1]
|
|
+ beta0MX_coeff[2]*twoT
|
|
- beta0MX_coeff[3]*invT2
|
|
+ beta0MX_coeff[4]*invT;
|
|
m_Beta1MX_ij_L[counterIJ] = beta1MX_coeff[1]
|
|
+ beta1MX_coeff[2]*twoT
|
|
- beta1MX_coeff[3]*invT2
|
|
+ beta1MX_coeff[4]*invT;
|
|
m_Beta2MX_ij_L[counterIJ] = beta2MX_coeff[1]
|
|
+ beta2MX_coeff[2]*twoT
|
|
- beta2MX_coeff[3]*invT2
|
|
+ beta2MX_coeff[4]*invT;
|
|
m_CphiMX_ij_L[counterIJ] = CphiMX_coeff[1]
|
|
+ CphiMX_coeff[2]*twoT
|
|
- CphiMX_coeff[3]*invT2
|
|
+ CphiMX_coeff[4]*invT;
|
|
m_Theta_ij_L[counterIJ] = Theta_coeff[1]
|
|
+ Theta_coeff[2]*twoT
|
|
- Theta_coeff[3]*invT2
|
|
+ Theta_coeff[4]*invT;
|
|
doDerivs = 2;
|
|
if (doDerivs > 1) {
|
|
m_Beta0MX_ij_LL[counterIJ] =
|
|
+ beta0MX_coeff[2]*2.0
|
|
+ beta0MX_coeff[3]*twoinvT3
|
|
- beta0MX_coeff[4]*invT2;
|
|
m_Beta1MX_ij_LL[counterIJ] =
|
|
+ beta1MX_coeff[2]*2.0
|
|
+ beta1MX_coeff[3]*twoinvT3
|
|
- beta1MX_coeff[4]*invT2;
|
|
m_Beta2MX_ij_LL[counterIJ] =
|
|
+ beta2MX_coeff[2]*2.0
|
|
+ beta2MX_coeff[3]*twoinvT3
|
|
- beta2MX_coeff[4]*invT2;
|
|
m_CphiMX_ij_LL[counterIJ] =
|
|
+ CphiMX_coeff[2]*2.0
|
|
+ CphiMX_coeff[3]*twoinvT3
|
|
- CphiMX_coeff[4]*invT2;
|
|
m_Theta_ij_LL[counterIJ] =
|
|
+ Theta_coeff[2]*2.0
|
|
+ Theta_coeff[3]*twoinvT3
|
|
- Theta_coeff[4]*invT2;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lambda interactions and Mu_nnn
|
|
// i must be neutral for this term to be nonzero. We take advantage of this
|
|
// here to lower the operation count.
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
if (charge(i) == 0.0) {
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
size_t n = i * m_kk + j;
|
|
const double* Lambda_coeff = m_Lambda_nj_coeff.ptrColumn(n);
|
|
switch (m_formPitzerTemp) {
|
|
case PITZER_TEMP_CONSTANT:
|
|
m_Lambda_nj(i,j) = Lambda_coeff[0];
|
|
break;
|
|
case PITZER_TEMP_LINEAR:
|
|
m_Lambda_nj(i,j) = Lambda_coeff[0] + Lambda_coeff[1]*tlin;
|
|
m_Lambda_nj_L(i,j) = Lambda_coeff[1];
|
|
m_Lambda_nj_LL(i,j) = 0.0;
|
|
break;
|
|
case PITZER_TEMP_COMPLEX1:
|
|
m_Lambda_nj(i,j) = Lambda_coeff[0]
|
|
+ Lambda_coeff[1]*tlin
|
|
+ Lambda_coeff[2]*tquad
|
|
+ Lambda_coeff[3]*tinv
|
|
+ Lambda_coeff[4]*tln;
|
|
|
|
m_Lambda_nj_L(i,j) = Lambda_coeff[1]
|
|
+ Lambda_coeff[2]*twoT
|
|
- Lambda_coeff[3]*invT2
|
|
+ Lambda_coeff[4]*invT;
|
|
|
|
m_Lambda_nj_LL(i,j) =
|
|
Lambda_coeff[2]*2.0
|
|
+ Lambda_coeff[3]*twoinvT3
|
|
- Lambda_coeff[4]*invT2;
|
|
}
|
|
|
|
if (j == i) {
|
|
const double* Mu_coeff = m_Mu_nnn_coeff.ptrColumn(i);
|
|
switch (m_formPitzerTemp) {
|
|
case PITZER_TEMP_CONSTANT:
|
|
m_Mu_nnn[i] = Mu_coeff[0];
|
|
break;
|
|
case PITZER_TEMP_LINEAR:
|
|
m_Mu_nnn[i] = Mu_coeff[0] + Mu_coeff[1]*tlin;
|
|
m_Mu_nnn_L[i] = Mu_coeff[1];
|
|
m_Mu_nnn_LL[i] = 0.0;
|
|
break;
|
|
case PITZER_TEMP_COMPLEX1:
|
|
m_Mu_nnn[i] = Mu_coeff[0]
|
|
+ Mu_coeff[1]*tlin
|
|
+ Mu_coeff[2]*tquad
|
|
+ Mu_coeff[3]*tinv
|
|
+ Mu_coeff[4]*tln;
|
|
m_Mu_nnn_L[i] = Mu_coeff[1]
|
|
+ Mu_coeff[2]*twoT
|
|
- Mu_coeff[3]*invT2
|
|
+ Mu_coeff[4]*invT;
|
|
m_Mu_nnn_LL[i] =
|
|
Mu_coeff[2]*2.0
|
|
+ Mu_coeff[3]*twoinvT3
|
|
- Mu_coeff[4]*invT2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch(m_formPitzerTemp) {
|
|
case PITZER_TEMP_CONSTANT:
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
size_t n = i * m_kk *m_kk + j * m_kk + k;
|
|
const double* Psi_coeff = m_Psi_ijk_coeff.ptrColumn(n);
|
|
m_Psi_ijk[n] = Psi_coeff[0];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PITZER_TEMP_LINEAR:
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
size_t n = i * m_kk *m_kk + j * m_kk + k;
|
|
const double* Psi_coeff = m_Psi_ijk_coeff.ptrColumn(n);
|
|
m_Psi_ijk[n] = Psi_coeff[0] + Psi_coeff[1]*tlin;
|
|
m_Psi_ijk_L[n] = Psi_coeff[1];
|
|
m_Psi_ijk_LL[n] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PITZER_TEMP_COMPLEX1:
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
size_t n = i * m_kk *m_kk + j * m_kk + k;
|
|
const double* Psi_coeff = m_Psi_ijk_coeff.ptrColumn(n);
|
|
m_Psi_ijk[n] = Psi_coeff[0]
|
|
+ Psi_coeff[1]*tlin
|
|
+ Psi_coeff[2]*tquad
|
|
+ Psi_coeff[3]*tinv
|
|
+ Psi_coeff[4]*tln;
|
|
m_Psi_ijk_L[n] = Psi_coeff[1]
|
|
+ Psi_coeff[2]*twoT
|
|
- Psi_coeff[3]*invT2
|
|
+ Psi_coeff[4]*invT;
|
|
m_Psi_ijk_LL[n] =
|
|
Psi_coeff[2]*2.0
|
|
+ Psi_coeff[3]*twoinvT3
|
|
- Psi_coeff[4]*invT2;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updatePitzer_lnMolalityActCoeff() const
|
|
{
|
|
// Use the CROPPED molality of the species in solution.
|
|
const vector_fp& molality = m_molalitiesCropped;
|
|
|
|
// These are data inputs about the Pitzer correlation. They come from the
|
|
// input file for the Pitzer model.
|
|
vector_fp& gamma_Unscaled = m_gamma_tmp;
|
|
|
|
// Local variables defined by Coltrin
|
|
double etheta[5][5], etheta_prime[5][5], sqrtIs;
|
|
|
|
// Molality based ionic strength of the solution
|
|
double Is = 0.0;
|
|
|
|
// Molarcharge of the solution: In Pitzer's notation, this is his variable
|
|
// called "Z".
|
|
double molarcharge = 0.0;
|
|
|
|
// molalitysum is the sum of the molalities over all solutes, even those
|
|
// with zero charge.
|
|
double molalitysumUncropped = 0.0;
|
|
|
|
// Make sure the counter variables are setup
|
|
counterIJ_setup();
|
|
|
|
// ---------- Calculate common sums over solutes ---------------------
|
|
for (size_t n = 1; n < m_kk; n++) {
|
|
// ionic strength
|
|
Is += charge(n) * charge(n) * molality[n];
|
|
// total molar charge
|
|
molarcharge += fabs(charge(n)) * molality[n];
|
|
molalitysumUncropped += m_molalities[n];
|
|
}
|
|
Is *= 0.5;
|
|
|
|
// Store the ionic molality in the object for reference.
|
|
m_IionicMolality = Is;
|
|
sqrtIs = sqrt(Is);
|
|
|
|
// The following call to calc_lambdas() calculates all 16 elements of the
|
|
// elambda and elambda1 arrays, given the value of the ionic strength (Is)
|
|
calc_lambdas(Is);
|
|
|
|
// Step 2: Find the coefficients E-theta and E-thetaprime for all
|
|
// combinations of positive unlike charges up to 4
|
|
for (int z1 = 1; z1 <=4; z1++) {
|
|
for (int z2 =1; z2 <=4; z2++) {
|
|
calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]);
|
|
}
|
|
}
|
|
|
|
// calculate g(x) and hfunc(x) for each cation-anion pair MX. In the
|
|
// original literature, hfunc, was called gprime. However, it's not the
|
|
// derivative of g(x), so I renamed it.
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// Only loop over oppositely charge species
|
|
if (charge(i)*charge(j) < 0) {
|
|
// x is a reduced function variable
|
|
double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
|
|
if (x1 > 1.0E-100) {
|
|
m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 * x1);
|
|
m_hfunc_IJ[counterIJ] = -2.0 *
|
|
(1.0-(1.0 + x1 + 0.5 * x1 * x1) * exp(-x1)) / (x1 * x1);
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
|
|
if (m_Beta2MX_ij[counterIJ] != 0.0) {
|
|
double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
|
|
if (x2 > 1.0E-100) {
|
|
m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
|
|
m_h2func_IJ[counterIJ] = -2.0 *
|
|
(1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
|
|
} else {
|
|
m_g2func_IJ[counterIJ] = 0.0;
|
|
m_h2func_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION TO CALCULATE BMX, BprimeMX, BphiMX
|
|
// Agrees with Pitzer, Eq. (49), (51), (55)
|
|
for (size_t i = 1; i < m_kk - 1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive and the
|
|
// other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_BMX_IJ[counterIJ] = m_Beta0MX_ij[counterIJ]
|
|
+ m_Beta1MX_ij[counterIJ] * m_gfunc_IJ[counterIJ]
|
|
+ m_Beta2MX_ij[counterIJ] * m_g2func_IJ[counterIJ];
|
|
|
|
if (Is > 1.0E-150) {
|
|
m_BprimeMX_IJ[counterIJ] = (m_Beta1MX_ij[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
|
|
m_Beta2MX_ij[counterIJ] * m_h2func_IJ[counterIJ]/Is);
|
|
} else {
|
|
m_BprimeMX_IJ[counterIJ] = 0.0;
|
|
}
|
|
m_BphiMX_IJ[counterIJ] = m_BMX_IJ[counterIJ] + Is*m_BprimeMX_IJ[counterIJ];
|
|
} else {
|
|
m_BMX_IJ[counterIJ] = 0.0;
|
|
m_BprimeMX_IJ[counterIJ] = 0.0;
|
|
m_BphiMX_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION TO CALCULATE CMX
|
|
// Agrees with Pitzer, Eq. (53).
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_CMX_IJ[counterIJ] = m_CphiMX_ij[counterIJ]/
|
|
(2.0* sqrt(fabs(charge(i)*charge(j))));
|
|
} else {
|
|
m_CMX_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi
|
|
// Agrees with Pitzer, Eq. 72, 73, 74
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive and the
|
|
// other is negative
|
|
if (charge(i)*charge(j) > 0) {
|
|
int z1 = (int) fabs(charge(i));
|
|
int z2 = (int) fabs(charge(j));
|
|
m_Phi_IJ[counterIJ] = m_Theta_ij[counterIJ] + etheta[z1][z2];
|
|
m_Phiprime_IJ[counterIJ] = etheta_prime[z1][z2];
|
|
m_PhiPhi_IJ[counterIJ] = m_Phi_IJ[counterIJ] + Is * m_Phiprime_IJ[counterIJ];
|
|
} else {
|
|
m_Phi_IJ[counterIJ] = 0.0;
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION FOR CALCULATION OF F
|
|
// Agrees with Pitzer Eqn. (65)
|
|
double Aphi = A_Debye_TP() / 3.0;
|
|
double F = -Aphi * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
|
|
+ (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive and the
|
|
// other is negative
|
|
if (charge(i)*charge(j) < 0) {
|
|
F += molality[i]*molality[j] * m_BprimeMX_IJ[counterIJ];
|
|
}
|
|
|
|
// Both species have a non-zero charge, and they
|
|
// have the same sign
|
|
if (charge(i)*charge(j) > 0) {
|
|
F += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
|
|
// SUBSECTION FOR CALCULATING THE ACTCOEFF FOR CATIONS
|
|
// equations agree with my notes, Eqn. (118).
|
|
// Equations agree with Pitzer, eqn.(63)
|
|
if (charge(i) > 0.0) {
|
|
// species i is the cation (positive) to calc the actcoeff
|
|
double zsqF = charge(i)*charge(i)*F;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
sum1 += molality[j] *
|
|
(2.0*m_BMX_IJ[counterIJ] + molarcharge*m_CMX_IJ[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
// This term is the ternary interaction involving the
|
|
// non-duplicate sum over double anions, j, k, with
|
|
// respect to the cation, i.
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all anions
|
|
if (charge(k) < 0.0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (charge(j) > 0.0) {
|
|
// sum over all cations
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// two inner sums over anions
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk[n];
|
|
|
|
// Find the counterIJ for the j,k interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += (fabs(charge(i))*
|
|
molality[j]*molality[k]*m_CMX_IJ[counterIJ2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle neutral j species
|
|
if (charge(j) == 0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj(j,i);
|
|
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + k;
|
|
double zeta = m_Psi_ijk[n];
|
|
if (zeta != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add all of the contributions up to yield the log of the solute
|
|
// activity coefficients (molality scale)
|
|
m_lnActCoeffMolal_Unscaled[i] = zsqF + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
gamma_Unscaled[i] = exp(m_lnActCoeffMolal_Unscaled[i]);
|
|
}
|
|
|
|
// SUBSECTION FOR CALCULATING THE ACTCOEFF FOR ANIONS
|
|
// equations agree with my notes, Eqn. (119).
|
|
// Equations agree with Pitzer, eqn.(64)
|
|
if (charge(i) < 0) {
|
|
// species i is an anion (negative)
|
|
double zsqF = charge(i)*charge(i)*F;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// For Anions, do the cation interactions.
|
|
if (charge(j) > 0) {
|
|
sum1 += molality[j]*
|
|
(2.0*m_BMX_IJ[counterIJ]+molarcharge*m_CMX_IJ[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all cations
|
|
if (charge(k) > 0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Anions, do the other anion interactions.
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
// two inner sums over cations
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk[n];
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i))*
|
|
molality[j]*molality[k]*m_CMX_IJ[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// for Anions, do the neutral species interaction
|
|
if (charge(j) == 0.0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj(j,i);
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = k;
|
|
size_t kzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
|
|
double zeta = m_Psi_ijk[n];
|
|
if (zeta != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_lnActCoeffMolal_Unscaled[i] = zsqF + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
gamma_Unscaled[i] = exp(m_lnActCoeffMolal_Unscaled[i]);
|
|
}
|
|
|
|
// SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF
|
|
// equations agree with my notes,
|
|
// Equations agree with Pitzer,
|
|
if (charge(i) == 0.0) {
|
|
double sum1 = 0.0;
|
|
double sum3 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
sum1 += molality[j]*2.0*m_Lambda_nj(i,j);
|
|
// Zeta term -> we piggyback on the psi term
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
double sum2 = 3.0 * molality[i]* molality[i] * m_Mu_nnn[i];
|
|
m_lnActCoeffMolal_Unscaled[i] = sum1 + sum2 + sum3;
|
|
gamma_Unscaled[i] = exp(m_lnActCoeffMolal_Unscaled[i]);
|
|
}
|
|
}
|
|
|
|
// SUBSECTION FOR CALCULATING THE OSMOTIC COEFF
|
|
// equations agree with my notes, Eqn. (117).
|
|
// Equations agree with Pitzer, eqn.(62)
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
double sum6 = 0.0;
|
|
double sum7 = 0.0;
|
|
|
|
// term1 is the DH term in the osmotic coefficient expression
|
|
// b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer
|
|
// implementations.
|
|
// Is = Ionic strength on the molality scale (units of (gmol/kg))
|
|
// Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
|
|
double term1 = -Aphi * pow(Is,1.5) / (1.0 + 1.2 * sqrt(Is));
|
|
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Loop Over Cations
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
sum1 += molality[j]*molality[k]*
|
|
(m_BphiMX_IJ[counterIJ] + molarcharge*m_CMX_IJ[counterIJ]);
|
|
}
|
|
}
|
|
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == (m_kk-1)) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_lnMolalityActCoeff",
|
|
"logic error 1 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between 2 cations.
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum2 += molality[j]*molality[k]*m_PhiPhi_IJ[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) < 0.0) {
|
|
// species m is an anion
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Anions
|
|
if (charge(j) < 0) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == m_kk-1) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_lnMolalityActCoeff",
|
|
"logic error 2 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) < 0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between two anions
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum3 += molality[j]*molality[k]*m_PhiPhi_IJ[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Neutral Species
|
|
if (charge(j) == 0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
sum4 += molality[j]*molality[k]*m_Lambda_nj(j,k);
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
sum5 += molality[j]*molality[k]*m_Lambda_nj(j,k);
|
|
}
|
|
if (charge(k) == 0.0) {
|
|
if (k > j) {
|
|
sum6 += molality[j]*molality[k]*m_Lambda_nj(j,k);
|
|
} else if (k == j) {
|
|
sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj(j,k);
|
|
}
|
|
}
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
size_t jzeta = m;
|
|
size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
|
|
double zeta = m_Psi_ijk[n];
|
|
if (zeta != 0.0) {
|
|
sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sum7 += molality[j]*molality[j]*molality[j]*m_Mu_nnn[j];
|
|
}
|
|
}
|
|
double sum_m_phi_minus_1 = 2.0 *
|
|
(term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
|
|
// Calculate the osmotic coefficient from
|
|
// osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
|
|
double osmotic_coef;
|
|
if (molalitysumUncropped > 1.0E-150) {
|
|
osmotic_coef = 1.0 + (sum_m_phi_minus_1 / molalitysumUncropped);
|
|
} else {
|
|
osmotic_coef = 1.0;
|
|
}
|
|
double lnwateract = -(m_weightSolvent/1000.0) * molalitysumUncropped * osmotic_coef;
|
|
|
|
// In Cantera, we define the activity coefficient of the solvent as
|
|
//
|
|
// act_0 = actcoeff_0 * Xmol_0
|
|
//
|
|
// We have just computed act_0. However, this routine returns
|
|
// ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
|
|
double xmolSolvent = moleFraction(0);
|
|
double xx = std::max(m_xmolSolventMIN, xmolSolvent);
|
|
m_lnActCoeffMolal_Unscaled[0] = lnwateract - log(xx);
|
|
}
|
|
|
|
void HMWSoln::s_update_dlnMolalityActCoeff_dT() const
|
|
{
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
|
|
return;
|
|
}
|
|
|
|
// Zero the unscaled 2nd derivatives
|
|
m_dlnActCoeffMolaldT_Unscaled.assign(m_kk, 0.0);
|
|
|
|
// Do the actual calculation of the unscaled temperature derivatives
|
|
s_updatePitzer_dlnMolalityActCoeff_dT();
|
|
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (CROP_speciesCropped_[k] == 2) {
|
|
m_dlnActCoeffMolaldT_Unscaled[k] = 0.0;
|
|
}
|
|
}
|
|
|
|
if (CROP_speciesCropped_[0]) {
|
|
m_dlnActCoeffMolaldT_Unscaled[0] = 0.0;
|
|
}
|
|
|
|
// Do the pH scaling to the derivatives
|
|
s_updateScaling_pHScaling_dT();
|
|
}
|
|
|
|
void HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dT() const
|
|
{
|
|
// It may be assumed that the Pitzer activity coefficient routine is called
|
|
// immediately preceding the calling of this routine. Therefore, some
|
|
// quantities do not need to be recalculated in this routine.
|
|
|
|
const vector_fp& molality = m_molalitiesCropped;
|
|
double* d_gamma_dT_Unscaled = m_gamma_tmp.data();
|
|
|
|
// Local variables defined by Coltrin
|
|
double etheta[5][5], etheta_prime[5][5], sqrtIs;
|
|
|
|
// Molality based ionic strength of the solution
|
|
double Is = 0.0;
|
|
|
|
// Molarcharge of the solution: In Pitzer's notation, this is his variable
|
|
// called "Z".
|
|
double molarcharge = 0.0;
|
|
|
|
// molalitysum is the sum of the molalities over all solutes, even those
|
|
// with zero charge.
|
|
double molalitysum = 0.0;
|
|
|
|
// Make sure the counter variables are setup
|
|
counterIJ_setup();
|
|
|
|
// ---------- Calculate common sums over solutes ---------------------
|
|
for (size_t n = 1; n < m_kk; n++) {
|
|
// ionic strength
|
|
Is += charge(n) * charge(n) * molality[n];
|
|
// total molar charge
|
|
molarcharge += fabs(charge(n)) * molality[n];
|
|
molalitysum += molality[n];
|
|
}
|
|
Is *= 0.5;
|
|
|
|
// Store the ionic molality in the object for reference.
|
|
m_IionicMolality = Is;
|
|
sqrtIs = sqrt(Is);
|
|
|
|
// The following call to calc_lambdas() calculates all 16 elements of the
|
|
// elambda and elambda1 arrays, given the value of the ionic strength (Is)
|
|
calc_lambdas(Is);
|
|
|
|
// Step 2: Find the coefficients E-theta and E-thetaprime for all
|
|
// combinations of positive unlike charges up to 4
|
|
for (int z1 = 1; z1 <=4; z1++) {
|
|
for (int z2 =1; z2 <=4; z2++) {
|
|
calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]);
|
|
}
|
|
}
|
|
|
|
// calculate g(x) and hfunc(x) for each cation-anion pair MX
|
|
// In the original literature, hfunc, was called gprime. However,
|
|
// it's not the derivative of g(x), so I renamed it.
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// Only loop over oppositely charge species
|
|
if (charge(i)*charge(j) < 0) {
|
|
// x is a reduced function variable
|
|
double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
|
|
if (x1 > 1.0E-100) {
|
|
m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 * x1);
|
|
m_hfunc_IJ[counterIJ] = -2.0 *
|
|
(1.0-(1.0 + x1 + 0.5 * x1 *x1) * exp(-x1)) / (x1 * x1);
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
|
|
if (m_Beta2MX_ij_L[counterIJ] != 0.0) {
|
|
double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
|
|
if (x2 > 1.0E-100) {
|
|
m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
|
|
m_h2func_IJ[counterIJ] = -2.0 *
|
|
(1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
|
|
} else {
|
|
m_g2func_IJ[counterIJ] = 0.0;
|
|
m_h2func_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION TO CALCULATE BMX_L, BprimeMX_L, BphiMX_L
|
|
// These are now temperature derivatives of the previously calculated
|
|
// quantities.
|
|
for (size_t i = 1; i < m_kk - 1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_BMX_IJ_L[counterIJ] = m_Beta0MX_ij_L[counterIJ]
|
|
+ m_Beta1MX_ij_L[counterIJ] * m_gfunc_IJ[counterIJ]
|
|
+ m_Beta2MX_ij_L[counterIJ] * m_gfunc_IJ[counterIJ];
|
|
if (Is > 1.0E-150) {
|
|
m_BprimeMX_IJ_L[counterIJ] = (m_Beta1MX_ij_L[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
|
|
m_Beta2MX_ij_L[counterIJ] * m_h2func_IJ[counterIJ]/Is);
|
|
} else {
|
|
m_BprimeMX_IJ_L[counterIJ] = 0.0;
|
|
}
|
|
m_BphiMX_IJ_L[counterIJ] = m_BMX_IJ_L[counterIJ] + Is*m_BprimeMX_IJ_L[counterIJ];
|
|
} else {
|
|
m_BMX_IJ_L[counterIJ] = 0.0;
|
|
m_BprimeMX_IJ_L[counterIJ] = 0.0;
|
|
m_BphiMX_IJ_L[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------- SUBSECTION TO CALCULATE CMX_L ----------
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_CMX_IJ_L[counterIJ] = m_CphiMX_ij_L[counterIJ]/
|
|
(2.0* sqrt(fabs(charge(i)*charge(j))));
|
|
} else {
|
|
m_CMX_IJ_L[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ----------
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) > 0) {
|
|
m_Phi_IJ_L[counterIJ] = m_Theta_ij_L[counterIJ];
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ_L[counterIJ] = m_Phi_IJ_L[counterIJ] + Is * m_Phiprime_IJ[counterIJ];
|
|
} else {
|
|
m_Phi_IJ_L[counterIJ] = 0.0;
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ_L[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------- SUBSECTION FOR CALCULATION OF dFdT ---------------------
|
|
double dA_DebyedT = dA_DebyedT_TP();
|
|
double dAphidT = dA_DebyedT /3.0;
|
|
double dFdT = -dAphidT * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
|
|
+ (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0) {
|
|
dFdT += molality[i]*molality[j] * m_BprimeMX_IJ_L[counterIJ];
|
|
}
|
|
|
|
// Both species have a non-zero charge, and they
|
|
// have the same sign, e.g., both positive or both negative.
|
|
if (charge(i)*charge(j) > 0) {
|
|
dFdT += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
// -------- SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR CATIONS -----
|
|
if (charge(i) > 0) {
|
|
// species i is the cation (positive) to calc the actcoeff
|
|
double zsqdFdT = charge(i)*charge(i)*dFdT;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
sum1 += molality[j]*
|
|
(2.0*m_BMX_IJ_L[counterIJ] + molarcharge*m_CMX_IJ_L[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
// This term is the ternary interaction involving the
|
|
// non-duplicate sum over double anions, j, k, with
|
|
// respect to the cation, i.
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all anions
|
|
if (charge(k) < 0.0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_L[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (charge(j) > 0.0) {
|
|
// sum over all cations
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ_L[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// two inner sums over anions
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk_L[n];
|
|
|
|
// Find the counterIJ for the j,k interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i))*
|
|
molality[j]*molality[k]*m_CMX_IJ_L[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle neutral j species
|
|
if (charge(j) == 0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj_L(j,i);
|
|
}
|
|
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + k;
|
|
double zeta_L = m_Psi_ijk_L[n];
|
|
if (zeta_L != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta_L;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add all of the contributions up to yield the log of the
|
|
// solute activity coefficients (molality scale)
|
|
m_dlnActCoeffMolaldT_Unscaled[i] =
|
|
zsqdFdT + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
d_gamma_dT_Unscaled[i] = exp(m_dlnActCoeffMolaldT_Unscaled[i]);
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR ANIONS ------
|
|
if (charge(i) < 0) {
|
|
// species i is an anion (negative)
|
|
double zsqdFdT = charge(i)*charge(i)*dFdT;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// For Anions, do the cation interactions.
|
|
if (charge(j) > 0) {
|
|
sum1 += molality[j]*
|
|
(2.0*m_BMX_IJ_L[counterIJ] + molarcharge*m_CMX_IJ_L[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all cations
|
|
if (charge(k) > 0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_L[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Anions, do the other anion interactions.
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ_L[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
// two inner sums over cations
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk_L[n];
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i)) *
|
|
molality[j]*molality[k]*m_CMX_IJ_L[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// for Anions, do the neutral species interaction
|
|
if (charge(j) == 0.0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj_L(j,i);
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = k;
|
|
size_t kzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
|
|
double zeta_L = m_Psi_ijk_L[n];
|
|
if (zeta_L != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta_L;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_dlnActCoeffMolaldT_Unscaled[i] =
|
|
zsqdFdT + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
d_gamma_dT_Unscaled[i] = exp(m_dlnActCoeffMolaldT_Unscaled[i]);
|
|
}
|
|
|
|
// SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF
|
|
// equations agree with my notes,
|
|
// Equations agree with Pitzer,
|
|
if (charge(i) == 0.0) {
|
|
double sum1 = 0.0;
|
|
double sum3 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
sum1 += molality[j]*2.0*m_Lambda_nj_L(i,j);
|
|
// Zeta term -> we piggyback on the psi term
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_L[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
double sum2 = 3.0 * molality[i] * molality[i] * m_Mu_nnn_L[i];
|
|
m_dlnActCoeffMolaldT_Unscaled[i] = sum1 + sum2 + sum3;
|
|
d_gamma_dT_Unscaled[i] = exp(m_dlnActCoeffMolaldT_Unscaled[i]);
|
|
}
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING THE d OSMOTIC COEFF dT ---------
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
double sum6 = 0.0;
|
|
double sum7 = 0.0;
|
|
|
|
// term1 is the temperature derivative of the DH term in the osmotic
|
|
// coefficient expression
|
|
// b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer implementations.
|
|
// Is = Ionic strength on the molality scale (units of (gmol/kg))
|
|
// Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
|
|
double term1 = -dAphidT * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is));
|
|
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Loop Over Cations
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum1 += molality[j]*molality[k]*
|
|
(m_BphiMX_IJ_L[counterIJ] + molarcharge*m_CMX_IJ_L[counterIJ]);
|
|
}
|
|
}
|
|
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == (m_kk-1)) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dT",
|
|
"logic error 1 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between 2 cations.
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum2 += molality[j]*molality[k]*m_PhiPhi_IJ_L[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) < 0.0) {
|
|
// species m is an anion
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_L[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Anions
|
|
if (charge(j) < 0) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == m_kk-1) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dT",
|
|
"logic error 2 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) < 0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between two anions
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum3 += molality[j]*molality[k]*m_PhiPhi_IJ_L[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_L[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Neutral Species
|
|
if (charge(j) == 0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
sum4 += molality[j]*molality[k]*m_Lambda_nj_L(j,k);
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
sum5 += molality[j]*molality[k]*m_Lambda_nj_L(j,k);
|
|
}
|
|
if (charge(k) == 0.0) {
|
|
if (k > j) {
|
|
sum6 += molality[j]*molality[k]*m_Lambda_nj_L(j,k);
|
|
} else if (k == j) {
|
|
sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj_L(j,k);
|
|
}
|
|
}
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
size_t jzeta = m;
|
|
size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
|
|
double zeta_L = m_Psi_ijk_L[n];
|
|
if (zeta_L != 0.0) {
|
|
sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta_L;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sum7 += molality[j]*molality[j]*molality[j]*m_Mu_nnn_L[j];
|
|
}
|
|
}
|
|
double sum_m_phi_minus_1 = 2.0 *
|
|
(term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
|
|
// Calculate the osmotic coefficient from
|
|
// osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
|
|
double d_osmotic_coef_dT;
|
|
if (molalitysum > 1.0E-150) {
|
|
d_osmotic_coef_dT = 0.0 + (sum_m_phi_minus_1 / molalitysum);
|
|
} else {
|
|
d_osmotic_coef_dT = 0.0;
|
|
}
|
|
|
|
double d_lnwateract_dT = -(m_weightSolvent/1000.0) * molalitysum * d_osmotic_coef_dT;
|
|
|
|
// In Cantera, we define the activity coefficient of the solvent as
|
|
//
|
|
// act_0 = actcoeff_0 * Xmol_0
|
|
//
|
|
// We have just computed act_0. However, this routine returns
|
|
// ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
|
|
m_dlnActCoeffMolaldT_Unscaled[0] = d_lnwateract_dT;
|
|
}
|
|
|
|
void HMWSoln::s_update_d2lnMolalityActCoeff_dT2() const
|
|
{
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
|
|
return;
|
|
}
|
|
|
|
// Zero the unscaled 2nd derivatives
|
|
m_d2lnActCoeffMolaldT2_Unscaled.assign(m_kk, 0.0);
|
|
|
|
//! Calculate the unscaled 2nd derivatives
|
|
s_updatePitzer_d2lnMolalityActCoeff_dT2();
|
|
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (CROP_speciesCropped_[k] == 2) {
|
|
m_d2lnActCoeffMolaldT2_Unscaled[k] = 0.0;
|
|
}
|
|
}
|
|
|
|
if (CROP_speciesCropped_[0]) {
|
|
m_d2lnActCoeffMolaldT2_Unscaled[0] = 0.0;
|
|
}
|
|
|
|
// Scale the 2nd derivatives
|
|
s_updateScaling_pHScaling_dT2();
|
|
}
|
|
|
|
void HMWSoln::s_updatePitzer_d2lnMolalityActCoeff_dT2() const
|
|
{
|
|
const double* molality = m_molalitiesCropped.data();
|
|
|
|
// Local variables defined by Coltrin
|
|
double etheta[5][5], etheta_prime[5][5], sqrtIs;
|
|
|
|
// Molality based ionic strength of the solution
|
|
double Is = 0.0;
|
|
|
|
// Molarcharge of the solution: In Pitzer's notation, this is his variable
|
|
// called "Z".
|
|
double molarcharge = 0.0;
|
|
|
|
// molalitysum is the sum of the molalities over all solutes, even those
|
|
// with zero charge.
|
|
double molalitysum = 0.0;
|
|
|
|
// Make sure the counter variables are setup
|
|
counterIJ_setup();
|
|
|
|
// ---------- Calculate common sums over solutes ---------------------
|
|
for (size_t n = 1; n < m_kk; n++) {
|
|
// ionic strength
|
|
Is += charge(n) * charge(n) * molality[n];
|
|
// total molar charge
|
|
molarcharge += fabs(charge(n)) * molality[n];
|
|
molalitysum += molality[n];
|
|
}
|
|
Is *= 0.5;
|
|
|
|
// Store the ionic molality in the object for reference.
|
|
m_IionicMolality = Is;
|
|
sqrtIs = sqrt(Is);
|
|
|
|
// The following call to calc_lambdas() calculates all 16 elements of the
|
|
// elambda and elambda1 arrays, given the value of the ionic strength (Is)
|
|
calc_lambdas(Is);
|
|
|
|
// Step 2: Find the coefficients E-theta and E-thetaprime for all
|
|
// combinations of positive unlike charges up to 4
|
|
for (int z1 = 1; z1 <=4; z1++) {
|
|
for (int z2 =1; z2 <=4; z2++) {
|
|
calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]);
|
|
}
|
|
}
|
|
|
|
// calculate gfunc(x) and hfunc(x) for each cation-anion pair MX. In the
|
|
// original literature, hfunc, was called gprime. However, it's not the
|
|
// derivative of gfunc(x), so I renamed it.
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// Only loop over oppositely charge species
|
|
if (charge(i)*charge(j) < 0) {
|
|
// x is a reduced function variable
|
|
double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
|
|
if (x1 > 1.0E-100) {
|
|
m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 *x1);
|
|
m_hfunc_IJ[counterIJ] = -2.0*
|
|
(1.0-(1.0 + x1 + 0.5*x1 * x1) * exp(-x1)) / (x1 * x1);
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
|
|
if (m_Beta2MX_ij_LL[counterIJ] != 0.0) {
|
|
double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
|
|
if (x2 > 1.0E-100) {
|
|
m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
|
|
m_h2func_IJ[counterIJ] = -2.0 *
|
|
(1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
|
|
} else {
|
|
m_g2func_IJ[counterIJ] = 0.0;
|
|
m_h2func_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION TO CALCULATE BMX_L, BprimeMX_LL, BphiMX_L
|
|
// These are now temperature derivatives of the previously calculated
|
|
// quantities.
|
|
for (size_t i = 1; i < m_kk - 1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_BMX_IJ_LL[counterIJ] = m_Beta0MX_ij_LL[counterIJ]
|
|
+ m_Beta1MX_ij_LL[counterIJ] * m_gfunc_IJ[counterIJ]
|
|
+ m_Beta2MX_ij_LL[counterIJ] * m_g2func_IJ[counterIJ];
|
|
if (Is > 1.0E-150) {
|
|
m_BprimeMX_IJ_LL[counterIJ] = (m_Beta1MX_ij_LL[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
|
|
m_Beta2MX_ij_LL[counterIJ] * m_h2func_IJ[counterIJ]/Is);
|
|
} else {
|
|
m_BprimeMX_IJ_LL[counterIJ] = 0.0;
|
|
}
|
|
m_BphiMX_IJ_LL[counterIJ] = m_BMX_IJ_LL[counterIJ] + Is*m_BprimeMX_IJ_LL[counterIJ];
|
|
} else {
|
|
m_BMX_IJ_LL[counterIJ] = 0.0;
|
|
m_BprimeMX_IJ_LL[counterIJ] = 0.0;
|
|
m_BphiMX_IJ_LL[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------- SUBSECTION TO CALCULATE CMX_LL ----------
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_CMX_IJ_LL[counterIJ] = m_CphiMX_ij_LL[counterIJ]/
|
|
(2.0* sqrt(fabs(charge(i)*charge(j))));
|
|
} else {
|
|
m_CMX_IJ_LL[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ----------
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) > 0) {
|
|
m_Phi_IJ_LL[counterIJ] = m_Theta_ij_LL[counterIJ];
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ_LL[counterIJ] = m_Phi_IJ_LL[counterIJ];
|
|
} else {
|
|
m_Phi_IJ_LL[counterIJ] = 0.0;
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ_LL[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------- SUBSECTION FOR CALCULATION OF d2FdT2 ---------------------
|
|
double d2AphidT2 = d2A_DebyedT2_TP() / 3.0;
|
|
double d2FdT2 = -d2AphidT2 * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
|
|
+ (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0) {
|
|
d2FdT2 += molality[i]*molality[j] * m_BprimeMX_IJ_LL[counterIJ];
|
|
}
|
|
|
|
// Both species have a non-zero charge, and they
|
|
// have the same sign, e.g., both positive or both negative.
|
|
if (charge(i)*charge(j) > 0) {
|
|
d2FdT2 += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
// -------- SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR CATIONS -----
|
|
if (charge(i) > 0) {
|
|
// species i is the cation (positive) to calc the actcoeff
|
|
double zsqd2FdT2 = charge(i)*charge(i)*d2FdT2;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
sum1 += molality[j]*
|
|
(2.0*m_BMX_IJ_LL[counterIJ] + molarcharge*m_CMX_IJ_LL[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
// This term is the ternary interaction involving the
|
|
// non-duplicate sum over double anions, j, k, with
|
|
// respect to the cation, i.
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all anions
|
|
if (charge(k) < 0.0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (charge(j) > 0.0) {
|
|
// sum over all cations
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ_LL[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// two inner sums over anions
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
|
|
|
|
// Find the counterIJ for the j,k interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i)) *
|
|
molality[j]*molality[k]*m_CMX_IJ_LL[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle neutral j species
|
|
if (charge(j) == 0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj_LL(j,i);
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + k;
|
|
double zeta_LL = m_Psi_ijk_LL[n];
|
|
if (zeta_LL != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta_LL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Add all of the contributions up to yield the log of the
|
|
// solute activity coefficients (molality scale)
|
|
m_d2lnActCoeffMolaldT2_Unscaled[i] =
|
|
zsqd2FdT2 + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING THE d2ACTCOEFFdT2 FOR ANIONS ------
|
|
if (charge(i) < 0) {
|
|
// species i is an anion (negative)
|
|
double zsqd2FdT2 = charge(i)*charge(i)*d2FdT2;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// For Anions, do the cation interactions.
|
|
if (charge(j) > 0) {
|
|
sum1 += molality[j]*
|
|
(2.0*m_BMX_IJ_LL[counterIJ] + molarcharge*m_CMX_IJ_LL[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all cations
|
|
if (charge(k) > 0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Anions, do the other anion interactions.
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ_LL[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
// two inner sums over cations
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i)) *
|
|
molality[j]*molality[k]*m_CMX_IJ_LL[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// for Anions, do the neutral species interaction
|
|
if (charge(j) == 0.0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj_LL(j,i);
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = k;
|
|
size_t kzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
|
|
double zeta_LL = m_Psi_ijk_LL[n];
|
|
if (zeta_LL != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta_LL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_d2lnActCoeffMolaldT2_Unscaled[i] =
|
|
zsqd2FdT2 + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
}
|
|
|
|
// SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF
|
|
// equations agree with my notes,
|
|
// Equations agree with Pitzer,
|
|
if (charge(i) == 0.0) {
|
|
double sum1 = 0.0;
|
|
double sum3 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
sum1 += molality[j]*2.0*m_Lambda_nj_LL(i,j);
|
|
// Zeta term -> we piggyback on the psi term
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
double sum2 = 3.0 * molality[i] * molality[i] * m_Mu_nnn_LL[i];
|
|
m_d2lnActCoeffMolaldT2_Unscaled[i] = sum1 + sum2 + sum3;
|
|
}
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING THE d2 OSMOTIC COEFF dT2 ---------
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
double sum6 = 0.0;
|
|
double sum7 = 0.0;
|
|
|
|
// term1 is the temperature derivative of the DH term in the osmotic
|
|
// coefficient expression
|
|
// b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer implementations.
|
|
// Is = Ionic strength on the molality scale (units of (gmol/kg))
|
|
// Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
|
|
double term1 = -d2AphidT2 * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is));
|
|
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Loop Over Cations
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
sum1 += molality[j]*molality[k] *
|
|
(m_BphiMX_IJ_LL[counterIJ] + molarcharge*m_CMX_IJ_LL[counterIJ]);
|
|
}
|
|
}
|
|
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == (m_kk-1)) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_d2lnMolalityActCoeff_dT2",
|
|
"logic error 1 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between 2 cations.
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum2 += molality[j]*molality[k]*m_PhiPhi_IJ_LL[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) < 0.0) {
|
|
// species m is an anion
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_LL[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Anions
|
|
if (charge(j) < 0) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == m_kk-1) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_d2lnMolalityActCoeff_dT2",
|
|
"logic error 2 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) < 0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between two anions
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
sum3 += molality[j]*molality[k]*m_PhiPhi_IJ_LL[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_LL[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Neutral Species
|
|
if (charge(j) == 0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
sum4 += molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
sum5 += molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
|
|
}
|
|
if (charge(k) == 0.0) {
|
|
if (k > j) {
|
|
sum6 += molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
|
|
} else if (k == j) {
|
|
sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
|
|
}
|
|
}
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
size_t jzeta = m;
|
|
size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
|
|
double zeta_LL = m_Psi_ijk_LL[n];
|
|
if (zeta_LL != 0.0) {
|
|
sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta_LL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sum7 += molality[j] * molality[j] * molality[j] * m_Mu_nnn_LL[j];
|
|
}
|
|
}
|
|
double sum_m_phi_minus_1 = 2.0 *
|
|
(term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
|
|
// Calculate the osmotic coefficient from
|
|
// osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
|
|
double d2_osmotic_coef_dT2;
|
|
if (molalitysum > 1.0E-150) {
|
|
d2_osmotic_coef_dT2 = 0.0 + (sum_m_phi_minus_1 / molalitysum);
|
|
} else {
|
|
d2_osmotic_coef_dT2 = 0.0;
|
|
}
|
|
double d2_lnwateract_dT2 = -(m_weightSolvent/1000.0) * molalitysum * d2_osmotic_coef_dT2;
|
|
|
|
// In Cantera, we define the activity coefficient of the solvent as
|
|
//
|
|
// act_0 = actcoeff_0 * Xmol_0
|
|
//
|
|
// We have just computed act_0. However, this routine returns
|
|
// ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
|
|
m_d2lnActCoeffMolaldT2_Unscaled[0] = d2_lnwateract_dT2;
|
|
}
|
|
|
|
void HMWSoln::s_update_dlnMolalityActCoeff_dP() const
|
|
{
|
|
static const int cacheId = m_cache.getId();
|
|
CachedScalar cached = m_cache.getScalar(cacheId);
|
|
if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
|
|
return;
|
|
}
|
|
|
|
m_dlnActCoeffMolaldP_Unscaled.assign(m_kk, 0.0);
|
|
s_updatePitzer_dlnMolalityActCoeff_dP();
|
|
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (CROP_speciesCropped_[k] == 2) {
|
|
m_dlnActCoeffMolaldP_Unscaled[k] = 0.0;
|
|
}
|
|
}
|
|
|
|
if (CROP_speciesCropped_[0]) {
|
|
m_dlnActCoeffMolaldP_Unscaled[0] = 0.0;
|
|
}
|
|
|
|
s_updateScaling_pHScaling_dP();
|
|
}
|
|
|
|
void HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dP() const
|
|
{
|
|
const double* molality = m_molalitiesCropped.data();
|
|
|
|
// Local variables defined by Coltrin
|
|
double etheta[5][5], etheta_prime[5][5], sqrtIs;
|
|
|
|
// Molality based ionic strength of the solution
|
|
double Is = 0.0;
|
|
|
|
// Molarcharge of the solution: In Pitzer's notation, this is his variable
|
|
// called "Z".
|
|
double molarcharge = 0.0;
|
|
|
|
// molalitysum is the sum of the molalities over all solutes, even those
|
|
// with zero charge.
|
|
double molalitysum = 0.0;
|
|
double currTemp = temperature();
|
|
double currPres = pressure();
|
|
|
|
// Make sure the counter variables are setup
|
|
counterIJ_setup();
|
|
|
|
// ---------- Calculate common sums over solutes ---------------------
|
|
for (size_t n = 1; n < m_kk; n++) {
|
|
// ionic strength
|
|
Is += charge(n) * charge(n) * molality[n];
|
|
// total molar charge
|
|
molarcharge += fabs(charge(n)) * molality[n];
|
|
molalitysum += molality[n];
|
|
}
|
|
Is *= 0.5;
|
|
|
|
// Store the ionic molality in the object for reference.
|
|
m_IionicMolality = Is;
|
|
sqrtIs = sqrt(Is);
|
|
|
|
// The following call to calc_lambdas() calculates all 16 elements of the
|
|
// elambda and elambda1 arrays, given the value of the ionic strength (Is)
|
|
calc_lambdas(Is);
|
|
|
|
// Step 2: Find the coefficients E-theta and E-thetaprime for all
|
|
// combinations of positive unlike charges up to 4
|
|
for (int z1 = 1; z1 <=4; z1++) {
|
|
for (int z2 =1; z2 <=4; z2++) {
|
|
calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]);
|
|
}
|
|
}
|
|
|
|
// calculate g(x) and hfunc(x) for each cation-anion pair MX
|
|
// In the original literature, hfunc, was called gprime. However,
|
|
// it's not the derivative of g(x), so I renamed it.
|
|
for (size_t i = 1; i < (m_kk - 1); i++) {
|
|
for (size_t j = (i+1); j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// Only loop over oppositely charge species
|
|
if (charge(i)*charge(j) < 0) {
|
|
// x is a reduced function variable
|
|
double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
|
|
if (x1 > 1.0E-100) {
|
|
m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 * x1);
|
|
m_hfunc_IJ[counterIJ] = -2.0*
|
|
(1.0-(1.0 + x1 + 0.5 * x1 * x1) * exp(-x1)) / (x1 * x1);
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
|
|
if (m_Beta2MX_ij_P[counterIJ] != 0.0) {
|
|
double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
|
|
if (x2 > 1.0E-100) {
|
|
m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
|
|
m_h2func_IJ[counterIJ] = -2.0 *
|
|
(1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
|
|
} else {
|
|
m_g2func_IJ[counterIJ] = 0.0;
|
|
m_h2func_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
} else {
|
|
m_gfunc_IJ[counterIJ] = 0.0;
|
|
m_hfunc_IJ[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// SUBSECTION TO CALCULATE BMX_P, BprimeMX_P, BphiMX_P
|
|
// These are now temperature derivatives of the previously calculated
|
|
// quantities.
|
|
for (size_t i = 1; i < m_kk - 1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_BMX_IJ_P[counterIJ] = m_Beta0MX_ij_P[counterIJ]
|
|
+ m_Beta1MX_ij_P[counterIJ] * m_gfunc_IJ[counterIJ]
|
|
+ m_Beta2MX_ij_P[counterIJ] * m_g2func_IJ[counterIJ];
|
|
if (Is > 1.0E-150) {
|
|
m_BprimeMX_IJ_P[counterIJ] = (m_Beta1MX_ij_P[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
|
|
m_Beta2MX_ij_P[counterIJ] * m_h2func_IJ[counterIJ]/Is);
|
|
} else {
|
|
m_BprimeMX_IJ_P[counterIJ] = 0.0;
|
|
}
|
|
m_BphiMX_IJ_P[counterIJ] = m_BMX_IJ_P[counterIJ] + Is*m_BprimeMX_IJ_P[counterIJ];
|
|
} else {
|
|
m_BMX_IJ_P[counterIJ] = 0.0;
|
|
m_BprimeMX_IJ_P[counterIJ] = 0.0;
|
|
m_BphiMX_IJ_P[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------- SUBSECTION TO CALCULATE CMX_P ----------
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0.0) {
|
|
m_CMX_IJ_P[counterIJ] = m_CphiMX_ij_P[counterIJ]/
|
|
(2.0* sqrt(fabs(charge(i)*charge(j))));
|
|
} else {
|
|
m_CMX_IJ_P[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ----------
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) > 0) {
|
|
m_Phi_IJ_P[counterIJ] = m_Theta_ij_P[counterIJ];
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ_P[counterIJ] = m_Phi_IJ_P[counterIJ] + Is * m_Phiprime_IJ[counterIJ];
|
|
} else {
|
|
m_Phi_IJ_P[counterIJ] = 0.0;
|
|
m_Phiprime_IJ[counterIJ] = 0.0;
|
|
m_PhiPhi_IJ_P[counterIJ] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------- SUBSECTION FOR CALCULATION OF dFdT ---------------------
|
|
double dA_DebyedP = dA_DebyedP_TP(currTemp, currPres);
|
|
double dAphidP = dA_DebyedP /3.0;
|
|
double dFdP = -dAphidP * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
|
|
+ (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
|
|
for (size_t i = 1; i < m_kk-1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// both species have a non-zero charge, and one is positive
|
|
// and the other is negative
|
|
if (charge(i)*charge(j) < 0) {
|
|
dFdP += molality[i]*molality[j] * m_BprimeMX_IJ_P[counterIJ];
|
|
}
|
|
|
|
// Both species have a non-zero charge, and they
|
|
// have the same sign, e.g., both positive or both negative.
|
|
if (charge(i)*charge(j) > 0) {
|
|
dFdP += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
// -------- SUBSECTION FOR CALCULATING THE dACTCOEFFdP FOR CATIONS -----
|
|
if (charge(i) > 0) {
|
|
// species i is the cation (positive) to calc the actcoeff
|
|
double zsqdFdP = charge(i)*charge(i)*dFdP;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
sum1 += molality[j]*
|
|
(2.0*m_BMX_IJ_P[counterIJ] + molarcharge*m_CMX_IJ_P[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
// This term is the ternary interaction involving the
|
|
// non-duplicate sum over double anions, j, k, with
|
|
// respect to the cation, i.
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all anions
|
|
if (charge(k) < 0.0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_P[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (charge(j) > 0.0) {
|
|
// sum over all cations
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ_P[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// two inner sums over anions
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk_P[n];
|
|
|
|
// Find the counterIJ for the j,k interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i)) *
|
|
molality[j]*molality[k]*m_CMX_IJ_P[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// for Anions, do the neutral species interaction
|
|
if (charge(j) == 0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj_P(j,i);
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + k;
|
|
double zeta_P = m_Psi_ijk_P[n];
|
|
if (zeta_P != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta_P;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add all of the contributions up to yield the log of the
|
|
// solute activity coefficients (molality scale)
|
|
m_dlnActCoeffMolaldP_Unscaled[i] =
|
|
zsqdFdP + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING THE dACTCOEFFdP FOR ANIONS ------
|
|
if (charge(i) < 0) {
|
|
// species i is an anion (negative)
|
|
double zsqdFdP = charge(i)*charge(i)*dFdP;
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
size_t n = m_kk*i + j;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
// For Anions, do the cation interactions.
|
|
if (charge(j) > 0) {
|
|
sum1 += molality[j] *
|
|
(2.0*m_BMX_IJ_P[counterIJ] + molarcharge*m_CMX_IJ_P[counterIJ]);
|
|
if (j < m_kk-1) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
// an inner sum over all cations
|
|
if (charge(k) > 0) {
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_P[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Anions, do the other anion interactions.
|
|
if (charge(j) < 0.0) {
|
|
// sum over all anions
|
|
if (j != i) {
|
|
sum2 += molality[j]*(2.0*m_Phi_IJ_P[counterIJ]);
|
|
}
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
// two inner sums over cations
|
|
n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*m_Psi_ijk_P[n];
|
|
// Find the counterIJ for the symmetric binary interaction
|
|
n = m_kk*j + k;
|
|
size_t counterIJ2 = m_CounterIJ[n];
|
|
sum4 += fabs(charge(i))*
|
|
molality[j]*molality[k]*m_CMX_IJ_P[counterIJ2];
|
|
}
|
|
}
|
|
}
|
|
|
|
// for Anions, do the neutral species interaction
|
|
if (charge(j) == 0.0) {
|
|
sum5 += molality[j]*2.0*m_Lambda_nj_P(j,i);
|
|
// Zeta interaction term
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) > 0.0) {
|
|
size_t izeta = j;
|
|
size_t jzeta = k;
|
|
size_t kzeta = i;
|
|
n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
|
|
double zeta_P = m_Psi_ijk_P[n];
|
|
if (zeta_P != 0.0) {
|
|
sum5 += molality[j]*molality[k]*zeta_P;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_dlnActCoeffMolaldP_Unscaled[i] =
|
|
zsqdFdP + sum1 + sum2 + sum3 + sum4 + sum5;
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING d NEUTRAL SOLUTE ACT COEFF dP -----
|
|
if (charge(i) == 0.0) {
|
|
double sum1 = 0.0;
|
|
double sum3 = 0.0;
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
sum1 += molality[j]*2.0*m_Lambda_nj_P(i,j);
|
|
// Zeta term -> we piggyback on the psi term
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
size_t n = k + j * m_kk + i * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*m_Psi_ijk_P[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
double sum2 = 3.0 * molality[i] * molality[i] * m_Mu_nnn_P[i];
|
|
m_dlnActCoeffMolaldP_Unscaled[i] = sum1 + sum2 + sum3;
|
|
}
|
|
}
|
|
|
|
// ------ SUBSECTION FOR CALCULATING THE d OSMOTIC COEFF dP ---------
|
|
double sum1 = 0.0;
|
|
double sum2 = 0.0;
|
|
double sum3 = 0.0;
|
|
double sum4 = 0.0;
|
|
double sum5 = 0.0;
|
|
double sum6 = 0.0;
|
|
double sum7 = 0.0;
|
|
|
|
// term1 is the temperature derivative of the DH term in the osmotic
|
|
// coefficient expression
|
|
// b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer implementations.
|
|
// Is = Ionic strength on the molality scale (units of (gmol/kg))
|
|
// Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
|
|
double term1 = -dAphidP * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is));
|
|
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
// Loop Over Cations
|
|
if (charge(j) > 0.0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum1 += molality[j]*molality[k]*
|
|
(m_BphiMX_IJ_P[counterIJ] + molarcharge*m_CMX_IJ_P[counterIJ]);
|
|
}
|
|
}
|
|
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == (m_kk-1)) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dP",
|
|
"logic error 1 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between 2 cations.
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
sum2 += molality[j]*molality[k]*m_PhiPhi_IJ_P[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) < 0.0) {
|
|
// species m is an anion
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_P[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Anions
|
|
if (charge(j) < 0) {
|
|
for (size_t k = j+1; k < m_kk; k++) {
|
|
if (j == m_kk-1) {
|
|
// we should never reach this step
|
|
throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dP",
|
|
"logic error 2 in Step 9 of hmw_act");
|
|
}
|
|
if (charge(k) < 0) {
|
|
// Find the counterIJ for the symmetric j,k binary interaction
|
|
// between two anions
|
|
size_t n = m_kk*j + k;
|
|
size_t counterIJ = m_CounterIJ[n];
|
|
|
|
sum3 += molality[j]*molality[k]*m_PhiPhi_IJ_P[counterIJ];
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
n = m + k * m_kk + j * m_kk * m_kk;
|
|
sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_P[n];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop Over Neutral Species
|
|
if (charge(j) == 0) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
if (charge(k) < 0.0) {
|
|
sum4 += molality[j]*molality[k]*m_Lambda_nj_P(j,k);
|
|
}
|
|
if (charge(k) > 0.0) {
|
|
sum5 += molality[j]*molality[k]*m_Lambda_nj_P(j,k);
|
|
}
|
|
if (charge(k) == 0.0) {
|
|
if (k > j) {
|
|
sum6 += molality[j]*molality[k]*m_Lambda_nj_P(j,k);
|
|
} else if (k == j) {
|
|
sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj_P(j,k);
|
|
}
|
|
}
|
|
if (charge(k) < 0.0) {
|
|
size_t izeta = j;
|
|
for (size_t m = 1; m < m_kk; m++) {
|
|
if (charge(m) > 0.0) {
|
|
size_t jzeta = m;
|
|
size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
|
|
double zeta_P = m_Psi_ijk_P[n];
|
|
if (zeta_P != 0.0) {
|
|
sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta_P;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sum7 += molality[j] * molality[j] * molality[j] * m_Mu_nnn_P[j];
|
|
}
|
|
}
|
|
double sum_m_phi_minus_1 = 2.0 *
|
|
(term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
|
|
|
|
// Calculate the osmotic coefficient from
|
|
// osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
|
|
double d_osmotic_coef_dP;
|
|
if (molalitysum > 1.0E-150) {
|
|
d_osmotic_coef_dP = 0.0 + (sum_m_phi_minus_1 / molalitysum);
|
|
} else {
|
|
d_osmotic_coef_dP = 0.0;
|
|
}
|
|
double d_lnwateract_dP = -(m_weightSolvent/1000.0) * molalitysum * d_osmotic_coef_dP;
|
|
|
|
// In Cantera, we define the activity coefficient of the solvent as
|
|
//
|
|
// act_0 = actcoeff_0 * Xmol_0
|
|
//
|
|
// We have just computed act_0. However, this routine returns
|
|
// ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
|
|
m_dlnActCoeffMolaldP_Unscaled[0] = d_lnwateract_dP;
|
|
}
|
|
|
|
void HMWSoln::calc_lambdas(double is) const
|
|
{
|
|
if( m_last_is == is ) {
|
|
return;
|
|
}
|
|
m_last_is = is;
|
|
|
|
// Coefficients c1-c4 are used to approximate the integral function "J";
|
|
// aphi is the Debye-Huckel constant at 25 C
|
|
double c1 = 4.581, c2 = 0.7237, c3 = 0.0120, c4 = 0.528;
|
|
double aphi = 0.392; /* Value at 25 C */
|
|
if (is < 1.0E-150) {
|
|
for (int i = 0; i < 17; i++) {
|
|
elambda[i] = 0.0;
|
|
elambda1[i] = 0.0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Calculate E-lambda terms for charge combinations of like sign,
|
|
// using method of Pitzer (1975). Charges up to 4 are calculated.
|
|
for (int i=1; i<=4; i++) {
|
|
for (int j=i; j<=4; j++) {
|
|
int ij = i*j;
|
|
|
|
// calculate the product of the charges
|
|
double zprod = (double)ij;
|
|
|
|
// calculate Xmn (A1) from Harvie, Weare (1980).
|
|
double x = 6.0* zprod * aphi * sqrt(is); // eqn 23
|
|
|
|
double jfunc = x / (4.0 + c1*pow(x,-c2)*exp(-c3*pow(x,c4))); // eqn 47
|
|
|
|
double t = c3 * c4 * pow(x,c4);
|
|
double dj = c1* pow(x,(-c2-1.0)) * (c2+t) * exp(-c3*pow(x,c4));
|
|
double jprime = (jfunc/x)*(1.0 + jfunc*dj);
|
|
|
|
elambda[ij] = zprod*jfunc / (4.0*is); // eqn 14
|
|
elambda1[ij] = (3.0*zprod*zprod*aphi*jprime/(4.0*sqrt(is))
|
|
- elambda[ij])/is;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HMWSoln::calc_thetas(int z1, int z2,
|
|
double* etheta, double* etheta_prime) const
|
|
{
|
|
// Calculate E-theta(i) and E-theta'(I) using method of Pitzer (1987)
|
|
int i = abs(z1);
|
|
int j = abs(z2);
|
|
|
|
AssertThrowMsg(i <= 4 && j <= 4, "HMWSoln::calc_thetas",
|
|
"we shouldn't be here");
|
|
AssertThrowMsg(i != 0 && j != 0, "HMWSoln::calc_thetas",
|
|
"called with one species being neutral");
|
|
|
|
// Check to see if the charges are of opposite sign. If they are of opposite
|
|
// sign then their etheta interaction is zero.
|
|
if (z1*z2 < 0) {
|
|
*etheta = 0.0;
|
|
*etheta_prime = 0.0;
|
|
} else {
|
|
// Actually calculate the interaction.
|
|
double f1 = (double)i / (2.0 * j);
|
|
double f2 = (double)j / (2.0 * i);
|
|
*etheta = elambda[i*j] - f1*elambda[j*j] - f2*elambda[i*i];
|
|
*etheta_prime = elambda1[i*j] - f1*elambda1[j*j] - f2*elambda1[i*i];
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updateIMS_lnMolalityActCoeff() const
|
|
{
|
|
// Calculate the molalities. Currently, the molalities may not be current
|
|
// with respect to the contents of the State objects' data.
|
|
calcMolalities();
|
|
double xmolSolvent = moleFraction(0);
|
|
double xx = std::max(m_xmolSolventMIN, xmolSolvent);
|
|
// Exponentials - trial 2
|
|
if (xmolSolvent > IMS_X_o_cutoff_) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
IMS_lnActCoeffMolal_[k]= 0.0;
|
|
}
|
|
IMS_lnActCoeffMolal_[0] = - log(xx) + (xx - 1.0)/xx;
|
|
return;
|
|
} else {
|
|
double xoverc = xmolSolvent/IMS_cCut_;
|
|
double eterm = std::exp(-xoverc);
|
|
|
|
double fptmp = IMS_bfCut_ - IMS_afCut_ / IMS_cCut_ - IMS_bfCut_*xoverc
|
|
+ 2.0*IMS_dfCut_*xmolSolvent - IMS_dfCut_*xmolSolvent*xoverc;
|
|
double f_prime = 1.0 + eterm*fptmp;
|
|
double f = xmolSolvent + IMS_efCut_
|
|
+ eterm * (IMS_afCut_ + xmolSolvent * (IMS_bfCut_ + IMS_dfCut_*xmolSolvent));
|
|
|
|
double gptmp = IMS_bgCut_ - IMS_agCut_ / IMS_cCut_ - IMS_bgCut_*xoverc
|
|
+ 2.0*IMS_dgCut_*xmolSolvent - IMS_dgCut_*xmolSolvent*xoverc;
|
|
double g_prime = 1.0 + eterm*gptmp;
|
|
double g = xmolSolvent + IMS_egCut_
|
|
+ eterm * (IMS_agCut_ + xmolSolvent * (IMS_bgCut_ + IMS_dgCut_*xmolSolvent));
|
|
|
|
double tmp = (xmolSolvent / g * g_prime + (1.0 - xmolSolvent) / f * f_prime);
|
|
double lngammak = -1.0 - log(f) + tmp * xmolSolvent;
|
|
double lngammao =-log(g) - tmp * (1.0-xmolSolvent);
|
|
|
|
tmp = log(xx) + lngammak;
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
IMS_lnActCoeffMolal_[k]= tmp;
|
|
}
|
|
IMS_lnActCoeffMolal_[0] = lngammao;
|
|
}
|
|
return;
|
|
}
|
|
|
|
void HMWSoln::printCoeffs() const
|
|
{
|
|
calcMolalities();
|
|
vector_fp& moleF = m_tmpV;
|
|
|
|
// Update the coefficients wrt Temperature. Calculate the derivatives as well
|
|
s_updatePitzer_CoeffWRTemp(2);
|
|
getMoleFractions(moleF.data());
|
|
|
|
writelog("Index Name MoleF MolalityCropped Charge\n");
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
writelogf("%2d %-16s %14.7le %14.7le %5.1f \n",
|
|
k, speciesName(k), moleF[k], m_molalitiesCropped[k], charge(k));
|
|
}
|
|
|
|
writelog("\n Species Species beta0MX "
|
|
"beta1MX beta2MX CphiMX alphaMX thetaij\n");
|
|
for (size_t i = 1; i < m_kk - 1; i++) {
|
|
for (size_t j = i+1; j < m_kk; j++) {
|
|
size_t n = i * m_kk + j;
|
|
size_t ct = m_CounterIJ[n];
|
|
writelogf(" %-16s %-16s %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f \n",
|
|
speciesName(i), speciesName(j),
|
|
m_Beta0MX_ij[ct], m_Beta1MX_ij[ct],
|
|
m_Beta2MX_ij[ct], m_CphiMX_ij[ct],
|
|
m_Alpha1MX_ij[ct], m_Theta_ij[ct]);
|
|
}
|
|
}
|
|
|
|
writelog("\n Species Species Species psi \n");
|
|
for (size_t i = 1; i < m_kk; i++) {
|
|
for (size_t j = 1; j < m_kk; j++) {
|
|
for (size_t k = 1; k < m_kk; k++) {
|
|
size_t n = k + j * m_kk + i * m_kk * m_kk;
|
|
if (m_Psi_ijk[n] != 0.0) {
|
|
writelogf(" %-16s %-16s %-16s %9.5f \n",
|
|
speciesName(i), speciesName(j),
|
|
speciesName(k), m_Psi_ijk[n]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HMWSoln::applyphScale(doublereal* acMolality) const
|
|
{
|
|
if (m_pHScalingType == PHSCALE_PITZER) {
|
|
return;
|
|
}
|
|
AssertTrace(m_pHScalingType == PHSCALE_NBS);
|
|
doublereal lnGammaClMs2 = s_NBS_CLM_lnMolalityActCoeff();
|
|
doublereal lnGammaCLMs1 = m_lnActCoeffMolal_Unscaled[m_indexCLM];
|
|
doublereal afac = -1.0 *(lnGammaClMs2 - lnGammaCLMs1);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
acMolality[k] *= exp(charge(k) * afac);
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updateScaling_pHScaling() const
|
|
{
|
|
if (m_pHScalingType == PHSCALE_PITZER) {
|
|
m_lnActCoeffMolal_Scaled = m_lnActCoeffMolal_Unscaled;
|
|
return;
|
|
}
|
|
AssertTrace(m_pHScalingType == PHSCALE_NBS);
|
|
doublereal lnGammaClMs2 = s_NBS_CLM_lnMolalityActCoeff();
|
|
doublereal lnGammaCLMs1 = m_lnActCoeffMolal_Unscaled[m_indexCLM];
|
|
doublereal afac = -1.0 *(lnGammaClMs2 - lnGammaCLMs1);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_lnActCoeffMolal_Scaled[k] = m_lnActCoeffMolal_Unscaled[k] + charge(k) * afac;
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updateScaling_pHScaling_dT() const
|
|
{
|
|
if (m_pHScalingType == PHSCALE_PITZER) {
|
|
m_dlnActCoeffMolaldT_Scaled = m_dlnActCoeffMolaldT_Unscaled;
|
|
return;
|
|
}
|
|
AssertTrace(m_pHScalingType == PHSCALE_NBS);
|
|
doublereal dlnGammaClM_dT_s2 = s_NBS_CLM_dlnMolalityActCoeff_dT();
|
|
doublereal dlnGammaCLM_dT_s1 = m_dlnActCoeffMolaldT_Unscaled[m_indexCLM];
|
|
doublereal afac = -1.0 *(dlnGammaClM_dT_s2 - dlnGammaCLM_dT_s1);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_dlnActCoeffMolaldT_Scaled[k] = m_dlnActCoeffMolaldT_Unscaled[k] + charge(k) * afac;
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updateScaling_pHScaling_dT2() const
|
|
{
|
|
if (m_pHScalingType == PHSCALE_PITZER) {
|
|
m_d2lnActCoeffMolaldT2_Scaled = m_d2lnActCoeffMolaldT2_Unscaled;
|
|
return;
|
|
}
|
|
AssertTrace(m_pHScalingType == PHSCALE_NBS);
|
|
doublereal d2lnGammaClM_dT2_s2 = s_NBS_CLM_d2lnMolalityActCoeff_dT2();
|
|
doublereal d2lnGammaCLM_dT2_s1 = m_d2lnActCoeffMolaldT2_Unscaled[m_indexCLM];
|
|
doublereal afac = -1.0 *(d2lnGammaClM_dT2_s2 - d2lnGammaCLM_dT2_s1);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_d2lnActCoeffMolaldT2_Scaled[k] = m_d2lnActCoeffMolaldT2_Unscaled[k] + charge(k) * afac;
|
|
}
|
|
}
|
|
|
|
void HMWSoln::s_updateScaling_pHScaling_dP() const
|
|
{
|
|
if (m_pHScalingType == PHSCALE_PITZER) {
|
|
m_dlnActCoeffMolaldP_Scaled = m_dlnActCoeffMolaldP_Unscaled;
|
|
return;
|
|
}
|
|
AssertTrace(m_pHScalingType == PHSCALE_NBS);
|
|
doublereal dlnGammaClM_dP_s2 = s_NBS_CLM_dlnMolalityActCoeff_dP();
|
|
doublereal dlnGammaCLM_dP_s1 = m_dlnActCoeffMolaldP_Unscaled[m_indexCLM];
|
|
doublereal afac = -1.0 *(dlnGammaClM_dP_s2 - dlnGammaCLM_dP_s1);
|
|
for (size_t k = 0; k < m_kk; k++) {
|
|
m_dlnActCoeffMolaldP_Scaled[k] = m_dlnActCoeffMolaldP_Unscaled[k] + charge(k) * afac;
|
|
}
|
|
}
|
|
|
|
doublereal HMWSoln::s_NBS_CLM_lnMolalityActCoeff() const
|
|
{
|
|
doublereal sqrtIs = sqrt(m_IionicMolality);
|
|
doublereal A = A_Debye_TP();
|
|
doublereal lnGammaClMs2 = - A * sqrtIs /(1.0 + 1.5 * sqrtIs);
|
|
return lnGammaClMs2;
|
|
}
|
|
|
|
doublereal HMWSoln::s_NBS_CLM_dlnMolalityActCoeff_dT() const
|
|
{
|
|
doublereal sqrtIs = sqrt(m_IionicMolality);
|
|
doublereal dAdT = dA_DebyedT_TP();
|
|
return - dAdT * sqrtIs /(1.0 + 1.5 * sqrtIs);
|
|
}
|
|
|
|
doublereal HMWSoln::s_NBS_CLM_d2lnMolalityActCoeff_dT2() const
|
|
{
|
|
doublereal sqrtIs = sqrt(m_IionicMolality);
|
|
doublereal d2AdT2 = d2A_DebyedT2_TP();
|
|
return - d2AdT2 * sqrtIs /(1.0 + 1.5 * sqrtIs);
|
|
}
|
|
|
|
doublereal HMWSoln::s_NBS_CLM_dlnMolalityActCoeff_dP() const
|
|
{
|
|
doublereal sqrtIs = sqrt(m_IionicMolality);
|
|
doublereal dAdP = dA_DebyedP_TP();
|
|
return - dAdP * sqrtIs /(1.0 + 1.5 * sqrtIs);
|
|
}
|
|
|
|
}
|