From 6aa92874389fe47331b3da72bb03e555a59acf35 Mon Sep 17 00:00:00 2001 From: Harry Moffat Date: Thu, 6 Jul 2006 15:39:34 +0000 Subject: [PATCH] Added Pitzer Object --- Cantera/src/thermo/HMWSoln.cpp | 5265 +++++++++++++++++++++++++ Cantera/src/thermo/HMWSoln.h | 1492 +++++++ Cantera/src/thermo/HMWSoln_input.cpp | 1130 ++++++ Cantera/src/thermo/IdealMolalSoln.cpp | 2 +- Cantera/src/thermo/IdealMolalSoln.h | 6 +- Cantera/src/thermo/Makefile.in | 5 +- Cantera/src/thermo/MolalityVPSSTP.h | 6 +- 7 files changed, 7898 insertions(+), 8 deletions(-) create mode 100644 Cantera/src/thermo/HMWSoln.cpp create mode 100644 Cantera/src/thermo/HMWSoln.h create mode 100644 Cantera/src/thermo/HMWSoln_input.cpp diff --git a/Cantera/src/thermo/HMWSoln.cpp b/Cantera/src/thermo/HMWSoln.cpp new file mode 100644 index 000000000..96e8b79dc --- /dev/null +++ b/Cantera/src/thermo/HMWSoln.cpp @@ -0,0 +1,5265 @@ +/** + * @file HMWSoln.cpp + * + * Member functions of Pitzer activity coefficient implementation. + */ +/* + * Copywrite (2006) Sandia Corporation. Under the terms of + * Contract DE-AC04-94AL85000 with Sandia Corporation, the + * U.S. Government retains certain rights in this software. + */ +/* + * $Id$ + */ + +#ifndef MAX +#define MAX(x,y) (( (x) > (y) ) ? (x) : (y)) +#endif + +#include "HMWSoln.h" +#include "importCTML.h" +#include "WaterProps.h" +#include "WaterPDSS.h" + +namespace Cantera { + + /** + * Default constructor + */ + HMWSoln::HMWSoln() : + MolalityVPSSTP(), + m_formPitzer(PITZERFORM_BASE), + m_formPitzerTemp(PITZER_TEMP_CONSTANT), + m_formGC(2), + m_Pcurrent(OneAtm), + m_IionicMolality(0.0), + m_maxIionicStrength(100.0), + m_TempPitzerRef(298.15), + m_IionicMolalityStoich(0.0), + m_form_A_Debye(A_DEBYE_WATER), + m_A_Debye(1.172576), // units = sqrt(kg/gmol) + m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m + m_waterSS(0), + m_densWaterSS(1000.), + m_waterProps(0), + m_debugCalc(0) + { + for (int i = 0; i < 17; i++) { + elambda[i] = 0.0; + elambda1[i] = 0.0; + } + } + /** + * Working constructors + * + * The two constructors below are the normal way + * the phase initializes itself. They are shells that call + * the routine initThermo(), with a reference to the + * XML database to get the info for the phase. + */ + HMWSoln::HMWSoln(string inputFile, string id) : + MolalityVPSSTP(), + m_formPitzer(PITZERFORM_BASE), + m_formPitzerTemp(PITZER_TEMP_CONSTANT), + m_formGC(2), + m_Pcurrent(OneAtm), + m_IionicMolality(0.0), + m_maxIionicStrength(100.0), + m_TempPitzerRef(298.15), + m_IionicMolalityStoich(0.0), + m_form_A_Debye(A_DEBYE_WATER), + m_A_Debye(1.172576), // units = sqrt(kg/gmol) + m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m + m_waterSS(0), + m_densWaterSS(1000.), + m_waterProps(0), + m_debugCalc(0) + { + for (int i = 0; i < 17; i++) { + elambda[i] = 0.0; + elambda1[i] = 0.0; + } + constructPhaseFile(inputFile, id); + } + + HMWSoln::HMWSoln(XML_Node& phaseRoot, string id) : + MolalityVPSSTP(), + m_formPitzer(PITZERFORM_BASE), + m_formPitzerTemp(PITZER_TEMP_CONSTANT), + m_formGC(2), + m_Pcurrent(OneAtm), + m_IionicMolality(0.0), + m_maxIionicStrength(100.0), + m_TempPitzerRef(298.15), + m_IionicMolalityStoich(0.0), + m_form_A_Debye(A_DEBYE_WATER), + m_A_Debye(1.172576), // units = sqrt(kg/gmol) + m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m + m_waterSS(0), + m_densWaterSS(1000.), + m_waterProps(0), + m_debugCalc(0) + { + for (int i = 0; i < 17; i++) { + elambda[i] = 0.0; + elambda1[i] = 0.0; + } + constructPhaseXML(phaseRoot, id); + } + + /** + * Copy Constructor: + * + * Note this stuff will not work until the underlying phase + * has a working copy constructor + */ + HMWSoln::HMWSoln(const HMWSoln &b) : + MolalityVPSSTP(b) + { + /* + * Use the assignment operator to do the brunt + * of the work for the copy construtor. + */ + *this = b; + } + + /** + * operator=() + * + * Note this stuff will not work until the underlying phase + * has a working assignment operator + */ + HMWSoln& HMWSoln:: + operator=(const HMWSoln &b) { + if (&b != this) { + MolalityVPSSTP::operator=(b); + m_formPitzer = b.m_formPitzer; + m_formPitzerTemp = b.m_formPitzerTemp; + m_formGC = b.m_formGC; + m_Pcurrent = b.m_Pcurrent; + m_Aionic = b.m_Aionic; + m_IionicMolality = b.m_IionicMolality; + m_maxIionicStrength = b.m_maxIionicStrength; + m_TempPitzerRef = b.m_TempPitzerRef; + m_IionicMolalityStoich= b.m_IionicMolalityStoich; + m_form_A_Debye = b.m_form_A_Debye; + m_A_Debye = b.m_A_Debye; + m_B_Debye = b.m_B_Debye; + if (!m_waterSS) { + m_waterSS = new WaterPDSS(this, 0); + } + m_waterSS = b.m_waterSS; + m_densWaterSS = b.m_densWaterSS; + if (!m_waterProps) { + m_waterProps = new WaterProps(*b.m_waterProps); + } else { + m_waterProps = b.m_waterProps; + } + m_waterSS = b.m_waterSS; + m_expg0_RT = b.m_expg0_RT; + m_pe = b.m_pe; + m_pp = b.m_pp; + m_tmpV = b.m_tmpV; + m_speciesCharge_Stoich= b.m_speciesCharge_Stoich; + m_Beta0MX_ij = b.m_Beta0MX_ij; + m_Beta0MX_ij_L = b.m_Beta0MX_ij_L; + m_Beta0MX_ij_LL = b.m_Beta0MX_ij_LL; + m_Beta0MX_ij_P = b.m_Beta0MX_ij_P; + m_Beta0MX_ij_coeff = b.m_Beta0MX_ij_coeff; + m_Beta1MX_ij = b.m_Beta1MX_ij; + m_Beta1MX_ij_L = b.m_Beta1MX_ij_L; + m_Beta1MX_ij_LL = b.m_Beta1MX_ij_LL; + m_Beta1MX_ij_P = b.m_Beta1MX_ij_P; + m_Beta1MX_ij_coeff = b.m_Beta1MX_ij_coeff; + m_Beta2MX_ij = b.m_Beta2MX_ij; + m_Beta2MX_ij_L = b.m_Beta2MX_ij_L; + m_Beta2MX_ij_LL = b.m_Beta2MX_ij_LL; + m_Beta2MX_ij_P = b.m_Beta2MX_ij_P; + m_Alpha1MX_ij = b.m_Alpha1MX_ij; + m_CphiMX_ij = b.m_CphiMX_ij; + m_CphiMX_ij_L = b.m_CphiMX_ij_L; + m_CphiMX_ij_LL = b.m_CphiMX_ij_LL; + m_CphiMX_ij_P = b.m_CphiMX_ij_P; + m_CphiMX_ij_coeff = b.m_CphiMX_ij_coeff; + m_Theta_ij = b.m_Theta_ij; + m_Theta_ij_L = b.m_Theta_ij_L; + m_Theta_ij_LL = b.m_Theta_ij_LL; + m_Theta_ij_P = b.m_Theta_ij_P; + m_Psi_ijk = b.m_Psi_ijk; + m_Psi_ijk_L = b.m_Psi_ijk_L; + m_Psi_ijk_LL = b.m_Psi_ijk_LL; + m_Psi_ijk_P = b.m_Psi_ijk_P; + m_Lambda_ij = b.m_Lambda_ij; + m_Lambda_ij_L = b.m_Lambda_ij_L; + m_Lambda_ij_LL = b.m_Lambda_ij_LL; + m_Lambda_ij_P = b.m_Lambda_ij_P; + m_lnActCoeffMolal = b.m_lnActCoeffMolal; + m_dlnActCoeffMolaldT = b.m_dlnActCoeffMolaldT; + m_d2lnActCoeffMolaldT2= b.m_d2lnActCoeffMolaldT2; + m_dlnActCoeffMolaldP = b.m_dlnActCoeffMolaldP; + + m_gfunc_IJ = b.m_gfunc_IJ; + m_hfunc_IJ = b.m_hfunc_IJ; + m_BMX_IJ = b.m_BMX_IJ; + m_BMX_IJ_L = b.m_BMX_IJ_L; + m_BMX_IJ_LL = b.m_BMX_IJ_LL; + m_BMX_IJ_P = b.m_BMX_IJ_P; + m_BprimeMX_IJ = b.m_BprimeMX_IJ; + m_BprimeMX_IJ_L = b.m_BprimeMX_IJ_L; + m_BprimeMX_IJ_LL = b.m_BprimeMX_IJ_LL; + m_BprimeMX_IJ_P = b.m_BprimeMX_IJ_P; + m_BphiMX_IJ = b.m_BphiMX_IJ; + m_BphiMX_IJ_L = b.m_BphiMX_IJ_L; + m_BphiMX_IJ_LL = b.m_BphiMX_IJ_LL; + m_BphiMX_IJ_P = b.m_BphiMX_IJ_P; + m_Phi_IJ = b.m_Phi_IJ; + m_Phi_IJ_L = b.m_Phi_IJ_L; + m_Phi_IJ_LL = b.m_Phi_IJ_LL; + m_Phi_IJ_P = b.m_Phi_IJ_P; + m_Phiprime_IJ = b.m_Phiprime_IJ; + m_PhiPhi_IJ = b.m_PhiPhi_IJ; + m_PhiPhi_IJ_L = b.m_PhiPhi_IJ_L; + m_PhiPhi_IJ_LL = b.m_PhiPhi_IJ_LL; + m_PhiPhi_IJ_P = b.m_PhiPhi_IJ_P; + m_CMX_IJ = b.m_CMX_IJ; + m_CMX_IJ_L = b.m_CMX_IJ_L; + m_CMX_IJ_LL = b.m_CMX_IJ_LL; + m_CMX_IJ_P = b.m_CMX_IJ_P; + m_gamma = b.m_gamma; + + m_CounterIJ = b.m_CounterIJ; + m_debugCalc = b.m_debugCalc; + } + return *this; + } + + + /** + * Test matrix for this object + * + * + * test problems: + * 1 = NaCl problem - 5 species - + * the thermo is read in from an XML file + * + * speci molality charge + * Cl- 6.0954 6.0997E+00 -1 + * H+ 1.0000E-08 2.1628E-09 1 + * Na+ 6.0954E+00 6.0997E+00 1 + * OH- 7.5982E-07 1.3977E-06 -1 + * HMW_params____beta0MX__beta1MX__beta2MX__CphiMX_____alphaMX__thetaij + * 10 + * 1 2 0.1775 0.2945 0.0 0.00080 2.0 0.0 + * 1 3 0.0765 0.2664 0.0 0.00127 2.0 0.0 + * 1 4 0.0 0.0 0.0 0.0 0.0 -0.050 + * 2 3 0.0 0.0 0.0 0.0 0.0 0.036 + * 2 4 0.0 0.0 0.0 0.0 0.0 0.0 + * 3 4 0.0864 0.253 0.0 0.0044 2.0 0.0 + * Triplet_interaction_parameters_psiaa'_or_psicc' + * 2 + * 1 2 3 -0.004 + * 1 3 4 -0.006 + */ + HMWSoln::HMWSoln(int testProb) : + MolalityVPSSTP(), + m_formPitzer(PITZERFORM_BASE), + m_formPitzerTemp(PITZER_TEMP_CONSTANT), + m_formGC(2), + m_Pcurrent(OneAtm), + m_IionicMolality(0.0), + m_maxIionicStrength(30.0), + m_TempPitzerRef(298.15), + m_IionicMolalityStoich(0.0), + m_form_A_Debye(A_DEBYE_WATER), + m_A_Debye(1.172576), // units = sqrt(kg/gmol) + m_B_Debye(3.28640E9), // units = sqrt(kg/gmol) / m + m_waterSS(0), + m_densWaterSS(1000.), + m_waterProps(0), + m_debugCalc(0) + { + if (testProb != 1) { + printf("unknown test problem\n"); + exit(-1); + } + + constructPhaseFile("HMW_NaCl.xml", ""); + + int i = speciesIndex("Cl-"); + int j = speciesIndex("H+"); + int n = i * m_kk + j; + int ct = m_CounterIJ[n]; + m_Beta0MX_ij[ct] = 0.1775; + m_Beta1MX_ij[ct] = 0.2945; + m_CphiMX_ij[ct] = 0.0008; + m_Alpha1MX_ij[ct]= 2.000; + + + i = speciesIndex("Cl-"); + j = speciesIndex("Na+"); + n = i * m_kk + j; + ct = m_CounterIJ[n]; + m_Beta0MX_ij[ct] = 0.0765; + m_Beta1MX_ij[ct] = 0.2664; + m_CphiMX_ij[ct] = 0.00127; + m_Alpha1MX_ij[ct]= 2.000; + + + i = speciesIndex("Cl-"); + j = speciesIndex("OH-"); + n = i * m_kk + j; + ct = m_CounterIJ[n]; + m_Theta_ij[ct] = -0.05; + + i = speciesIndex("H+"); + j = speciesIndex("Na+"); + n = i * m_kk + j; + ct = m_CounterIJ[n]; + m_Theta_ij[ct] = 0.036; + + i = speciesIndex("Na+"); + j = speciesIndex("OH-"); + n = i * m_kk + j; + ct = m_CounterIJ[n]; + m_Beta0MX_ij[ct] = 0.0864; + m_Beta1MX_ij[ct] = 0.253; + m_CphiMX_ij[ct] = 0.0044; + m_Alpha1MX_ij[ct]= 2.000; + + i = speciesIndex("Cl-"); + j = speciesIndex("H+"); + int k = speciesIndex("Na+"); + double param = -0.004; + n = i * m_kk *m_kk + j * m_kk + k ; + m_Psi_ijk[n] = param; + n = i * m_kk *m_kk + k * m_kk + j ; + m_Psi_ijk[n] = param; + n = j * m_kk *m_kk + i * m_kk + k ; + m_Psi_ijk[n] = param; + n = j * m_kk *m_kk + k * m_kk + i ; + m_Psi_ijk[n] = param; + n = k * m_kk *m_kk + j * m_kk + i ; + m_Psi_ijk[n] = param; + n = k * m_kk *m_kk + i * m_kk + j ; + m_Psi_ijk[n] = param; + + i = speciesIndex("Cl-"); + j = speciesIndex("Na+"); + k = speciesIndex("OH-"); + param = -0.006; + n = i * m_kk *m_kk + j * m_kk + k ; + m_Psi_ijk[n] = param; + n = i * m_kk *m_kk + k * m_kk + j ; + m_Psi_ijk[n] = param; + n = j * m_kk *m_kk + i * m_kk + k ; + m_Psi_ijk[n] = param; + n = j * m_kk *m_kk + k * m_kk + i ; + m_Psi_ijk[n] = param; + n = k * m_kk *m_kk + j * m_kk + i ; + m_Psi_ijk[n] = param; + n = k * m_kk *m_kk + i * m_kk + j ; + m_Psi_ijk[n] = param; + + printCoeffs(); + } + + /** + * ~HMWSoln(): (virtual) + * + * Destructor: does nothing: + */ + HMWSoln::~HMWSoln() { + delete m_waterProps; + delete m_waterSS; + } + + /** + * duplMyselfAsThermoPhase(): + * + * This routine operates at the ThermoPhase level to + * duplicate the current object. It uses the copy constructor + * defined above. + */ + ThermoPhase* HMWSoln::duplMyselfAsThermoPhase() { + HMWSoln* mtp = new HMWSoln(*this); + return (ThermoPhase *) mtp; + } + + /** + * Equation of state type flag. The base class returns + * zero. Subclasses should define this to return a unique + * non-zero value. Constants defined for this purpose are + * listed in mix_defs.h. + */ + int HMWSoln::eosType() const { + int res; + switch (m_formGC) { + case 0: + res = cHMWSoln0; + break; + case 1: + res = cHMWSoln1; + break; + case 2: + res = cHMWSoln2; + break; + default: + throw CanteraError("eosType", "Unknown type"); + break; + } + return res; + } + + // + // -------- Molar Thermodynamic Properties of the Solution --------------- + // + /** + * Molar enthalpy of the solution. Units: J/kmol. + */ + doublereal HMWSoln::enthalpy_mole() const { + getPartialMolarEnthalpies(DATA_PTR(m_tmpV)); + getMoleFractions(DATA_PTR(m_pp)); + double val = mean_X(DATA_PTR(m_tmpV)); +#ifdef DEBUG_HKM + double val0 = 0.0; + for (int k = 0; k < m_kk; k++) { + val0 += m_tmpV[k] * m_pp[k]; + } + //if (val != val0) { + // printf("ERROR\n"); + //} +#endif + return val; + } + + doublereal HMWSoln::relative_enthalpy() const { + getPartialMolarEnthalpies(DATA_PTR(m_tmpV)); + double hbar = mean_X(DATA_PTR(m_tmpV)); + getEnthalpy_RT(DATA_PTR(m_gamma)); + double RT = GasConstant * temperature(); + for (int k = 0; k < m_kk; k++) { + m_gamma[k] *= RT; + } + double h0bar = mean_X(DATA_PTR(m_gamma)); + return (hbar - h0bar); + } + + + + doublereal HMWSoln::relative_molal_enthalpy() const { + double L = relative_enthalpy(); + getMoleFractions(DATA_PTR(m_tmpV)); + double xanion = 0.0; + int kcation = -1; + double xcation = 0.0; + int kanion = -1; + const double *charge = DATA_PTR(m_speciesCharge); + for (int 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 < 0 || kanion < 0) { + return L; + } + double xuse = xcation; + int kuse = kcation; + double factor = 1; + if (xanion < xcation) { + xuse = xanion; + kuse = kanion; + if (charge[kcation] != 1.0) { + factor = charge[kcation]; + } + } else { + if (charge[kanion] != 1.0) { + factor = charge[kanion]; + } + } + xuse = xuse / factor; + L = L / xuse; + return L; + } + + /** + * Molar internal energy of the solution. Units: J/kmol. + */ + doublereal HMWSoln::intEnergy_mole() const { + double hh = enthalpy_mole(); + double pres = pressure(); + double molarV = 1.0/molarDensity(); + double uu = hh - pres * molarV; + return uu; + } + + doublereal HMWSoln::entropy_mole() const { + getPartialMolarEntropies(DATA_PTR(m_tmpV)); + return mean_X(DATA_PTR(m_tmpV)); + } + + /// Molar Gibbs function. Units: J/kmol. + doublereal HMWSoln::gibbs_mole() const { + getChemPotentials(DATA_PTR(m_tmpV)); + return mean_X(DATA_PTR(m_tmpV)); + } + + /// Molar heat capacity at constant pressure. Units: J/kmol/K. + doublereal HMWSoln::cp_mole() const { + getPartialMolarCp(DATA_PTR(m_tmpV)); + double val = mean_X(DATA_PTR(m_tmpV)); + return val; + } + + /// Molar heat capacity at constant volume. Units: J/kmol/K. + doublereal HMWSoln::cv_mole() const { + //getPartialMolarCv(m_tmpV.begin()); + //return mean_X(m_tmpV.begin()); + err("not implemented"); + return 0.0; + } + + // + // ------- Mechanical Equation of State Properties ------------------------ + // + + /** + * Pressure. Units: Pa. + * For this incompressible system, we return the internally storred + * independent value of the pressure. + */ + doublereal HMWSoln::pressure() const { + return m_Pcurrent; + } + + /** + * Set the pressure at constant temperature. Units: Pa. + * This method sets a constant within the object. + * The mass density is not a function of pressure. + */ + void HMWSoln::setPressure(doublereal p) { +#ifdef DEBUG_HKM + //printf("setPressure: %g\n", p); +#endif + double temp = temperature(); + /* + * Call the water SS and set it's internal state + */ + m_waterSS->setTempPressure(temp, p); + + /* + * Store the internal density of the water SS. + * Note, we would have to do this for all other + * species if they had pressure dependent properties. + */ + m_densWaterSS = m_waterSS->density(); + /* + * Store the current pressure + */ + m_Pcurrent = p; + /* + * Calculate all of the other standard volumes + * -> note these are constant for now + */ + /* + * Get the partial molar volumes of all of the + * species. -> note this is a lookup for + * water, here since it was done above. + */ + double *vbar = &m_pp[0]; + getPartialMolarVolumes(vbar); + + /* + * Get mole fractions of all species. + */ + double *x = &m_tmpV[0]; + getMoleFractions(x); + + /* + * Calculate the solution molar volume and the + * solution density. + */ + doublereal vtotal = 0.0; + for (int i = 0; i < m_kk; i++) { + vtotal += vbar[i] * x[i]; + } + doublereal dd = meanMolecularWeight() / vtotal; + + /* + * Now, update the State class with the results. This + * store the denisty. + */ + State::setDensity(dd); + + } + + /** + * The isothermal compressibility. Units: 1/Pa. + * The isothermal compressibility is defined as + * \f[ + * \kappa_T = -\frac{1}{v}\left(\frac{\partial v}{\partial P}\right)_T + * \f] + * + * It's equal to zero for this model, since the molar volume + * doesn't change with pressure or temperature. + */ + doublereal HMWSoln::isothermalCompressibility() const { + return 0.0; + } + + /** + * The thermal expansion coefficient. Units: 1/K. + * The thermal expansion coefficient is defined as + * + * \f[ + * \beta = \frac{1}{v}\left(\frac{\partial v}{\partial T}\right)_P + * \f] + * + * It's equal to zero for this model, since the molar volume + * doesn't change with pressure or temperature. + */ + doublereal HMWSoln::thermalExpansionCoeff() const { + throw CanteraError("HMWSoln::thermalExpansionCoeff", + "unimplemented"); + return 0.0; + } + + /** + * Overwritten setDensity() function is necessary because the + * density is not an indendent variable. + * + * This function will now throw an error condition + * + * Note, in general, setting the phase density is now a nonlinear + * calculation. P and T are the fundamental variables. This + * routine should be revamped to do the nonlinear problem + * + * @internal May have to adjust the strategy here to make + * the eos for these materials slightly compressible, in order + * to create a condition where the density is a function of + * the pressure. + * + * This function will now throw an error condition. + * + * NOTE: This is an overwritten function from the State.h + * class + */ + void HMWSoln::setDensity(doublereal rho) { + double dens_old = density(); + + if (rho != dens_old) { + throw CanteraError("HMWSoln::setDensity", + "Density is not an independent variable"); + } + } + + /** + * Overwritten setMolarDensity() function is necessary because the + * density is not an indendent variable. + * + * This function will now throw an error condition. + * + * NOTE: This is an overwritten function from the State.h + * class + */ + void HMWSoln::setMolarDensity(doublereal rho) { + throw CanteraError("HMWSoln::setMolarDensity", + "Density is not an independent variable"); + } + + /** + * Overwritten setTemperature(double) from State.h. This + * function sets the temperature, and makes sure that + * the value propagates to underlying objects. + */ + void HMWSoln::setTemperature(double temp) { + m_waterSS->setTemperature(temp); + State::setTemperature(temp); + } + + // + // ------- Activities and Activity Concentrations + // + + /** + * This method returns an array of generalized concentrations + * \f$ C_k\f$ that are defined such that + * \f$ a_k = C_k / C^0_k, \f$ where \f$ C^0_k \f$ + * is a standard concentration + * defined below. These generalized concentrations are used + * by kinetics manager classes to compute the forward and + * reverse rates of elementary reactions. + * + * @param c Array of generalized concentrations. The + * units depend upon the implementation of the + * reaction rate expressions within the phase. + */ + void HMWSoln::getActivityConcentrations(doublereal* c) const { + double c_solvent = standardConcentration(); + getActivities(c); + for (int k = 0; k < m_kk; k++) { + c[k] *= c_solvent; + } + } + + /** + * The standard concentration \f$ C^0_k \f$ used to normalize + * the generalized concentration. In many cases, this quantity + * will be the same for all species in a phase - for example, + * for an ideal gas \f$ C^0_k = P/\hat R T \f$. For this + * reason, this method returns a single value, instead of an + * array. However, for phases in which the standard + * concentration is species-specific (e.g. surface species of + * different sizes), this method may be called with an + * optional parameter indicating the species. + * + * For the time being we will use the concentration of pure + * solvent for the the standard concentration of all species. + * This has the effect of making reaction rates + * based on the molality of species proportional to the + * molality of the species. + */ + doublereal HMWSoln::standardConcentration(int k) const { + double mvSolvent = m_speciesSize[m_indexSolvent]; + return 1.0 / mvSolvent; + } + + /** + * Returns the natural logarithm of the standard + * concentration of the kth species + */ + doublereal HMWSoln::logStandardConc(int k) const { + double c_solvent = standardConcentration(k); + return log(c_solvent); + } + + /** + * Returns the units of the standard and general concentrations + * Note they have the same units, as their divisor is + * defined to be equal to the activity of the kth species + * in the solution, which is unitless. + * + * This routine is used in print out applications where the + * units are needed. Usually, MKS units are assumed throughout + * the program and in the XML input files. + * + * On return uA contains the powers of the units (MKS assumed) + * of the standard concentrations and generalized concentrations + * for the kth species. + * + * uA[0] = kmol units - default = 1 + * uA[1] = m units - default = -nDim(), the number of spatial + * dimensions in the Phase class. + * uA[2] = kg units - default = 0; + * uA[3] = Pa(pressure) units - default = 0; + * uA[4] = Temperature units - default = 0; + * uA[5] = time units - default = 0 + */ + void HMWSoln::getUnitsStandardConc(double *uA, int k, int sizeUA) { + for (int i = 0; i < sizeUA; i++) { + if (i == 0) uA[0] = 1.0; + if (i == 1) uA[1] = -nDim(); + if (i == 2) uA[2] = 0.0; + if (i == 3) uA[3] = 0.0; + if (i == 4) uA[4] = 0.0; + if (i == 5) uA[5] = 0.0; + } + } + + + /** + * Get the array of non-dimensional activities at + * the current solution temperature, pressure, and + * solution concentration. + * (note solvent activity coefficient is on the molar scale). + * + */ + void HMWSoln::getActivities(doublereal* ac) const { + /* + * Update the molality array, m_molalities() + * This requires an update due to mole fractions + */ + s_update_lnMolalityActCoeff(); + /* + * Now calculate the array of activities. + */ + for (int k = 0; k < m_kk; k++) { + if (k != m_indexSolvent) { + ac[k] = m_molalities[k] * exp(m_lnActCoeffMolal[k]); + } + } + double xmolSolvent = moleFraction(m_indexSolvent); + ac[m_indexSolvent] = + exp(m_lnActCoeffMolal[m_indexSolvent]) * xmolSolvent; + } + + /** + * getMolalityActivityCoefficients() (virtual, const) + * + * Get the array of non-dimensional Molality based + * activity coefficients at + * the current solution temperature, pressure, and + * solution concentration. + * (note solvent activity coefficient is on the molar scale). + * + * Note, most of the work is done in an internal private routine + */ + void HMWSoln:: + getMolalityActivityCoefficients(doublereal* acMolality) const { + + A_Debye_TP(-1.0, -1.0); + s_update_lnMolalityActCoeff(); + copy(m_lnActCoeffMolal.begin(), m_lnActCoeffMolal.end(), acMolality); + for (int k = 0; k < m_kk; k++) { + acMolality[k] = exp(acMolality[k]); + } + } + + // + // ------ Partial Molar Properties of the Solution ----------------- + // + /** + * Get the species chemical potentials. Units: J/kmol. + * + * This function returns a vector of chemical potentials of the + * species in solution. + * + * \f[ + * \mu_k = \mu^{o}_k(T,P) + R T ln(m_k) + * \f] + * + * \f[ + * \mu_solvent = \mu^{o}_solvent(T,P) + + * R T ((X_solvent - 1.0) / X_solvent) + * \f] + */ + void HMWSoln::getChemPotentials(doublereal* mu) const{ + double xx; + const double xxSmall = 1.0E-150; + /* + * First get the standard chemical potentials in + * molar form. + * -> this requires updates of standard state as a function + * of T and P + */ + getStandardChemPotentials(mu); + /* + * Update the activity coefficients + * This also updates the internal molality array. + */ + s_update_lnMolalityActCoeff(); + /* + * + */ + doublereal RT = GasConstant * temperature(); + double xmolSolvent = moleFraction(m_indexSolvent); + for (int k = 0; k < m_kk; k++) { + if (m_indexSolvent != k) { + xx = MAX(m_molalities[k], xxSmall); + mu[k] += RT * (log(xx) + m_lnActCoeffMolal[k]); + } + } + xx = MAX(xmolSolvent, xxSmall); + mu[m_indexSolvent] += + RT * (log(xx) + m_lnActCoeffMolal[m_indexSolvent]); + } + + + /** + * Returns an array of partial molar enthalpies for the species + * in the mixture. + * Units (J/kmol) + * + * We calculate this quantity partially from the relation and + * partially by calling the standard state enthalpy function. + * + * hbar_i = - T**2 * d(chemPot_i/T)/dT + * + * We calculate + */ + void HMWSoln::getPartialMolarEnthalpies(doublereal* hbar) const { + getEnthalpy_RT(hbar); + + /* + * Update the activity coefficients, This also update the + * internally storred molalities. + */ + s_update_lnMolalityActCoeff(); + s_update_dlnMolalityActCoeff_dT(); + double T = temperature(); + double RT = GasConstant * T; + for (int k = 0; k < m_kk; k++) { + hbar[k] *= RT; + } + double RTT = RT * T; + for (int k = 0; k < m_kk; k++) { + hbar[k] -= RTT * m_dlnActCoeffMolaldT[k]; + } + } + + /** + * + * getPartialMolarEntropies() (virtual, const) + * + * Returns an array of partial molar entropies of the species in the + * solution. Units: J/kmol. + * + * Maxwell's equations provide an insight in how to calculate this + * (p.215 Smith and Van Ness) + * + * d(chemPot_i)/dT = -sbar_i + * + * Combining this with the expression H = G + TS yields: + * + * \f[ + * \bar s_k(T,P) = \hat s^0_k(T) - R log(M0 * molality[k] ac[k]) + * - R T^2 d log(ac[k]) / dT + * \f] + * + * + * The reference-state pure-species entropies,\f$ \hat s^0_k(T) \f$, + * at the reference pressure, \f$ P_{ref} \f$, are computed by the + * species thermodynamic + * property manager. They are polynomial functions of temperature. + * @see SpeciesThermo + */ + void HMWSoln:: + getPartialMolarEntropies(doublereal* sbar) const { + int k; + /* + * Get the standard state entropies at the temperature + * and pressure of the solution. + */ + getEntropy_R(sbar); + /* + * Update the activity coefficients, This also update the + * internally stored molalities. + */ + s_update_lnMolalityActCoeff(); + + doublereal R = GasConstant; + doublereal mm; + /* + * First we will add in the obvious dependence on the T + * term out front of the log activity term + */ + for (k = 0; k < m_kk; k++) { + if (k != m_indexSolvent) { + mm = fmaxx(SmallNumber, m_molalities[k]); + sbar[k] -= R * (log(mm) + m_lnActCoeffMolal[k]); + } + } + double xmolSolvent = moleFraction(m_indexSolvent); + mm = fmaxx(SmallNumber, xmolSolvent); + sbar[m_indexSolvent] -= + R *(log(mm) + m_lnActCoeffMolal[m_indexSolvent]); + /* + * Check to see whether activity coefficients are temperature + * dependent. If they are, then calculate the their temperature + * derivatives and add them into the result. + */ + s_update_dlnMolalityActCoeff_dT(); + double RT = R * temperature(); + for (k = 0; k < m_kk; k++) { + sbar[k] -= RT * m_dlnActCoeffMolaldT[k]; + } + + } + + /** + * getPartialMolarVolumes() (virtual, const) + * + * returns an array of partial molar volumes of the species + * in the solution. Units: m^3 kmol-1. + * + * For this solution, the partial molar volumes are equal to the + * species standard state molar volumes. However, extensions + * to this will be implemented in the future. + * + * The general relation is + * + * vbar_i = d(chemPot_i)/dP at const T, n + * + * = V0_i + d(Gex)/dP)_T,M + * + * = V0_i + RT d(lnActCoeffi)dP _T,M + * + * So, if the activity coefficients depended on pressure this + * function would be nontrivial. + */ + void HMWSoln::getPartialMolarVolumes(doublereal* vbar) const { + /* + * Get the standard state values + */ + getStandardVolumes(vbar); + /* + * Update the derivatives wrt the activity coefficients. + */ + s_update_lnMolalityActCoeff(); + s_Pitzer_dlnMolalityActCoeff_dP(); + double T = temperature(); + double RT = GasConstant * T; + for (int k = 0; k < m_kk; k++) { + vbar[k] += RT * m_dlnActCoeffMolaldP[k]; + } + } + + /* + * Partial molar heat capacity of the solution: + * The kth partial molar heat capacity is equal to + * the temperature derivative of the partial molar + * enthalpy of the kth species in the solution at constant + * P and composition (p. 220 Smith and Van Ness). + * + * Cp = -T d2(chemPot_i)/dT2 + */ + void HMWSoln::getPartialMolarCp(doublereal* cpbar) const { + /* + * Get the nondimensional gibbs standard state of the + * species at the T and P of the solution. + */ + getCp_R(cpbar); + + for (int k = 0; k < m_kk; k++) { + cpbar[k] *= GasConstant; + } + + /* + * Check to see whether activity coefficients are temperature + * dependent. If they are, then calculate the their temperature + * derivatives and add them into the result. + */ + double dAdT = dA_DebyedT_TP(); + if (dAdT != 0.0) { + /* + * Update the activity coefficients, This also update the + * internally storred molalities. + */ + s_update_lnMolalityActCoeff(); + s_update_dlnMolalityActCoeff_dT(); + s_update_d2lnMolalityActCoeff_dT2(); + double T = temperature(); + double RT = GasConstant * T; + double RTT = RT * T; + for (int k = 0; k < m_kk; k++) { + cpbar[k] -= (2.0 * RT * m_dlnActCoeffMolaldT[k] + + RTT * m_d2lnActCoeffMolaldT2[k]); + } + } + } + + + /* + * -------- Properties of the Standard State of the Species + * in the Solution ------------------ + */ + + /** + * getStandardChemPotentials() (virtual, const) + * + * + * Get the standard state chemical potentials of the species. + * This is the array of chemical potentials at unit activity + * (Mole fraction scale) + * \f$ \mu^0_k(T,P) \f$. + * We define these here as the chemical potentials of the pure + * species at the temperature and pressure of the solution. + * This function is used in the evaluation of the + * equilibrium constant Kc. Therefore, Kc will also depend + * on T and P. This is the norm for liquid and solid systems. + * + * units = J / kmol + */ + void HMWSoln::getStandardChemPotentials(doublereal* mu) const { + getGibbs_ref(mu); + doublereal pref; + doublereal delta_p; + for (int k = 1; k < m_kk; k++) { + pref = m_spthermo->refPressure(k); + delta_p = m_Pcurrent - pref; + mu[k] += delta_p * m_speciesSize[k]; + } + mu[0] = m_waterSS->gibbs_mole(); + } + + /** + * Get the nondimensional gibbs function for the species + * standard states at the current T and P of the solution. + * + * \f[ + * \mu^0_k(T,P) = \mu^{ref}_k(T) + (P - P_{ref}) * V_k + * \f] + * where \f$V_k\f$ is the molar volume of pure species k. + * \f$ \mu^{ref}_k(T)\f$ is the chemical potential of pure + * species k at the reference pressure, \f$P_{ref}\f$. + * + * @param grt Vector of length m_kk, which on return sr[k] + * will contain the nondimensional + * standard state gibbs function for species k. + */ + void HMWSoln::getGibbs_RT(doublereal* grt) const { + getStandardChemPotentials(grt); + doublereal invRT = 1.0 / _RT(); + for (int k = 0; k < m_kk; k++) { + grt[k] *= invRT; + } + } + + /** + * + * getPureGibbs() + * + * Get the Gibbs functions for the pure species + * at the current T and P of the solution. + * We assume an incompressible constant partial molar + * volume here: + * \f[ + * \mu^0_k(T,p) = \mu^{ref}_k(T) + (P - P_{ref}) * V_k + * \f] + * where \f$V_k\f$ is the molar volume of pure species k<\I>. + * \f$ u^{ref}_k(T)\f$ is the chemical potential of pure + * species k<\I> at the reference pressure, \f$P_{ref}\f$. + */ + void HMWSoln::getPureGibbs(doublereal* gpure) const { + getStandardChemPotentials(gpure); + } + + /** + * + * getEnthalpy_RT() (virtual, const) + * + * Get the array of nondimensional Enthalpy functions for the ss + * species at the current T and P of the solution. + * We assume an incompressible constant partial molar + * volume here: + * \f[ + * h^0_k(T,P) = h^{ref}_k(T) + (P - P_{ref}) * V_k + * \f] + * where \f$V_k\f$ is the molar volume of SS species k<\I>. + * \f$ h^{ref}_k(T)\f$ is the enthalpy of the SS + * species k<\I> at the reference pressure, \f$P_{ref}\f$. + */ + void HMWSoln:: + getEnthalpy_RT(doublereal* hrt) const { + getEnthalpy_RT_ref(hrt); + doublereal pref; + doublereal delta_p; + double RT = _RT(); + for (int k = 1; k < m_kk; k++) { + pref = m_spthermo->refPressure(k); + delta_p = m_Pcurrent - pref; + hrt[k] += delta_p/ RT * m_speciesSize[k]; + } + hrt[0] = m_waterSS->enthalpy_mole(); + hrt[0] /= RT; + } + + /** + * getEntropy_R() (virtual, const) + * + * Get the nondimensional Entropies for the species + * standard states at the current T and P of the solution. + * + * Note, this is equal to the reference state entropies + * due to the zero volume expansivity: + * i.e., (dS/dp)_T = (dV/dT)_P = 0.0 + * + * @param sr Vector of length m_kk, which on return sr[k] + * will contain the nondimensional + * standard state entropy of species k. + */ + void HMWSoln:: + getEntropy_R(doublereal* sr) const { + getEntropy_R_ref(sr); + sr[0] = m_waterSS->entropy_mole(); + sr[0] /= GasConstant; + } + + /** + * Get the nondimensional heat capacity at constant pressure + * function for the species + * standard states at the current T and P of the solution. + * \f[ + * Cp^0_k(T,P) = Cp^{ref}_k(T) + * \f] + * where \f$V_k\f$ is the molar volume of pure species k. + * \f$ Cp^{ref}_k(T)\f$ is the constant pressure heat capacity + * of species k at the reference pressure, \f$p_{ref}\f$. + * + * @param cpr Vector of length m_kk, which on return cpr[k] + * will contain the nondimensional + * constant pressure heat capacity for species k. + */ + void HMWSoln::getCp_R(doublereal* cpr) const { + getCp_R_ref(cpr); + cpr[0] = m_waterSS->cp_mole(); + cpr[0] /= GasConstant; + } + + /** + * Get the molar volumes of each species in their standard + * states at the current + * T and P of the solution. + * units = m^3 / kmol + * + * The water calculation is done separately. + */ + void HMWSoln::getStandardVolumes(doublereal *vol) const { + copy(m_speciesSize.begin(), + m_speciesSize.end(), vol); + double dd = m_waterSS->density(); + vol[0] = molecularWeight(0)/dd; + } + + /* + * ------ Thermodynamic Values for the Species Reference States --- + */ + + // -> This is handled by VPStandardStatesTP + + /* + * -------------- Utilities ------------------------------- + */ + + /** + * @internal + * Set equation of state parameters. The number and meaning of + * these depends on the subclass. + * @param n number of parameters + * @param c array of \i n coefficients + * + */ + void HMWSoln::setParameters(int n, doublereal* c) { + } + void HMWSoln::getParameters(int &n, doublereal * const c) { + } + /** + * Set equation of state parameter values from XML + * entries. This method is called by function importPhase in + * file importCTML.cpp when processing a phase definition in + * an input file. It should be overloaded in subclasses to set + * any parameters that are specific to that particular phase + * model. + * + * @param eosdata An XML_Node object corresponding to + * the "thermo" entry for this phase in the input file. + * + * HKM -> Right now, the parameters are set elsewhere (initThermoXML) + * It just didn't seem to fit. + */ + void HMWSoln::setParametersFromXML(const XML_Node& eosdata) { + } + + /** + * Get the saturation pressure for a given temperature. + * Note the limitations of this function. Stability considerations + * concernting multiphase equilibrium are ignored in this + * calculation. Therefore, the call is made directly to the SS of + * water underneath. The object is put back into its original + * state at the end of the call. + */ + doublereal HMWSoln::satPressure(doublereal t) const { + 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; + } + + /** + * Report the molar volume of species k + * + * units - \f$ m^3 kmol^-1 \f$ + */ + double HMWSoln::speciesMolarVolume(int k) const { + double vol = m_speciesSize[k]; + if (k == 0) { + double dd = m_waterSS->density(); + vol = molecularWeight(0)/dd; + } + return vol; + } + + + /** + * A_Debye_TP() (virtual) + * + * Returns the A_Debye parameter as a function of temperature + * and pressure. This function also sets the internal value + * of the parameter within the object, if it is changeable. + * + * The default is to assume that it is constant, given + * in the initialization process and storred in the + * member double, m_A_Debye + * + * A_Debye = (1/(8 Pi)) sqrt(2 Na dw /1000) + * (e e/(epsilon R T))^3/2 + * + * where epsilon = e_rel * e_naught + * + * Note, this is si units. Frequently, gaussian units are + * used in Pitzer's papers where D is used, D = epsilon/(4 Pi) + * units = A_Debye has units of sqrt(gmol kg-1). + */ + 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; + } + + 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); + //A = WaterProps::ADebye(T, P, 0); + m_A_Debye = A; + break; + default: + printf("shouldn't be here\n"); + exit(-1); + } + return A; + } + + /** + * dA_DebyedT_TP() (virtual) + * + * Returns the derivative of the A_Debye parameter with + * respect to temperature as a function of temperature + * and pressure. + * + * units = A_Debye has units of sqrt(gmol kg-1). + * Temp has units of Kelvin. + */ + double HMWSoln::dA_DebyedT_TP(double tempArg, double presArg) const { + double T = temperature(); + if (tempArg != -1.0) { + T = tempArg; + } + double P = pressure(); + if (presArg != -1.0) { + P = presArg; + } + double dAdT; + switch (m_form_A_Debye) { + case A_DEBYE_CONST: + dAdT = 0.0; + break; + case A_DEBYE_WATER: + dAdT = m_waterProps->ADebye(T, P, 1); + //dAdT = WaterProps::ADebye(T, P, 1); + break; + default: + printf("shouldn't be here\n"); + exit(-1); + } + return dAdT; + } + + /** + * dA_DebyedP_TP() (virtual) + * + * Returns the derivative of the A_Debye parameter with + * respect to pressure, as a function of temperature + * and pressure. + * + * units = A_Debye has units of sqrt(gmol kg-1). + * Pressure has units of pascals. + */ + double 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; + switch (m_form_A_Debye) { + case A_DEBYE_CONST: + dAdP = 0.0; + break; + case A_DEBYE_WATER: + dAdP = m_waterProps->ADebye(T, P, 3); + break; + default: + printf("shouldn't be here\n"); + exit(-1); + } + return dAdP; + } + + + /** + * Calculate the DH Parameter used for the Enthalpy calcalations + * + * ADebye_L = 4 R T**2 d(Aphi) / dT + * + * where Aphi = A_Debye/3 + * + * units -> J / (kmolK) * sqrt( kg/gmol) + * + */ + 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; + } + double retn = dAphidT * (4.0 * GasConstant * T * T); + return retn; + } + + /** + * Calculate the DH Parameter used for the Volume calcalations + * + * ADebye_V = - 4 R T d(Aphi) / dP + * + * where Aphi = A_Debye/3 + * + * units -> J / (kmolK) * sqrt( kg/gmol) + * + */ + 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; + } + double retn = - dAphidP * (4.0 * GasConstant * T); + return retn; + } + + /** + * Return Pitzer's definition of A_J. This is basically the + * temperature derivative of A_L, and the second derivative + * of Aphi + * It's the DH parameter used in heat capacity calculations + * + * A_J = 2 A_L/T + 4 * R * T * T * d2(A_phi)/dT2 + * + * Units = sqrt(kg/gmol) (R) + * + * where + * ADebye_L = 4 R T**2 d(Aphi) / dT + * + * where Aphi = A_Debye/3 + * + * units -> J / (kmolK) * sqrt( kg/gmol) + * + */ + 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; + double retn = 2.0 * A_L / T + 4.0 * GasConstant * T * T *d2Aphi; + return retn; + } + + /** + * d2A_DebyedT2_TP() (virtual) + * + * Returns the 2nd derivative of the A_Debye parameter with + * respect to temperature as a function of temperature + * and pressure. + * + * The default is to assume that it is equal to zero + * -> note, placeholder until a better formalism is + * put in place. + * + * units = A_Debye has units of sqrt(gmol kg-1). + * Temp has units of Kelvin. + */ + 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: + printf("shouldn't be here\n"); + exit(-1); + } + return d2AdT2; + } + + /* + * ----------- Critical State Properties -------------------------- + */ + + /* + * ---------- Other Property Functions + */ + double HMWSoln::AionicRadius(int k) const { + return m_Aionic[k]; + } + + /* + * ------------ Private and Restricted Functions ------------------ + */ + + /** + * Bail out of functions with an error exit if they are not + * implemented. + */ + doublereal HMWSoln::err(string msg) const { + throw CanteraError("HMWSoln", + "Unfinished func called: " + msg ); + return 0.0; + } + + + /** + * initLengths(): + * + * This internal function adjusts the lengths of arrays based on + * the number of species. This is done before these arrays are + * populated with parameter values. + */ + void HMWSoln::initLengths() { + m_kk = nSpecies(); + MolalityVPSSTP::initThermo(); + + /* + * Resize lengths equal to the number of species in + * the phase. + */ + int leng = m_kk; + m_electrolyteSpeciesType.resize(m_kk, cEST_polarNeutral); + m_speciesSize.resize(leng); + m_Aionic.resize(leng, 0.0); + + m_expg0_RT.resize(leng, 0.0); + m_pe.resize(leng, 0.0); + m_pp.resize(leng, 0.0); + m_tmpV.resize(leng, 0.0); + + + int maxCounterIJlen = 1 + (leng-1) * (leng-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_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, 0.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_Psi_ijk.resize(m_kk*m_kk*m_kk, 0.0); + m_Psi_ijk_L.resize(m_kk*m_kk*m_kk, 0.0); + m_Psi_ijk_LL.resize(m_kk*m_kk*m_kk, 0.0); + m_Psi_ijk_P.resize(m_kk*m_kk*m_kk, 0.0); + + m_Lambda_ij.resize(leng, leng, 0.0); + m_Lambda_ij_L.resize(leng, leng, 0.0); + m_Lambda_ij_LL.resize(leng, leng, 0.0); + m_Lambda_ij_P.resize(leng, leng, 0.0); + + m_lnActCoeffMolal.resize(leng, 0.0); + m_dlnActCoeffMolaldT.resize(leng, 0.0); + m_d2lnActCoeffMolaldT2.resize(leng, 0.0); + m_dlnActCoeffMolaldP.resize(leng, 0.0); + + m_CounterIJ.resize(m_kk*m_kk, 0); + + m_gfunc_IJ.resize(maxCounterIJlen, 0.0); + m_hfunc_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.resize(leng, 0.0); + + counterIJ_setup(); + } + + + + /** + * Calcuate the natural log of the molality-based + * activity coefficients. + * + */ + void HMWSoln::s_update_lnMolalityActCoeff() const { + + /* + * Calculate the molalities. Currently, the molalities + * may not be current with respect to the contents of the + * State objects' data. + */ + calcMolalities(); + /* + * Calculate the stoichiometric ionic charge. This isn't used in the + * Pitzer formulation. + */ + m_IionicMolalityStoich = 0.0; + for (int k = 0; k < m_kk; k++) { + double z_k = m_speciesCharge[k]; + double zs_k1 = m_speciesCharge_Stoich[k]; + if (z_k == zs_k1) { + m_IionicMolalityStoich += m_molalities[k] * z_k * z_k; + } else { + double zs_k2 = z_k - zs_k1; + m_IionicMolalityStoich + += m_molalities[k] * (zs_k1 * zs_k1 + zs_k2 * zs_k2); + } + } + m_IionicMolalityStoich /= 2.0; + if (m_IionicMolalityStoich > m_maxIionicStrength) { + m_IionicMolalityStoich = m_maxIionicStrength; + } + + /* + * Update the temperature dependence of the pitzer coefficients + * and their derivatives + */ + s_updatePitzerCoeffWRTemp(); + + /* + * Now do the main calculation. + */ + s_updatePitzerSublnMolalityActCoeff(); + } + + /* + * Set up a counter variable for keeping track of symmetric binary + * interactactions amongst the solute species. + * + * n = m_kk*i + j + * m_Counter[n] = counter + */ + void HMWSoln::counterIJ_setup(void) const { + int n, nc, i, j; + m_CounterIJ.resize(m_kk * m_kk); + int counter = 0; + for (i = 0; i < m_kk; i++) { + n = i; + nc = m_kk * i; + m_CounterIJ[n] = 0; + m_CounterIJ[nc] = 0; + } + for (i = 1; i < (m_kk - 1); i++) { + n = m_kk * i + i; + m_CounterIJ[n] = 0; + for (j = (i+1); j < m_kk; j++) { + n = m_kk * j + i; + nc = m_kk * i + j; + counter++; + m_CounterIJ[n] = counter; + m_CounterIJ[nc] = counter; + } + } + } + + /** + * Calculates the Pitzer coefficients' dependence on the + * temperature. It will also calculate the temperature + * derivatives of the coefficients, as they are important + * in the calculation of the latent heats and the + * heat capacities of the mixtures. + * + * @param doDerivs If >= 1, then the routine will calculate + * the first derivative. If >= 2, the + * routine will calculate the first and second + * temperature derivative. + * default = 2 + */ + void HMWSoln::s_updatePitzerCoeffWRTemp(int doDerivs) const { + + int i, j, n, counterIJ; + const double *beta0MX_coeff; + const double *beta1MX_coeff; + const double *CphiMX_coeff; + double T = temperature(); + double Tr = m_TempPitzerRef; + double tinv = 0.0, tln = 0.0, tlin = 0.0, tquad = 0.0; + if (m_formPitzerTemp == PITZER_TEMP_LINEAR) { + tlin = T - Tr; + } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) { + tlin = T - Tr; + tquad = T * T - Tr * Tr; + tln = log(T/ Tr); + tinv = 1.0/T - 1.0/Tr; + } + + for (i = 1; i < (m_kk - 1); i++) { + for (j = (i+1); j < m_kk; j++) { + + + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + beta0MX_coeff = m_Beta0MX_ij_coeff.ptrColumn(counterIJ); + beta1MX_coeff = m_Beta1MX_ij_coeff.ptrColumn(counterIJ); + CphiMX_coeff = m_CphiMX_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_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; + 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_CphiMX_ij[counterIJ] = CphiMX_coeff[0] + + CphiMX_coeff[1]*tlin + + CphiMX_coeff[2]*tquad + + CphiMX_coeff[3]*tinv + + CphiMX_coeff[4]*tln; + + m_Beta0MX_ij_L[counterIJ] = beta0MX_coeff[1] + + beta0MX_coeff[2]*2.0*T + - beta0MX_coeff[3]/(T*T) + + beta0MX_coeff[4]/T; + + m_Beta1MX_ij_L[counterIJ] = beta1MX_coeff[1] + + beta1MX_coeff[2]*2.0*T + - beta1MX_coeff[3]/(T*T) + + beta1MX_coeff[4]/T; + + + m_CphiMX_ij_L[counterIJ] = CphiMX_coeff[1] + + CphiMX_coeff[2]*2.0*T + - CphiMX_coeff[3]/(T*T) + + CphiMX_coeff[4]/T; + + doDerivs = 2; + if (doDerivs > 1) { + m_Beta0MX_ij_LL[counterIJ] = + + beta0MX_coeff[2]*2.0 + + 2.0*beta0MX_coeff[3]/(T*T*T) + - beta0MX_coeff[4]/(T*T); + + m_Beta1MX_ij_LL[counterIJ] = + + beta1MX_coeff[2]*2.0 + + 2.0*beta1MX_coeff[3]/(T*T*T) + - beta1MX_coeff[4]/(T*T); + + m_CphiMX_ij_LL[counterIJ] = + + CphiMX_coeff[2]*2.0 + + 2.0*CphiMX_coeff[3]/(T*T*T) + - CphiMX_coeff[4]/(T*T); + } + +#ifdef DEBUG_HKM + /* + * Turn terms off for debugging + */ + //m_Beta0MX_ij_L[counterIJ] = 0; + //m_Beta0MX_ij_LL[counterIJ] = 0; + //m_Beta1MX_ij_L[counterIJ] = 0; + //m_Beta1MX_ij_LL[counterIJ] = 0; + //m_CphiMX_ij_L[counterIJ] = 0; + //m_CphiMX_ij_LL[counterIJ] = 0; +#endif + break; + } + + + + } + } + + } + /** + * Calculate the Pitzer portion of the activity coefficients. + * + * This is the main routine in the whole module. It calculates the + * molality based activity coefficients for the solutes, and + * the activity of water. + */ + void HMWSoln:: + s_updatePitzerSublnMolalityActCoeff() const { + + /* + * HKM -> Assumption is made that the solvent is + * species 0. + */ + if (m_indexSolvent != 0) { + printf("Wrong index solvent value!\n"); + exit(-1); + } + +#ifdef DEBUG_HKM + int printE = 0; + if (temperature() == 323.15) { + printE = 0; + } +#endif + double wateract; + string sni, snj, snk; + + /* + * This is the molality of the species in solution. + */ + const double *molality = DATA_PTR(m_molalities); + /* + * These are the charges of the species accessed from Constituents.h + */ + const double *charge = DATA_PTR(m_speciesCharge); + + /* + * These are data inputs about the Pitzer correlation. They come + * from the input file for the Pitzer model. + */ + const double *beta0MX = DATA_PTR(m_Beta0MX_ij); + const double *beta1MX = DATA_PTR(m_Beta1MX_ij); + const double *beta2MX = DATA_PTR(m_Beta2MX_ij); + const double *CphiMX = DATA_PTR(m_CphiMX_ij); + const double *thetaij = DATA_PTR(m_Theta_ij); + const double *alphaMX = DATA_PTR(m_Alpha1MX_ij); + + const double *psi_ijk = DATA_PTR(m_Psi_ijk); + //n = k + j * m_kk + i * m_kk * m_kk; + + + double *gamma = DATA_PTR(m_gamma); + /* + * 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 *g = DATA_PTR(m_gfunc_IJ); + double *hfunc = DATA_PTR(m_hfunc_IJ); + double *BMX = DATA_PTR(m_BMX_IJ); + double *BprimeMX = DATA_PTR(m_BprimeMX_IJ); + double *BphiMX = DATA_PTR(m_BphiMX_IJ); + double *Phi = DATA_PTR(m_Phi_IJ); + double *Phiprime = DATA_PTR(m_Phiprime_IJ); + double *Phiphi = DATA_PTR(m_PhiPhi_IJ); + double *CMX = DATA_PTR(m_CMX_IJ); + + + double x, g12rooti, gprime12rooti; + double Aphi, F, zsqF; + double sum1, sum2, sum3, sum4, sum5, term1; + double sum_m_phi_minus_1, osmotic_coef, lnwateract; + + int z1, z2; + int n, i, j, k, m, counterIJ, counterIJ2; + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("\n Debugging information from hmw_act \n"); + } +#endif + /* + * Make sure the counter variables are setup + */ + counterIJ_setup(); + + /* + * ---------- Calculate common sums over solutes --------------------- + */ + for (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; + if (Is > m_maxIionicStrength) { + Is = m_maxIionicStrength; + } + /* + * Store the ionic molality in the object for reference. + */ + m_IionicMolality = Is; + sqrtIs = sqrt(Is); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 1: \n"); + printf(" ionic strenth = %14.7le \n total molar " + "charge = %14.7le \n", Is, molarcharge); + } +#endif + + /* + * 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 + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 2: \n"); + } +#endif + for (z1 = 1; z1 <=4; z1++) { + for (z2 =1; z2 <=4; z2++) { + calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" z1=%3d z2=%3d E-theta(I) = %f, E-thetaprime(I) = %f\n", + z1, z2, etheta[z1][z2], etheta_prime[z1][z2]); + } +#endif + } + } + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 3: \n"); + printf(" Species Species g(x) " + " hfunc(x) \n"); + } +#endif + + /* + * + * 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 (i = 1; i < (m_kk - 1); i++) { + for (j = (i+1); j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + /* + * Only loop over oppositely charge species + */ + if (charge[i]*charge[j] < 0) { + /* + * x is a reduced function variable + */ + x = sqrtIs * alphaMX[counterIJ]; + if (x > 1.0E-100) { + g[counterIJ] = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + hfunc[counterIJ] = -2.0* + (1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %9.5f %9.5f \n", sni.c_str(), snj.c_str(), + g[counterIJ], hfunc[counterIJ]); + } +#endif + } + } + + /* + * --------- SUBSECTION TO CALCULATE BMX, BprimeMX, BphiMX ---------- + * --------- Agrees with Pitzer, Eq. (49), (51), (55) + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 4: \n"); + printf(" Species Species BMX " + "BprimeMX BphiMX \n"); + } +#endif + x = 12.0 * sqrtIs; + if (x > 1.0E-100) { + g12rooti = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + gprime12rooti = -2.0*(1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } else { + g12rooti = 0.0; + gprime12rooti = 0.0; + } + + for (i = 1; i < m_kk - 1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + +#ifdef DEBUG_HKM + if (printE) { + if (counterIJ == 2) { + printf("%s %s\n", speciesName(i).c_str(), + speciesName(j).c_str()); + printf("beta0MX[%d] = %g\n", counterIJ, beta0MX[counterIJ]); + printf("beta1MX[%d] = %g\n", counterIJ, beta1MX[counterIJ]); + } + } +#endif + /* + * both species have a non-zero charge, and one is positive + * and the other is negative + */ + if (charge[i]*charge[j] < 0.0) { + BMX[counterIJ] = beta0MX[counterIJ] + + beta1MX[counterIJ] * g[counterIJ] + + beta2MX[counterIJ] * g12rooti; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("%d %g: %g %g %g\n", + counterIJ, BMX[counterIJ], beta0MX[counterIJ], + beta1MX[counterIJ], g[counterIJ]); + } +#endif + if (Is > 1.0E-150) { + BprimeMX[counterIJ] = (beta1MX[counterIJ] * hfunc[counterIJ]/Is + + beta2MX[counterIJ] * gprime12rooti/Is); + } else { + BprimeMX[counterIJ] = 0.0; + } + BphiMX[counterIJ] = BMX[counterIJ] + Is*BprimeMX[counterIJ]; + } + else { + BMX[counterIJ] = 0.0; + BprimeMX[counterIJ] = 0.0; + BphiMX[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f %11.7f %11.7f \n", + sni.c_str(), snj.c_str(), + BMX[counterIJ], BprimeMX[counterIJ], BphiMX[counterIJ] ); + } +#endif + } + } + + /* + * --------- SUBSECTION TO CALCULATE CMX ---------- + * --------- Agrees with Pitzer, Eq. (53). + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 5: \n"); + printf(" Species Species CMX \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + CMX[counterIJ] = CphiMX[counterIJ]/ + (2.0* sqrt(fabs(charge[i]*charge[j]))); + } + else { + CMX[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (printE) { + if (counterIJ == 2) { + printf("%s %s\n", speciesName(i).c_str(), + speciesName(j).c_str()); + printf("CphiMX[%d] = %g\n", counterIJ, CphiMX[counterIJ]); + } + } +#endif +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f \n", sni.c_str(), snj.c_str(), + CMX[counterIJ]); + } +#endif + } + } + + /* + * ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ---------- + * --------- Agrees with Pitzer, Eq. 72, 73, 74 + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 6: \n"); + printf(" Species Species Phi_ij " + " Phiprime_ij Phi^phi_ij \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + z1 = (int) fabs(charge[i]); + z2 = (int) fabs(charge[j]); + Phi[counterIJ] = thetaij[counterIJ] + etheta[z1][z2]; + Phiprime[counterIJ] = etheta_prime[z1][z2]; + Phiphi[counterIJ] = Phi[counterIJ] + Is * Phiprime[counterIJ]; + } + else { + Phi[counterIJ] = 0.0; + Phiprime[counterIJ] = 0.0; + Phiphi[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %10.6f %10.6f %10.6f \n", + sni.c_str(), snj.c_str(), + Phi[counterIJ], Phiprime[counterIJ], Phiphi[counterIJ] ); + } +#endif + } + } + + /* + * ------------- SUBSECTION FOR CALCULATION OF F ---------------------- + * ------------ Agrees with Pitzer Eqn. (65) -------------------------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 7: \n"); + } +#endif + // A_Debye_Huckel = 0.5092; (units = sqrt(kg/gmol)) + // A_Debye_Huckel = 0.5107; <- This value is used to match GWB data + // ( A * ln(10) = 1.17593) + // Aphi = A_Debye_Huckel * 2.30258509 / 3.0; + Aphi = m_A_Debye / 3.0; + F = -Aphi * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); +#ifdef DEBUG_HKM + if (printE) { + printf("Aphi = %20.13g\n", Aphi); + } +#endif +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" initial value of F = %10.6f \n", F ); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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 = F + molality[i]*molality[j] * BprimeMX[counterIJ]; + } + /* + * Both species have a non-zero charge, and they + * have the same sign + */ + if (charge[i]*charge[j] > 0) { + F = F + molality[i]*molality[j] * Phiprime[counterIJ]; + } +#ifdef DEBUG_HKM + if (m_debugCalc) printf(" F = %10.6f \n", F ); +#endif + } + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 8: \n"); + } +#endif + + for (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 ) { + // species i is the cation (positive) to calc the actcoeff + zsqF = charge[i]*charge[i]*F; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + if (charge[j] < 0.0) { + // sum over all anions + sum1 = sum1 + molality[j]* + (2.0*BMX[counterIJ]+molarcharge*CMX[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 (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 = sum3 + molality[j]*molality[k]*psi_ijk[n]; + } + } + } + } + + + if (charge[j] > 0.0) { + // sum over all cations + if (j != i) sum2 = sum2 + molality[j]*(2.0*Phi[counterIJ]); + for (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 = sum2 + molality[j]*molality[k]*psi_ijk[n]; + /* + * Find the counterIJ for the j,k interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + (fabs(charge[i])* + molality[j]*molality[k]*CMX[counterIJ2]); + } + } + } + + /* + * Handle neutral j species + */ + if (charge[j] == 0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij(j,i); + } + } + /* + * Add all of the contributions up to yield the log of the + * solute activity coefficients (molality scale) + */ + m_lnActCoeffMolal[i] = zsqF + sum1 + sum2 + sum3 + sum4 + sum5; + gamma[i] = exp(m_lnActCoeffMolal[i]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f gamma[i]=%10.6f \n", + sni.c_str(), m_lnActCoeffMolal[i], gamma[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqF, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + + /* + * -------- 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) + zsqF = charge[i]*charge[i]*F; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + /* + * For Anions, do the cation interactions. + */ + if (charge[j] > 0) { + sum1 = sum1 + molality[j]* + (2.0*BMX[counterIJ]+molarcharge*CMX[counterIJ]); + if (j < m_kk-1) { + for (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 = sum3 + molality[j]*molality[k]*psi_ijk[n]; + } + } + } + } + + /* + * For Anions, do the other anion interactions. + */ + if (charge[j] < 0.0) { + // sum over all anions + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk[n]; + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + + (fabs(charge[i])* + molality[j]*molality[k]*CMX[counterIJ2]); + } + } + } + + /* + * for Anions, do the neutral species interaction + */ + if (charge[j] == 0.0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij(j,i); + } + } + m_lnActCoeffMolal[i] = zsqF + sum1 + sum2 + sum3 + sum4 + sum5; + gamma[i] = exp(m_lnActCoeffMolal[i]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f gamma[i]=%10.6f\n", + sni.c_str(), m_lnActCoeffMolal[i], gamma[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqF, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + /* + * ------ SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF ------- + * ------ -> equations agree with my notes, + * -> Equations agree with Pitzer, + */ + if (charge[i] == 0.0 ) { + sum1 = 0.0; + for (j = 1; j < m_kk; j++) { + sum1 = sum1 + molality[j]*2.0*m_Lambda_ij(i,j); + } + m_lnActCoeffMolal[i] = sum1; + gamma[i] = exp(m_lnActCoeffMolal[i]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f gamma[i]=%10.6f \n", + sni.c_str(), m_lnActCoeffMolal[i], gamma[i]); + } +#endif + } + + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 9: \n"); + } +#endif + /* + * -------- SUBSECTION FOR CALCULATING THE OSMOTIC COEFF --------- + * -------- -> equations agree with my notes, Eqn. (117). + * -> Equations agree with Pitzer, eqn.(62) + */ + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + double sum6 = 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)) + */ + term1 = -Aphi * pow(Is,1.5) / (1.0 + 1.2 * sqrt(Is)); + + for (j = 1; j < m_kk; j++) { + /* + * Loop Over Cations + */ + if (charge[j] > 0.0) { + for (k = 1; k < m_kk; k++){ + if (charge[k] < 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum1 = sum1 + molality[j]*molality[k]* + (BphiMX[counterIJ] + molarcharge*CMX[counterIJ]); + } + } + + for (k = j+1; k < m_kk; k++) { + if (j == (m_kk-1)) { + // we should never reach this step + printf("logic error 1 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] > 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between 2 cations. + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + sum2 = sum2 + molality[j]*molality[k]*Phiphi[counterIJ]; + for (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 = sum2 + + molality[j]*molality[k]*molality[m]*psi_ijk[n]; + } + } + } + } + } + + /* + * Loop Over Anions + */ + if (charge[j] < 0) { + for (k = j+1; k < m_kk; k++) { + if (j == m_kk-1) { + // we should never reach this step + printf("logic error 2 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] < 0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between two anions + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum3 = sum3 + molality[j]*molality[k]*Phiphi[counterIJ]; + for (m = 1; m < m_kk; m++) { + if (charge[m] > 0.0) { + n = m + k * m_kk + j * m_kk * m_kk; + sum3 = sum3 + + molality[j]*molality[k]*molality[m]*psi_ijk[n]; + } + } + } + } + } + + /* + * Loop Over Neutral Species + */ + if (charge[j] == 0) { + for (k = 1; k < m_kk; k++) { + if (charge[k] < 0.0) { + sum4 = sum4 + molality[j]*molality[k]*m_Lambda_ij(j,k); + } + if (charge[k] > 0.0) { + sum5 = sum5 + molality[j]*molality[k]*m_Lambda_ij(j,k); + } + if (charge[k] == 0.0) { + if (k > j) { + sum6 = sum6 + molality[j]*molality[k]*m_Lambda_ij(j,k); + } else if (k == j) { + sum6 = sum6 + 0.5 * molality[j]*molality[k]*m_Lambda_ij(j,k); + } + } + } + } + } + sum_m_phi_minus_1 = 2.0 * + (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6); + /* + * Calculate the osmotic coefficient from + * osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i) + */ + if (molalitysum > 1.0E-150) { + osmotic_coef = 1.0 + (sum_m_phi_minus_1 / molalitysum); + } else { + osmotic_coef = 1.0; + } +#ifdef DEBUG_HKM + if (printE) { + + printf("OsmCoef - 1 = %20.13g\n", osmotic_coef - 1.0); + } +#endif +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" term1=%10.6f sum1=%10.6f sum2=%10.6f " + "sum3=%10.6f sum4=%10.6f sum5=%10.6f\n", + term1, sum1, sum2, sum3, sum4, sum5); + printf(" sum_m_phi_minus_1=%10.6f osmotic_coef=%10.6f\n", + sum_m_phi_minus_1, osmotic_coef); + } + + if (m_debugCalc) { + printf(" Step 10: \n"); + } +#endif + lnwateract = -(m_weightSolvent/1000.0) * molalitysum * osmotic_coef; + wateract = exp(lnwateract); + + /* + * 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(m_indexSolvent); + m_lnActCoeffMolal[0] = lnwateract - log(xmolSolvent); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Weight of Solvent = %16.7g\n", m_weightSolvent); + printf(" molalitySum = %16.7g\n", molalitysum); + printf(" ln_a_water=%10.6f a_water=%10.6f\n\n", + lnwateract, wateract); + } +#endif + } + + /** + * s_update_dlnMolalityActCoeff_dT() (private, const ) + * + * Using internally stored values, this function calculates + * the temperature derivative of the logarithm of the + * activity coefficient for all species in the mechanism. + * + * We assume that the activity coefficients are current. + * + * solvent activity coefficient is on the molality + * scale. It's derivative is too. + */ + void HMWSoln::s_update_dlnMolalityActCoeff_dT() const { + + for (int k = 0; k < m_kk; k++) { + m_dlnActCoeffMolaldT[k] = 0.0; + } + s_Pitzer_dlnMolalityActCoeff_dT(); + } + + /*************************************************************************************/ + + /** + * Calculate the Pitzer portion of the temperature + * derivative of the log activity coefficients. + * This is an internal routine. + * + * 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. + * + */ + void HMWSoln::s_Pitzer_dlnMolalityActCoeff_dT() const { + + /* + * HKM -> Assumption is made that the solvent is + * species 0. + */ +#ifdef DEBUG_HKM + m_debugCalc = 0; +#endif + if (m_indexSolvent != 0) { + printf("Wrong index solvent value!\n"); + exit(-1); + } + + double d_wateract_dT; + string sni, snj, snk; + + const double *molality = DATA_PTR(m_molalities); + const double *charge = DATA_PTR(m_speciesCharge); + const double *beta0MX_L = DATA_PTR(m_Beta0MX_ij_L); + const double *beta1MX_L = DATA_PTR(m_Beta1MX_ij_L); + const double *beta2MX_L = DATA_PTR(m_Beta2MX_ij_L); + const double *CphiMX_L = DATA_PTR(m_CphiMX_ij_L); + const double *thetaij_L = DATA_PTR(m_Theta_ij_L); + const double *alphaMX = DATA_PTR(m_Alpha1MX_ij); + const double *psi_ijk_L = DATA_PTR(m_Psi_ijk_L); + double *gamma = DATA_PTR(m_gamma); + /* + * 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 *g = DATA_PTR(m_gfunc_IJ); + double *hfunc = DATA_PTR(m_hfunc_IJ); + double *BMX_L = DATA_PTR(m_BMX_IJ_L); + double *BprimeMX_L= DATA_PTR(m_BprimeMX_IJ_L); + double *BphiMX_L = DATA_PTR(m_BphiMX_IJ_L); + double *Phi_L = DATA_PTR(m_Phi_IJ_L); + double *Phiprime = DATA_PTR(m_Phiprime_IJ); + double *Phiphi_L = DATA_PTR(m_PhiPhi_IJ_L); + double *CMX_L = DATA_PTR(m_CMX_IJ_L); + + double x, g12rooti, gprime12rooti; + double Aphi, dFdT, zsqdFdT; + double sum1, sum2, sum3, sum4, sum5, term1; + double sum_m_phi_minus_1, d_osmotic_coef_dT, d_lnwateract_dT; + + int z1, z2; + int n, i, j, k, m, counterIJ, counterIJ2; + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("\n Debugging information from " + "s_Pitzer_dlnMolalityActCoeff_dT()\n"); + } +#endif + /* + * Make sure the counter variables are setup + */ + counterIJ_setup(); + + /* + * ---------- Calculate common sums over solutes --------------------- + */ + for (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; + if (Is > m_maxIionicStrength) { + Is = m_maxIionicStrength; + } + /* + * Store the ionic molality in the object for reference. + */ + m_IionicMolality = Is; + sqrtIs = sqrt(Is); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 1: \n"); + printf(" ionic strenth = %14.7le \n total molar " + "charge = %14.7le \n", Is, molarcharge); + } +#endif + + /* + * 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 + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 2: \n"); + } +#endif + for (z1 = 1; z1 <=4; z1++) { + for (z2 =1; z2 <=4; z2++) { + calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" z1=%3d z2=%3d E-theta(I) = %f, E-thetaprime(I) = %f\n", + z1, z2, etheta[z1][z2], etheta_prime[z1][z2]); + } +#endif + } + } + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 3: \n"); + printf(" Species Species g(x) " + " hfunc(x) \n"); + } +#endif + + /* + * + * 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 (i = 1; i < (m_kk - 1); i++) { + for (j = (i+1); j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + /* + * Only loop over oppositely charge species + */ + if (charge[i]*charge[j] < 0) { + /* + * x is a reduced function variable + */ + x = sqrtIs * alphaMX[counterIJ]; + if (x > 1.0E-100) { + g[counterIJ] = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + hfunc[counterIJ] = -2.0* + (1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %9.5f %9.5f \n", sni.c_str(), snj.c_str(), + g[counterIJ], hfunc[counterIJ]); + } +#endif + } + } + + /* + * ------- SUBSECTION TO CALCULATE BMX_L, BprimeMX_L, BphiMX_L ---------- + * ------- These are now temperature derivatives of the + * previously calculated quantities. + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 4: \n"); + printf(" Species Species BMX " + "BprimeMX BphiMX \n"); + } +#endif + x = 12.0 * sqrtIs; + if (x > 1.0E-100) { + g12rooti = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + gprime12rooti = -2.0*(1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } else { + g12rooti = 0.0; + gprime12rooti = 0.0; + } + + for (i = 1; i < m_kk - 1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + BMX_L[counterIJ] = beta0MX_L[counterIJ] + + beta1MX_L[counterIJ] * g[counterIJ] + + beta2MX_L[counterIJ] * g12rooti; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("%d %g: %g %g %g\n", + counterIJ, BMX_L[counterIJ], beta0MX_L[counterIJ], + beta1MX_L[counterIJ], g[counterIJ]); + } +#endif + if (Is > 1.0E-150) { + BprimeMX_L[counterIJ] = (beta1MX_L[counterIJ] * hfunc[counterIJ]/Is + + beta2MX_L[counterIJ] * gprime12rooti/Is); + } else { + BprimeMX_L[counterIJ] = 0.0; + } + BphiMX_L[counterIJ] = BMX_L[counterIJ] + Is*BprimeMX_L[counterIJ]; + } + else { + BMX_L[counterIJ] = 0.0; + BprimeMX_L[counterIJ] = 0.0; + BphiMX_L[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f %11.7f %11.7f \n", + sni.c_str(), snj.c_str(), + BMX_L[counterIJ], BprimeMX_L[counterIJ], BphiMX_L[counterIJ]); + } +#endif + } + } + + /* + * --------- SUBSECTION TO CALCULATE CMX_L ---------- + * --------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 5: \n"); + printf(" Species Species CMX \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + CMX_L[counterIJ] = CphiMX_L[counterIJ]/ + (2.0* sqrt(fabs(charge[i]*charge[j]))); + } + else { + CMX_L[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f \n", sni.c_str(), snj.c_str(), + CMX_L[counterIJ]); + } +#endif + } + } + + /* + * ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ---------- + * -------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 6: \n"); + printf(" Species Species Phi_ij " + " Phiprime_ij Phi^phi_ij \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + z1 = (int) fabs(charge[i]); + z2 = (int) fabs(charge[j]); + //Phi[counterIJ] = thetaij_L[counterIJ] + etheta[z1][z2]; + Phi_L[counterIJ] = thetaij_L[counterIJ]; + //Phiprime[counterIJ] = etheta_prime[z1][z2]; + Phiprime[counterIJ] = 0.0; + Phiphi_L[counterIJ] = Phi_L[counterIJ] + Is * Phiprime[counterIJ]; + } + else { + Phi_L[counterIJ] = 0.0; + Phiprime[counterIJ] = 0.0; + Phiphi_L[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %10.6f %10.6f %10.6f \n", + sni.c_str(), snj.c_str(), + Phi_L[counterIJ], Phiprime[counterIJ], Phiphi_L[counterIJ] ); + } +#endif + } + } + + /* + * ----------- SUBSECTION FOR CALCULATION OF dFdT --------------------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 7: \n"); + } +#endif + // A_Debye_Huckel = 0.5092; (units = sqrt(kg/gmol)) + // A_Debye_Huckel = 0.5107; <- This value is used to match GWB data + // ( A * ln(10) = 1.17593) + // Aphi = A_Debye_Huckel * 2.30258509 / 3.0; + Aphi = m_A_Debye / 3.0; + + double dA_DebyedT = dA_DebyedT_TP(); + double dAphidT = dA_DebyedT /3.0; +#ifdef DEBUG_HKM + //dAphidT = 0.0; +#endif + //F = -Aphi * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + // + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); + //dAphidT = Al / (4.0 * GasConstant * T * T); + dFdT = -dAphidT * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" initial value of dFdT = %10.6f \n", dFdT ); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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 = dFdT + molality[i]*molality[j] * BprimeMX_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 = dFdT + molality[i]*molality[j] * Phiprime[counterIJ]; + } +#ifdef DEBUG_HKM + if (m_debugCalc) printf(" dFdT = %10.6f \n", dFdT); +#endif + } + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 8: \n"); + } +#endif + + for (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 + zsqdFdT = charge[i]*charge[i]*dFdT; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + if (charge[j] < 0.0) { + // sum over all anions + sum1 = sum1 + molality[j]* + (2.0*BMX_L[counterIJ] + molarcharge*CMX_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 (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 = sum3 + molality[j]*molality[k]*psi_ijk_L[n]; + } + } + } + } + + + if (charge[j] > 0.0) { + // sum over all cations + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi_L[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk_L[n]; + /* + * Find the counterIJ for the j,k interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + (fabs(charge[i])* + molality[j]*molality[k]*CMX_L[counterIJ2]); + } + } + } + + /* + * Handle neutral j species + */ + if (charge[j] == 0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij_L(j,i); + } + } + /* + * Add all of the contributions up to yield the log of the + * solute activity coefficients (molality scale) + */ + m_dlnActCoeffMolaldT[i] = + zsqdFdT + sum1 + sum2 + sum3 + sum4 + sum5; + gamma[i] = exp(m_dlnActCoeffMolaldT[i]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f gamma[i]=%10.6f \n", + sni.c_str(), m_dlnActCoeffMolaldT[i], gamma[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqdFdT, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + + /* + * ------ SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR ANIONS ------ + * + */ + if (charge[i] < 0 ) { + // species i is an anion (negative) + zsqdFdT = charge[i]*charge[i]*dFdT; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + /* + * For Anions, do the cation interactions. + */ + if (charge[j] > 0) { + sum1 = sum1 + molality[j]* + (2.0*BMX_L[counterIJ] + molarcharge*CMX_L[counterIJ]); + if (j < m_kk-1) { + for (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 = sum3 + molality[j]*molality[k]*psi_ijk_L[n]; + } + } + } + } + + /* + * For Anions, do the other anion interactions. + */ + if (charge[j] < 0.0) { + // sum over all anions + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi_L[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk_L[n]; + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + + (fabs(charge[i])* + molality[j]*molality[k]*CMX_L[counterIJ2]); + } + } + } + + /* + * for Anions, do the neutral species interaction + */ + if (charge[j] == 0.0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij_L(j,i); + } + } + m_dlnActCoeffMolaldT[i] = + zsqdFdT + sum1 + sum2 + sum3 + sum4 + sum5; + gamma[i] = exp(m_dlnActCoeffMolaldT[i]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f gamma[i]=%10.6f\n", + sni.c_str(), m_dlnActCoeffMolaldT[i], gamma[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqdFdT, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + /* + * ------ SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF ------- + * ------ -> equations agree with my notes, + * -> Equations agree with Pitzer, + */ + if (charge[i] == 0.0 ) { + sum1 = 0.0; + for (j = 1; j < m_kk; j++) { + sum1 = sum1 + molality[j]*2.0*m_Lambda_ij_L(i,j); + } + m_dlnActCoeffMolaldT[i] = sum1; + gamma[i] = exp(m_dlnActCoeffMolaldT[i]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f gamma[i]=%10.6f \n", + sni.c_str(), m_dlnActCoeffMolaldT[i], gamma[i]); + } +#endif + } + + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 9: \n"); + } +#endif + /* + * ------ SUBSECTION FOR CALCULATING THE d OSMOTIC COEFF dT --------- + * + */ + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + double sum6 = 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)) + */ + term1 = -dAphidT * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is)); + + for (j = 1; j < m_kk; j++) { + /* + * Loop Over Cations + */ + if (charge[j] > 0.0) { + for (k = 1; k < m_kk; k++){ + if (charge[k] < 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum1 = sum1 + molality[j]*molality[k]* + (BphiMX_L[counterIJ] + molarcharge*CMX_L[counterIJ]); + } + } + + for (k = j+1; k < m_kk; k++) { + if (j == (m_kk-1)) { + // we should never reach this step + printf("logic error 1 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] > 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between 2 cations. + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + sum2 = sum2 + molality[j]*molality[k]*Phiphi_L[counterIJ]; + for (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 = sum2 + + molality[j]*molality[k]*molality[m]*psi_ijk_L[n]; + } + } + } + } + } + + /* + * Loop Over Anions + */ + if (charge[j] < 0) { + for (k = j+1; k < m_kk; k++) { + if (j == m_kk-1) { + // we should never reach this step + printf("logic error 2 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] < 0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between two anions + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum3 = sum3 + molality[j]*molality[k]*Phiphi_L[counterIJ]; + for (m = 1; m < m_kk; m++) { + if (charge[m] > 0.0) { + n = m + k * m_kk + j * m_kk * m_kk; + sum3 = sum3 + + molality[j]*molality[k]*molality[m]*psi_ijk_L[n]; + } + } + } + } + } + + /* + * Loop Over Neutral Species + */ + if (charge[j] == 0) { + for (k = 1; k < m_kk; k++) { + if (charge[k] < 0.0) { + sum4 = sum4 + molality[j]*molality[k]*m_Lambda_ij_L(j,k); + } + if (charge[k] > 0.0) { + sum5 = sum5 + molality[j]*molality[k]*m_Lambda_ij_L(j,k); + } + if (charge[k] == 0.0) { + if (k > j) { + sum6 = sum6 + molality[j]*molality[k]*m_Lambda_ij_L(j,k); + } else if (k == j) { + sum6 = sum6 + 0.5 * molality[j]*molality[k]*m_Lambda_ij_L(j,k); + } + } + } + } + } + sum_m_phi_minus_1 = 2.0 * + (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6); + /* + * Calculate the osmotic coefficient from + * osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i) + */ + if (molalitysum > 1.0E-150) { + d_osmotic_coef_dT = 0.0 + (sum_m_phi_minus_1 / molalitysum); + } else { + d_osmotic_coef_dT = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" term1=%10.6f sum1=%10.6f sum2=%10.6f " + "sum3=%10.6f sum4=%10.6f sum5=%10.6f\n", + term1, sum1, sum2, sum3, sum4, sum5); + printf(" sum_m_phi_minus_1=%10.6f d_osmotic_coef_dT =%10.6f\n", + sum_m_phi_minus_1, d_osmotic_coef_dT); + } + + if (m_debugCalc) { + printf(" Step 10: \n"); + } +#endif + d_lnwateract_dT = -(m_weightSolvent/1000.0) * molalitysum * d_osmotic_coef_dT; + d_wateract_dT = exp(d_lnwateract_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). + */ + //double xmolSolvent = moleFraction(m_indexSolvent); + m_dlnActCoeffMolaldT[0] = d_lnwateract_dT; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" d_ln_a_water_dT = %10.6f d_a_water_dT=%10.6f\n\n", + d_lnwateract_dT, d_wateract_dT); + } +#endif + } + + /*************************************************************************************/ + + + /** + * s_update_d2lnMolalityActCoeff_dT2() (private, const ) + * + * Using internally stored values, this function calculates + * the temperature 2nd derivative of the logarithm of the + * activity coefficient for all species in the mechanism. + * This is an internal routine + * + * We assume that the activity coefficients and first temperature + * derivatives of the activity coefficients are current. + * + * It may be assumed that the + * Pitzer activity coefficient and first deriv routine are called immediately + * preceding the calling of this routine. Therefore, some + * quantities do not need to be recalculated in this routine. + * + * solvent activity coefficient is on the molality + * scale. It's derivatives are too. + */ + void HMWSoln::s_update_d2lnMolalityActCoeff_dT2() const { + + /* + * HKM -> Assumption is made that the solvent is + * species 0. + */ +#ifdef DEBUG_HKM + m_debugCalc = 0; +#endif + if (m_indexSolvent != 0) { + printf("Wrong index solvent value!\n"); + exit(-1); + } + + double d2_wateract_dT2; + string sni, snj, snk; + + const double *molality = DATA_PTR(m_molalities); + const double *charge = DATA_PTR(m_speciesCharge); + const double *beta0MX_LL= DATA_PTR(m_Beta0MX_ij_LL); + const double *beta1MX_LL= DATA_PTR(m_Beta1MX_ij_LL); + const double *beta2MX_LL= DATA_PTR(m_Beta2MX_ij_LL); + const double *CphiMX_LL = DATA_PTR(m_CphiMX_ij_LL); + const double *thetaij_LL= DATA_PTR(m_Theta_ij_LL); + const double *alphaMX = DATA_PTR(m_Alpha1MX_ij); + const double *psi_ijk_LL= DATA_PTR(m_Psi_ijk_LL); + + /* + * 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 *g = DATA_PTR(m_gfunc_IJ); + double *hfunc = DATA_PTR(m_hfunc_IJ); + double *BMX_LL = DATA_PTR(m_BMX_IJ_LL); + double *BprimeMX_LL=DATA_PTR(m_BprimeMX_IJ_LL); + double *BphiMX_LL= DATA_PTR(m_BphiMX_IJ_LL); + double *Phi_LL = DATA_PTR(m_Phi_IJ_LL); + double *Phiprime = DATA_PTR(m_Phiprime_IJ); + double *Phiphi_LL= DATA_PTR(m_PhiPhi_IJ_LL); + double *CMX_LL = DATA_PTR(m_CMX_IJ_LL); + + + double x, g12rooti, gprime12rooti; + double d2FdT2, zsqd2FdT2; + double sum1, sum2, sum3, sum4, sum5, term1; + double sum_m_phi_minus_1, d2_osmotic_coef_dT2, d2_lnwateract_dT2; + + int z1, z2; + int n, i, j, k, m, counterIJ, counterIJ2; + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("\n Debugging information from " + "s_Pitzer_d2lnMolalityActCoeff_dT2()\n"); + } +#endif + /* + * Make sure the counter variables are setup + */ + counterIJ_setup(); + + + /* + * ---------- Calculate common sums over solutes --------------------- + */ + for (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; + if (Is > m_maxIionicStrength) { + Is = m_maxIionicStrength; + } + /* + * Store the ionic molality in the object for reference. + */ + m_IionicMolality = Is; + sqrtIs = sqrt(Is); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 1: \n"); + printf(" ionic strenth = %14.7le \n total molar " + "charge = %14.7le \n", Is, molarcharge); + } +#endif + + /* + * 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 + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 2: \n"); + } +#endif + for (z1 = 1; z1 <=4; z1++) { + for (z2 =1; z2 <=4; z2++) { + calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" z1=%3d z2=%3d E-theta(I) = %f, E-thetaprime(I) = %f\n", + z1, z2, etheta[z1][z2], etheta_prime[z1][z2]); + } +#endif + } + } + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 3: \n"); + printf(" Species Species g(x) " + " hfunc(x) \n"); + } +#endif + + /* + * + * 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 (i = 1; i < (m_kk - 1); i++) { + for (j = (i+1); j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + /* + * Only loop over oppositely charge species + */ + if (charge[i]*charge[j] < 0) { + /* + * x is a reduced function variable + */ + x = sqrtIs * alphaMX[counterIJ]; + if (x > 1.0E-100) { + g[counterIJ] = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + hfunc[counterIJ] = -2.0* + (1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %9.5f %9.5f \n", sni.c_str(), snj.c_str(), + g[counterIJ], hfunc[counterIJ]); + } +#endif + } + } + /* + * ------- SUBSECTION TO CALCULATE BMX_L, BprimeMX_LL, BphiMX_L ---------- + * ------- These are now temperature derivatives of the + * previously calculated quantities. + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 4: \n"); + printf(" Species Species BMX " + "BprimeMX BphiMX \n"); + } +#endif + x = 12.0 * sqrtIs; + if (x > 1.0E-100) { + g12rooti = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + gprime12rooti = -2.0*(1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } else { + g12rooti = 0.0; + gprime12rooti = 0.0; + } + + for (i = 1; i < m_kk - 1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + BMX_LL[counterIJ] = beta0MX_LL[counterIJ] + + beta1MX_LL[counterIJ] * g[counterIJ] + + beta2MX_LL[counterIJ] * g12rooti; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("%d %g: %g %g %g\n", + counterIJ, BMX_LL[counterIJ], beta0MX_LL[counterIJ], + beta1MX_LL[counterIJ], g[counterIJ]); + } +#endif + if (Is > 1.0E-150) { + BprimeMX_LL[counterIJ] = (beta1MX_LL[counterIJ] * hfunc[counterIJ]/Is + + beta2MX_LL[counterIJ] * gprime12rooti/Is); + } else { + BprimeMX_LL[counterIJ] = 0.0; + } + BphiMX_LL[counterIJ] = BMX_LL[counterIJ] + Is*BprimeMX_LL[counterIJ]; + } + else { + BMX_LL[counterIJ] = 0.0; + BprimeMX_LL[counterIJ] = 0.0; + BphiMX_LL[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f %11.7f %11.7f \n", + sni.c_str(), snj.c_str(), + BMX_LL[counterIJ], BprimeMX_LL[counterIJ], BphiMX_LL[counterIJ]); + } +#endif + } + } + + /* + * --------- SUBSECTION TO CALCULATE CMX_LL ---------- + * --------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 5: \n"); + printf(" Species Species CMX \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + CMX_LL[counterIJ] = CphiMX_LL[counterIJ]/ + (2.0* sqrt(fabs(charge[i]*charge[j]))); + } else { + CMX_LL[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f \n", sni.c_str(), snj.c_str(), + CMX_LL[counterIJ]); + } +#endif + } + } + + /* + * ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ---------- + * -------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 6: \n"); + printf(" Species Species Phi_ij " + " Phiprime_ij Phi^phi_ij \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + z1 = (int) fabs(charge[i]); + z2 = (int) fabs(charge[j]); + //Phi[counterIJ] = thetaij[counterIJ] + etheta[z1][z2]; + //Phi_L[counterIJ] = thetaij_L[counterIJ]; + Phi_LL[counterIJ] = thetaij_LL[counterIJ]; + //Phiprime[counterIJ] = etheta_prime[z1][z2]; + Phiprime[counterIJ] = 0.0; + //Phiphi[counterIJ] = Phi[counterIJ] + Is * Phiprime[counterIJ]; + //Phiphi_L[counterIJ] = Phi_L[counterIJ] + Is * Phiprime[counterIJ]; + Phiphi_LL[counterIJ] = Phi_LL[counterIJ]; + } + else { + Phi_LL[counterIJ] = 0.0; + Phiprime[counterIJ] = 0.0; + Phiphi_LL[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + //printf(" %-16s %-16s %10.6f %10.6f %10.6f \n", + // sni.c_str(), snj.c_str(), + // Phi_L[counterIJ], Phiprime[counterIJ], Phiphi_L[counterIJ] ); + printf(" %-16s %-16s %10.6f %10.6f %10.6f \n", + sni.c_str(), snj.c_str(), + Phi_LL[counterIJ], Phiprime[counterIJ], Phiphi_LL[counterIJ] ); + } +#endif + } + } + + /* + * ----------- SUBSECTION FOR CALCULATION OF d2FdT2 --------------------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 7: \n"); + } +#endif + // A_Debye_Huckel = 0.5092; (units = sqrt(kg/gmol)) + // A_Debye_Huckel = 0.5107; <- This value is used to match GWB data + // ( A * ln(10) = 1.17593) + // Aphi = A_Debye_Huckel * 2.30258509 / 3.0; + // Aphi = m_A_Debye / 3.0; + + //double dA_DebyedT = dA_DebyedT_TP(); + //double dAphidT = dA_DebyedT /3.0; + double d2AphidT2 = d2A_DebyedT2_TP() / 3.0; +#ifdef DEBUG_HKM + //d2AphidT2 = 0.0; +#endif + //F = -Aphi * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + // + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); + //dAphidT = Al / (4.0 * GasConstant * T * T); + //dFdT = -dAphidT * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + // + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); + d2FdT2 = -d2AphidT2 * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" initial value of d2FdT2 = %10.6f \n", d2FdT2 ); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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 = d2FdT2 + molality[i]*molality[j] * BprimeMX_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 = d2FdT2 + molality[i]*molality[j] * Phiprime[counterIJ]; + } +#ifdef DEBUG_HKM + if (m_debugCalc) printf(" d2FdT2 = %10.6f \n", d2FdT2); +#endif + } + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 8: \n"); + } +#endif + + for (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 + zsqd2FdT2 = charge[i]*charge[i]*d2FdT2; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + if (charge[j] < 0.0) { + // sum over all anions + sum1 = sum1 + molality[j]* + (2.0*BMX_LL[counterIJ] + molarcharge*CMX_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 (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 = sum3 + molality[j]*molality[k]*psi_ijk_LL[n]; + } + } + } + } + + + if (charge[j] > 0.0) { + // sum over all cations + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi_LL[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk_LL[n]; + /* + * Find the counterIJ for the j,k interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + (fabs(charge[i])* + molality[j]*molality[k]*CMX_LL[counterIJ2]); + } + } + } + + /* + * Handle neutral j species + */ + if (charge[j] == 0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij_LL(j,i); + } + } + /* + * Add all of the contributions up to yield the log of the + * solute activity coefficients (molality scale) + */ + m_d2lnActCoeffMolaldT2[i] = + zsqd2FdT2 + sum1 + sum2 + sum3 + sum4 + sum5; +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s d2lngammadT2[i]=%10.6f \n", + sni.c_str(), m_d2lnActCoeffMolaldT2[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqd2FdT2, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + + + /* + * ------ SUBSECTION FOR CALCULATING THE d2ACTCOEFFdT2 FOR ANIONS ------ + * + */ + if (charge[i] < 0 ) { + // species i is an anion (negative) + zsqd2FdT2 = charge[i]*charge[i]*d2FdT2; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + /* + * For Anions, do the cation interactions. + */ + if (charge[j] > 0) { + sum1 = sum1 + molality[j]* + (2.0*BMX_LL[counterIJ] + molarcharge*CMX_LL[counterIJ]); + if (j < m_kk-1) { + for (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 = sum3 + molality[j]*molality[k]*psi_ijk_LL[n]; + } + } + } + } + + /* + * For Anions, do the other anion interactions. + */ + if (charge[j] < 0.0) { + // sum over all anions + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi_LL[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk_LL[n]; + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + + (fabs(charge[i])* + molality[j]*molality[k]*CMX_LL[counterIJ2]); + } + } + } + + /* + * for Anions, do the neutral species interaction + */ + if (charge[j] == 0.0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij_LL(j,i); + } + } + m_d2lnActCoeffMolaldT2[i] = + zsqd2FdT2 + sum1 + sum2 + sum3 + sum4 + sum5; +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s d2lngammadT2[i]=%10.6f\n", + sni.c_str(), m_d2lnActCoeffMolaldT2[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqd2FdT2, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + /* + * ------ SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF ------- + * ------ -> equations agree with my notes, + * -> Equations agree with Pitzer, + */ + if (charge[i] == 0.0 ) { + sum1 = 0.0; + for (j = 1; j < m_kk; j++) { + sum1 = sum1 + molality[j]*2.0*m_Lambda_ij_LL(i,j); + } + m_d2lnActCoeffMolaldT2[i] = sum1; +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s d2lngammadT2[i]=%10.6f \n", + sni.c_str(), m_d2lnActCoeffMolaldT2[i]); + } +#endif + } + + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 9: \n"); + } +#endif + + /* + * ------ SUBSECTION FOR CALCULATING THE d2 OSMOTIC COEFF dT2 --------- + * + */ + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + double sum6 = 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)) + */ + term1 = -d2AphidT2 * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is)); + + for (j = 1; j < m_kk; j++) { + /* + * Loop Over Cations + */ + if (charge[j] > 0.0) { + for (k = 1; k < m_kk; k++){ + if (charge[k] < 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum1 = sum1 + molality[j]*molality[k]* + (BphiMX_LL[counterIJ] + molarcharge*CMX_LL[counterIJ]); + } + } + + for (k = j+1; k < m_kk; k++) { + if (j == (m_kk-1)) { + // we should never reach this step + printf("logic error 1 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] > 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between 2 cations. + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + sum2 = sum2 + molality[j]*molality[k]*Phiphi_LL[counterIJ]; + for (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 = sum2 + + molality[j]*molality[k]*molality[m]*psi_ijk_LL[n]; + } + } + } + } + } + + /* + * Loop Over Anions + */ + if (charge[j] < 0) { + for (k = j+1; k < m_kk; k++) { + if (j == m_kk-1) { + // we should never reach this step + printf("logic error 2 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] < 0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between two anions + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum3 = sum3 + molality[j]*molality[k]*Phiphi_LL[counterIJ]; + for (m = 1; m < m_kk; m++) { + if (charge[m] > 0.0) { + n = m + k * m_kk + j * m_kk * m_kk; + sum3 = sum3 + + molality[j]*molality[k]*molality[m]*psi_ijk_LL[n]; + } + } + } + } + } + + /* + * Loop Over Neutral Species + */ + if (charge[j] == 0) { + for (k = 1; k < m_kk; k++) { + if (charge[k] < 0.0) { + sum4 = sum4 + molality[j]*molality[k]*m_Lambda_ij_LL(j,k); + } + if (charge[k] > 0.0) { + sum5 = sum5 + molality[j]*molality[k]*m_Lambda_ij_LL(j,k); + } + if (charge[k] == 0.0) { + if (k > j) { + sum6 = sum6 + molality[j]*molality[k]*m_Lambda_ij_LL(j,k); + } else if (k == j) { + sum6 = sum6 + 0.5 * molality[j]*molality[k]*m_Lambda_ij_LL(j,k); + } + } + } + } + } + sum_m_phi_minus_1 = 2.0 * + (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6); + /* + * Calculate the osmotic coefficient from + * osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i) + */ + if (molalitysum > 1.0E-150) { + d2_osmotic_coef_dT2 = 0.0 + (sum_m_phi_minus_1 / molalitysum); + } else { + d2_osmotic_coef_dT2 = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" term1=%10.6f sum1=%10.6f sum2=%10.6f " + "sum3=%10.6f sum4=%10.6f sum5=%10.6f\n", + term1, sum1, sum2, sum3, sum4, sum5); + printf(" sum_m_phi_minus_1=%10.6f d2_osmotic_coef_dT2=%10.6f\n", + sum_m_phi_minus_1, d2_osmotic_coef_dT2); + } + + if (m_debugCalc) { + printf(" Step 10: \n"); + } +#endif + 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[0] = d2_lnwateract_dT2; + +#ifdef DEBUG_HKM + if (m_debugCalc) { + d2_wateract_dT2 = exp(d2_lnwateract_dT2); + printf(" d2_ln_a_water_dT2 = %10.6f d2_a_water_dT2=%10.6f\n\n", + d2_lnwateract_dT2, d2_wateract_dT2); + } +#endif + } + + /***********************************************************************************************/ + + /** + * s_Pitzer_dlnMolalityActCoeff_dP() (private, const ) + * + * Using internally stored values, this function calculates + * the pressure derivative of the logarithm of the + * activity coefficient for all species in the mechanism. + * + * We assume that the activity coefficients are current. + * + * solvent activity coefficient is on the molality + * scale. It's derivative is too. + */ + void HMWSoln::s_Pitzer_dlnMolalityActCoeff_dP() const { + + for (int k = 0; k < m_kk; k++) { + m_dlnActCoeffMolaldP[k] = 0.0; + } + s_update_dlnMolalityActCoeff_dP(); + } + + /** + * s_update_dlnMolalityActCoeff_dP() (private, const ) + * + * Using internally stored values, this function calculates + * the pressure derivative of the logarithm of the + * activity coefficient for all species in the mechanism. + * This is an internal routine + * + * We assume that the activity coefficients are current. + * + * It may be assumed that the + * Pitzer activity coefficient and first deriv routine are called immediately + * preceding the calling of this routine. Therefore, some + * quantities do not need to be recalculated in this routine. + * + * solvent activity coefficient is on the molality + * scale. It's derivatives are too. + */ + void HMWSoln::s_update_dlnMolalityActCoeff_dP() const { + + + /* + * HKM -> Assumption is made that the solvent is + * species 0. + */ +#ifdef DEBUG_HKM + m_debugCalc = 0; +#endif + if (m_indexSolvent != 0) { + printf("Wrong index solvent value!\n"); + exit(-1); + } + + double d_wateract_dP; + string sni, snj, snk; + + const double *molality = DATA_PTR(m_molalities); + const double *charge = DATA_PTR(m_speciesCharge); + const double *beta0MX_P = DATA_PTR(m_Beta0MX_ij_P); + const double *beta1MX_P = DATA_PTR(m_Beta1MX_ij_P); + const double *beta2MX_P = DATA_PTR(m_Beta2MX_ij_P); + const double *CphiMX_P = DATA_PTR(m_CphiMX_ij_P); + const double *thetaij_P = DATA_PTR(m_Theta_ij_P); + const double *alphaMX = DATA_PTR(m_Alpha1MX_ij); + const double *psi_ijk_P = DATA_PTR(m_Psi_ijk_P); + + /* + * 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 *g = DATA_PTR(m_gfunc_IJ); + double *hfunc = DATA_PTR(m_hfunc_IJ); + double *BMX_P = DATA_PTR(m_BMX_IJ_P); + double *BprimeMX_P= DATA_PTR(m_BprimeMX_IJ_P); + double *BphiMX_P = DATA_PTR(m_BphiMX_IJ_P); + double *Phi_P = DATA_PTR(m_Phi_IJ_P); + double *Phiprime = DATA_PTR(m_Phiprime_IJ); + double *Phiphi_P = DATA_PTR(m_PhiPhi_IJ_P); + double *CMX_P = DATA_PTR(m_CMX_IJ_P); + + double x, g12rooti, gprime12rooti; + double Aphi, dFdP, zsqdFdP; + double sum1, sum2, sum3, sum4, sum5, term1; + double sum_m_phi_minus_1, d_osmotic_coef_dP, d_lnwateract_dP; + + int z1, z2; + int n, i, j, k, m, counterIJ, counterIJ2; + + double currTemp = temperature(); + double currPres = pressure(); + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("\n Debugging information from " + "s_Pitzer_dlnMolalityActCoeff_dP()\n"); + } +#endif + /* + * Make sure the counter variables are setup + */ + counterIJ_setup(); + + /* + * ---------- Calculate common sums over solutes --------------------- + */ + for (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; + if (Is > m_maxIionicStrength) { + Is = m_maxIionicStrength; + } + /* + * Store the ionic molality in the object for reference. + */ + m_IionicMolality = Is; + sqrtIs = sqrt(Is); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 1: \n"); + printf(" ionic strenth = %14.7le \n total molar " + "charge = %14.7le \n", Is, molarcharge); + } +#endif + + /* + * 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 + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 2: \n"); + } +#endif + for (z1 = 1; z1 <=4; z1++) { + for (z2 =1; z2 <=4; z2++) { + calc_thetas(z1, z2, ðeta[z1][z2], ðeta_prime[z1][z2]); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" z1=%3d z2=%3d E-theta(I) = %f, E-thetaprime(I) = %f\n", + z1, z2, etheta[z1][z2], etheta_prime[z1][z2]); + } +#endif + } + } + +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 3: \n"); + printf(" Species Species g(x) " + " hfunc(x) \n"); + } +#endif + + + /* + * + * 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 (i = 1; i < (m_kk - 1); i++) { + for (j = (i+1); j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + /* + * Only loop over oppositely charge species + */ + if (charge[i]*charge[j] < 0) { + /* + * x is a reduced function variable + */ + x = sqrtIs * alphaMX[counterIJ]; + if (x > 1.0E-100) { + g[counterIJ] = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + hfunc[counterIJ] = -2.0* + (1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } + } + else { + g[counterIJ] = 0.0; + hfunc[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %9.5f %9.5f \n", sni.c_str(), snj.c_str(), + g[counterIJ], hfunc[counterIJ]); + } +#endif + } + } + + + /* + * ------- SUBSECTION TO CALCULATE BMX_L, BprimeMX_L, BphiMX_L ---------- + * ------- These are now temperature derivatives of the + * previously calculated quantities. + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 4: \n"); + printf(" Species Species BMX " + "BprimeMX BphiMX \n"); + } +#endif + x = 12.0 * sqrtIs; + if (x > 1.0E-100) { + g12rooti = 2.0*(1.0-(1.0 + x) * exp(-x)) / (x*x); + gprime12rooti = -2.0*(1.0-(1.0 + x + 0.5*x*x) * exp(-x)) / (x*x); + } else { + g12rooti = 0.0; + gprime12rooti = 0.0; + } + + for (i = 1; i < m_kk - 1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + BMX_P[counterIJ] = beta0MX_P[counterIJ] + + beta1MX_P[counterIJ] * g[counterIJ] + + beta2MX_P[counterIJ] * g12rooti; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf("%d %g: %g %g %g\n", + counterIJ, BMX_P[counterIJ], beta0MX_P[counterIJ], + beta1MX_P[counterIJ], g[counterIJ]); + } +#endif + if (Is > 1.0E-150) { + BprimeMX_P[counterIJ] = (beta1MX_P[counterIJ] * hfunc[counterIJ]/Is + + beta2MX_P[counterIJ] * gprime12rooti/Is); + } else { + BprimeMX_P[counterIJ] = 0.0; + } + BphiMX_P[counterIJ] = BMX_P[counterIJ] + Is*BprimeMX_P[counterIJ]; + } + else { + BMX_P[counterIJ] = 0.0; + BprimeMX_P[counterIJ] = 0.0; + BphiMX_P[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f %11.7f %11.7f \n", + sni.c_str(), snj.c_str(), + BMX_P[counterIJ], BprimeMX_P[counterIJ], BphiMX_P[counterIJ]); + } +#endif + } + } + + + /* + * --------- SUBSECTION TO CALCULATE CMX_L ---------- + * --------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 5: \n"); + printf(" Species Species CMX \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + CMX_P[counterIJ] = CphiMX_P[counterIJ]/ + (2.0* sqrt(fabs(charge[i]*charge[j]))); + } + else { + CMX_P[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %11.7f \n", sni.c_str(), snj.c_str(), + CMX_P[counterIJ]); + } +#endif + } + } + + /* + * ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ---------- + * -------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 6: \n"); + printf(" Species Species Phi_ij " + " Phiprime_ij Phi^phi_ij \n"); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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) { + z1 = (int) fabs(charge[i]); + z2 = (int) fabs(charge[j]); + //Phi[counterIJ] = thetaij_L[counterIJ] + etheta[z1][z2]; + Phi_P[counterIJ] = thetaij_P[counterIJ]; + //Phiprime[counterIJ] = etheta_prime[z1][z2]; + Phiprime[counterIJ] = 0.0; + Phiphi_P[counterIJ] = Phi_P[counterIJ] + Is * Phiprime[counterIJ]; + } + else { + Phi_P[counterIJ] = 0.0; + Phiprime[counterIJ] = 0.0; + Phiphi_P[counterIJ] = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + snj = speciesName(j); + printf(" %-16s %-16s %10.6f %10.6f %10.6f \n", + sni.c_str(), snj.c_str(), + Phi_P[counterIJ], Phiprime[counterIJ], Phiphi_P[counterIJ] ); + } +#endif + } + } + + /* + * ----------- SUBSECTION FOR CALCULATION OF dFdT --------------------- + */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 7: \n"); + } +#endif + // A_Debye_Huckel = 0.5092; (units = sqrt(kg/gmol)) + // A_Debye_Huckel = 0.5107; <- This value is used to match GWB data + // ( A * ln(10) = 1.17593) + // Aphi = A_Debye_Huckel * 2.30258509 / 3.0; + Aphi = m_A_Debye / 3.0; + + double dA_DebyedP = dA_DebyedP_TP(currTemp, currPres); + double dAphidP = dA_DebyedP /3.0; +#ifdef DEBUG_HKM + //dAphidT = 0.0; +#endif + //F = -Aphi * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + // + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); + //dAphidT = Al / (4.0 * GasConstant * T * T); + dFdP = -dAphidP * ( sqrt(Is) / (1.0 + 1.2*sqrt(Is)) + + (2.0/1.2) * log(1.0+1.2*(sqrtIs))); +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" initial value of dFdP = %10.6f \n", dFdP ); + } +#endif + for (i = 1; i < m_kk-1; i++) { + for (j = i+1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + 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 = dFdP + molality[i]*molality[j] * BprimeMX_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 = dFdP + molality[i]*molality[j] * Phiprime[counterIJ]; + } +#ifdef DEBUG_HKM + if (m_debugCalc) printf(" dFdP = %10.6f \n", dFdP); +#endif + } + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 8: \n"); + } +#endif + + + for (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 + zsqdFdP = charge[i]*charge[i]*dFdP; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + if (charge[j] < 0.0) { + // sum over all anions + sum1 = sum1 + molality[j]* + (2.0*BMX_P[counterIJ] + molarcharge*CMX_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 (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 = sum3 + molality[j]*molality[k]*psi_ijk_P[n]; + } + } + } + } + + + + if (charge[j] > 0.0) { + // sum over all cations + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi_P[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk_P[n]; + /* + * Find the counterIJ for the j,k interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + (fabs(charge[i])* + molality[j]*molality[k]*CMX_P[counterIJ2]); + } + } + } + + /* + * Handle neutral j species + */ + if (charge[j] == 0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij_L(j,i); + } + } + + /* + * Add all of the contributions up to yield the log of the + * solute activity coefficients (molality scale) + */ + m_dlnActCoeffMolaldP[i] = + zsqdFdP + sum1 + sum2 + sum3 + sum4 + sum5; + +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lngamma[i]=%10.6f \n", + sni.c_str(), m_dlnActCoeffMolaldP[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqdFdP, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + + /* + * ------ SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR ANIONS ------ + * + */ + if (charge[i] < 0 ) { + // species i is an anion (negative) + zsqdFdP = charge[i]*charge[i]*dFdP; + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + for (j = 1; j < m_kk; j++) { + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*i + j; + counterIJ = m_CounterIJ[n]; + + /* + * For Anions, do the cation interactions. + */ + if (charge[j] > 0) { + sum1 = sum1 + molality[j]* + (2.0*BMX_P[counterIJ] + molarcharge*CMX_P[counterIJ]); + if (j < m_kk-1) { + for (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 = sum3 + molality[j]*molality[k]*psi_ijk_P[n]; + } + } + } + } + + /* + * For Anions, do the other anion interactions. + */ + if (charge[j] < 0.0) { + // sum over all anions + if (j != i) { + sum2 = sum2 + molality[j]*(2.0*Phi_P[counterIJ]); + } + for (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 = sum2 + molality[j]*molality[k]*psi_ijk_P[n]; + /* + * Find the counterIJ for the symmetric binary interaction + */ + n = m_kk*j + k; + counterIJ2 = m_CounterIJ[n]; + sum4 = sum4 + + (fabs(charge[i])* + molality[j]*molality[k]*CMX_P[counterIJ2]); + } + } + } + + /* + * for Anions, do the neutral species interaction + */ + if (charge[j] == 0.0) { + sum5 = sum5 + molality[j]*2.0*m_Lambda_ij_L(j,i); + } + } + m_dlnActCoeffMolaldP[i] = + zsqdFdP + sum1 + sum2 + sum3 + sum4 + sum5; +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s lndactcoeffmolaldP[i]=%10.6f \n", + sni.c_str(), m_dlnActCoeffMolaldP[i]); + printf(" %12g %12g %12g %12g %12g %12g\n", + zsqdFdP, sum1, sum2, sum3, sum4, sum5); + } +#endif + } + + + /* + * ------ SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF ------- + * ------ -> equations agree with my notes, + * -> Equations agree with Pitzer, + */ + if (charge[i] == 0.0 ) { + sum1 = 0.0; + for (j = 1; j < m_kk; j++) { + sum1 = sum1 + molality[j]*2.0*m_Lambda_ij_L(i,j); + } + m_dlnActCoeffMolaldP[i] = sum1; +#ifdef DEBUG_HKM + if (m_debugCalc) { + sni = speciesName(i); + printf(" %-16s dlnActCoeffMolaldP[i]=%10.6f \n", + sni.c_str(), m_dlnActCoeffMolaldP[i]); + } +#endif + } + + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Step 9: \n"); + } +#endif + + /* + * ------ SUBSECTION FOR CALCULATING THE d OSMOTIC COEFF dT --------- + * + */ + sum1 = 0.0; + sum2 = 0.0; + sum3 = 0.0; + sum4 = 0.0; + sum5 = 0.0; + double sum6 = 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)) + */ + term1 = -dAphidP * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is)); + + for (j = 1; j < m_kk; j++) { + /* + * Loop Over Cations + */ + if (charge[j] > 0.0) { + for (k = 1; k < m_kk; k++){ + if (charge[k] < 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum1 = sum1 + molality[j]*molality[k]* + (BphiMX_P[counterIJ] + molarcharge*CMX_P[counterIJ]); + } + } + + for (k = j+1; k < m_kk; k++) { + if (j == (m_kk-1)) { + // we should never reach this step + printf("logic error 1 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] > 0.0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between 2 cations. + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + sum2 = sum2 + molality[j]*molality[k]*Phiphi_P[counterIJ]; + for (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 = sum2 + + molality[j]*molality[k]*molality[m]*psi_ijk_P[n]; + } + } + } + } + } + + + /* + * Loop Over Anions + */ + if (charge[j] < 0) { + for (k = j+1; k < m_kk; k++) { + if (j == m_kk-1) { + // we should never reach this step + printf("logic error 2 in Step 9 of hmw_act"); + exit(1); + } + if (charge[k] < 0) { + /* + * Find the counterIJ for the symmetric j,k binary interaction + * between two anions + */ + n = m_kk*j + k; + counterIJ = m_CounterIJ[n]; + + sum3 = sum3 + molality[j]*molality[k]*Phiphi_P[counterIJ]; + for (m = 1; m < m_kk; m++) { + if (charge[m] > 0.0) { + n = m + k * m_kk + j * m_kk * m_kk; + sum3 = sum3 + + molality[j]*molality[k]*molality[m]*psi_ijk_P[n]; + } + } + } + } + } + + /* + * Loop Over Neutral Species + */ + if (charge[j] == 0) { + for (k = 1; k < m_kk; k++) { + if (charge[k] < 0.0) { + sum4 = sum4 + molality[j]*molality[k]*m_Lambda_ij_P(j,k); + } + if (charge[k] > 0.0) { + sum5 = sum5 + molality[j]*molality[k]*m_Lambda_ij_P(j,k); + } + if (charge[k] == 0.0) { + if (k > j) { + sum6 = sum6 + molality[j]*molality[k]*m_Lambda_ij_P(j,k); + } else if (k == j) { + sum6 = sum6 + 0.5 * molality[j]*molality[k]*m_Lambda_ij_P(j,k); + } + } + } + } + } + sum_m_phi_minus_1 = 2.0 * + (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6); + + + /* + * Calculate the osmotic coefficient from + * osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i) + */ + if (molalitysum > 1.0E-150) { + d_osmotic_coef_dP = 0.0 + (sum_m_phi_minus_1 / molalitysum); + } else { + d_osmotic_coef_dP = 0.0; + } +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" term1=%10.6f sum1=%10.6f sum2=%10.6f " + "sum3=%10.6f sum4=%10.6f sum5=%10.6f\n", + term1, sum1, sum2, sum3, sum4, sum5); + printf(" sum_m_phi_minus_1=%10.6f d_osmotic_coef_dP =%10.6f\n", + sum_m_phi_minus_1, d_osmotic_coef_dP); + } + + if (m_debugCalc) { + printf(" Step 10: \n"); + } +#endif + d_lnwateract_dP = -(m_weightSolvent/1000.0) * molalitysum * d_osmotic_coef_dP; + d_wateract_dP = exp(d_lnwateract_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). + */ + //double xmolSolvent = moleFraction(m_indexSolvent); + m_dlnActCoeffMolaldP[0] = d_lnwateract_dP; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" d_ln_a_water_dP = %10.6f d_a_water_dP=%10.6f\n\n", + d_lnwateract_dP, d_wateract_dP); + } +#endif + + + + } + + /***********************************************************************************************/ + + /* + * Calculate the lambda interactions. + * + * Calculate E-lambda terms for charge combinations of like sign, + * using method of Pitzer (1975). + * + * This code snipet is included from Bethke, Appendix 2. + */ + void HMWSoln::calc_lambdas(double is) const { + double aphi, dj, jfunc, jprime, t, x, zprod; + int i, ij, j; + /* + * 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; + + aphi = 0.392; /* Value at 25 C */ +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" Is = %g\n", is); + } +#endif + if (is < 1.0E-150) { + for (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 (i=1; i<=4; i++) { + for (j=i; j<=4; j++) { + ij = i*j; + /* + * calculate the product of the charges + */ + zprod = (double)ij; + /* + * calculate Xmn (A1) from Harvie, Weare (1980). + */ + x = 6.0* zprod * aphi * sqrt(is); /* eqn 23 */ + + jfunc = x / (4.0 + c1*pow(x,-c2)*exp(-c3*pow(x,c4))); /* eqn 47 */ + + t = c3 * c4 * pow(x,c4); + dj = c1* pow(x,(-c2-1.0)) * (c2+t) * exp(-c3*pow(x,c4)); + 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; +#ifdef DEBUG_HKM + if (m_debugCalc) { + printf(" ij = %d, elambda = %g, elambda1 = %g\n", + ij, elambda[ij], elambda1[ij]); + } +#endif + } + } + } + + /* + * Calculate the etheta interaction. + * This interaction accounts for the mixing effects of like-signed + * ions with different charges. There is fairly extensive literature + * on this effect. See the notes. + * This interaction will be nonzero for species with the same charge. + * + * This code snipet is included from Bethke, Appendix 2. + */ + void HMWSoln::calc_thetas(int z1, int z2, + double *etheta, double *etheta_prime) const { + int i, j; + double f1, f2; + + /* + * Calculate E-theta(i) and E-theta'(I) using method of + * Pitzer (1987) + */ + i = abs(z1); + j = abs(z2); + +#ifdef DEBUG_HKM + if (i > 4 || j > 4) { + printf("we shouldn't be here\n"); + exit(-1); + } +#endif + + if ((i == 0) || (j == 0)) { + printf("ERROR calc_thetas called with one species being neutral\n"); + exit(-1); + } + + /* + * 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; + } + /* + * Actually calculate the interaction. + */ + else { + f1 = (double)i / (2.0 * j); + 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]; + } + } + + /** + * This routine prints out the input pitzer coefficients for the + * current mechanism + */ + void HMWSoln::printCoeffs() const { + int i, j, k; + string sni, snj; + calcMolalities(); + const double *charge = DATA_PTR(m_speciesCharge); + double *molality = DATA_PTR(m_molalities); + double *moleF = DATA_PTR(m_tmpV); + /* + * Update the coefficients wrt Temperature + * Calculate the derivatives as well + */ + s_updatePitzerCoeffWRTemp(2); + getMoleFractions(moleF); + + printf("Index Name MoleF Molality Charge\n"); + for (k = 0; k < m_kk; k++) { + sni = speciesName(k); + printf("%2d %-16s %14.7le %14.7le %5.1f \n", + k, sni.c_str(), moleF[k], molality[k], charge[k]); + } + + printf("\n Species Species beta0MX " + "beta1MX beta2MX CphiMX alphaMX thetaij \n"); + for (i = 1; i < m_kk - 1; i++) { + sni = speciesName(i); + for (j = i+1; j < m_kk; j++) { + snj = speciesName(j); + int n = i * m_kk + j; + int ct = m_CounterIJ[n]; + printf(" %-16s %-16s %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f \n", + sni.c_str(), snj.c_str(), + m_Beta0MX_ij[ct], m_Beta1MX_ij[ct], + m_Beta2MX_ij[ct], m_CphiMX_ij[ct], + m_Alpha1MX_ij[ct], m_Theta_ij[ct] ); + + + } + } + + printf("\n Species Species Species " + "psi \n"); + for (i = 1; i < m_kk; i++) { + sni = speciesName(i); + for (j = 1; j < m_kk; j++) { + snj = speciesName(j); + for (k = 1; k < m_kk; k++) { + string snk = speciesName(k); + int n = k + j * m_kk + i * m_kk * m_kk; + if (m_Psi_ijk[n] != 0.0) { + printf(" %-16s %-16s %-16s %9.5f \n", + sni.c_str(), snj.c_str(), + snk.c_str(), m_Psi_ijk[n]); + } + } + } + } + } + + + /*****************************************************************************/ +} +/*****************************************************************************/ diff --git a/Cantera/src/thermo/HMWSoln.h b/Cantera/src/thermo/HMWSoln.h new file mode 100644 index 000000000..d21e38bc7 --- /dev/null +++ b/Cantera/src/thermo/HMWSoln.h @@ -0,0 +1,1492 @@ +/** + * @file HMWSoln.h + * + * Header file for Pitzer activity coefficient implementation + */ +/* + * Copywrite (2006) Sandia Corporation. Under the terms of + * Contract DE-AC04-94AL85000 with Sandia Corporation, the + * U.S. Government retains certain rights in this software. + */ +/* + * $Id$ + */ + +#ifndef CT_HMWSOLN_H +#define CT_HMWSOLN_H + +#include "MolalityVPSSTP.h" +#include "electrolytes.h" + +namespace Cantera { + + /** + * @defgroup thermoprops Thermodynamic Properties + * + * These classes are used to compute thermodynamic properties. + */ + + /** + * HMWSoln.h + * + * Major Parameters: + * The form of the Pitzer expression refers to the + * form of the Gibbs free energy expression. The temperature + * dependence of the Pitzer coefficients are handled by + * another parameter. + * + * m_formPitzer = Form of the Pitzer expression + * + * PITZERFORM_BASE = 0 + * + * Only one form is supported atm. This parameter is included for + * future expansion. + * + */ +#define PITZERFORM_BASE 0 + + + /* + * Formulations for the temperature dependence of the Pitzer + * coefficients. Note, the temperature dependence of the + * Gibbs free energy also depends on the temperature dependence + * of the standard state and the temperature dependence of the + * Debye-Huckel constant, which includes the dielectric constant + * and the density. Therefore, this expression defines only part + * of the temperature dependence for the mixture thermodynamic + * functions. + * + * PITZER_TEMP_CONSTANT + * All coefficients are considered constant wrt temperature + * PITZER_TEMP_LINEAR + * All coefficients are assumed to have a linear dependence + * wrt to temperature. + * PITZER_TEMP_COMPLEX1 + * All coefficnets are assumed to have a complex functional + * based dependence wrt temperature; See: + * (Silvester, Pitzer, J. Phys. Chem. 81, 19 1822 (1977)). + * + * beta0 = q0 + q3(1/T - 1/Tr) + q4(ln(T/Tr)) + + * q1(T - Tr) + q2(T**2 - Tr**2) + */ +#define PITZER_TEMP_CONSTANT 0 +#define PITZER_TEMP_LINEAR 1 +#define PITZER_TEMP_COMPLEX1 2 + + /* + * Acceptable ways to calculate the value of A_Debye + */ +#define A_DEBYE_CONST 0 +#define A_DEBYE_WATER 1 + + class WaterProps; + class WaterPDSS; + + /** + * Definition of the HMWSoln object + */ + class HMWSoln : public MolalityVPSSTP { + + public: + + /// Constructors + HMWSoln(); + + HMWSoln(const HMWSoln &); + HMWSoln& operator=(const HMWSoln&); + + HMWSoln(string inputFile, string id = ""); + HMWSoln(XML_Node& phaseRef, string id = ""); + + /** + * This is a special constructor, used to replicate test problems + * during the initial verification of the object + */ + HMWSoln(int testProb); + + /// Destructor. + virtual ~HMWSoln(); + + + ThermoPhase *duplMyselfAsThermoPhase(); + + /** + * + * @name Utilities + * @{ + */ + + /** + * Equation of state type flag. The base class returns + * zero. Subclasses should define this to return a unique + * non-zero value. Constants defined for this purpose are + * listed in mix_defs.h. + */ + virtual int eosType() const; + + /** + * @} + * @name Molar Thermodynamic Properties of the Solution -------------- + * @{ + */ + + /// Molar enthalpy. Units: J/kmol. + /** + * Molar enthalpy of the solution. Units: J/kmol. + * (HKM -> Bump up to Parent object) + */ + virtual doublereal enthalpy_mole() const; + + /** + * Excess molar enthalpy of the solution from + * the mixing process. Units: J/ kmol. + * + * Note this is kmol of the total solution. + */ + virtual doublereal relative_enthalpy() const; + + /** + * Excess molar enthalpy of the solution from + * the mixing process on a molality basis. + * Units: J/ (kmol add salt). + * + * Note this is kmol of the guessed at salt composition + */ + virtual doublereal relative_molal_enthalpy() const; + + + /// Molar internal energy. Units: J/kmol. + /** + * Molar internal energy of the solution. Units: J/kmol. + * (HKM -> Bump up to Parent object) + */ + virtual doublereal intEnergy_mole() const; + + /// Molar entropy. Units: J/kmol/K. + /** + * Molar entropy of the solution. Units: J/kmol/K. + * For an ideal, constant partial molar volume solution mixture with + * pure species phases which exhibit zero volume expansivity: + * \f[ + * \hat s(T, P, X_k) = \sum_k X_k \hat s^0_k(T) + * - \hat R \sum_k X_k log(X_k) + * \f] + * The reference-state pure-species entropies + * \f$ \hat s^0_k(T,p_{ref}) \f$ are computed by the + * species thermodynamic + * property manager. The pure species entropies are independent of + * temperature since the volume expansivities are equal to zero. + * @see SpeciesThermo + * + * (HKM -> Bump up to Parent object) + */ + virtual doublereal entropy_mole() const; + + /// Molar Gibbs function. Units: J/kmol. + /* + * (HKM -> Bump up to Parent object) + */ + virtual doublereal gibbs_mole() const; + + /// Molar heat capacity at constant pressure. Units: J/kmol/K. + /* + * (HKM -> Bump up to Parent object) + */ + virtual doublereal cp_mole() const; + + /// Molar heat capacity at constant volume. Units: J/kmol/K. + /* + * (HKM -> Bump up to Parent object) + */ + virtual doublereal cv_mole() const; + + //@} + /// @name Mechanical Equation of State Properties --------------------- + //@{ + /** + * In this equation of state implementation, the density is a + * function only of the mole fractions. Therefore, it can't be + * an independent variable. Instead, the pressure is used as the + * independent variable. Functions which try to set the thermodynamic + * state by calling setDensity() may cause an exception to be + * thrown. + */ + + /** + * Pressure. Units: Pa. + * For this incompressible system, we return the internally storred + * independent value of the pressure. + */ + virtual doublereal pressure() const; + + /** + * Set the pressure at constant temperature. Units: Pa. + * This method sets a constant within the object. + * The mass density is not a function of pressure. + */ + virtual void setPressure(doublereal p); + + /** + * Calculate the density of the mixture using the partial + * molar volumes and mole fractions as input + * + * The formula for this is + * + * \f[ + * \rho = \frac{\sum_k{X_k W_k}}{\sum_k{X_k V_k}} + * \f] + * + * where \f$X_k\f$ are the mole fractions, \f$W_k\f$ are + * the molecular weights, and \f$V_k\f$ are the pure species + * molar volumes. + * + * Note, the basis behind this formula is that in an ideal + * solution the partial molar volumes are equal to the pure + * species molar volumes. We have additionally specified + * in this class that the pure species molar volumes are + * independent of temperature and pressure. + * + * NOTE: This is a non-virtual function, which is not a + * member of the ThermoPhase base class. + */ + void calcDensity(); + + /** + * Overwritten setDensity() function is necessary because the + * density is not an indendent variable. + * + * This function will now throw an error condition + * + * @internal May have to adjust the strategy here to make + * the eos for these materials slightly compressible, in order + * to create a condition where the density is a function of + * the pressure. + * + * This function will now throw an error condition. + * + * NOTE: This is an overwritten function from the State.h + * class + */ + void setDensity(doublereal rho); + + /** + * Overwritten setMolarDensity() function is necessary because the + * density is not an indendent variable. + * + * This function will now throw an error condition. + * + * NOTE: This is an overwritten function from the State.h + * class + */ + void setMolarDensity(doublereal rho); + + /** + * Overwritten setTemperature(double) from State.h. This + * function sets the temperature, and makes sure that + * the value propagates to underlying objects. + */ + virtual void setTemperature(doublereal temp); + + /** + * The isothermal compressibility. Units: 1/Pa. + * The isothermal compressibility is defined as + * \f[ + * \kappa_T = -\frac{1}{v}\left(\frac{\partial v}{\partial P}\right)_T + * \f] + */ + virtual doublereal isothermalCompressibility() const; + + /** + * The thermal expansion coefficient. Units: 1/K. + * The thermal expansion coefficient is defined as + * + * \f[ + * \beta = \frac{1}{v}\left(\frac{\partial v}{\partial T}\right)_P + * \f] + */ + virtual doublereal thermalExpansionCoeff() const; + + /** + * @} + * @name Potential Energy + * + * Species may have an additional potential energy due to the + * presence of external gravitation or electric fields. These + * methods allow specifying a potential energy for individual + * species. + * @{ + */ + + /** + * Set the potential energy of species k to pe. + * Units: J/kmol. + * This function must be reimplemented in inherited classes + * of ThermoPhase. + */ + virtual void setPotentialEnergy(int k, doublereal pe) { + err("setPotentialEnergy"); + } + + /** + * Get the potential energy of species k. + * Units: J/kmol. + * This function must be reimplemented in inherited classes + * of ThermoPhase. + */ + virtual doublereal potentialEnergy(int k) const { + return err("potentialEnergy"); + } + + /** + * Set the electric potential of this phase (V). + * This is used by classes InterfaceKinetics and EdgeKinetics to + * compute the rates of charge-transfer reactions, and in computing + * the electrochemical potentials of the species. + */ + void setElectricPotential(doublereal v) { + m_phi = v; + } + + /// The electric potential of this phase (V). + doublereal electricPotential() const { return m_phi; } + + + /** + * @} + * @name Activities, Standard States, and Activity Concentrations + * + * The activity \f$a_k\f$ of a species in solution is + * related to the chemical potential by \f[ \mu_k = \mu_k^0(T) + * + \hat R T \log a_k. \f] The quantity \f$\mu_k^0(T,P)\f$ is + * the chemical potential at unit activity, which depends only + * on temperature and the pressure. + * Activity is assumed to be molality-based here. + * @{ + */ + + /** + * This method returns an array of generalized concentrations + * \f$ C_k\f$ that are defined such that + * \f$ a_k = C_k / C^0_k, \f$ where \f$ C^0_k \f$ + * is a standard concentration + * defined below. These generalized concentrations are used + * by kinetics manager classes to compute the forward and + * reverse rates of elementary reactions. + * + * @param c Array of generalized concentrations. The + * units depend upon the implementation of the + * reaction rate expressions within the phase. + */ + virtual void getActivityConcentrations(doublereal* c) const; + + /** + * The standard concentration \f$ C^0_k \f$ used to normalize + * the generalized concentration. In many cases, this quantity + * will be the same for all species in a phase - for example, + * for an ideal gas \f$ C^0_k = P/\hat R T \f$. For this + * reason, this method returns a single value, instead of an + * array. However, for phases in which the standard + * concentration is species-specific (e.g. surface species of + * different sizes), this method may be called with an + * optional parameter indicating the species. + */ + virtual doublereal standardConcentration(int k=0) const; + + /** + * Returns the natural logarithm of the standard + * concentration of the kth species + */ + virtual doublereal logStandardConc(int k=0) const; + + /** + * Returns the units of the standard and generalized + * concentrations Note they have the same units, as their + * ratio is defined to be equal to the activity of the kth + * species in the solution, which is unitless. + * + * This routine is used in print out applications where the + * units are needed. Usually, MKS units are assumed throughout + * the program and in the XML input files. + * + * uA[0] = kmol units - default = 1 + * uA[1] = m units - default = -nDim(), the number of spatial + * dimensions in the Phase class. + * uA[2] = kg units - default = 0; + * uA[3] = Pa(pressure) units - default = 0; + * uA[4] = Temperature units - default = 0; + * uA[5] = time units - default = 0 + */ + virtual void getUnitsStandardConc(double *uA, int k = 0, + int sizeUA = 6); + + /** + * Get the array of non-dimensional molality-based activities at + * the current solution temperature, pressure, and + * solution concentration. + * (note solvent is on molar scale). + */ + virtual void getActivities(doublereal* ac) const; + + /** + * Get the array of non-dimensional molality-based + * activity coefficients at + * the current solution temperature, pressure, and + * solution concentration. + * (note solvent is on molar scale. The solvent molar + * based activity coefficient is returned). + */ + virtual void + getMolalityActivityCoefficients(doublereal* acMolality) const; + + //@} + /// @name Partial Molar Properties of the Solution ----------------- + //@{ + + /** + * Get the species chemical potentials. Units: J/kmol. + * + * This function returns a vector of chemical potentials of the + * species in solution. + * \f[ + * \mu_k = \mu^{ref}_k(T) + V_k * (p - p_o) + R T ln(X_k) + * \f] + * or another way to phrase this is + * \f[ + * \mu_k = \mu^o_k(T,p) + R T ln(X_k) + * \f] + * where \f$ \mu^o_k(T,p) = \mu^{ref}_k(T) + V_k * (p - p_o)\f$ + */ + virtual void getChemPotentials(doublereal* mu) const; + + /** + * Get the species electrochemical potentials. + * These are partial molar quantities. + * This method adds a term \f$ Fz_k \phi_k \f$ to the + * to each chemical potential. + * + * Units: J/kmol + */ + void getElectrochemPotentials(doublereal* mu) const { + getChemPotentials(mu); + double ve = Faraday * electricPotential(); + for (int k = 0; k < m_kk; k++) { + mu[k] += ve*charge(k); + } + } + + /** + * Returns an array of partial molar enthalpies for the species + * in the mixture. + * Units (J/kmol) + * For this phase, the partial molar enthalpies are equal to the + * pure species enthalpies + * \f[ + * \bar h_k(T,P) = \hat h^{ref}_k(T) + (P - P_{ref}) \hat V^0_k + * \f] + * The reference-state pure-species enthalpies, + * \f$ \hat h^{ref}_k(T) \f$, + * at the reference pressure,\f$ P_{ref} \f$, + * are computed by the species thermodynamic + * property manager. They are polynomial functions of temperature. + * @see SpeciesThermo + */ + virtual void getPartialMolarEnthalpies(doublereal* hbar) const; + + /** + * getPartialMolarEntropies() (virtual, const) + * + * Returns an array of partial molar entropies of the species in the + * solution. Units: J/kmol. + * + * Maxwell's equations provide an insight in how to calculate this + * (p.215 Smith and Van Ness) + * + * d(chemPot_i)/dT = -sbar_i + * + * + * For this phase, the partial molar entropies are equal to the + * SS species entropies plus the ideal solution contribution.following + * contribution: + * \f[ + * \bar s_k(T,P) = \hat s^0_k(T) - R log(M0 * molality[k]) + * \f] + * \f[ + * \bar s_solvent(T,P) = \hat s^0_solvent(T) + * - R ((xmolSolvent - 1.0) / xmolSolvent) + * \f] + * + * The reference-state pure-species entropies,\f$ \hat s^0_k(T) \f$, + * at the reference pressure, \f$ P_{ref} \f$, are computed by the + * species thermodynamic + * property manager. They are polynomial functions of temperature. + * @see SpeciesThermo + */ + virtual void getPartialMolarEntropies(doublereal* sbar) const; + + /** + * returns an array of partial molar volumes of the species + * in the solution. Units: m^3 kmol-1. + * + * For this solution, thepartial molar volumes are equal to the + * constant species molar volumes. + */ + virtual void getPartialMolarVolumes(doublereal* vbar) const; + + virtual void getPartialMolarCp(doublereal* cpbar) const; + + + //@} + + /// @name Properties of the Standard State of the Species + // in the Solution -- + //@{ + + + /** + * Get the standard state chemical potentials of the species. + * This is the array of chemical potentials at unit activity + * \f$ \mu^0_k(T,P) \f$. + * Activity is molality based in this object. + * We define these here as the chemical potentials of the pure + * species at the temperature and pressure of the solution. + * This function is used in the evaluation of the + * equilibrium constant Kc. Therefore, Kc will also depend + * on T and P. This is the norm for liquid and solid systems. + * + * units = J / kmol + */ + virtual void getStandardChemPotentials(doublereal* mu) const; + + /** + * Get the nondimensional gibbs function for the species + * standard states at the current T and P of the solution. + * + * \f[ + * \mu^0_k(T,P) = \mu^{ref}_k(T) + (P - P_{ref}) * V_k + * \f] + * where \f$V_k\f$ is the molar volume of pure species k. + * \f$ \mu^{ref}_k(T)\f$ is the chemical potential of pure + * species k at the reference pressure, \f$P_{ref}\f$. + * + * @param grt Vector of length m_kk, which on return sr[k] + * will contain the nondimensional + * standard state gibbs function for species k. + */ + virtual void getGibbs_RT(doublereal* grt) const; + + /** + * Get the nondimensional Gibbs functions for the standard + * state of the species at the current T and P. + */ + virtual void getPureGibbs(doublereal* gpure) const; + + /** + * + * getEnthalpy_RT() (virtual, const) + * + * Get the array of nondimensional Enthalpy functions for the + * standard states + * species at the current T and P of the solution. + * We assume an incompressible constant partial molar + * volume here: + * \f[ + * h^0_k(T,P) = h^{ref}_k(T) + (P - P_{ref}) * V_k + * \f] + * where \f$V_k\f$ is the molar volume of SS species k<\I>. + * \f$ h^{ref}_k(T)\f$ is the enthalpy of the SS + * species k<\I> at the reference pressure, \f$P_{ref}\f$. + */ + virtual void getEnthalpy_RT(doublereal* hrt) const; + + /** + * Get the nondimensional Entropies for the species + * standard states at the current T and P of the solution. + * + * Note, this is equal to the reference state entropies + * due to the zero volume expansivity: + * i.e., (dS/dp)_T = (dV/dT)_P = 0.0 + * + * @param sr Vector of length m_kk, which on return sr[k] + * will contain the nondimensional + * standard state entropy of species k. + */ + virtual void getEntropy_R(doublereal* sr) const; + + /** + * Get the nondimensional heat capacity at constant pressure + * function for the species + * standard states at the current T and P of the solution. + * \f[ + * Cp^0_k(T,P) = Cp^{ref}_k(T) + * \f] + * where \f$V_k\f$ is the molar volume of pure species k. + * \f$ Cp^{ref}_k(T)\f$ is the constant pressure heat capacity + * of species k at the reference pressure, \f$p_{ref}\f$. + * + * @param cpr Vector of length m_kk, which on return cpr[k] + * will contain the nondimensional + * constant pressure heat capacity for species k. + */ + virtual void getCp_R(doublereal* cpr) const; + + /** + * Get the molar volumes of each species in their standard + * states at the current + * T and P of the solution. + * units = m^3 / kmol + */ + virtual void getStandardVolumes(doublereal *vol) const; + + //@} + /// @name Thermodynamic Values for the Species Reference States --- + //@{ + + + /////////////////////////////////////////////////////// + // + // The methods below are not virtual, and should not + // be overloaded. + // + ////////////////////////////////////////////////////// + + /** + * @name Specific Properties + * @{ + */ + + + /** + * @name Setting the State + * + * These methods set all or part of the thermodynamic + * state. + * @{ + */ + + //@} + + /** + * @name Chemical Equilibrium + * Chemical equilibrium. + * @{ + */ + + /** + * This method is used by the ChemEquil equilibrium solver. + * It sets the state such that the chemical potentials satisfy + * \f[ \frac{\mu_k}{\hat R T} = \sum_m A_{k,m} + * \left(\frac{\lambda_m} {\hat R T}\right) \f] where + * \f$ \lambda_m \f$ is the element potential of element m. The + * temperature is unchanged. Any phase (ideal or not) that + * implements this method can be equilibrated by ChemEquil. + */ + virtual void setToEquilState(const doublereal* lambda_RT) { + err("setToEquilState"); + } + + // called by function 'equilibrate' in ChemEquil.h to transfer + // the element potentials to this object + void setElementPotentials(const vector_fp& lambda) { + m_lambda = lambda; + } + + void getElementPotentials(doublereal* lambda) { + copy(m_lambda.begin(), m_lambda.end(), lambda); + } + + //@} + + + /** + * @internal + * Set equation of state parameters. The number and meaning of + * these depends on the subclass. + * @param n number of parameters + * @param c array of \i n coefficients + * + */ + virtual void setParameters(int n, doublereal* c); + virtual void getParameters(int &n, doublereal * const c); + + /** + * Set equation of state parameter values from XML + * entries. This method is called by function importPhase in + * file importCTML.cpp when processing a phase definition in + * an input file. It should be overloaded in subclasses to set + * any parameters that are specific to that particular phase + * model. + * + * @param eosdata An XML_Node object corresponding to + * the "thermo" entry for this phase in the input file. + */ + virtual void setParametersFromXML(const XML_Node& eosdata); + + //--------------------------------------------------------- + /// @name Critical state properties. + /// These methods are only implemented by some subclasses. + + //@{ + + /// Critical temperature (K). + virtual doublereal critTemperature() const { + err("critTemperature"); return -1.0; + } + + /// Critical pressure (Pa). + virtual doublereal critPressure() const { + err("critPressure"); return -1.0; + } + + /// Critical density (kg/m3). + virtual doublereal critDensity() const { + err("critDensity"); return -1.0; + } + + //@} + + /// @name Saturation properties. + /// These methods are only implemented by subclasses that + /// implement full liquid-vapor equations of state. + /// + virtual doublereal satTemperature(doublereal p) const { + err("satTemperature"); return -1.0; + } + + virtual doublereal satPressure(doublereal t) const; + + virtual doublereal vaporFraction() const { + err("vaprFraction"); return -1.0; + } + + virtual void setState_Tsat(doublereal t, doublereal x) { + err("setState_sat"); + } + + virtual void setState_Psat(doublereal p, doublereal x) { + err("setState_sat"); + } + + //@} + + + /* + * -------------- Utilities ------------------------------- + */ + + /** + * @internal Install a species thermodynamic property + * manager. The species thermodynamic property manager + * computes properties of the pure species for use in + * constructing solution properties. It is meant for internal + * use, and some classes derived from ThermoPhase may not use + * any species thermodynamic property manager. + */ + void setSpeciesThermo(SpeciesThermo* spthermo) + { m_spthermo = spthermo; } + + /** + * Return a reference to the species thermodynamic property + * manager. @todo This method will fail if no species thermo + * manager has been installed. + */ + SpeciesThermo& speciesThermo() { return *m_spthermo; } + + /* + * constructPhaseFile() (virtual from HMWSoln) + * + * Import, construct, and initialize a HMWSoln phase + * specification from an XML tree into the current object. + * + * This routine is a precursor to constructPhaseXML(XML_Node*) + * routine, which does most of the work. + */ + virtual void constructPhaseFile(string inputFile, string id); + + /* + * constructPhaseXML (virtual from HMWSoln) + * + * This is the main routine for constructing the phase. + * + * Most of the work is carried out by the cantera base + * routine, importPhase(). That routine imports all of the + * species and element data, including the standard states + * of the species. + * + * Then, In this routine, we read the information + * particular to the specification of the activity + * coefficient model for the Pitzer parameterization. + */ + virtual void constructPhaseXML(XML_Node& phaseNode, string id); + + /** + * @internal Initialize. This method is provided to allow + * subclasses to perform any initialization required after all + * species have been added. For example, it might be used to + * resize internal work arrays that must have an entry for + * each species. The base class implementation does nothing, + * and subclasses that do not require initialization do not + * need to overload this method. When importing a CTML phase + * description, this method is called just prior to returning + * from function importPhase. + * + * @see importCTML.cpp + */ + virtual void initThermo(); + + /* + * initThermoXML() (virtual from ThermoPhase) + * + * This gets called from importPhase(). It processes the XML file + * after the species are set up. This is the main routine for + * reading in activity coefficient parameters. + * + * @param phaseNode This object must be the phase node of a + * complete XML tree + * description of the phase, including all of the + * species data. In other words while "phase" must + * point to an XML phase object, it must have + * sibling nodes "speciesData" that describe + * the species in the phase. + * @param id ID of the phase. If nonnull, a check is done + * to see if phaseNode is pointing to the phase + * with the correct id. + */ + virtual void initThermoXML(XML_Node& phaseNode, string id); + + /** + * Report the molar volume of species k + * + * units - \f$ m^3 kmol^-1 \f$ + */ + double speciesMolarVolume(int k) const; + + /** + * Fill in a return vector containing the species molar volumes + * units - \f$ m^3 kmol^-1 \f$ + */ + //void getSpeciesMolarVolumes(double *smv) const; + + + /** + * Value of the Debye Huckel constant as a function of temperature + * and pressure. + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * + * Units = sqrt(kg/gmol) + */ + virtual double A_Debye_TP(double temperature = -1.0, + double pressure = -1.0) const; + + /** + * Value of the derivative of the Debye Huckel constant with + * respect to temperature as a function of temperature + * and pressure. + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * + * Units = sqrt(kg/gmol) + */ + virtual double dA_DebyedT_TP(double temperature = -1.0, + double pressure = -1.0) const; + + /** + * Value of the derivative of the Debye Huckel constant with + * respect to pressure, as a function of temperature + * and pressure. + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * + * Units = sqrt(kg/gmol) + */ + virtual double dA_DebyedP_TP(double temperature = -1.0, + double pressure = -1.0) const; + + /** + * Return Pitzer's definition of A_L. This is basically the + * derivative of the A_phi multiplied by 4 R T**2 + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * dA_phidT = d(A_Debye)/dT / 3.0 + * A_L = dA_phidT * (4 * R * T * T) + * + * Units = sqrt(kg/gmol) (RT) + * + */ + double ADebye_L(double temperature = -1.0, + double pressure = -1.0) const; + + + /** + * Return Pitzer's definition of A_J. This is basically the + * temperature derivative of A_L, and the second derivative + * of A_phi + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * dA_phidT = d(A_Debye)/dT / 3.0 + * A_J = 2 A_L/T + 4 * R * T * T * d2(A_phi)/dT2 + * + * Units = sqrt(kg/gmol) (R) + */ + double ADebye_J(double temperature = -1.0, + double pressure = -1.0) const; + /** + * Return Pitzer's definition of A_V. This is the + * derivative wrt pressure of A_phi multiplied by - 4 R T + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * dA_phidT = d(A_Debye)/dP / 3.0 + * A_V = - dA_phidP * (4 * R * T) + * + * Units = sqrt(kg/gmol) (RT) / Pascal + * + */ + double ADebye_V(double temperature = -1.0, + double pressure = -1.0) const; + /** + * Value of the 2nd derivative of the Debye Huckel constant with + * respect to temperature as a function of temperature + * and pressure. + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * + * Units = sqrt(kg/gmol) + */ + virtual double d2A_DebyedT2_TP(double temperature = -1.0, + double pressure = -1.0) const; + + /* + * AionicRadius() + * + * Reports the ionic radius of the kth species + */ + double AionicRadius(int k = 0) const; + + /** + * + * formPitzer(): + * + * Returns the form of the Pitzer parameterization used + */ + int formPitzer() const { return m_formPitzer; } + + /** + * Print out all of the input coefficients. + */ + void printCoeffs () const; + + + //@} + + protected: + + /** + * This is the form of the Pitzer parameterization + * used in this model. + * The options are described at the top of this document, + * and in the general documentation. + * The list is repeated here: + * + * PITZERFORM_BASE = 0 (only one supported atm) + * + */ + int m_formPitzer; + + /** + * This is the form of the temperature dependence of Pitzer + * parameterization used in the model. + * + * PITZER_TEMP_CONSTANT 0 + * PITZER_TEMP_LINEAR 1 + * PITZER_TEMP_COMPLEX1 2 + */ + int m_formPitzerTemp; + + /** + * Format for the generalized concentration: + * + * 0 = unity + * 1 = molar_volume + * 2 = solvent_volume (default) + * + * The generalized concentrations can have three different forms + * depending on the value of the member attribute m_formGC, which + * is supplied in the constructor. + * + * + * + * + * + *
m_formGC GeneralizedConc StandardConc
0 X_k 1.0
1 X_k / V_k 1.0 / V_k
2 X_k / V_N 1.0 / V_N
+ * + * The value and form of the generalized concentration will affect + * reaction rate constants involving species in this phase. + * + * (HKM Note: Using option #1 may lead to spurious results and + * has been included only with warnings. The reason is that it + * molar volumes of electrolytes may often be negative. The + * molar volume of H+ is defined to be zero too. Either options + * 0 or 2 are the appropriate choice. Option 0 leads to + * bulk reaction rate constants which have units of s-1. + * Option 2 leads to bulk reaction rate constants for + * bimolecular rxns which have units of m-3 kmol-1 s-1.) + */ + int m_formGC; + + /** + * Current pressure in Pascal. This is now the independent variable + * as it must be for multicomponent solutions. + */ + double m_Pcurrent; + + + vector_int m_electrolyteSpeciesType; + + /** + * Species molar volumes \f$ m^3 kmol^-1 \f$ + * -> m_speciesSize in Constituents.h + */ + //array_fp m_speciesMolarVolume; + + /** + * a_k = Size of the ionic species in the DH formulation + * units = meters + */ + array_fp m_Aionic; + + /** + * Current value of the ionic strength on the molality scale + * Associated Salts, if present in the mechanism, + * don't contribute to the value of the ionic strength + * in this version of the Ionic strength. + */ + mutable double m_IionicMolality; + + /** + * Maximum value of the ionic strength allowed in the + * calculation of the activity coefficients. + */ + double m_maxIionicStrength; + + /** + * Reference Temperature for the Pitzer formulations. + */ + double m_TempPitzerRef; + + protected: + /** + * Stoichiometric ionic strength on the molality scale. + * This differs from m_IionicMolality in the sense that + * associated salts are treated as unassociated salts, + * when calculating the Ionic strength by this method. + */ + mutable double m_IionicMolalityStoich; + + public: + /** + * Form of the constant outside the Debye-Huckel term + * called A. It's normally a function of temperature + * and pressure. However, it can be set from the + * input file in order to aid in numerical comparisons. + * Acceptable forms: + * + * A_DEBYE_CONST 0 + * A_DEBYE_WATER 1 + * + * The A_DEBYE_WATER form may be used for water solvents + * with needs to cover varying temperatures and pressures. + * Note, the dielectric constant of water is a relatively + * strong function of T, and its variability must be + * accounted for, + */ + mutable int m_form_A_Debye; + + protected: + /** + * A_Debye -> this expression appears on the top of the + * ln actCoeff term in the general Debye-Huckel + * expression + * It depends on temperature. And, therefore, + * most be recalculated whenever T or P changes. + * + * A_Debye = (F e B_Debye) / (8 Pi epsilon R T) + * + * where B_Debye = F / sqrt(epsilon R T/2) + * (dw/1000)^(1/2) + * + * A_Debye = (1/ (8 Pi)) (2 Pi * Na * dw/1000)^(1/2) + * (e * e / (epsilon * kb * T))^(3/2) + * + * Units = sqrt(kg/gmol) + * + * Nominal value = 1.172576 sqrt(kg/gmol) + * based on: + * epsilon/epsilon_0 = 78.54 + * (water at 25C) + * epsilon_0 = 8.854187817E-12 C2 N-1 m-2 + * e = 1.60217653 E-19 C + * F = 9.6485309E7 C kmol-1 + * R = 8.314472E3 kg m2 s-2 kmol-1 K-1 + * T = 298.15 K + * B_Debye = 3.28640E9 sqrt(kg/gmol)/m + * dw = C_0 * M_0 (density of water) (kg/m3) + * = 1.0E3 at 25C + */ + mutable double m_A_Debye; + + /** + * B_Debye -> this expression appears on the bottom of the + * ln actCoeff term in the general Debye-Huckel + * expression + * It depends on temperature + * + * B_Bebye = F / sqrt( epsilon R T / 2 ) + * + * Units = sqrt(kg/gmol) / m + * + * Nominal value = 3.28640E9 sqrt(kg/gmol) / m + * based on: + * epsilon/epsilon_0 = 78.54 + * (water at 25C) + * epsilon_0 = 8.854187817E12 C2 N-1 m-2 + * e = 8.314472E3 kg m2 s-2 kmol-1 K-1 + * F = 9.6485309E7 C kmol-1 + * R = 8.314472E3 kg m2 s-2 kmol-1 K-1 + * T = 298.15 K + */ + double m_B_Debye; + + /** + * Water standard state -> derived from the + * equation of state for water. + */ + WaterPDSS *m_waterSS; + double m_densWaterSS; + + /** + * Water property calculator + */ + WaterProps *m_waterProps; + + /** + * Vector containing the species reference exp(-G/RT) functions + * at T = m_tlast + */ + mutable vector_fp m_expg0_RT; + + /** + * Vector of potential energies for the species. + */ + mutable vector_fp m_pe; + + /** + * Temporary array used in equilibrium calculations + */ + mutable vector_fp m_pp; + + /** + * vector of size m_kk, used as a temporary holding area. + */ + mutable vector_fp m_tmpV; + + /** + * Stoichiometric species charge -> This is for calculations + * of the ionic strength which ignore ion-ion pairing into + * neutral molecules. The Stoichiometric species charge is the + * charge of one of the ion that would occur if the species broke + * into two charged ion pairs. + * NaCl -> m_speciesCharge_Stoich = -1; + * HSO4- -> H+ + SO42- = -2 + * -> The other charge is calculated. + * For species that aren't ion pairs, its equal to the + * m_speciesCharge[] value. + */ + vector_fp m_speciesCharge_Stoich; + + /** + * Array of 2D data used in the Pitzer/HMW formulation. + * Beta0_ij[i][j] is the value of the Beta0 coefficient + * for the ij salt. It will be nonzero iff i and j are + * both charged and have opposite sign. The array is also + * symmetric. + * counterIJ where counterIJ = m_counterIJ[i][j] + * is used to access this array. + */ + mutable vector_fp m_Beta0MX_ij; + mutable vector_fp m_Beta0MX_ij_L; + mutable vector_fp m_Beta0MX_ij_LL; + mutable vector_fp m_Beta0MX_ij_P; + mutable Array2D m_Beta0MX_ij_coeff; + + /** + * Array of 2D data used in the Pitzer/HMW formulation. + * Beta1_ij[i][j] is the value of the Beta1 coefficient + * for the ij salt. It will be nonzero iff i and j are + * both charged and have opposite sign. The array is also + * symmetric. + * counterIJ where counterIJ = m_counterIJ[i][j] + * is used to access this array. + */ + mutable vector_fp m_Beta1MX_ij; + mutable vector_fp m_Beta1MX_ij_L; + mutable vector_fp m_Beta1MX_ij_LL; + mutable vector_fp m_Beta1MX_ij_P; + mutable Array2D m_Beta1MX_ij_coeff; + + /** + * Array of 2D data used in the Pitzer/HMW formulation. + * Beta2_ij[i][j] is the value of the Beta2 coefficient + * for the ij salt. It will be nonzero iff i and j are + * both charged and have opposite sign, and i and j + * both have charges of 2 or more. The array is also + * symmetric. + * counterIJ where counterIJ = m_counterIJ[i][j] + * is used to access this array. + */ + vector_fp m_Beta2MX_ij; + vector_fp m_Beta2MX_ij_L; + vector_fp m_Beta2MX_ij_LL; + vector_fp m_Beta2MX_ij_P; + + /** + * Array of 2D data used in the Pitzer/HMW formulation. + * Alpha1MX_ij[i][j] is the value of the alpha1 coefficient + * for the ij interaction. It will be nonzero iff i and j are + * both charged and have opposite sign, and i and j + * both have charges of 2 or more. The array is also + * symmetric. + * counterIJ where counterIJ = m_counterIJ[i][j] + * is used to access this array. + */ + vector_fp m_Alpha1MX_ij; + + /** + * Array of 2D data used in the Pitzer/HMW formulation. + * CphiMX_ij[i][j] is the value of the Cphi coefficient + * for the ij interaction. It will be nonzero iff i and j are + * both charged and have opposite sign, and i and j + * both have charges of 2 or more. The array is also + * symmetric. + * counterIJ where counterIJ = m_counterIJ[i][j] + * is used to access this array. + */ + mutable vector_fp m_CphiMX_ij; + mutable vector_fp m_CphiMX_ij_L; + mutable vector_fp m_CphiMX_ij_LL; + mutable vector_fp m_CphiMX_ij_P; + mutable Array2D m_CphiMX_ij_coeff; + + /** + * Array of 2D data used in the Pitzer/HMW formulation. + * Theta_ij[i][j] is the value of the theta coefficient + * for the ij interaction. It will be nonzero for charged + * ions with the same sign. It is symmetric. + * counterIJ where counterIJ = m_counterIJ[i][j] + * is used to access this array. + * + * HKM Recent Pitzer papers have used a functional form + * for Theta_ij, which depends on the ionic strength. + */ + vector_fp m_Theta_ij; + vector_fp m_Theta_ij_L; + vector_fp m_Theta_ij_LL; + vector_fp m_Theta_ij_P; + + /** + * Array of 3D data sed in the Pitzer/HMW formulation. + * Psi_ijk[n] is the value of the psi coefficient for the + * ijk interaction where + * + * n = k + j * m_kk + i * m_kk * m_kk; + * + * It is potentially nonzero everywhere. + * The first two coordinates are symmetric wrt cations, + * and the last two coordinates are symmetric wrt anions. + */ + vector_fp m_Psi_ijk; + vector_fp m_Psi_ijk_L; + vector_fp m_Psi_ijk_LL; + vector_fp m_Psi_ijk_P; + + /* + * Array of 2D data used in the Pitzer/HMW formulation. + * Lambda_ij[i][j] represents the lambda coefficient for the + * ij interaction. This is a general interaction representing + * neutral species. The neutral species occupy the first + * index, i.e., i. The charged species occupy the j coordinate. + * neutral, neutral interactions are also included here. + */ + Array2D m_Lambda_ij; + Array2D m_Lambda_ij_L; + Array2D m_Lambda_ij_LL; + Array2D m_Lambda_ij_P; + + /** + * Logarithm of the activity coefficients on the molality + * scale. + * mutable because we change this if the composition + * or temperature or pressure changes. + */ + mutable vector_fp m_lnActCoeffMolal; + mutable vector_fp m_dlnActCoeffMolaldT; + mutable vector_fp m_d2lnActCoeffMolaldT2; + mutable vector_fp m_dlnActCoeffMolaldP; + + /* + * -------- Temporary Variables Used in the Activity Coeff Calc + */ + + /* + * Set up a counter variable for keeping track of symmetric binary + * interactions amongst the solute species. + * + * n = m_kk*i + j + * m_CounterIJ[n] = counterIJ + */ + mutable array_int m_CounterIJ; + + /** + * This is elambda, MEC + */ + mutable double elambda[17]; + + /** + * This is elambda1, MEC + */ + mutable double elambda1[17]; + + /** + * Various temporary arrays used in the calculation of + * the Pitzer activity coefficents. + * The subscript, L, denotes the same quantity's derivative + * wrt temperature + */ + mutable vector_fp m_gfunc_IJ; + mutable vector_fp m_hfunc_IJ; + mutable vector_fp m_BMX_IJ; + mutable vector_fp m_BMX_IJ_L; + mutable vector_fp m_BMX_IJ_LL; + mutable vector_fp m_BMX_IJ_P; + mutable vector_fp m_BprimeMX_IJ; + mutable vector_fp m_BprimeMX_IJ_L; + mutable vector_fp m_BprimeMX_IJ_LL; + mutable vector_fp m_BprimeMX_IJ_P; + mutable vector_fp m_BphiMX_IJ; + mutable vector_fp m_BphiMX_IJ_L; + mutable vector_fp m_BphiMX_IJ_LL; + mutable vector_fp m_BphiMX_IJ_P; + mutable vector_fp m_Phi_IJ; + mutable vector_fp m_Phi_IJ_L; + mutable vector_fp m_Phi_IJ_LL; + mutable vector_fp m_Phi_IJ_P; + mutable vector_fp m_Phiprime_IJ; + mutable vector_fp m_PhiPhi_IJ; + mutable vector_fp m_PhiPhi_IJ_L; + mutable vector_fp m_PhiPhi_IJ_LL; + mutable vector_fp m_PhiPhi_IJ_P; + mutable vector_fp m_CMX_IJ; + mutable vector_fp m_CMX_IJ_L; + mutable vector_fp m_CMX_IJ_LL; + mutable vector_fp m_CMX_IJ_P; + + mutable vector_fp m_gamma; + + private: + doublereal err(string msg) const; + + + void initLengths(); + + /* + * This function will be called to update the internally storred + * natural logarithm of the molality activity coefficients + */ + //void s_updateDHlnMolalityActCoeff() const; + void s_update_lnMolalityActCoeff() const; + public: + void s_Pitzer_dlnMolalityActCoeff_dT() const; + void s_Pitzer_dlnMolalityActCoeff_dP() const; + private: + /** + * This function calculates the temperature derivative of the + * natural logarithm of the molality activity coefficients. + */ + void s_update_dlnMolalityActCoeff_dT() const; + + /** + * This function calcultes the temperature second derivative + * of the natural logarithm of the molality activity + * coefficients. + */ + void s_update_d2lnMolalityActCoeff_dT2() const; + /** + * This function calculates the pressure derivative of the + * natural logarithm of the molality activity coefficients. + */ + void s_update_dlnMolalityActCoeff_dP() const; + + /** + * This function calculates the temperature derivatives + * of the Pitzer coefficients + */ + void s_updatePitzerCoeffWRTemp(int doDerivs = 2) const; + + /** + * This function does the main pitzer coefficient + * calculation + */ + void s_updatePitzerSublnMolalityActCoeff() const; + /* + * Calculate the lambda interactions. + * + * + * Calculate E-lambda terms for charge combinations of like sign, + * using method of Pitzer (1975). + */ + void calc_lambdas(double is) const; + + /** + * Calculate etheta and etheta_prime + * + * This interaction will be nonzero for species with the + * same charge. this routine is not to be called for + * neutral species; it core dumps or error exits. + * + * MEC implementation routine. + * + * @param z1 charge of the first molecule + * @param z2 charge of the second molecule + * @param etheta return pointer containing etheta + * @param etheta_prime Return pointer containing etheta_prime. + * + * This routine uses the internal variables, + * elambda[] and elambda1[]. + * + * There is no prohibition against calling + * + */ + void calc_thetas(int z1, int z2, + double *etheta, double *etheta_prime) const; + + + void counterIJ_setup(void) const; + void readXMLBinarySalt(XML_Node &BinSalt); + void readXMLThetaAnion(XML_Node &BinSalt); + void readXMLThetaCation(XML_Node &BinSalt); + void readXMLPsiCommonAnion(XML_Node &BinSalt); + void readXMLPsiCommonCation(XML_Node &BinSalt); + void readXMLLambdaNeutral(XML_Node &BinSalt); + + + public: + /* + * Turn on copious debug printing + */ + mutable int m_debugCalc; + + }; + +} + +#endif + diff --git a/Cantera/src/thermo/HMWSoln_input.cpp b/Cantera/src/thermo/HMWSoln_input.cpp new file mode 100644 index 000000000..e9d33ec98 --- /dev/null +++ b/Cantera/src/thermo/HMWSoln_input.cpp @@ -0,0 +1,1130 @@ +/** + * @file HMWSoln_input.cpp + */ +/* + * Copywrite (2006) Sandia Corporation. Under the terms of + * Contract DE-AC04-94AL85000 with Sandia Corporation, the + * U.S. Government retains certain rights in this software. + */ +/* + * $Id$ + */ + +#include "HMWSoln.h" +#include "importCTML.h" +#include "WaterProps.h" +#include "WaterPDSS.h" + +namespace Cantera { + + /** + * interp_est() (static) + * + * utility function to assign an integer value from a string + * for the ElectrolyteSpeciesType field. + */ + static int interp_est(string estString) { + const char *cc = estString.c_str(); + if (!strcasecmp(cc, "solvent")) { + return cEST_solvent; + } else if (!strcasecmp(cc, "chargedspecies")) { + return cEST_chargedSpecies; + } else if (!strcasecmp(cc, "weakAcidAssociated")) { + return cEST_weakAcidAssociated; + } else if (!strcasecmp(cc, "strongAcidAssociated")) { + return cEST_strongAcidAssociated; + } else if (!strcasecmp(cc, "polarNeutral")) { + return cEST_polarNeutral; + } else if (!strcasecmp(cc, "nonpolarNeutral")) { + return cEST_nonpolarNeutral; + } + int retn, rval; + if ((retn = sscanf(cc, "%d", &rval)) != 1) { + return -1; + } + return rval; + } + + /** + * Process an XML node called "SimpleSaltParameters. + * This node contains all of the parameters necessary to describe + * the Pitzer model for that particular binary salt. + * This function reads the XML file and writes the coefficients + * it finds to an internal data structures. + */ + void HMWSoln::readXMLBinarySalt(XML_Node &BinSalt) { + double *charge = DATA_PTR(m_speciesCharge); + string stemp; + int nParamsFound, i; + vector_fp vParams; + 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 + */ + int iSpecies = speciesIndex(iName); + if (iSpecies < 0) { + return; + } + string ispName = speciesName(iSpecies); + if (charge[iSpecies] <= 0) { + throw CanteraError("HMWSoln::readXMLBinarySalt", "cation charge problem"); + } + int jSpecies = speciesIndex(jName); + if (jSpecies < 0) { + return; + } + string jspName = speciesName(jSpecies); + if (charge[jSpecies] >= 0) { + throw CanteraError("HMWSoln::readXMLBinarySalt", "anion charge problem"); + } + + int n = iSpecies * m_kk + jSpecies; + int counter = m_CounterIJ[n]; + int num = BinSalt.nChildren(); + for (int iChild = 0; iChild < num; iChild++) { + XML_Node &xmlChild = BinSalt.child(iChild); + stemp = xmlChild.name(); + string nodeName = lowercase(stemp); + /* + * Process the binary salt child elements + */ + if (nodeName == "beta0") { + /* + * Get the string containing all of the values + */ + getFloatArray(xmlChild, vParams, false, "", "beta0"); + nParamsFound = vParams.size(); + if (m_formPitzerTemp == PITZER_TEMP_CONSTANT) { + if (nParamsFound != 1) { + throw CanteraError("HMWSoln::readXMLBinarySalt::beta0 for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + m_Beta0MX_ij[counter] = vParams[0]; + m_Beta0MX_ij_coeff(0,counter) = m_Beta0MX_ij[counter]; + } else if (m_formPitzerTemp == PITZER_TEMP_LINEAR) { + if (nParamsFound != 2) { + throw CanteraError("HMWSoln::readXMLBinarySalt::beta0 for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + m_Beta0MX_ij_coeff(0,counter) = vParams[0]; + m_Beta0MX_ij_coeff(1,counter) = vParams[1]; + m_Beta0MX_ij[counter] = vParams[0]; + } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) { + if (nParamsFound != 5) { + throw CanteraError("HMWSoln::readXMLBinarySalt::beta0 for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + for (i = 0; i < nParamsFound; i++) { + m_Beta0MX_ij_coeff(i, counter) = vParams[i]; + } + m_Beta0MX_ij[counter] = vParams[0]; + } + } + if (nodeName == "beta1") { + + /* + * Get the string containing all of the values + */ + getFloatArray(xmlChild, vParams, false, "", "beta1"); + nParamsFound = vParams.size(); + if (m_formPitzerTemp == PITZER_TEMP_CONSTANT) { + if (nParamsFound != 1) { + throw CanteraError("HMWSoln::readXMLBinarySalt::beta1 for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + m_Beta1MX_ij[counter] = vParams[0]; + m_Beta1MX_ij_coeff(0,counter) = m_Beta1MX_ij[counter]; + } else if (m_formPitzerTemp == PITZER_TEMP_LINEAR) { + if (nParamsFound != 2) { + throw CanteraError("HMWSoln::readXMLBinarySalt::beta1 for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + m_Beta1MX_ij_coeff(0,counter) = vParams[0]; + m_Beta1MX_ij_coeff(1,counter) = vParams[1]; + m_Beta1MX_ij[counter] = vParams[0]; + } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) { + if (nParamsFound < 3) { + throw CanteraError("HMWSoln::readXMLBinarySalt::beta1 for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + for (i = 0; i < nParamsFound; i++) { + m_Beta1MX_ij_coeff(i, counter) = vParams[i]; + } + m_Beta1MX_ij[counter] = vParams[0]; + } + + } + if (nodeName == "beta2") { + stemp = xmlChild.value(); + m_Beta2MX_ij[counter] = atofCheck(stemp.c_str()); + } + if (nodeName == "cphi") { + /* + * Get the string containing all of the values + */ + getFloatArray(xmlChild, vParams, false, "", "Cphi"); + nParamsFound = vParams.size(); + if (m_formPitzerTemp == PITZER_TEMP_CONSTANT) { + if (nParamsFound != 1) { + throw CanteraError("HMWSoln::readXMLBinarySalt::Cphi for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + m_CphiMX_ij[counter] = vParams[0]; + m_CphiMX_ij_coeff(0,counter) = m_CphiMX_ij[counter]; + } else if (m_formPitzerTemp == PITZER_TEMP_LINEAR) { + if (nParamsFound != 2) { + throw CanteraError("HMWSoln::readXMLBinarySalt::Cphi for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + m_CphiMX_ij_coeff(0,counter) = vParams[0]; + m_CphiMX_ij_coeff(1,counter) = vParams[1]; + m_CphiMX_ij[counter] = vParams[0]; + } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) { + if (nParamsFound != 5) { + throw CanteraError("HMWSoln::readXMLBinarySalt::Cphi for " + ispName + + "::" + jspName, + "wrong number of params found"); + } + for (i = 0; i < nParamsFound; i++) { + m_CphiMX_ij_coeff(i, counter) = vParams[i]; + } + m_CphiMX_ij[counter] = vParams[0]; + } + } + + if (nodeName == "alpha1") { + stemp = xmlChild.value(); + m_Alpha1MX_ij[counter] = atofCheck(stemp.c_str()); + } + } + } + + /** + * Process an XML node called "ThetaAnion". + * This node contains all of the parameters necessary to describe + * the binary interactions between two anions. + */ + void HMWSoln::readXMLThetaAnion(XML_Node &BinSalt) { + double *charge = DATA_PTR(m_speciesCharge); + string stemp; + string iName = BinSalt.attrib("anion1"); + if (iName == "") { + throw CanteraError("HMWSoln::readXMLThetaAnion", "no anion1 attrib"); + } + string jName = BinSalt.attrib("anion2"); + if (jName == "") { + throw CanteraError("HMWSoln::readXMLThetaAnion", "no anion2 attrib"); + } + /* + * Find the index of the species in the current phase. It's not + * an error to not find the species + */ + int iSpecies = speciesIndex(iName); + if (iSpecies < 0) { + return; + } + if (charge[iSpecies] >= 0) { + throw CanteraError("HMWSoln::readXMLThetaAnion", "anion1 charge problem"); + } + int jSpecies = speciesIndex(jName); + if (jSpecies < 0) { + return; + } + if (charge[jSpecies] >= 0) { + throw CanteraError("HMWSoln::readXMLThetaAnion", "anion2 charge problem"); + } + + int n = iSpecies * m_kk + jSpecies; + int counter = m_CounterIJ[n]; + int num = BinSalt.nChildren(); + for (int i = 0; i < num; i++) { + XML_Node &xmlChild = BinSalt.child(i); + stemp = xmlChild.name(); + string nodeName = lowercase(stemp); + if (nodeName == "theta") { + stemp = xmlChild.value(); + double old = m_Theta_ij[counter]; + m_Theta_ij[counter] = atofCheck(stemp.c_str()); + if (old != 0.0) { + if (old != m_Theta_ij[counter]) { + throw CanteraError("HMWSoln::readXMLThetaAnion", "conflicting values"); + } + } + } + } + } + + /** + * Process an XML node called "ThetaCation". + * This node contains all of the parameters necessary to describe + * the binary interactions between two cation. + */ + void HMWSoln::readXMLThetaCation(XML_Node &BinSalt) { + double *charge = DATA_PTR(m_speciesCharge); + string stemp; + string iName = BinSalt.attrib("cation1"); + if (iName == "") { + throw CanteraError("HMWSoln::readXMLThetaCation", "no cation1 attrib"); + } + string jName = BinSalt.attrib("cation2"); + if (jName == "") { + throw CanteraError("HMWSoln::readXMLThetaCation", "no cation2 attrib"); + } + /* + * Find the index of the species in the current phase. It's not + * an error to not find the species + */ + int iSpecies = speciesIndex(iName); + if (iSpecies < 0) { + return; + } + if (charge[iSpecies] <= 0) { + throw CanteraError("HMWSoln::readXMLThetaCation", "cation1 charge problem"); + } + int jSpecies = speciesIndex(jName); + if (jSpecies < 0) { + return; + } + if (charge[jSpecies] <= 0) { + throw CanteraError("HMWSoln::readXMLThetaCation", "cation2 charge problem"); + } + + int n = iSpecies * m_kk + jSpecies; + int counter = m_CounterIJ[n]; + int num = BinSalt.nChildren(); + for (int i = 0; i < num; i++) { + XML_Node &xmlChild = BinSalt.child(i); + stemp = xmlChild.name(); + string nodeName = lowercase(stemp); + if (nodeName == "theta") { + stemp = xmlChild.value(); + double old = m_Theta_ij[counter]; + m_Theta_ij[counter] = atofCheck(stemp.c_str()); + if (old != 0.0) { + if (old != m_Theta_ij[counter]) { + throw CanteraError("HMWSoln::readXMLThetaCation", "conflicting values"); + } + } + } + } + } + + /** + * Process an XML node called "readXMLPsiCommonCation". + * This node contains all of the parameters necessary to describe + * the binary interactions between two anions and one common cation. + */ + void HMWSoln::readXMLPsiCommonCation(XML_Node &BinSalt) { + double *charge = DATA_PTR(m_speciesCharge); + string stemp; + string kName = BinSalt.attrib("cation"); + if (kName == "") { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", "no cation attrib"); + } + string iName = BinSalt.attrib("anion1"); + if (iName == "") { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", "no anion1 attrib"); + } + string jName = BinSalt.attrib("anion2"); + if (jName == "") { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", "no anion2 attrib"); + } + /* + * Find the index of the species in the current phase. It's not + * an error to not find the species + */ + int kSpecies = speciesIndex(kName); + if (kSpecies < 0) { + return; + } + if (charge[kSpecies] <= 0) { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", + "cation charge problem"); + } + int iSpecies = speciesIndex(iName); + if (iSpecies < 0) { + return; + } + if (charge[iSpecies] >= 0) { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", + "anion1 charge problem"); + } + int jSpecies = speciesIndex(jName); + if (jSpecies < 0) { + return; + } + if (charge[jSpecies] >= 0) { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", + "anion2 charge problem"); + } + + int n = iSpecies * m_kk + jSpecies; + int counter = m_CounterIJ[n]; + int num = BinSalt.nChildren(); + for (int i = 0; i < num; i++) { + XML_Node &xmlChild = BinSalt.child(i); + stemp = xmlChild.name(); + string nodeName = lowercase(stemp); + if (nodeName == "theta") { + stemp = xmlChild.value(); + double old = m_Theta_ij[counter]; + m_Theta_ij[counter] = atofCheck(stemp.c_str()); + if (old != 0.0) { + if (old != m_Theta_ij[counter]) { + throw CanteraError("HMWSoln::readXMLPsiCommonCation", + "conflicting values"); + } + } + } + if (nodeName == "psi") { + stemp = xmlChild.value(); + double param = atofCheck(stemp.c_str()); + n = iSpecies * m_kk *m_kk + jSpecies * m_kk + kSpecies ; + m_Psi_ijk[n] = param; + n = iSpecies * m_kk *m_kk + kSpecies * m_kk + jSpecies ; + m_Psi_ijk[n] = param; + n = jSpecies * m_kk *m_kk + iSpecies * m_kk + kSpecies ; + m_Psi_ijk[n] = param; + n = jSpecies * m_kk *m_kk + kSpecies * m_kk + iSpecies ; + m_Psi_ijk[n] = param; + n = kSpecies * m_kk *m_kk + jSpecies * m_kk + iSpecies ; + m_Psi_ijk[n] = param; + n = kSpecies * m_kk *m_kk + iSpecies * m_kk + jSpecies ; + m_Psi_ijk[n] = param; + } + } + } + + + + /** + * Process an XML node called "PsiCommonAnion". + * This node contains all of the parameters necessary to describe + * the binary interactions between two cations and one common anion. + */ + void HMWSoln::readXMLPsiCommonAnion(XML_Node &BinSalt) { + double *charge = DATA_PTR(m_speciesCharge); + string stemp; + string kName = BinSalt.attrib("anion"); + if (kName == "") { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", "no anion attrib"); + } + string iName = BinSalt.attrib("cation1"); + if (iName == "") { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", "no cation1 attrib"); + } + string jName = BinSalt.attrib("cation2"); + if (jName == "") { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", "no cation2 attrib"); + } + /* + * Find the index of the species in the current phase. It's not + * an error to not find the species + */ + int kSpecies = speciesIndex(kName); + if (kSpecies < 0) { + return; + } + if (charge[kSpecies] >= 0) { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", "anion charge problem"); + } + int iSpecies = speciesIndex(iName); + if (iSpecies < 0) { + return; + } + if (charge[iSpecies] <= 0) { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", + "cation1 charge problem"); + } + int jSpecies = speciesIndex(jName); + if (jSpecies < 0) { + return; + } + if (charge[jSpecies] <= 0) { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", + "cation2 charge problem"); + } + + int n = iSpecies * m_kk + jSpecies; + int counter = m_CounterIJ[n]; + int num = BinSalt.nChildren(); + for (int i = 0; i < num; i++) { + XML_Node &xmlChild = BinSalt.child(i); + stemp = xmlChild.name(); + string nodeName = lowercase(stemp); + if (nodeName == "theta") { + stemp = xmlChild.value(); + double old = m_Theta_ij[counter]; + m_Theta_ij[counter] = atofCheck(stemp.c_str()); + if (old != 0.0) { + if (old != m_Theta_ij[counter]) { + throw CanteraError("HMWSoln::readXMLPsiCommonAnion", + "conflicting values"); + } + } + } + if (nodeName == "psi") { + stemp = xmlChild.value(); + double param = atofCheck(stemp.c_str()); + n = iSpecies * m_kk *m_kk + jSpecies * m_kk + kSpecies ; + m_Psi_ijk[n] = param; + n = iSpecies * m_kk *m_kk + kSpecies * m_kk + jSpecies ; + m_Psi_ijk[n] = param; + n = jSpecies * m_kk *m_kk + iSpecies * m_kk + kSpecies ; + m_Psi_ijk[n] = param; + n = jSpecies * m_kk *m_kk + kSpecies * m_kk + iSpecies ; + m_Psi_ijk[n] = param; + n = kSpecies * m_kk *m_kk + jSpecies * m_kk + iSpecies ; + m_Psi_ijk[n] = param; + n = kSpecies * m_kk *m_kk + iSpecies * m_kk + jSpecies ; + m_Psi_ijk[n] = param; + } + } + } + + /** + * Process an XML node called "LambdaNeutral". + * This node contains all of the parameters necessary to describe + * the binary interactions between one neutral species and + * any other species (neutral or otherwise) in the mechanism. + */ + void HMWSoln::readXMLLambdaNeutral(XML_Node &BinSalt) { + double *charge = DATA_PTR(m_speciesCharge); + string stemp; + string iName = BinSalt.attrib("neutral"); + if (iName == "") { + throw CanteraError("HMWSoln::readXMLLambdaNeutral", "no neutral attrib"); + } + string jName = BinSalt.attrib("speciesj"); + if (jName == "") { + throw CanteraError("HMWSoln::readXMLLambdaNeutral", "no speciesj attrib"); + } + /* + * Find the index of the species in the current phase. It's not + * an error to not find the species + */ + int iSpecies = speciesIndex(iName); + if (iSpecies < 0) { + return; + } + if (charge[iSpecies] != 0) { + throw CanteraError("HMWSoln::readXMLLambdaNeutral", "neutral charge problem"); + } + int jSpecies = speciesIndex(jName); + if (jSpecies < 0) { + return; + } + + int num = BinSalt.nChildren(); + for (int i = 0; i < num; i++) { + XML_Node &xmlChild = BinSalt.child(i); + stemp = xmlChild.name(); + string nodeName = lowercase(stemp); + if (nodeName == "lambda") { + stemp = xmlChild.value(); + double old = m_Lambda_ij(iSpecies,jSpecies); + m_Lambda_ij(iSpecies,jSpecies) = atofCheck(stemp.c_str()); + if (old != 0.0) { + if (old != m_Lambda_ij(iSpecies,jSpecies)) { + throw CanteraError("HMWSoln::readXMLLambdaNeutral", "conflicting values"); + } + } + } + } + } + + /** + * Initialization routine for a HMWSoln phase. + * + * This is a virtual routine. This routine will call initThermo() + * for the parent class as well. + */ + void HMWSoln::initThermo() { + MolalityVPSSTP::initThermo(); + initLengths(); + } + + /** + * Import, construct, and initialize a HMWSoln phase + * specification from an XML tree into the current object. + * + * This routine is a precursor to constructPhaseXML(XML_Node*) + * routine, which does most of the work. + * + * @param infile XML file containing the description of the + * phase + * + * @param id Optional parameter identifying the name of the + * phase. If none is given, the first XML + * phase element will be used. + */ + void HMWSoln::constructPhaseFile(string inputFile, string id) { + + if (inputFile.size() == 0) { + throw CanteraError("HMWSoln:constructPhaseFile", + "input file is null"); + } + string path = findInputFile(inputFile); + ifstream fin(path.c_str()); + if (!fin) { + throw CanteraError("HMWSoln:constructPhaseFile","could not open " + +path+" for reading."); + } + /* + * The phase object automatically constructs an XML object. + * Use this object to store information. + */ + XML_Node &phaseNode_XML = xml(); + XML_Node *fxml = new XML_Node(); + fxml->build(fin); + XML_Node *fxml_phase = findXMLPhase(fxml, id); + if (!fxml_phase) { + throw CanteraError("HMWSoln:constructPhaseFile", + "ERROR: Can not find phase named " + + id + " in file named " + inputFile); + } + fxml_phase->copy(&phaseNode_XML); + constructPhaseXML(*fxml_phase, id); + delete fxml; + } + + /** + * Import, construct, and initialize a HMWSoln phase + * specification from an XML tree into the current object. + * + * Most of the work is carried out by the cantera base + * routine, importPhase(). That routine imports all of the + * species and element data, including the standard states + * of the species. + * + * Then, In this routine, we read the information + * particular to the specification of the activity + * coefficient model for the Pitzer parameterization. + * + * We also read information about the molar volumes of the + * standard states if present in the XML file. + * + * @param phaseNode This object must be the phase node of a + * complete XML tree + * description of the phase, including all of the + * species data. In other words while "phase" must + * point to an XML phase object, it must have + * sibling nodes "speciesData" that describe + * the species in the phase. + * @param id ID of the phase. If nonnull, a check is done + * to see if phaseNode is pointing to the phase + * with the correct id. + */ + void HMWSoln::constructPhaseXML(XML_Node& phaseNode, string id) { + string stemp; + if (id.size() > 0) { + string idp = phaseNode.id(); + if (idp != id) { + throw CanteraError("HMWSoln::constructPhaseXML", + "phasenode and Id are incompatible"); + } + } + + /* + * Find the Thermo XML node + */ + if (!phaseNode.hasChild("thermo")) { + throw CanteraError("HMWSoln::constructPhaseXML", + "no thermo XML node"); + } + XML_Node& thermoNode = phaseNode.child("thermo"); + + /* + * Possibly change the form of the standard concentrations + */ + if (thermoNode.hasChild("standardConc")) { + XML_Node& scNode = thermoNode.child("standardConc"); + m_formGC = 2; + stemp = scNode.attrib("model"); + string formString = lowercase(stemp); + if (formString != "") { + if (formString == "unity") { + m_formGC = 0; + printf("exit standardConc = unity not done\n"); + exit(-1); + } else if (formString == "molar_volume") { + m_formGC = 1; + printf("exit standardConc = molar_volume not done\n"); + exit(-1); + } else if (formString == "solvent_volume") { + m_formGC = 2; + } else { + throw CanteraError("HMWSoln::constructPhaseXML", + "Unknown standardConc model: " + formString); + } + } + } + /* + * Get the Name of the Solvent: + * solventName + */ + string solventName = ""; + if (thermoNode.hasChild("solvent")) { + XML_Node& scNode = thermoNode.child("solvent"); + vector nameSolventa; + getStringArray(scNode, nameSolventa); + int nsp = static_cast(nameSolventa.size()); + if (nsp != 1) { + throw CanteraError("HMWSoln::constructPhaseXML", + "badly formed solvent XML node"); + } + solventName = nameSolventa[0]; + } + + /* + * 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"); + m_formPitzer = m_formPitzer; + stemp = scNode.attrib("model"); + string formString = lowercase(stemp); + if (formString != "") { + if (formString == "pitzer" || formString == "default") { + m_formPitzer = PITZERFORM_BASE; + } else if (formString == "base") { + m_formPitzer = PITZERFORM_BASE; + } else { + throw CanteraError("HMWSoln::constructPhaseXML", + "Unknown Pitzer ActivityCoeff model: " + + formString); + } + } + + /* + * Determine the form of the temperature dependence + * of the Pitzer activity coefficient model. + */ + stemp = scNode.attrib("TempModel"); + formString = lowercase(stemp); + if (formString != "") { + if (formString == "constant" || formString == "default") { + m_formPitzerTemp = PITZER_TEMP_CONSTANT; + } else if (formString == "linear") { + m_formPitzerTemp = PITZER_TEMP_LINEAR; + } else if (formString == "complex" || formString == "complex1") { + m_formPitzerTemp = PITZER_TEMP_COMPLEX1; + } else { + throw CanteraError("HMWSoln::constructPhaseXML", + "Unknown Pitzer ActivityCoeff Temp model: " + + formString); + } + } + + /* + * Determine the reference temperature + * of the Pitzer activity coefficient model's temperature + * dependence formulation: defaults to 25C + */ + stemp = scNode.attrib("TempReference"); + formString = lowercase(stemp); + if (formString != "") { + m_TempPitzerRef = atofCheck(formString.c_str()); + } else { + m_TempPitzerRef = 273.15 + 25; + } + + } + + /* + * Call the Cantera importPhase() function. This will import + * all of the species into the phase. This will also handle + * all of the solvent and solute standard states + */ + bool m_ok = importPhase(phaseNode, this); + if (!m_ok) { + throw CanteraError("HMWSoln::constructPhaseXML","importPhase failed "); + } + + + } + + /** + * Process the XML file after species are set up. + * + * This gets called from importPhase(). It processes the XML file + * after the species are set up. This is the main routine for + * reading in activity coefficient parameters. + * + * @param phaseNode This object must be the phase node of a + * complete XML tree + * description of the phase, including all of the + * species data. In other words while "phase" must + * point to an XML phase object, it must have + * sibling nodes "speciesData" that describe + * the species in the phase. + * @param id ID of the phase. If nonnull, a check is done + * to see if phaseNode is pointing to the phase + * with the correct id. + */ + void HMWSoln:: + initThermoXML(XML_Node& phaseNode, string id) { + int k; + string stemp; + /* + * Find the Thermo XML node + */ + if (!phaseNode.hasChild("thermo")) { + throw CanteraError("HMWSoln::initThermoXML", + "no thermo XML node"); + } + XML_Node& thermoNode = phaseNode.child("thermo"); + + /* + * Get the Name of the Solvent: + * solventName + */ + string solventName = ""; + if (thermoNode.hasChild("solvent")) { + XML_Node& scNode = thermoNode.child("solvent"); + vector nameSolventa; + getStringArray(scNode, nameSolventa); + int nsp = static_cast(nameSolventa.size()); + if (nsp != 1) { + throw CanteraError("HMWSoln::initThermoXML", + "badly formed solvent XML node"); + } + solventName = nameSolventa[0]; + } + + /* + * Initialize all of the lengths of arrays in the object + * now that we know what species are in the phase. + */ + initLengths(); + + /* + * Reconcile the solvent name and index. + */ + for (k = 0; k < m_kk; k++) { + string sname = speciesName(k); + if (solventName == sname) { + setSolvent(k); + if (k != 0) { + throw CanteraError("HMWSoln::initThermoXML", + "Solvent must be species 0 atm"); + } + m_indexSolvent = k; + break; + } + } + if (m_indexSolvent == -1) { + cout << "HMWSoln::initThermo: Solvent Name not found" + << endl; + throw CanteraError("HMWSoln::initThermoXML", + "Solvent name not found"); + } + if (m_indexSolvent != 0) { + throw CanteraError("HMWSoln::initThermoXML", + "Solvent " + solventName + + " should be first species"); + } + + /* + * Now go get the molar volumes + */ + XML_Node& speciesList = phaseNode.child("speciesArray"); + XML_Node* speciesDB = + get_XML_NameID("speciesData", speciesList["datasrc"], + &phaseNode.root()); + const vector&sss = speciesNames(); + + for (k = 0; k < m_kk; k++) { + XML_Node* s = speciesDB->findByAttr("name", sss[k]); + XML_Node *ss = s->findByName("standardState"); + m_speciesSize[k] = getFloat(*ss, "molarVolume", "-"); +#ifdef DEBUG_HKM_NOT + cout << "species " << sss[k] << " has volume " << + m_speciesSize[k] << endl; +#endif + } + + /* + * Initialize the water standard state model + */ + if (m_waterSS) delete m_waterSS; + m_waterSS = new WaterPDSS(this, 0); + + /* + * Initialize the water property calculator. It will share + * the internal eos water calculator. + */ + m_waterProps = new WaterProps(m_waterSS); + + /* + * Go get all of the coefficients and factors in the + * activityCoefficients XML block + */ + XML_Node *acNodePtr = 0; + if (thermoNode.hasChild("activityCoefficients")) { + XML_Node& acNode = thermoNode.child("activityCoefficients"); + acNodePtr = &acNode; + /* + * Look for parameters for A_Debye + */ + if (acNode.hasChild("A_Debye")) { + XML_Node &ADebye = acNode.child("A_Debye"); + m_form_A_Debye = A_DEBYE_CONST; + stemp = "model"; + if (ADebye.hasAttrib(stemp)) { + string atemp = ADebye.attrib(stemp); + stemp = lowercase(atemp); + if (stemp == "water") { + m_form_A_Debye = A_DEBYE_WATER; + } + } + if (m_form_A_Debye == A_DEBYE_CONST) { + m_A_Debye = getFloat(acNode, "A_Debye"); + } +#ifdef DEBUG_HKM_NOT + cout << "A_Debye = " << m_A_Debye << endl; +#endif + } + + /* + * Look for parameters for B_Debye + */ + if (acNode.hasChild("B_Debye")) { + m_B_Debye = getFloat(acNode, "B_Debye"); +#ifdef DEBUG_HKM_NOT + cout << "B_Debye = " << m_B_Debye << endl; +#endif + } + + /* + * Look for Parameters for the Maximum Ionic Strength + */ + if (acNode.hasChild("maxIonicStrength")) { + m_maxIionicStrength = getFloat(acNode, "maxIonicStrength"); +#ifdef DEBUG_HKM_NOT + cout << "m_maxIionicStrength = " + < Look for the subelement "stoichIsMods" + * in each of the species SS databases. + */ + const XML_Node *phaseSpecies = speciesData(); + if (phaseSpecies) { + string kname, jname; + vector xspecies; + phaseSpecies->getChildren("species", xspecies); + int jj = xspecies.size(); + for (k = 0; k < m_kk; k++) { + int jmap = -1; + kname = speciesName(k); + for (int j = 0; j < jj; j++) { + const XML_Node& sp = *xspecies[j]; + jname = sp["name"]; + if (jname == kname) { + jmap = j; + break; + } + } + if (jmap > -1) { + const XML_Node& sp = *xspecies[jmap]; + if (sp.hasChild("stoichIsMods")) { + double val = getFloat(sp, "stoichIsMods"); + m_speciesCharge_Stoich[k] = val; + } + } + } + } + /* + * Now look at the activity coefficient database + */ + if (acNodePtr) { + if (acNodePtr->hasChild("stoichIsMods")) { + XML_Node& sIsNode = acNodePtr->child("stoichIsMods"); + + map msIs; + getMap(sIsNode, msIs); + map::const_iterator _b = msIs.begin(); + for (; _b != msIs.end(); ++_b) { + int kk = speciesIndex(_b->first); + if (kk < 0) { + //throw CanteraError( + // "HMWSoln::initThermo error", + // "no species match was found" + // ); + } else { + double val = fpValue(_b->second); + m_speciesCharge_Stoich[kk] = val; + } + } + } + } + + /* + * Loop through the children getting multiple instances of + * parameters + */ + if (acNodePtr) { + int n = acNodePtr->nChildren(); + for (int i = 0; i < n; i++) { + XML_Node &xmlACChild = acNodePtr->child(i); + stemp = xmlACChild.name(); + string nodeName = lowercase(stemp); + /* + * Process a binary salt field, or any of the other XML fields + * that make up the Pitzer Database. Entries will be ignored + * if any of the species in the entry isn't in the solution. + */ + if (nodeName == "binarysaltparameters") { + readXMLBinarySalt(xmlACChild); + } else if (nodeName == "thetaanion") { + readXMLThetaAnion(xmlACChild); + } else if (nodeName == "thetacation") { + readXMLThetaCation(xmlACChild); + } else if (nodeName == "psicommonanion") { + readXMLPsiCommonAnion(xmlACChild); + } else if (nodeName == "psicommoncation") { + readXMLPsiCommonCation(xmlACChild); + } else if (nodeName == "lambdaneutral") { + readXMLLambdaNeutral(xmlACChild); + } + } + } + + + } + + /* + * Fill in the vector specifying the electrolyte species + * type + * + * First fill in default values. Everthing is either + * a charge species, a nonpolar neutral, or the solvent. + */ + for (k = 0; k < m_kk; k++) { + if (fabs(m_speciesCharge[k]) > 0.0001) { + m_electrolyteSpeciesType[k] = cEST_chargedSpecies; + if (fabs(m_speciesCharge_Stoich[k] - m_speciesCharge[k]) + > 0.0001) { + m_electrolyteSpeciesType[k] = cEST_weakAcidAssociated; + } + } else if (fabs(m_speciesCharge_Stoich[k]) > 0.0001) { + m_electrolyteSpeciesType[k] = cEST_weakAcidAssociated; + } else { + m_electrolyteSpeciesType[k] = cEST_nonpolarNeutral; + } + } + m_electrolyteSpeciesType[m_indexSolvent] = cEST_solvent; + /* + * First look at the species database. + * -> Look for the subelement "stoichIsMods" + * in each of the species SS databases. + */ + const XML_Node *phaseSpecies = speciesData(); + const XML_Node *spPtr = 0; + if (phaseSpecies) { + string kname; + for (k = 0; k < m_kk; k++) { + kname = speciesName(k); + spPtr = speciesXML_Node(kname, phaseSpecies); + if (!spPtr) { + if (spPtr->hasChild("electrolyteSpeciesType")) { + string est = getString(*spPtr, "electrolyteSpeciesType"); + if ((m_electrolyteSpeciesType[k] = interp_est(est)) == -1) { + throw CanteraError("HMWSoln::initThermoXML", + "Bad electrolyte type: " + est); + } + } + } + } + } + /* + * Then look at the phase thermo specification + */ + if (acNodePtr) { + if (acNodePtr->hasChild("electrolyteSpeciesType")) { + XML_Node& ESTNode = acNodePtr->child("electrolyteSpeciesType"); + map msEST; + getMap(ESTNode, msEST); + map::const_iterator _b = msEST.begin(); + for (; _b != msEST.end(); ++_b) { + int kk = speciesIndex(_b->first); + if (kk < 0) { + } else { + string est = _b->second; + if ((m_electrolyteSpeciesType[kk] = interp_est(est)) == -1) { + throw CanteraError("HMWSoln::initThermoXML", + "Bad electrolyte type: " + est); + } + } + } + } + } + + + /* + * Lastly set the state + */ + if (phaseNode.hasChild("state")) { + XML_Node& stateNode = phaseNode.child("state"); + setStateFromXML(stateNode); + } + + } +} diff --git a/Cantera/src/thermo/IdealMolalSoln.cpp b/Cantera/src/thermo/IdealMolalSoln.cpp index b7f7316bf..58f31d694 100644 --- a/Cantera/src/thermo/IdealMolalSoln.cpp +++ b/Cantera/src/thermo/IdealMolalSoln.cpp @@ -779,7 +779,7 @@ namespace Cantera { */ /** - * Initialization routine for an IdealMolalSoln phase: + * Initialization routine for an IdealMolalSoln phase. * * This is a virtual routine. This routine will call initThermo() * for the parent class as well. diff --git a/Cantera/src/thermo/IdealMolalSoln.h b/Cantera/src/thermo/IdealMolalSoln.h index 16621544d..44f5ae2eb 100644 --- a/Cantera/src/thermo/IdealMolalSoln.h +++ b/Cantera/src/thermo/IdealMolalSoln.h @@ -8,7 +8,11 @@ * solution assumes that all molality-based activity * coefficients are equal to one. */ - +/* + * Copywrite (2006) Sandia Corporation. Under the terms of + * Contract DE-AC04-94AL85000 with Sandia Corporation, the + * U.S. Government retains certain rights in this software. + */ /* * $Author$ * $Date$ diff --git a/Cantera/src/thermo/Makefile.in b/Cantera/src/thermo/Makefile.in index 05ed803e2..261fccedc 100644 --- a/Cantera/src/thermo/Makefile.in +++ b/Cantera/src/thermo/Makefile.in @@ -26,13 +26,14 @@ ELECTRO_OBJ = SingleSpeciesTP.o StoichSubstanceSSTP.o \ MolalityVPSSTP.o VPStandardStateTP.o \ IdealSolidSolnPhase.o IdealMolalSoln.o \ WaterPropsIAPWSphi.o WaterPropsIAPWS.o WaterProps.o \ - PDSS.o WaterPDSS.o WaterTP.o + PDSS.o WaterPDSS.o WaterTP.o \ + HMWSoln.o HMWSoln_input.o ELECTRO_H = SingleSpeciesTP.h StoichSubstanceSSTP.h \ MolalityVPSSTP.h VPStandardStateTP.h \ IdealSolidSolnPhase.h IdealMolalSoln.h \ WaterPropsIAPWSphi.h WaterPropsIAPWS.h WaterProps.h \ - PDSS.h WaterPDSS.h WaterTP.h + PDSS.h WaterPDSS.h WaterTP.h HMWSoln.h electrolytes.h endif ifeq ($(do_issp),1) ISSP_OBJ = IdealSolidSolnPhase.o diff --git a/Cantera/src/thermo/MolalityVPSSTP.h b/Cantera/src/thermo/MolalityVPSSTP.h index afd86b1dd..9a8b08e89 100644 --- a/Cantera/src/thermo/MolalityVPSSTP.h +++ b/Cantera/src/thermo/MolalityVPSSTP.h @@ -8,14 +8,12 @@ * calculating liquid electrolyte thermodynamics. */ /* - * Copywrite (2005) Sandia Corporation. Under the terms of + * Copywrite (2006) Sandia Corporation. Under the terms of * Contract DE-AC04-94AL85000 with Sandia Corporation, the * U.S. Government retains certain rights in this software. */ /* - * $Author$ - * $Date$ - * $Revision$ + * $Id$ */ #ifndef CT_MOLALITYVPSSTP_H