Cantera 2.6.0
HMWSoln.cpp
Go to the documentation of this file.
1/**
2 * @file HMWSoln.cpp
3 * Definitions for the HMWSoln ThermoPhase object, which
4 * models concentrated electrolyte solutions
5 * (see \ref thermoprops and \link Cantera::HMWSoln HMWSoln \endlink) .
6 *
7 * Class HMWSoln represents a concentrated liquid electrolyte phase which obeys
8 * the Pitzer formulation for nonideality using molality-based standard states.
9 *
10 * This version of the code was modified to have the binary Beta2 Pitzer
11 * parameter consistent with the temperature expansions used for Beta0,
12 * Beta1, and Cphi.(CFJC, SNL)
13 */
14
15// This file is part of Cantera. See License.txt in the top-level directory or
16// at https://cantera.org/license.txt for license and copyright information.
17
23#include "cantera/base/ctml.h"
24
25using namespace std;
26
27namespace Cantera
28{
29
30namespace {
31double A_Debye_default = 1.172576; // units = sqrt(kg/gmol)
32double maxIionicStrength_default = 100.0;
33double crop_ln_gamma_o_min_default = -6.0;
34double crop_ln_gamma_o_max_default = 3.0;
35double crop_ln_gamma_k_min_default = -5.0;
36double crop_ln_gamma_k_max_default = 15.0;
37}
38
39HMWSoln::~HMWSoln()
40{
41}
42
43HMWSoln::HMWSoln(const std::string& inputFile, const std::string& id_) :
44 m_formPitzerTemp(PITZER_TEMP_CONSTANT),
45 m_IionicMolality(0.0),
46 m_maxIionicStrength(maxIionicStrength_default),
47 m_TempPitzerRef(298.15),
48 m_form_A_Debye(A_DEBYE_CONST),
49 m_A_Debye(A_Debye_default),
50 m_waterSS(0),
51 m_molalitiesAreCropped(false),
52 IMS_X_o_cutoff_(0.2),
53 IMS_cCut_(0.05),
54 IMS_slopegCut_(0.0),
55 IMS_dfCut_(0.0),
56 IMS_efCut_(0.0),
57 IMS_afCut_(0.0),
58 IMS_bfCut_(0.0),
59 IMS_dgCut_(0.0),
60 IMS_egCut_(0.0),
61 IMS_agCut_(0.0),
62 IMS_bgCut_(0.0),
63 MC_X_o_cutoff_(0.0),
64 MC_dpCut_(0.0),
65 MC_epCut_(0.0),
66 MC_apCut_(0.0),
67 MC_bpCut_(0.0),
68 MC_cpCut_(0.0),
69 CROP_ln_gamma_o_min(crop_ln_gamma_o_min_default),
70 CROP_ln_gamma_o_max(crop_ln_gamma_o_max_default),
71 CROP_ln_gamma_k_min(crop_ln_gamma_k_min_default),
72 CROP_ln_gamma_k_max(crop_ln_gamma_k_max_default),
73 m_last_is(-1.0)
74{
75 initThermoFile(inputFile, id_);
76}
77
78HMWSoln::HMWSoln(XML_Node& phaseRoot, const std::string& id_) :
79 m_formPitzerTemp(PITZER_TEMP_CONSTANT),
80 m_IionicMolality(0.0),
81 m_maxIionicStrength(maxIionicStrength_default),
82 m_TempPitzerRef(298.15),
83 m_form_A_Debye(A_DEBYE_CONST),
84 m_A_Debye(A_Debye_default),
85 m_waterSS(0),
86 m_molalitiesAreCropped(false),
87 IMS_X_o_cutoff_(0.2),
88 IMS_cCut_(0.05),
89 IMS_slopegCut_(0.0),
90 IMS_dfCut_(0.0),
91 IMS_efCut_(0.0),
92 IMS_afCut_(0.0),
93 IMS_bfCut_(0.0),
94 IMS_dgCut_(0.0),
95 IMS_egCut_(0.0),
96 IMS_agCut_(0.0),
97 IMS_bgCut_(0.0),
98 MC_X_o_cutoff_(0.0),
99 MC_dpCut_(0.0),
100 MC_epCut_(0.0),
101 MC_apCut_(0.0),
102 MC_bpCut_(0.0),
103 MC_cpCut_(0.0),
104 CROP_ln_gamma_o_min(crop_ln_gamma_o_min_default),
105 CROP_ln_gamma_o_max(crop_ln_gamma_o_max_default),
106 CROP_ln_gamma_k_min(crop_ln_gamma_k_min_default),
107 CROP_ln_gamma_k_max(crop_ln_gamma_k_max_default),
108 m_last_is(-1.0)
109{
110 importPhase(phaseRoot, this);
111}
112
113// -------- Molar Thermodynamic Properties of the Solution ---------------
114
115doublereal HMWSoln::enthalpy_mole() const
116{
118 return mean_X(m_tmpV);
119}
120
122{
124 double hbar = mean_X(m_tmpV);
126 for (size_t k = 0; k < m_kk; k++) {
127 m_gamma_tmp[k] *= RT();
128 }
129 double h0bar = mean_X(m_gamma_tmp);
130 return hbar - h0bar;
131}
132
134{
135 double L = relative_enthalpy();
136 getMoleFractions(m_tmpV.data());
137 double xanion = 0.0;
138 size_t kcation = npos;
139 double xcation = 0.0;
140 size_t kanion = npos;
141 for (size_t k = 0; k < m_kk; k++) {
142 if (charge(k) > 0.0) {
143 if (m_tmpV[k] > xanion) {
144 xanion = m_tmpV[k];
145 kanion = k;
146 }
147 } else if (charge(k) < 0.0) {
148 if (m_tmpV[k] > xcation) {
149 xcation = m_tmpV[k];
150 kcation = k;
151 }
152 }
153 }
154 if (kcation == npos || kanion == npos) {
155 return L;
156 }
157 double xuse = xcation;
158 double factor = 1;
159 if (xanion < xcation) {
160 xuse = xanion;
161 if (charge(kcation) != 1.0) {
162 factor = charge(kcation);
163 }
164 } else {
165 if (charge(kanion) != 1.0) {
166 factor = charge(kanion);
167 }
168 }
169 xuse = xuse / factor;
170 return L / xuse;
171}
172
173doublereal HMWSoln::entropy_mole() const
174{
176 return mean_X(m_tmpV);
177}
178
179doublereal HMWSoln::gibbs_mole() const
180{
182 return mean_X(m_tmpV);
183}
184
185doublereal HMWSoln::cp_mole() const
186{
188 return mean_X(m_tmpV);
189}
190
191doublereal HMWSoln::cv_mole() const
192{
193 double kappa_t = isothermalCompressibility();
194 double beta = thermalExpansionCoeff();
195 double cp = cp_mole();
196 double tt = temperature();
197 double molarV = molarVolume();
198 return cp - beta * beta * tt * molarV / kappa_t;
199}
200
201// ------- Mechanical Equation of State Properties ------------------------
202
204{
205 static const int cacheId = m_cache.getId();
206 CachedScalar cached = m_cache.getScalar(cacheId);
207 if(cached.validate(temperature(), pressure(), stateMFNumber())) {
208 return;
209 }
210
211 // Calculate all of the other standard volumes. Note these are constant for
212 // now
214 double dd = meanMolecularWeight() / mean_X(m_tmpV);
216}
217
218// ------- Activities and Activity Concentrations
219
220void HMWSoln::getActivityConcentrations(doublereal* c) const
221{
222 double cs_solvent = standardConcentration();
223 getActivities(c);
224 c[0] *= cs_solvent;
225 if (m_kk > 1) {
226 double cs_solute = standardConcentration(1);
227 for (size_t k = 1; k < m_kk; k++) {
228 c[k] *= cs_solute;
229 }
230 }
231}
232
233doublereal HMWSoln::standardConcentration(size_t k) const
234{
236 double mvSolvent = m_tmpV[0];
237 if (k > 0) {
238 return m_Mnaught / mvSolvent;
239 }
240 return 1.0 / mvSolvent;
241}
242
243void HMWSoln::getActivities(doublereal* ac) const
244{
246
247 // Update the molality array, m_molalities(). This requires an update due to
248 // mole fractions
249 s_update_lnMolalityActCoeff();
250
251 // Now calculate the array of activities.
252 for (size_t k = 1; k < m_kk; k++) {
253 ac[k] = m_molalities[k] * exp(m_lnActCoeffMolal_Scaled[k]);
254 }
255 double xmolSolvent = moleFraction(0);
256 ac[0] = exp(m_lnActCoeffMolal_Scaled[0]) * xmolSolvent;
257}
258
259void HMWSoln::getUnscaledMolalityActivityCoefficients(doublereal* acMolality) const
260{
262 A_Debye_TP(-1.0, -1.0);
263 s_update_lnMolalityActCoeff();
264 std::copy(m_lnActCoeffMolal_Unscaled.begin(), m_lnActCoeffMolal_Unscaled.end(), acMolality);
265 for (size_t k = 0; k < m_kk; k++) {
266 acMolality[k] = exp(acMolality[k]);
267 }
268}
269
270// ------ Partial Molar Properties of the Solution -----------------
271
272void HMWSoln::getChemPotentials(doublereal* mu) const
273{
274 double xx;
275
276 // First get the standard chemical potentials in molar form. This requires
277 // updates of standard state as a function of T and P
279
280 // Update the activity coefficients. This also updates the internal molality
281 // array.
282 s_update_lnMolalityActCoeff();
283 double xmolSolvent = moleFraction(0);
284 for (size_t k = 1; k < m_kk; k++) {
285 xx = std::max(m_molalities[k], SmallNumber);
286 mu[k] += RT() * (log(xx) + m_lnActCoeffMolal_Scaled[k]);
287 }
288 xx = std::max(xmolSolvent, SmallNumber);
289 mu[0] += RT() * (log(xx) + m_lnActCoeffMolal_Scaled[0]);
290}
291
292void HMWSoln::getPartialMolarEnthalpies(doublereal* hbar) const
293{
294 // Get the nondimensional standard state enthalpies
295 getEnthalpy_RT(hbar);
296
297 // dimensionalize it.
298 for (size_t k = 0; k < m_kk; k++) {
299 hbar[k] *= RT();
300 }
301
302 // Update the activity coefficients, This also update the internally stored
303 // molalities.
304 s_update_lnMolalityActCoeff();
306 for (size_t k = 0; k < m_kk; k++) {
307 hbar[k] -= RT() * temperature() * m_dlnActCoeffMolaldT_Scaled[k];
308 }
309}
310
311void HMWSoln::getPartialMolarEntropies(doublereal* sbar) const
312{
313 // Get the standard state entropies at the temperature and pressure of the
314 // solution.
315 getEntropy_R(sbar);
316
317 // Dimensionalize the entropies
318 for (size_t k = 0; k < m_kk; k++) {
319 sbar[k] *= GasConstant;
320 }
321
322 // Update the activity coefficients, This also update the internally stored
323 // molalities.
324 s_update_lnMolalityActCoeff();
325
326 // First we will add in the obvious dependence on the T term out front of
327 // the log activity term
328 doublereal mm;
329 for (size_t k = 1; k < m_kk; k++) {
330 mm = std::max(SmallNumber, m_molalities[k]);
331 sbar[k] -= GasConstant * (log(mm) + m_lnActCoeffMolal_Scaled[k]);
332 }
333 double xmolSolvent = moleFraction(0);
334 mm = std::max(SmallNumber, xmolSolvent);
335 sbar[0] -= GasConstant *(log(mm) + m_lnActCoeffMolal_Scaled[0]);
336
337 // Check to see whether activity coefficients are temperature dependent. If
338 // they are, then calculate the their temperature derivatives and add them
339 // into the result.
341 for (size_t k = 0; k < m_kk; k++) {
342 sbar[k] -= RT() * m_dlnActCoeffMolaldT_Scaled[k];
343 }
344}
345
346void HMWSoln::getPartialMolarVolumes(doublereal* vbar) const
347{
348 // Get the standard state values in m^3 kmol-1
349 getStandardVolumes(vbar);
350
351 // Update the derivatives wrt the activity coefficients.
352 s_update_lnMolalityActCoeff();
354 for (size_t k = 0; k < m_kk; k++) {
355 vbar[k] += RT() * m_dlnActCoeffMolaldP_Scaled[k];
356 }
357}
358
359void HMWSoln::getPartialMolarCp(doublereal* cpbar) const
360{
361 getCp_R(cpbar);
362 for (size_t k = 0; k < m_kk; k++) {
363 cpbar[k] *= GasConstant;
364 }
365
366 // Update the activity coefficients, This also update the internally stored
367 // molalities.
368 s_update_lnMolalityActCoeff();
371 for (size_t k = 0; k < m_kk; k++) {
372 cpbar[k] -= (2.0 * RT() * m_dlnActCoeffMolaldT_Scaled[k] +
374 }
375}
376
377// -------------- Utilities -------------------------------
378
379doublereal HMWSoln::satPressure(doublereal t) {
380 double p_old = pressure();
381 double t_old = temperature();
382 double pres = m_waterSS->satPressure(t);
383
384 // Set the underlying object back to its original state.
385 m_waterSS->setState_TP(t_old, p_old);
386 return pres;
387}
388
389static void check_nParams(const std::string& method, size_t nParams,
390 size_t m_formPitzerTemp)
391{
392 if (m_formPitzerTemp == PITZER_TEMP_CONSTANT && nParams != 1) {
393 throw CanteraError(method, "'constant' temperature model requires one"
394 " coefficient for each of parameter, but {} were given", nParams);
395 } else if (m_formPitzerTemp == PITZER_TEMP_LINEAR && nParams != 2) {
396 throw CanteraError(method, "'linear' temperature model requires two"
397 " coefficients for each parameter, but {} were given", nParams);
398 }
399 if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1 && nParams != 5) {
400 throw CanteraError(method, "'complex' temperature model requires five"
401 " coefficients for each parameter, but {} were given", nParams);
402 }
403}
404
405void HMWSoln::setBinarySalt(const std::string& sp1, const std::string& sp2,
406 size_t nParams, double* beta0, double* beta1, double* beta2,
407 double* Cphi, double alpha1, double alpha2)
408{
409 size_t k1 = speciesIndex(sp1);
410 size_t k2 = speciesIndex(sp2);
411 if (k1 == npos) {
412 throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' not found", sp1);
413 } else if (k2 == npos) {
414 throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' not found", sp2);
415 }
416 if (charge(k1) < 0 && charge(k2) > 0) {
417 std::swap(k1, k2);
418 } else if (charge(k1) * charge(k2) >= 0) {
419 throw CanteraError("HMWSoln::setBinarySalt", "Species '{}' and '{}' "
420 "do not have opposite charges ({}, {})", sp1, sp2,
421 charge(k1), charge(k2));
422 }
423 check_nParams("HMWSoln::setBinarySalt", nParams, m_formPitzerTemp);
424
425 size_t c = m_CounterIJ[k1 * m_kk + k2];
426 m_Beta0MX_ij[c] = beta0[0];
427 m_Beta1MX_ij[c] = beta1[0];
428 m_Beta2MX_ij[c] = beta2[0];
429 m_CphiMX_ij[c] = Cphi[0];
430 for (size_t n = 0; n < nParams; n++) {
431 m_Beta0MX_ij_coeff(n, c) = beta0[n];
432 m_Beta1MX_ij_coeff(n, c) = beta1[n];
433 m_Beta2MX_ij_coeff(n, c) = beta2[n];
434 m_CphiMX_ij_coeff(n, c) = Cphi[n];
435 }
436 m_Alpha1MX_ij[c] = alpha1;
437 m_Alpha2MX_ij[c] = alpha2;
438}
439
440void HMWSoln::setTheta(const std::string& sp1, const std::string& sp2,
441 size_t nParams, double* theta)
442{
443 size_t k1 = speciesIndex(sp1);
444 size_t k2 = speciesIndex(sp2);
445 if (k1 == npos) {
446 throw CanteraError("HMWSoln::setTheta", "Species '{}' not found", sp1);
447 } else if (k2 == npos) {
448 throw CanteraError("HMWSoln::setTheta", "Species '{}' not found", sp2);
449 }
450 if (charge(k1) * charge(k2) <= 0) {
451 throw CanteraError("HMWSoln::setTheta", "Species '{}' and '{}' "
452 "should both have the same (non-zero) charge ({}, {})", sp1, sp2,
453 charge(k1), charge(k2));
454 }
455 check_nParams("HMWSoln::setTheta", nParams, m_formPitzerTemp);
456 size_t c = m_CounterIJ[k1 * m_kk + k2];
457 m_Theta_ij[c] = theta[0];
458 for (size_t n = 0; n < nParams; n++) {
459 m_Theta_ij_coeff(n, c) = theta[n];
460 }
461}
462
463void HMWSoln::setPsi(const std::string& sp1, const std::string& sp2,
464 const std::string& sp3, size_t nParams, double* psi)
465{
466 size_t k1 = speciesIndex(sp1);
467 size_t k2 = speciesIndex(sp2);
468 size_t k3 = speciesIndex(sp3);
469 if (k1 == npos) {
470 throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp1);
471 } else if (k2 == npos) {
472 throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp2);
473 } else if (k3 == npos) {
474 throw CanteraError("HMWSoln::setPsi", "Species '{}' not found", sp3);
475 }
476
477 if (!charge(k1) || !charge(k2) || !charge(k3) ||
478 std::abs(sign(charge(k1) + sign(charge(k2)) + sign(charge(k3)))) != 1) {
479 throw CanteraError("HMWSoln::setPsi", "All species must be ions and"
480 " must include at least one cation and one anion, but given species"
481 " (charges) were: {} ({}), {} ({}), and {} ({}).",
482 sp1, charge(k1), sp2, charge(k2), sp3, charge(k3));
483 }
484 check_nParams("HMWSoln::setPsi", nParams, m_formPitzerTemp);
485 auto cc = {k1*m_kk*m_kk + k2*m_kk + k3,
486 k1*m_kk*m_kk + k3*m_kk + k2,
487 k2*m_kk*m_kk + k1*m_kk + k3,
488 k2*m_kk*m_kk + k3*m_kk + k1,
489 k3*m_kk*m_kk + k2*m_kk + k1,
490 k3*m_kk*m_kk + k1*m_kk + k2};
491 for (auto c : cc) {
492 for (size_t n = 0; n < nParams; n++) {
493 m_Psi_ijk_coeff(n, c) = psi[n];
494 }
495 m_Psi_ijk[c] = psi[0];
496 }
497}
498
499void HMWSoln::setLambda(const std::string& sp1, const std::string& sp2,
500 size_t nParams, double* lambda)
501{
502 size_t k1 = speciesIndex(sp1);
503 size_t k2 = speciesIndex(sp2);
504 if (k1 == npos) {
505 throw CanteraError("HMWSoln::setLambda", "Species '{}' not found", sp1);
506 } else if (k2 == npos) {
507 throw CanteraError("HMWSoln::setLambda", "Species '{}' not found", sp2);
508 }
509
510 if (charge(k1) != 0 && charge(k2) != 0) {
511 throw CanteraError("HMWSoln::setLambda", "Expected at least one neutral"
512 " species, but given species (charges) were: {} ({}) and {} ({}).",
513 sp1, charge(k1), sp2, charge(k2));
514 }
515 if (charge(k1) != 0) {
516 std::swap(k1, k2);
517 }
518 check_nParams("HMWSoln::setLambda", nParams, m_formPitzerTemp);
519 size_t c = k1*m_kk + k2;
520 for (size_t n = 0; n < nParams; n++) {
521 m_Lambda_nj_coeff(n, c) = lambda[n];
522 }
523 m_Lambda_nj(k1, k2) = lambda[0];
524}
525
526void HMWSoln::setMunnn(const std::string& sp, size_t nParams, double* munnn)
527{
528 size_t k = speciesIndex(sp);
529 if (k == npos) {
530 throw CanteraError("HMWSoln::setMunnn", "Species '{}' not found", sp);
531 }
532
533 if (charge(k) != 0) {
534 throw CanteraError("HMWSoln::setMunnn", "Expected a neutral species,"
535 " got {} ({}).", sp, charge(k));
536 }
537 check_nParams("HMWSoln::setMunnn", nParams, m_formPitzerTemp);
538 for (size_t n = 0; n < nParams; n++) {
539 m_Mu_nnn_coeff(n, k) = munnn[n];
540 }
541 m_Mu_nnn[k] = munnn[0];
542}
543
544void HMWSoln::setZeta(const std::string& sp1, const std::string& sp2,
545 const std::string& sp3, size_t nParams, double* psi)
546{
547 size_t k1 = speciesIndex(sp1);
548 size_t k2 = speciesIndex(sp2);
549 size_t k3 = speciesIndex(sp3);
550 if (k1 == npos) {
551 throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp1);
552 } else if (k2 == npos) {
553 throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp2);
554 } else if (k3 == npos) {
555 throw CanteraError("HMWSoln::setZeta", "Species '{}' not found", sp3);
556 }
557
558 if (charge(k1)*charge(k2)*charge(k3) != 0 ||
559 sign(charge(k1)) + sign(charge(k2)) + sign(charge(k3)) != 0) {
560 throw CanteraError("HMWSoln::setZeta", "Requires one neutral species, "
561 "one cation, and one anion, but given species (charges) were: "
562 "{} ({}), {} ({}), and {} ({}).",
563 sp1, charge(k1), sp2, charge(k2), sp3, charge(k3));
564 }
565
566 //! Make k1 the neutral species
567 if (charge(k2) == 0) {
568 std::swap(k1, k2);
569 } else if (charge(k3) == 0) {
570 std::swap(k1, k3);
571 }
572
573 // Make k2 the cation
574 if (charge(k3) > 0) {
575 std::swap(k2, k3);
576 }
577
578 check_nParams("HMWSoln::setZeta", nParams, m_formPitzerTemp);
579 // In contrast to setPsi, there are no duplicate entries
580 size_t c = k1 * m_kk *m_kk + k2 * m_kk + k3;
581 for (size_t n = 0; n < nParams; n++) {
582 m_Psi_ijk_coeff(n, c) = psi[n];
583 }
584 m_Psi_ijk[c] = psi[0];
585}
586
587void HMWSoln::setPitzerTempModel(const std::string& model)
588{
589 if (caseInsensitiveEquals(model, "constant") || caseInsensitiveEquals(model, "default")) {
590 m_formPitzerTemp = PITZER_TEMP_CONSTANT;
591 } else if (caseInsensitiveEquals(model, "linear")) {
592 m_formPitzerTemp = PITZER_TEMP_LINEAR;
593 } else if (caseInsensitiveEquals(model, "complex") || caseInsensitiveEquals(model, "complex1")) {
594 m_formPitzerTemp = PITZER_TEMP_COMPLEX1;
595 } else {
596 throw CanteraError("HMWSoln::setPitzerTempModel",
597 "Unknown Pitzer ActivityCoeff Temp model: {}", model);
598 }
599}
600
602{
603 if (A < 0) {
604 m_form_A_Debye = A_DEBYE_WATER;
605 } else {
606 m_form_A_Debye = A_DEBYE_CONST;
607 m_A_Debye = A;
608 }
609}
610
611void HMWSoln::setCroppingCoefficients(double ln_gamma_k_min,
612 double ln_gamma_k_max, double ln_gamma_o_min, double ln_gamma_o_max)
613{
614 CROP_ln_gamma_k_min = ln_gamma_k_min;
615 CROP_ln_gamma_k_max = ln_gamma_k_max;
616 CROP_ln_gamma_o_min = ln_gamma_o_min;
617 CROP_ln_gamma_o_max = ln_gamma_o_max;
618}
619
620vector_fp getSizedVector(const AnyMap& item, const std::string& key, size_t nCoeffs)
621{
622 vector_fp v;
623 if (item[key].is<double>()) {
624 // Allow a single value to be given directly, rather than as a list of
625 // one item
626 v.push_back(item[key].asDouble());
627 } else {
628 v = item[key].asVector<double>(1, nCoeffs);
629 }
630 if (v.size() == 1 && nCoeffs == 5) {
631 // Adapt constant-temperature data to be compatible with the "complex"
632 // temperature model
633 v.resize(5, 0.0);
634 }
635 return v;
636}
637
639{
641 if (m_input.hasKey("activity-data")) {
642 auto& actData = m_input["activity-data"].as<AnyMap>();
643 setPitzerTempModel(actData["temperature-model"].asString());
644 initLengths();
645 size_t nCoeffs = 1;
646 if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
647 nCoeffs = 2;
648 } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
649 nCoeffs = 5;
650 }
651 if (actData.hasKey("A_Debye")) {
652 if (actData["A_Debye"] == "variable") {
653 setA_Debye(-1);
654 } else {
655 setA_Debye(actData.convert("A_Debye", "kg^0.5/gmol^0.5"));
656 }
657 }
658 if (actData.hasKey("max-ionic-strength")) {
659 setMaxIonicStrength(actData["max-ionic-strength"].asDouble());
660 }
661 if (actData.hasKey("interactions")) {
662 for (auto& item : actData["interactions"].asVector<AnyMap>()) {
663 auto& species = item["species"].asVector<string>(1, 3);
664 size_t nsp = species.size();
665 double q0 = charge(speciesIndex(species[0]));
666 double q1 = (nsp > 1) ? charge(speciesIndex(species[1])) : 0;
667 double q2 = (nsp == 3) ? charge(speciesIndex(species[2])) : 0;
668 if (nsp == 2 && q0 * q1 < 0) {
669 // Two species with opposite charges - binary salt
670 vector_fp beta0 = getSizedVector(item, "beta0", nCoeffs);
671 vector_fp beta1 = getSizedVector(item, "beta1", nCoeffs);
672 vector_fp beta2 = getSizedVector(item, "beta2", nCoeffs);
673 vector_fp Cphi = getSizedVector(item, "Cphi", nCoeffs);
674 if (beta0.size() != beta1.size() || beta0.size() != beta2.size()
675 || beta0.size() != Cphi.size()) {
676 throw InputFileError("HMWSoln::initThermo", item,
677 "Inconsistent binary salt array sizes ({}, {}, {}, {})",
678 beta0.size(), beta1.size(), beta2.size(), Cphi.size());
679 }
680 double alpha1 = item["alpha1"].asDouble();
681 double alpha2 = item.getDouble("alpha2", 0.0);
682 setBinarySalt(species[0], species[1], beta0.size(),
683 beta0.data(), beta1.data(), beta2.data(), Cphi.data(),
684 alpha1, alpha2);
685 } else if (nsp == 2 && q0 * q1 > 0) {
686 // Two species with like charges - "theta" interaction
687 vector_fp theta = getSizedVector(item, "theta", nCoeffs);
688 setTheta(species[0], species[1], theta.size(), theta.data());
689 } else if (nsp == 2 && q0 * q1 == 0) {
690 // Two species, including at least one neutral
691 vector_fp lambda = getSizedVector(item, "lambda", nCoeffs);
692 setLambda(species[0], species[1], lambda.size(), lambda.data());
693 } else if (nsp == 3 && q0 * q1 * q2 != 0) {
694 // Three charged species - "psi" interaction
695 vector_fp psi = getSizedVector(item, "psi", nCoeffs);
696 setPsi(species[0], species[1], species[2],
697 psi.size(), psi.data());
698 } else if (nsp == 3 && q0 * q1 * q2 == 0) {
699 // Three species, including one neutral
700 vector_fp zeta = getSizedVector(item, "zeta", nCoeffs);
701 setZeta(species[0], species[1], species[2],
702 zeta.size(), zeta.data());
703 } else if (nsp == 1) {
704 // single species (should be neutral)
705 vector_fp mu = getSizedVector(item, "mu", nCoeffs);
706 setMunnn(species[0], mu.size(), mu.data());
707 }
708 }
709 }
710 if (actData.hasKey("cropping-coefficients")) {
711 auto& crop = actData["cropping-coefficients"].as<AnyMap>();
712 setCroppingCoefficients(
713 crop.getDouble("ln_gamma_k_min", crop_ln_gamma_k_min_default),
714 crop.getDouble("ln_gamma_k_max", crop_ln_gamma_k_max_default),
715 crop.getDouble("ln_gamma_o_min", crop_ln_gamma_o_min_default),
716 crop.getDouble("ln_gamma_o_max", crop_ln_gamma_o_max_default));
717 }
718 } else {
719 initLengths();
720 }
721
722 for (int i = 0; i < 17; i++) {
723 elambda[i] = 0.0;
724 elambda1[i] = 0.0;
725 }
726
727 // Store a local pointer to the water standard state model.
728 m_waterSS = providePDSS(0);
729
730 // Initialize the water property calculator. It will share the internal eos
731 // water calculator.
732 m_waterProps.reset(new WaterProps(dynamic_cast<PDSS_Water*>(m_waterSS)));
733
734 // Lastly calculate the charge balance and then add stuff until the charges
735 // compensate
736 vector_fp mf(m_kk, 0.0);
737 getMoleFractions(mf.data());
738 bool notDone = true;
739
740 while (notDone) {
741 double sum = 0.0;
742 size_t kMaxC = npos;
743 double MaxC = 0.0;
744 for (size_t k = 0; k < m_kk; k++) {
745 sum += mf[k] * charge(k);
746 if (fabs(mf[k] * charge(k)) > MaxC) {
747 kMaxC = k;
748 }
749 }
750 size_t kHp = speciesIndex("H+");
751 size_t kOHm = speciesIndex("OH-");
752
753 if (fabs(sum) > 1.0E-30) {
754 if (kHp != npos) {
755 if (mf[kHp] > sum * 1.1) {
756 mf[kHp] -= sum;
757 mf[0] += sum;
758 notDone = false;
759 } else {
760 if (sum > 0.0) {
761 mf[kHp] *= 0.5;
762 mf[0] += mf[kHp];
763 sum -= mf[kHp];
764 }
765 }
766 }
767 if (notDone) {
768 if (kOHm != npos) {
769 if (mf[kOHm] > -sum * 1.1) {
770 mf[kOHm] += sum;
771 mf[0] -= sum;
772 notDone = false;
773 } else {
774 if (sum < 0.0) {
775 mf[kOHm] *= 0.5;
776 mf[0] += mf[kOHm];
777 sum += mf[kOHm];
778 }
779 }
780 }
781 if (notDone && kMaxC != npos) {
782 if (mf[kMaxC] > (1.1 * sum / charge(kMaxC))) {
783 mf[kMaxC] -= sum / charge(kMaxC);
784 mf[0] += sum / charge(kMaxC);
785 } else {
786 mf[kMaxC] *= 0.5;
787 mf[0] += mf[kMaxC];
788 notDone = true;
789 }
790 }
791 }
792 setMoleFractions(mf.data());
793 } else {
794 notDone = false;
795 }
796 }
797
800 setMoleFSolventMin(1.0E-5);
801}
802
803void assignTrimmed(AnyMap& interaction, const std::string& key, vector_fp& values) {
804 while (values.size() > 1 && values.back() == 0) {
805 values.pop_back();
806 }
807 if (values.size() == 1) {
808 interaction[key] = values[0];
809 } else {
810 interaction[key] = values;
811 }
812}
813
814void HMWSoln::getParameters(AnyMap& phaseNode) const
815{
817 AnyMap activityNode;
818 size_t nParams = 1;
819 if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
820 activityNode["temperature-model"] = "linear";
821 nParams = 2;
822 } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
823 activityNode["temperature-model"] = "complex";
824 nParams = 5;
825 }
826
827 if (m_form_A_Debye == A_DEBYE_WATER) {
828 activityNode["A_Debye"] = "variable";
829 } else if (m_A_Debye != A_Debye_default) {
830 activityNode["A_Debye"].setQuantity(m_A_Debye, "kg^0.5/gmol^0.5");
831 }
832 if (m_maxIionicStrength != maxIionicStrength_default) {
833 activityNode["max-ionic-strength"] = m_maxIionicStrength;
834 }
835
836 vector<AnyMap> interactions;
837
838 // Binary interactions
839 for (size_t i = 1; i < m_kk; i++) {
840 for (size_t j = 1; j < m_kk; j++) {
841 size_t c = i*m_kk + j;
842 // lambda: neutral-charged / neutral-neutral interactions
843 bool lambda_found = false;
844 for (size_t n = 0; n < nParams; n++) {
845 if (m_Lambda_nj_coeff(n, c)) {
846 lambda_found = true;
847 break;
848 }
849 }
850 if (lambda_found) {
851 AnyMap interaction;
852 interaction["species"] = vector<std::string>{
853 speciesName(i), speciesName(j)};
854 vector_fp lambda(nParams);
855 for (size_t n = 0; n < nParams; n++) {
856 lambda[n] = m_Lambda_nj_coeff(n, c);
857 }
858 assignTrimmed(interaction, "lambda", lambda);
859 interactions.push_back(std::move(interaction));
860 continue;
861 }
862
863 c = static_cast<size_t>(m_CounterIJ[m_kk * i + j]);
864 if (c == 0 || i > j) {
865 continue;
866 }
867
868 // beta: opposite charged interactions
869 bool salt_found = false;
870 for (size_t n = 0; n < nParams; n++) {
871 if (m_Beta0MX_ij_coeff(n, c) || m_Beta1MX_ij_coeff(n, c) ||
873 {
874 salt_found = true;
875 break;
876 }
877 }
878 if (salt_found) {
879 AnyMap interaction;
880 interaction["species"] = vector<std::string>{
881 speciesName(i), speciesName(j)};
882 vector_fp beta0(nParams), beta1(nParams), beta2(nParams), Cphi(nParams);
883 size_t last_nonzero = 0;
884 for (size_t n = 0; n < nParams; n++) {
885 beta0[n] = m_Beta0MX_ij_coeff(n, c);
886 beta1[n] = m_Beta1MX_ij_coeff(n, c);
887 beta2[n] = m_Beta2MX_ij_coeff(n, c);
888 Cphi[n] = m_CphiMX_ij_coeff(n, c);
889 if (beta0[n] || beta1[n] || beta2[n] || Cphi[n]) {
890 last_nonzero = n;
891 }
892 }
893 if (last_nonzero == 0) {
894 interaction["beta0"] = beta0[0];
895 interaction["beta1"] = beta1[0];
896 interaction["beta2"] = beta2[0];
897 interaction["Cphi"] = Cphi[0];
898 } else {
899 beta0.resize(1 + last_nonzero);
900 beta1.resize(1 + last_nonzero);
901 beta2.resize(1 + last_nonzero);
902 Cphi.resize(1 + last_nonzero);
903 interaction["beta0"] = beta0;
904 interaction["beta1"] = beta1;
905 interaction["beta2"] = beta2;
906 interaction["Cphi"] = Cphi;
907 }
908 interaction["alpha1"] = m_Alpha1MX_ij[c];
909 if (m_Alpha2MX_ij[c]) {
910 interaction["alpha2"] = m_Alpha2MX_ij[c];
911 }
912 interactions.push_back(std::move(interaction));
913 continue;
914 }
915
916 // theta: like-charge interactions
917 bool theta_found = false;
918 for (size_t n = 0; n < nParams; n++) {
919 if (m_Theta_ij_coeff(n, c)) {
920 theta_found = true;
921 break;
922 }
923 }
924 if (theta_found) {
925 AnyMap interaction;
926 interaction["species"] = vector<std::string>{
927 speciesName(i), speciesName(j)};
928 vector_fp theta(nParams);
929 for (size_t n = 0; n < nParams; n++) {
930 theta[n] = m_Theta_ij_coeff(n, c);
931 }
932 assignTrimmed(interaction, "theta", theta);
933 interactions.push_back(std::move(interaction));
934 continue;
935 }
936 }
937 }
938
939 // psi: ternary charged species interactions
940 // Need to check species charges because both psi and zeta parameters
941 // are stored in m_Psi_ijk_coeff
942 for (size_t i = 1; i < m_kk; i++) {
943 if (charge(i) == 0) {
944 continue;
945 }
946 for (size_t j = i + 1; j < m_kk; j++) {
947 if (charge(j) == 0) {
948 continue;
949 }
950 for (size_t k = j + 1; k < m_kk; k++) {
951 if (charge(k) == 0) {
952 continue;
953 }
954 size_t c = i*m_kk*m_kk + j*m_kk + k;
955 for (size_t n = 0; n < nParams; n++) {
956 if (m_Psi_ijk_coeff(n, c) != 0) {
957 AnyMap interaction;
958 interaction["species"] = vector<std::string>{
960 vector_fp psi(nParams);
961 for (size_t m = 0; m < nParams; m++) {
962 psi[m] = m_Psi_ijk_coeff(m, c);
963 }
964 assignTrimmed(interaction, "psi", psi);
965 interactions.push_back(std::move(interaction));
966 break;
967 }
968 }
969 }
970 }
971 }
972
973 // zeta: neutral-cation-anion interactions
974 for (size_t i = 1; i < m_kk; i++) {
975 if (charge(i) != 0) {
976 continue; // first species must be neutral
977 }
978 for (size_t j = 1; j < m_kk; j++) {
979 if (charge(j) <= 0) {
980 continue; // second species must be cation
981 }
982 for (size_t k = 1; k < m_kk; k++) {
983 if (charge(k) >= 0) {
984 continue; // third species must be anion
985 }
986 size_t c = i*m_kk*m_kk + j*m_kk + k;
987 for (size_t n = 0; n < nParams; n++) {
988 if (m_Psi_ijk_coeff(n, c) != 0) {
989 AnyMap interaction;
990 interaction["species"] = vector<std::string>{
992 vector_fp zeta(nParams);
993 for (size_t m = 0; m < nParams; m++) {
994 zeta[m] = m_Psi_ijk_coeff(m, c);
995 }
996 assignTrimmed(interaction, "zeta", zeta);
997 interactions.push_back(std::move(interaction));
998 break;
999 }
1000 }
1001 }
1002 }
1003 }
1004
1005 // mu: neutral self-interaction
1006 for (size_t i = 1; i < m_kk; i++) {
1007 for (size_t n = 0; n < nParams; n++) {
1008 if (m_Mu_nnn_coeff(n, i) != 0) {
1009 AnyMap interaction;
1010 interaction["species"] = vector<std::string>{speciesName(i)};
1011 vector_fp mu(nParams);
1012 for (size_t m = 0; m < nParams; m++) {
1013 mu[m] = m_Mu_nnn_coeff(m, i);
1014 }
1015 assignTrimmed(interaction, "mu", mu);
1016 interactions.push_back(std::move(interaction));
1017 break;
1018 }
1019 }
1020 }
1021
1022 activityNode["interactions"] = std::move(interactions);
1023
1024 AnyMap croppingCoeffs;
1025 if (CROP_ln_gamma_k_min != crop_ln_gamma_k_min_default) {
1026 croppingCoeffs["ln_gamma_k_min"] = CROP_ln_gamma_k_min;
1027 }
1028 if (CROP_ln_gamma_k_max != crop_ln_gamma_k_max_default) {
1029 croppingCoeffs["ln_gamma_k_max"] = CROP_ln_gamma_k_max;
1030 }
1031 if (CROP_ln_gamma_o_min != crop_ln_gamma_o_min_default) {
1032 croppingCoeffs["ln_gamma_o_min"] = CROP_ln_gamma_o_min;
1033 }
1034 if (CROP_ln_gamma_o_max != crop_ln_gamma_o_max_default) {
1035 croppingCoeffs["ln_gamma_o_max"] = CROP_ln_gamma_o_max;
1036 }
1037 if (croppingCoeffs.size()) {
1038 activityNode["cropping-coefficients"] = std::move(croppingCoeffs);
1039 }
1040
1041 phaseNode["activity-data"] = std::move(activityNode);
1042}
1043
1044void HMWSoln::initThermoXML(XML_Node& phaseNode, const std::string& id_)
1045{
1046 if (id_.size() > 0) {
1047 string idp = phaseNode.id();
1048 if (idp != id_) {
1049 throw CanteraError("HMWSoln::initThermoXML",
1050 "phasenode and Id are incompatible");
1051 }
1052 }
1053
1054 // Find the Thermo XML node
1055 if (!phaseNode.hasChild("thermo")) {
1056 throw CanteraError("HMWSoln::initThermoXML",
1057 "no thermo XML node");
1058 }
1059 XML_Node& thermoNode = phaseNode.child("thermo");
1060
1061 // Determine the form of the Pitzer model, We will use this information to
1062 // size arrays below.
1063 if (thermoNode.hasChild("activityCoefficients")) {
1064 XML_Node& scNode = thermoNode.child("activityCoefficients");
1065
1066 // Determine the form of the temperature dependence of the Pitzer
1067 // activity coefficient model.
1068 string formString = scNode.attrib("TempModel");
1069 if (formString != "") {
1070 setPitzerTempModel(formString);
1071 }
1072
1073 // Determine the reference temperature of the Pitzer activity
1074 // coefficient model's temperature dependence formulation: defaults to
1075 // 25C
1076 formString = scNode.attrib("TempReference");
1077 if (formString != "") {
1078 setPitzerRefTemperature(fpValueCheck(formString));
1079 }
1080 }
1081
1082 // Initialize all of the lengths of arrays in the object
1083 // now that we know what species are in the phase.
1084 initLengths();
1085
1086 // Go get all of the coefficients and factors in the activityCoefficients
1087 // XML block
1088 if (thermoNode.hasChild("activityCoefficients")) {
1089 XML_Node& acNode = thermoNode.child("activityCoefficients");
1090
1091 // Look for parameters for A_Debye
1092 if (acNode.hasChild("A_Debye")) {
1093 XML_Node& ADebye = acNode.child("A_Debye");
1094 if (caseInsensitiveEquals(ADebye["model"], "water")) {
1095 setA_Debye(-1);
1096 } else {
1097 setA_Debye(getFloat(acNode, "A_Debye"));
1098 }
1099 }
1100
1101 // Look for Parameters for the Maximum Ionic Strength
1102 if (acNode.hasChild("maxIonicStrength")) {
1103 setMaxIonicStrength(getFloat(acNode, "maxIonicStrength"));
1104 }
1105
1106 for (const auto& xmlACChild : acNode.children()) {
1107 string nodeName = xmlACChild->name();
1108
1109 // Process any of the XML fields that make up the Pitzer Database.
1110 // Entries will be ignored if any of the species in the entry aren't
1111 // in the solution.
1112 if (caseInsensitiveEquals(nodeName, "binarysaltparameters")) {
1113 readXMLBinarySalt(*xmlACChild);
1114 } else if (caseInsensitiveEquals(nodeName, "thetaanion")) {
1115 readXMLTheta(*xmlACChild);
1116 } else if (caseInsensitiveEquals(nodeName, "thetacation")) {
1117 readXMLTheta(*xmlACChild);
1118 } else if (caseInsensitiveEquals(nodeName, "psicommonanion")) {
1119 readXMLPsi(*xmlACChild);
1120 } else if (caseInsensitiveEquals(nodeName, "psicommoncation")) {
1121 readXMLPsi(*xmlACChild);
1122 } else if (caseInsensitiveEquals(nodeName, "lambdaneutral")) {
1123 readXMLLambdaNeutral(*xmlACChild);
1124 } else if (caseInsensitiveEquals(nodeName, "zetacation")) {
1125 readXMLZetaCation(*xmlACChild);
1126 }
1127 }
1128
1129 // Go look up the optional Cropping parameters
1130 if (acNode.hasChild("croppingCoefficients")) {
1131 XML_Node& cropNode = acNode.child("croppingCoefficients");
1132 setCroppingCoefficients(
1133 getFloat(cropNode.child("ln_gamma_k_min"), "pureSolventValue"),
1134 getFloat(cropNode.child("ln_gamma_k_max"), "pureSolventValue"),
1135 getFloat(cropNode.child("ln_gamma_o_min"), "pureSolventValue"),
1136 getFloat(cropNode.child("ln_gamma_o_max"), "pureSolventValue"));
1137 }
1138 }
1139
1140 MolalityVPSSTP::initThermoXML(phaseNode, id_);
1141}
1142
1143double HMWSoln::A_Debye_TP(double tempArg, double presArg) const
1144{
1145 double T = temperature();
1146 double A;
1147 if (tempArg != -1.0) {
1148 T = tempArg;
1149 }
1150 double P = pressure();
1151 if (presArg != -1.0) {
1152 P = presArg;
1153 }
1154
1155 static const int cacheId = m_cache.getId();
1156 CachedScalar cached = m_cache.getScalar(cacheId);
1157 if(cached.validate(T, P)) {
1158 return m_A_Debye;
1159 }
1160
1161 switch (m_form_A_Debye) {
1162 case A_DEBYE_CONST:
1163 A = m_A_Debye;
1164 break;
1165 case A_DEBYE_WATER:
1166 A = m_waterProps->ADebye(T, P, 0);
1167 m_A_Debye = A;
1168 break;
1169 default:
1170 throw CanteraError("HMWSoln::A_Debye_TP", "shouldn't be here");
1171 }
1172 return A;
1173}
1174
1175double HMWSoln::dA_DebyedT_TP(double tempArg, double presArg) const
1176{
1177 doublereal T = temperature();
1178 if (tempArg != -1.0) {
1179 T = tempArg;
1180 }
1181 doublereal P = pressure();
1182 if (presArg != -1.0) {
1183 P = presArg;
1184 }
1185 doublereal dAdT;
1186 switch (m_form_A_Debye) {
1187 case A_DEBYE_CONST:
1188 dAdT = 0.0;
1189 break;
1190 case A_DEBYE_WATER:
1191 dAdT = m_waterProps->ADebye(T, P, 1);
1192 break;
1193 default:
1194 throw CanteraError("HMWSoln::dA_DebyedT_TP", "shouldn't be here");
1195 }
1196 return dAdT;
1197}
1198
1199double HMWSoln::dA_DebyedP_TP(double tempArg, double presArg) const
1200{
1201 double T = temperature();
1202 if (tempArg != -1.0) {
1203 T = tempArg;
1204 }
1205 double P = pressure();
1206 if (presArg != -1.0) {
1207 P = presArg;
1208 }
1209
1210 double dAdP;
1211 static const int cacheId = m_cache.getId();
1212 CachedScalar cached = m_cache.getScalar(cacheId);
1213 switch (m_form_A_Debye) {
1214 case A_DEBYE_CONST:
1215 dAdP = 0.0;
1216 break;
1217 case A_DEBYE_WATER:
1218 if(cached.validate(T, P)) {
1219 dAdP = cached.value;
1220 } else {
1221 dAdP = m_waterProps->ADebye(T, P, 3);
1222 cached.value = dAdP;
1223 }
1224 break;
1225 default:
1226 throw CanteraError("HMWSoln::dA_DebyedP_TP", "shouldn't be here");
1227 }
1228 return dAdP;
1229}
1230
1231double HMWSoln::ADebye_L(double tempArg, double presArg) const
1232{
1233 double dAdT = dA_DebyedT_TP();
1234 double dAphidT = dAdT /3.0;
1235 double T = temperature();
1236 if (tempArg != -1.0) {
1237 T = tempArg;
1238 }
1239 return dAphidT * (4.0 * GasConstant * T * T);
1240}
1241
1242double HMWSoln::ADebye_V(double tempArg, double presArg) const
1243{
1244 double dAdP = dA_DebyedP_TP();
1245 double dAphidP = dAdP /3.0;
1246 double T = temperature();
1247 if (tempArg != -1.0) {
1248 T = tempArg;
1249 }
1250 return - dAphidP * (4.0 * GasConstant * T);
1251}
1252
1253double HMWSoln::ADebye_J(double tempArg, double presArg) const
1254{
1255 double T = temperature();
1256 if (tempArg != -1.0) {
1257 T = tempArg;
1258 }
1259 double A_L = ADebye_L(T, presArg);
1260 double d2 = d2A_DebyedT2_TP(T, presArg);
1261 double d2Aphi = d2 / 3.0;
1262 return 2.0 * A_L / T + 4.0 * GasConstant * T * T *d2Aphi;
1263}
1264
1265double HMWSoln::d2A_DebyedT2_TP(double tempArg, double presArg) const
1266{
1267 double T = temperature();
1268 if (tempArg != -1.0) {
1269 T = tempArg;
1270 }
1271 double P = pressure();
1272 if (presArg != -1.0) {
1273 P = presArg;
1274 }
1275 double d2AdT2;
1276 switch (m_form_A_Debye) {
1277 case A_DEBYE_CONST:
1278 d2AdT2 = 0.0;
1279 break;
1280 case A_DEBYE_WATER:
1281 d2AdT2 = m_waterProps->ADebye(T, P, 2);
1282 break;
1283 default:
1284 throw CanteraError("HMWSoln::d2A_DebyedT2_TP", "shouldn't be here");
1285 }
1286 return d2AdT2;
1287}
1288
1289// ---------- Other Property Functions
1290
1291// ------------ Private and Restricted Functions ------------------
1292
1294{
1295 m_tmpV.resize(m_kk, 0.0);
1296 m_molalitiesCropped.resize(m_kk, 0.0);
1297
1298 size_t maxCounterIJlen = 1 + (m_kk-1) * (m_kk-2) / 2;
1299
1300 // Figure out the size of the temperature coefficient arrays
1301 int TCoeffLength = 1;
1302 if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
1303 TCoeffLength = 2;
1304 } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1305 TCoeffLength = 5;
1306 }
1307
1308 m_Beta0MX_ij.resize(maxCounterIJlen, 0.0);
1309 m_Beta0MX_ij_L.resize(maxCounterIJlen, 0.0);
1310 m_Beta0MX_ij_LL.resize(maxCounterIJlen, 0.0);
1311 m_Beta0MX_ij_P.resize(maxCounterIJlen, 0.0);
1312 m_Beta0MX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
1313
1314 m_Beta1MX_ij.resize(maxCounterIJlen, 0.0);
1315 m_Beta1MX_ij_L.resize(maxCounterIJlen, 0.0);
1316 m_Beta1MX_ij_LL.resize(maxCounterIJlen, 0.0);
1317 m_Beta1MX_ij_P.resize(maxCounterIJlen, 0.0);
1318 m_Beta1MX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
1319
1320 m_Beta2MX_ij.resize(maxCounterIJlen, 0.0);
1321 m_Beta2MX_ij_L.resize(maxCounterIJlen, 0.0);
1322 m_Beta2MX_ij_LL.resize(maxCounterIJlen, 0.0);
1323 m_Beta2MX_ij_P.resize(maxCounterIJlen, 0.0);
1324 m_Beta2MX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
1325
1326 m_CphiMX_ij.resize(maxCounterIJlen, 0.0);
1327 m_CphiMX_ij_L.resize(maxCounterIJlen, 0.0);
1328 m_CphiMX_ij_LL.resize(maxCounterIJlen, 0.0);
1329 m_CphiMX_ij_P.resize(maxCounterIJlen, 0.0);
1330 m_CphiMX_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
1331
1332 m_Alpha1MX_ij.resize(maxCounterIJlen, 2.0);
1333 m_Alpha2MX_ij.resize(maxCounterIJlen, 12.0);
1334 m_Theta_ij.resize(maxCounterIJlen, 0.0);
1335 m_Theta_ij_L.resize(maxCounterIJlen, 0.0);
1336 m_Theta_ij_LL.resize(maxCounterIJlen, 0.0);
1337 m_Theta_ij_P.resize(maxCounterIJlen, 0.0);
1338 m_Theta_ij_coeff.resize(TCoeffLength, maxCounterIJlen, 0.0);
1339
1340 size_t n = m_kk*m_kk*m_kk;
1341 m_Psi_ijk.resize(n, 0.0);
1342 m_Psi_ijk_L.resize(n, 0.0);
1343 m_Psi_ijk_LL.resize(n, 0.0);
1344 m_Psi_ijk_P.resize(n, 0.0);
1345 m_Psi_ijk_coeff.resize(TCoeffLength, n, 0.0);
1346
1347 m_Lambda_nj.resize(m_kk, m_kk, 0.0);
1351 m_Lambda_nj_coeff.resize(TCoeffLength, m_kk * m_kk, 0.0);
1352
1353 m_Mu_nnn.resize(m_kk, 0.0);
1354 m_Mu_nnn_L.resize(m_kk, 0.0);
1355 m_Mu_nnn_LL.resize(m_kk, 0.0);
1356 m_Mu_nnn_P.resize(m_kk, 0.0);
1357 m_Mu_nnn_coeff.resize(TCoeffLength, m_kk, 0.0);
1358
1359 m_lnActCoeffMolal_Scaled.resize(m_kk, 0.0);
1360 m_dlnActCoeffMolaldT_Scaled.resize(m_kk, 0.0);
1362 m_dlnActCoeffMolaldP_Scaled.resize(m_kk, 0.0);
1363 m_lnActCoeffMolal_Unscaled.resize(m_kk, 0.0);
1367
1368 m_CounterIJ.resize(m_kk*m_kk, 0);
1369 m_gfunc_IJ.resize(maxCounterIJlen, 0.0);
1370 m_g2func_IJ.resize(maxCounterIJlen, 0.0);
1371 m_hfunc_IJ.resize(maxCounterIJlen, 0.0);
1372 m_h2func_IJ.resize(maxCounterIJlen, 0.0);
1373 m_BMX_IJ.resize(maxCounterIJlen, 0.0);
1374 m_BMX_IJ_L.resize(maxCounterIJlen, 0.0);
1375 m_BMX_IJ_LL.resize(maxCounterIJlen, 0.0);
1376 m_BMX_IJ_P.resize(maxCounterIJlen, 0.0);
1377 m_BprimeMX_IJ.resize(maxCounterIJlen, 0.0);
1378 m_BprimeMX_IJ_L.resize(maxCounterIJlen, 0.0);
1379 m_BprimeMX_IJ_LL.resize(maxCounterIJlen, 0.0);
1380 m_BprimeMX_IJ_P.resize(maxCounterIJlen, 0.0);
1381 m_BphiMX_IJ.resize(maxCounterIJlen, 0.0);
1382 m_BphiMX_IJ_L.resize(maxCounterIJlen, 0.0);
1383 m_BphiMX_IJ_LL.resize(maxCounterIJlen, 0.0);
1384 m_BphiMX_IJ_P.resize(maxCounterIJlen, 0.0);
1385 m_Phi_IJ.resize(maxCounterIJlen, 0.0);
1386 m_Phi_IJ_L.resize(maxCounterIJlen, 0.0);
1387 m_Phi_IJ_LL.resize(maxCounterIJlen, 0.0);
1388 m_Phi_IJ_P.resize(maxCounterIJlen, 0.0);
1389 m_Phiprime_IJ.resize(maxCounterIJlen, 0.0);
1390 m_PhiPhi_IJ.resize(maxCounterIJlen, 0.0);
1391 m_PhiPhi_IJ_L.resize(maxCounterIJlen, 0.0);
1392 m_PhiPhi_IJ_LL.resize(maxCounterIJlen, 0.0);
1393 m_PhiPhi_IJ_P.resize(maxCounterIJlen, 0.0);
1394 m_CMX_IJ.resize(maxCounterIJlen, 0.0);
1395 m_CMX_IJ_L.resize(maxCounterIJlen, 0.0);
1396 m_CMX_IJ_LL.resize(maxCounterIJlen, 0.0);
1397 m_CMX_IJ_P.resize(maxCounterIJlen, 0.0);
1398
1399 m_gamma_tmp.resize(m_kk, 0.0);
1400 IMS_lnActCoeffMolal_.resize(m_kk, 0.0);
1401 CROP_speciesCropped_.resize(m_kk, 0);
1402
1404}
1405
1406void HMWSoln::s_update_lnMolalityActCoeff() const
1407{
1408 static const int cacheId = m_cache.getId();
1409 CachedScalar cached = m_cache.getScalar(cacheId);
1410 if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
1411 return;
1412 }
1413
1414 // Calculate the molalities. Currently, the molalities may not be current
1415 // with respect to the contents of the State objects' data.
1417
1418 // Calculate a cropped set of molalities that will be used in all activity
1419 // coefficient calculations.
1421
1422 // Update the temperature dependence of the Pitzer coefficients and their
1423 // derivatives
1425
1426 // Calculate the IMS cutoff factors
1428
1429 // Now do the main calculation.
1431
1432 double xmolSolvent = moleFraction(0);
1433 double xx = std::max(m_xmolSolventMIN, xmolSolvent);
1434 double lnActCoeffMolal0 = - log(xx) + (xx - 1.0)/xx;
1435 double lnxs = log(xx);
1436
1437 for (size_t k = 1; k < m_kk; k++) {
1438 CROP_speciesCropped_[k] = 0;
1440 if (m_lnActCoeffMolal_Unscaled[k] > (CROP_ln_gamma_k_max- 2.5 *lnxs)) {
1441 CROP_speciesCropped_[k] = 2;
1442 m_lnActCoeffMolal_Unscaled[k] = CROP_ln_gamma_k_max - 2.5 * lnxs;
1443 }
1444 if (m_lnActCoeffMolal_Unscaled[k] < (CROP_ln_gamma_k_min - 2.5 *lnxs)) {
1445 // -1.0 and -1.5 caused multiple solutions
1446 CROP_speciesCropped_[k] = 2;
1447 m_lnActCoeffMolal_Unscaled[k] = CROP_ln_gamma_k_min - 2.5 * lnxs;
1448 }
1449 }
1450 CROP_speciesCropped_[0] = 0;
1451 m_lnActCoeffMolal_Unscaled[0] += (IMS_lnActCoeffMolal_[0] - lnActCoeffMolal0);
1452 if (m_lnActCoeffMolal_Unscaled[0] < CROP_ln_gamma_o_min) {
1453 CROP_speciesCropped_[0] = 2;
1454 m_lnActCoeffMolal_Unscaled[0] = CROP_ln_gamma_o_min;
1455 }
1456 if (m_lnActCoeffMolal_Unscaled[0] > CROP_ln_gamma_o_max) {
1457 CROP_speciesCropped_[0] = 2;
1458 // -0.5 caused multiple solutions
1459 m_lnActCoeffMolal_Unscaled[0] = CROP_ln_gamma_o_max;
1460 }
1461 if (m_lnActCoeffMolal_Unscaled[0] > CROP_ln_gamma_o_max - 0.5 * lnxs) {
1462 CROP_speciesCropped_[0] = 2;
1463 m_lnActCoeffMolal_Unscaled[0] = CROP_ln_gamma_o_max - 0.5 * lnxs;
1464 }
1465
1466 // Now do the pH Scaling
1468}
1469
1471{
1472 doublereal Imax = 0.0;
1473 m_molalitiesAreCropped = false;
1474
1475 for (size_t k = 0; k < m_kk; k++) {
1477 Imax = std::max(m_molalities[k] * charge(k) * charge(k), Imax);
1478 }
1479
1480 int cropMethod = 1;
1481 if (cropMethod == 0) {
1482 // Quick return
1483 if (Imax < m_maxIionicStrength) {
1484 return;
1485 }
1486
1488 for (size_t i = 1; i < (m_kk - 1); i++) {
1489 double charge_i = charge(i);
1490 double abs_charge_i = fabs(charge_i);
1491 if (charge_i == 0.0) {
1492 continue;
1493 }
1494 for (size_t j = (i+1); j < m_kk; j++) {
1495 double charge_j = charge(j);
1496 double abs_charge_j = fabs(charge_j);
1497
1498 // Only loop over oppositely charge species
1499 if (charge_i * charge_j < 0) {
1500 double Iac_max = m_maxIionicStrength;
1501
1503 Imax = m_molalitiesCropped[i] * abs_charge_i * abs_charge_i;
1504 if (Imax > Iac_max) {
1505 m_molalitiesCropped[i] = Iac_max / (abs_charge_i * abs_charge_i);
1506 }
1507 Imax = m_molalitiesCropped[j] * fabs(abs_charge_j * abs_charge_i);
1508 if (Imax > Iac_max) {
1509 m_molalitiesCropped[j] = Iac_max / (abs_charge_j * abs_charge_i);
1510 }
1511 } else {
1512 Imax = m_molalitiesCropped[j] * abs_charge_j * abs_charge_j;
1513 if (Imax > Iac_max) {
1514 m_molalitiesCropped[j] = Iac_max / (abs_charge_j * abs_charge_j);
1515 }
1516 Imax = m_molalitiesCropped[i] * abs_charge_j * abs_charge_i;
1517 if (Imax > Iac_max) {
1518 m_molalitiesCropped[i] = Iac_max / (abs_charge_j * abs_charge_i);
1519 }
1520 }
1521 }
1522 }
1523 }
1524
1525 // Do this loop 10 times until we have achieved charge neutrality in the
1526 // cropped molalities
1527 for (int times = 0; times< 10; times++) {
1528 double anion_charge = 0.0;
1529 double cation_charge = 0.0;
1530 size_t anion_contrib_max_i = npos;
1531 double anion_contrib_max = -1.0;
1532 size_t cation_contrib_max_i = npos;
1533 double cation_contrib_max = -1.0;
1534 for (size_t i = 0; i < m_kk; i++) {
1535 double charge_i = charge(i);
1536 if (charge_i < 0.0) {
1537 double anion_contrib = - m_molalitiesCropped[i] * charge_i;
1538 anion_charge += anion_contrib;
1539 if (anion_contrib > anion_contrib_max) {
1540 anion_contrib_max = anion_contrib;
1541 anion_contrib_max_i = i;
1542 }
1543 } else if (charge_i > 0.0) {
1544 double cation_contrib = m_molalitiesCropped[i] * charge_i;
1545 cation_charge += cation_contrib;
1546 if (cation_contrib > cation_contrib_max) {
1547 cation_contrib_max = cation_contrib;
1548 cation_contrib_max_i = i;
1549 }
1550 }
1551 }
1552 double total_charge = cation_charge - anion_charge;
1553 if (total_charge > 1.0E-8) {
1554 double desiredCrop = total_charge/charge(cation_contrib_max_i);
1555 double maxCrop = 0.66 * m_molalitiesCropped[cation_contrib_max_i];
1556 if (desiredCrop < maxCrop) {
1557 m_molalitiesCropped[cation_contrib_max_i] -= desiredCrop;
1558 break;
1559 } else {
1560 m_molalitiesCropped[cation_contrib_max_i] -= maxCrop;
1561 }
1562 } else if (total_charge < -1.0E-8) {
1563 double desiredCrop = total_charge/charge(anion_contrib_max_i);
1564 double maxCrop = 0.66 * m_molalitiesCropped[anion_contrib_max_i];
1565 if (desiredCrop < maxCrop) {
1566 m_molalitiesCropped[anion_contrib_max_i] -= desiredCrop;
1567 break;
1568 } else {
1569 m_molalitiesCropped[anion_contrib_max_i] -= maxCrop;
1570 }
1571 } else {
1572 break;
1573 }
1574 }
1575 }
1576
1577 if (cropMethod == 1) {
1578 double* molF = m_gamma_tmp.data();
1579 getMoleFractions(molF);
1580 double xmolSolvent = molF[0];
1581 if (xmolSolvent >= MC_X_o_cutoff_) {
1582 return;
1583 }
1584
1586 double poly = MC_apCut_ + MC_bpCut_ * xmolSolvent + MC_dpCut_* xmolSolvent * xmolSolvent;
1587 double p = xmolSolvent + MC_epCut_ + exp(- xmolSolvent/ MC_cpCut_) * poly;
1588 double denomInv = 1.0/ (m_Mnaught * p);
1589 for (size_t k = 0; k < m_kk; k++) {
1590 m_molalitiesCropped[k] = molF[k] * denomInv;
1591 }
1592
1593 // Do a further check to see if the Ionic strength is below a max value
1594 // Reduce the molalities to enforce this. Note, this algorithm preserves
1595 // the charge neutrality of the solution after cropping.
1596 double Itmp = 0.0;
1597 for (size_t k = 0; k < m_kk; k++) {
1598 Itmp += m_molalitiesCropped[k] * charge(k) * charge(k);
1599 }
1600 if (Itmp > m_maxIionicStrength) {
1601 double ratio = Itmp / m_maxIionicStrength;
1602 for (size_t k = 0; k < m_kk; k++) {
1603 if (charge(k) != 0.0) {
1604 m_molalitiesCropped[k] *= ratio;
1605 }
1606 }
1607 }
1608 }
1609}
1610
1612{
1613 m_CounterIJ.resize(m_kk * m_kk);
1614 int counter = 0;
1615 for (size_t i = 0; i < m_kk; i++) {
1616 size_t n = i;
1617 size_t nc = m_kk * i;
1618 m_CounterIJ[n] = 0;
1619 m_CounterIJ[nc] = 0;
1620 }
1621 for (size_t i = 1; i < (m_kk - 1); i++) {
1622 size_t n = m_kk * i + i;
1623 m_CounterIJ[n] = 0;
1624 for (size_t j = (i+1); j < m_kk; j++) {
1625 n = m_kk * j + i;
1626 size_t nc = m_kk * i + j;
1627 counter++;
1628 m_CounterIJ[n] = counter;
1629 m_CounterIJ[nc] = counter;
1630 }
1631 }
1632}
1633
1635{
1636 if (BinSalt.name() != "binarySaltParameters") {
1637 throw CanteraError("HMWSoln::readXMLBinarySalt",
1638 "Incorrect name for processing this routine: " + BinSalt.name());
1639 }
1640
1641 string iName = BinSalt.attrib("cation");
1642 if (iName == "") {
1643 throw CanteraError("HMWSoln::readXMLBinarySalt", "no cation attrib");
1644 }
1645 string jName = BinSalt.attrib("anion");
1646 if (jName == "") {
1647 throw CanteraError("HMWSoln::readXMLBinarySalt", "no anion attrib");
1648 }
1649
1650 // Find the index of the species in the current phase. It's not an error to
1651 // not find the species
1652 if (speciesIndex(iName) == npos || speciesIndex(jName) == npos) {
1653 return;
1654 }
1655
1656 vector_fp beta0, beta1, beta2, Cphi;
1657 getFloatArray(BinSalt, beta0, false, "", "beta0");
1658 getFloatArray(BinSalt, beta1, false, "", "beta1");
1659 getFloatArray(BinSalt, beta2, false, "", "beta2");
1660 getFloatArray(BinSalt, Cphi, false, "", "Cphi");
1661 if (beta0.size() != beta1.size() || beta0.size() != beta2.size() ||
1662 beta0.size() != Cphi.size()) {
1663 throw CanteraError("HMWSoln::readXMLBinarySalt", "Inconsistent"
1664 " array sizes ({}, {}, {}, {})", beta0.size(), beta1.size(),
1665 beta2.size(), Cphi.size());
1666 }
1667 if (beta0.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1668 beta0.resize(5, 0.0);
1669 beta1.resize(5, 0.0);
1670 beta2.resize(5, 0.0);
1671 Cphi.resize(5, 0.0);
1672 }
1673 double alpha1 = getFloat(BinSalt, "Alpha1");
1674 double alpha2 = 0.0;
1675 getOptionalFloat(BinSalt, "Alpha2", alpha2);
1676 setBinarySalt(iName, jName, beta0.size(), beta0.data(), beta1.data(),
1677 beta2.data(), Cphi.data(), alpha1, alpha2);
1678}
1679
1681{
1682 string ispName, jspName;
1683 if (node.name() == "thetaAnion") {
1684 ispName = node.attrib("anion1");
1685 if (ispName == "") {
1686 throw CanteraError("HMWSoln::readXMLTheta", "no anion1 attrib");
1687 }
1688 jspName = node.attrib("anion2");
1689 if (jspName == "") {
1690 throw CanteraError("HMWSoln::readXMLTheta", "no anion2 attrib");
1691 }
1692 } else if (node.name() == "thetaCation") {
1693 ispName = node.attrib("cation1");
1694 if (ispName == "") {
1695 throw CanteraError("HMWSoln::readXMLTheta", "no cation1 attrib");
1696 }
1697 jspName = node.attrib("cation2");
1698 if (jspName == "") {
1699 throw CanteraError("HMWSoln::readXMLTheta", "no cation2 attrib");
1700 }
1701 } else {
1702 throw CanteraError("HMWSoln::readXMLTheta",
1703 "Incorrect name for processing this routine: " + node.name());
1704 }
1705
1706 // Find the index of the species in the current phase. It's not an error to
1707 // not find the species
1708 if (speciesIndex(ispName) == npos || speciesIndex(jspName) == npos) {
1709 return;
1710 }
1711
1712 vector_fp theta;
1713 getFloatArray(node, theta, false, "", "theta");
1714 if (theta.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1715 theta.resize(5, 0.0);
1716 }
1717 setTheta(ispName, jspName, theta.size(), theta.data());
1718}
1719
1721{
1722 string iName, jName, kName;
1723 if (node.name() == "psiCommonCation") {
1724 kName = node.attrib("cation");
1725 if (kName == "") {
1726 throw CanteraError("HMWSoln::readXMLPsi", "no cation attrib");
1727 }
1728 iName = node.attrib("anion1");
1729 if (iName == "") {
1730 throw CanteraError("HMWSoln::readXMLPsi", "no anion1 attrib");
1731 }
1732 jName = node.attrib("anion2");
1733 if (jName == "") {
1734 throw CanteraError("HMWSoln::readXMLPsi", "no anion2 attrib");
1735 }
1736 } else if (node.name() == "psiCommonAnion") {
1737 kName = node.attrib("anion");
1738 if (kName == "") {
1739 throw CanteraError("HMWSoln::readXMLPsi", "no anion attrib");
1740 }
1741 iName = node.attrib("cation1");
1742 if (iName == "") {
1743 throw CanteraError("HMWSoln::readXMLPsi", "no cation1 attrib");
1744 }
1745 jName = node.attrib("cation2");
1746 if (jName == "") {
1747 throw CanteraError("HMWSoln::readXMLPsi", "no cation2 attrib");
1748 }
1749 } else {
1750 throw CanteraError("HMWSoln::readXMLPsi",
1751 "Incorrect name for processing this routine: " + node.name());
1752 }
1753
1754 // Find the index of the species in the current phase. It's not an error to
1755 // not find the species
1756 if (speciesIndex(iName) == npos || speciesIndex(jName) == npos ||
1757 speciesIndex(kName) == npos) {
1758 return;
1759 }
1760
1761 vector_fp psi;
1762 getFloatArray(node, psi, false, "", "psi");
1763 if (psi.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1764 psi.resize(5, 0.0);
1765 }
1766 setPsi(iName, jName, kName, psi.size(), psi.data());
1767}
1768
1770{
1771 vector_fp vParams;
1772 if (node.name() != "lambdaNeutral") {
1773 throw CanteraError("HMWSoln::readXMLLambdaNeutral",
1774 "Incorrect name for processing this routine: " + node.name());
1775 }
1776 string iName = node.attrib("species1");
1777 if (iName == "") {
1778 throw CanteraError("HMWSoln::readXMLLambdaNeutral", "no species1 attrib");
1779 }
1780 string jName = node.attrib("species2");
1781 if (jName == "") {
1782 throw CanteraError("HMWSoln::readXMLLambdaNeutral", "no species2 attrib");
1783 }
1784
1785 // Find the index of the species in the current phase. It's not an error to
1786 // not find the species
1787 if (speciesIndex(iName) == npos || speciesIndex(jName) == npos) {
1788 return;
1789 }
1790
1791 vector_fp lambda;
1792 getFloatArray(node, lambda, false, "", "lambda");
1793 if (lambda.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1794 lambda.resize(5, 0.0);
1795 }
1796 setLambda(iName, jName, lambda.size(), lambda.data());
1797}
1798
1800{
1801 if (node.name() != "MunnnNeutral") {
1802 throw CanteraError("HMWSoln::readXMLMunnnNeutral",
1803 "Incorrect name for processing this routine: " + node.name());
1804 }
1805 string iName = node.attrib("species1");
1806 if (iName == "") {
1807 throw CanteraError("HMWSoln::readXMLMunnnNeutral", "no species1 attrib");
1808 }
1809
1810 // Find the index of the species in the current phase. It's not an error to
1811 // not find the species
1812 if (speciesIndex(iName) == npos) {
1813 return;
1814 }
1815
1816 vector_fp munnn;
1817 getFloatArray(node, munnn, false, "", "munnn");
1818 if (munnn.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1819 munnn.resize(5, 0.0);
1820 }
1821 setMunnn(iName, munnn.size(), munnn.data());
1822}
1823
1825{
1826 if (node.name() != "zetaCation") {
1827 throw CanteraError("HMWSoln::readXMLZetaCation",
1828 "Incorrect name for processing this routine: " + node.name());
1829 }
1830
1831 string iName = node.attrib("neutral");
1832 if (iName == "") {
1833 throw CanteraError("HMWSoln::readXMLZetaCation", "no neutral attrib");
1834 }
1835 string jName = node.attrib("cation1");
1836 if (jName == "") {
1837 throw CanteraError("HMWSoln::readXMLZetaCation", "no cation1 attrib");
1838 }
1839 string kName = node.attrib("anion1");
1840 if (kName == "") {
1841 throw CanteraError("HMWSoln::readXMLZetaCation", "no anion1 attrib");
1842 }
1843
1844 // Find the index of the species in the current phase. It's not an error to
1845 // not find the species
1846 if (speciesIndex(iName) == npos || speciesIndex(jName) == npos ||
1847 speciesIndex(kName) == npos) {
1848 return;
1849 }
1850
1851 vector_fp zeta;
1852 getFloatArray(node, zeta, false, "", "zeta");
1853 if (zeta.size() == 1 && m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1854 zeta.resize(5, 0.0);
1855 }
1856 setZeta(iName, jName, kName, zeta.size(), zeta.data());
1857}
1858
1860{
1861 double IMS_gamma_o_min_ = 1.0E-5; // value at the zero solvent point
1862 double IMS_gamma_k_min_ = 10.0; // minimum at the zero solvent point
1863 double IMS_slopefCut_ = 0.6; // slope of the f function at the zero solvent point
1864
1865 IMS_afCut_ = 1.0 / (std::exp(1.0) * IMS_gamma_k_min_);
1866 IMS_efCut_ = 0.0;
1867 bool converged = false;
1868 double oldV = 0.0;
1869 for (int its = 0; its < 100 && !converged; its++) {
1870 oldV = IMS_efCut_;
1871 IMS_afCut_ = 1.0 / (std::exp(1.0) * IMS_gamma_k_min_) -IMS_efCut_;
1872 IMS_bfCut_ = IMS_afCut_ / IMS_cCut_ + IMS_slopefCut_ - 1.0;
1873 IMS_dfCut_ = ((- IMS_afCut_/IMS_cCut_ + IMS_bfCut_ - IMS_bfCut_*IMS_X_o_cutoff_/IMS_cCut_)
1874 /
1876 double tmp = IMS_afCut_ + IMS_X_o_cutoff_*(IMS_bfCut_ + IMS_dfCut_ *IMS_X_o_cutoff_);
1877 double eterm = std::exp(-IMS_X_o_cutoff_/IMS_cCut_);
1878 IMS_efCut_ = - eterm * tmp;
1879 if (fabs(IMS_efCut_ - oldV) < 1.0E-14) {
1880 converged = true;
1881 }
1882 }
1883 if (!converged) {
1884 throw CanteraError("HMWSoln::calcIMSCutoffParams_()",
1885 " failed to converge on the f polynomial");
1886 }
1887 converged = false;
1888 double f_0 = IMS_afCut_ + IMS_efCut_;
1889 double f_prime_0 = 1.0 - IMS_afCut_ / IMS_cCut_ + IMS_bfCut_;
1890 IMS_egCut_ = 0.0;
1891 for (int its = 0; its < 100 && !converged; its++) {
1892 oldV = IMS_egCut_;
1893 double lng_0 = -log(IMS_gamma_o_min_) - f_prime_0 / f_0;
1894 IMS_agCut_ = exp(lng_0) - IMS_egCut_;
1895 IMS_bgCut_ = IMS_agCut_ / IMS_cCut_ + IMS_slopegCut_ - 1.0;
1896 IMS_dgCut_ = ((- IMS_agCut_/IMS_cCut_ + IMS_bgCut_ - IMS_bgCut_*IMS_X_o_cutoff_/IMS_cCut_)
1897 /
1899 double tmp = IMS_agCut_ + IMS_X_o_cutoff_*(IMS_bgCut_ + IMS_dgCut_ *IMS_X_o_cutoff_);
1900 double eterm = std::exp(-IMS_X_o_cutoff_/IMS_cCut_);
1901 IMS_egCut_ = - eterm * tmp;
1902 if (fabs(IMS_egCut_ - oldV) < 1.0E-14) {
1903 converged = true;
1904 }
1905 }
1906 if (!converged) {
1907 throw CanteraError("HMWSoln::calcIMSCutoffParams_()",
1908 " failed to converge on the g polynomial");
1909 }
1910}
1911
1913{
1914 double MC_X_o_min_ = 0.35; // value at the zero solvent point
1915 MC_X_o_cutoff_ = 0.6;
1916 double MC_slopepCut_ = 0.02; // slope of the p function at the zero solvent point
1917 MC_cpCut_ = 0.25;
1918
1919 // Initial starting values
1920 MC_apCut_ = MC_X_o_min_;
1921 MC_epCut_ = 0.0;
1922 bool converged = false;
1923 double oldV = 0.0;
1924 double damp = 0.5;
1925 for (int its = 0; its < 500 && !converged; its++) {
1926 oldV = MC_epCut_;
1927 MC_apCut_ = damp *(MC_X_o_min_ - MC_epCut_) + (1-damp) * MC_apCut_;
1928 double MC_bpCutNew = MC_apCut_ / MC_cpCut_ + MC_slopepCut_ - 1.0;
1929 MC_bpCut_ = damp * MC_bpCutNew + (1-damp) * MC_bpCut_;
1930 double MC_dpCutNew = ((- MC_apCut_/MC_cpCut_ + MC_bpCut_ - MC_bpCut_ * MC_X_o_cutoff_/MC_cpCut_)
1931 /
1932 (MC_X_o_cutoff_ * MC_X_o_cutoff_/MC_cpCut_ - 2.0 * MC_X_o_cutoff_));
1933 MC_dpCut_ = damp * MC_dpCutNew + (1-damp) * MC_dpCut_;
1934 double tmp = MC_apCut_ + MC_X_o_cutoff_*(MC_bpCut_ + MC_dpCut_ * MC_X_o_cutoff_);
1935 double eterm = std::exp(- MC_X_o_cutoff_ / MC_cpCut_);
1936 MC_epCut_ = - eterm * tmp;
1937 double diff = MC_epCut_ - oldV;
1938 if (fabs(diff) < 1.0E-14) {
1939 converged = true;
1940 }
1941 }
1942 if (!converged) {
1943 throw CanteraError("HMWSoln::calcMCCutoffParams_()",
1944 " failed to converge on the p polynomial");
1945 }
1946}
1947
1949{
1950 double T = temperature();
1951 const double twoT = 2.0 * T;
1952 const double invT = 1.0 / T;
1953 const double invT2 = invT * invT;
1954 const double twoinvT3 = 2.0 * invT * invT2;
1955 double tinv = 0.0, tln = 0.0, tlin = 0.0, tquad = 0.0;
1956 if (m_formPitzerTemp == PITZER_TEMP_LINEAR) {
1957 tlin = T - m_TempPitzerRef;
1958 } else if (m_formPitzerTemp == PITZER_TEMP_COMPLEX1) {
1959 tlin = T - m_TempPitzerRef;
1960 tquad = T * T - m_TempPitzerRef * m_TempPitzerRef;
1961 tln = log(T/ m_TempPitzerRef);
1962 tinv = 1.0/T - 1.0/m_TempPitzerRef;
1963 }
1964
1965 for (size_t i = 1; i < (m_kk - 1); i++) {
1966 for (size_t j = (i+1); j < m_kk; j++) {
1967 // Find the counterIJ for the symmetric binary interaction
1968 size_t n = m_kk*i + j;
1969 size_t counterIJ = m_CounterIJ[n];
1970
1971 const double* beta0MX_coeff = m_Beta0MX_ij_coeff.ptrColumn(counterIJ);
1972 const double* beta1MX_coeff = m_Beta1MX_ij_coeff.ptrColumn(counterIJ);
1973 const double* beta2MX_coeff = m_Beta2MX_ij_coeff.ptrColumn(counterIJ);
1974 const double* CphiMX_coeff = m_CphiMX_ij_coeff.ptrColumn(counterIJ);
1975 const double* Theta_coeff = m_Theta_ij_coeff.ptrColumn(counterIJ);
1976
1977 switch (m_formPitzerTemp) {
1978 case PITZER_TEMP_CONSTANT:
1979 break;
1980 case PITZER_TEMP_LINEAR:
1981
1982 m_Beta0MX_ij[counterIJ] = beta0MX_coeff[0]
1983 + beta0MX_coeff[1]*tlin;
1984 m_Beta0MX_ij_L[counterIJ] = beta0MX_coeff[1];
1985 m_Beta0MX_ij_LL[counterIJ] = 0.0;
1986 m_Beta1MX_ij[counterIJ] = beta1MX_coeff[0]
1987 + beta1MX_coeff[1]*tlin;
1988 m_Beta1MX_ij_L[counterIJ] = beta1MX_coeff[1];
1989 m_Beta1MX_ij_LL[counterIJ] = 0.0;
1990 m_Beta2MX_ij[counterIJ] = beta2MX_coeff[0]
1991 + beta2MX_coeff[1]*tlin;
1992 m_Beta2MX_ij_L[counterIJ] = beta2MX_coeff[1];
1993 m_Beta2MX_ij_LL[counterIJ] = 0.0;
1994 m_CphiMX_ij[counterIJ] = CphiMX_coeff[0]
1995 + CphiMX_coeff[1]*tlin;
1996 m_CphiMX_ij_L[counterIJ] = CphiMX_coeff[1];
1997 m_CphiMX_ij_LL[counterIJ] = 0.0;
1998 m_Theta_ij[counterIJ] = Theta_coeff[0] + Theta_coeff[1]*tlin;
1999 m_Theta_ij_L[counterIJ] = Theta_coeff[1];
2000 m_Theta_ij_LL[counterIJ] = 0.0;
2001 break;
2002
2003 case PITZER_TEMP_COMPLEX1:
2004 m_Beta0MX_ij[counterIJ] = beta0MX_coeff[0]
2005 + beta0MX_coeff[1]*tlin
2006 + beta0MX_coeff[2]*tquad
2007 + beta0MX_coeff[3]*tinv
2008 + beta0MX_coeff[4]*tln;
2009 m_Beta1MX_ij[counterIJ] = beta1MX_coeff[0]
2010 + beta1MX_coeff[1]*tlin
2011 + beta1MX_coeff[2]*tquad
2012 + beta1MX_coeff[3]*tinv
2013 + beta1MX_coeff[4]*tln;
2014 m_Beta2MX_ij[counterIJ] = beta2MX_coeff[0]
2015 + beta2MX_coeff[1]*tlin
2016 + beta2MX_coeff[2]*tquad
2017 + beta2MX_coeff[3]*tinv
2018 + beta2MX_coeff[4]*tln;
2019 m_CphiMX_ij[counterIJ] = CphiMX_coeff[0]
2020 + CphiMX_coeff[1]*tlin
2021 + CphiMX_coeff[2]*tquad
2022 + CphiMX_coeff[3]*tinv
2023 + CphiMX_coeff[4]*tln;
2024 m_Theta_ij[counterIJ] = Theta_coeff[0]
2025 + Theta_coeff[1]*tlin
2026 + Theta_coeff[2]*tquad
2027 + Theta_coeff[3]*tinv
2028 + Theta_coeff[4]*tln;
2029 m_Beta0MX_ij_L[counterIJ] = beta0MX_coeff[1]
2030 + beta0MX_coeff[2]*twoT
2031 - beta0MX_coeff[3]*invT2
2032 + beta0MX_coeff[4]*invT;
2033 m_Beta1MX_ij_L[counterIJ] = beta1MX_coeff[1]
2034 + beta1MX_coeff[2]*twoT
2035 - beta1MX_coeff[3]*invT2
2036 + beta1MX_coeff[4]*invT;
2037 m_Beta2MX_ij_L[counterIJ] = beta2MX_coeff[1]
2038 + beta2MX_coeff[2]*twoT
2039 - beta2MX_coeff[3]*invT2
2040 + beta2MX_coeff[4]*invT;
2041 m_CphiMX_ij_L[counterIJ] = CphiMX_coeff[1]
2042 + CphiMX_coeff[2]*twoT
2043 - CphiMX_coeff[3]*invT2
2044 + CphiMX_coeff[4]*invT;
2045 m_Theta_ij_L[counterIJ] = Theta_coeff[1]
2046 + Theta_coeff[2]*twoT
2047 - Theta_coeff[3]*invT2
2048 + Theta_coeff[4]*invT;
2049 doDerivs = 2;
2050 if (doDerivs > 1) {
2051 m_Beta0MX_ij_LL[counterIJ] =
2052 + beta0MX_coeff[2]*2.0
2053 + beta0MX_coeff[3]*twoinvT3
2054 - beta0MX_coeff[4]*invT2;
2055 m_Beta1MX_ij_LL[counterIJ] =
2056 + beta1MX_coeff[2]*2.0
2057 + beta1MX_coeff[3]*twoinvT3
2058 - beta1MX_coeff[4]*invT2;
2059 m_Beta2MX_ij_LL[counterIJ] =
2060 + beta2MX_coeff[2]*2.0
2061 + beta2MX_coeff[3]*twoinvT3
2062 - beta2MX_coeff[4]*invT2;
2063 m_CphiMX_ij_LL[counterIJ] =
2064 + CphiMX_coeff[2]*2.0
2065 + CphiMX_coeff[3]*twoinvT3
2066 - CphiMX_coeff[4]*invT2;
2067 m_Theta_ij_LL[counterIJ] =
2068 + Theta_coeff[2]*2.0
2069 + Theta_coeff[3]*twoinvT3
2070 - Theta_coeff[4]*invT2;
2071 }
2072 break;
2073 }
2074 }
2075 }
2076
2077 // Lambda interactions and Mu_nnn
2078 // i must be neutral for this term to be nonzero. We take advantage of this
2079 // here to lower the operation count.
2080 for (size_t i = 1; i < m_kk; i++) {
2081 if (charge(i) == 0.0) {
2082 for (size_t j = 1; j < m_kk; j++) {
2083 size_t n = i * m_kk + j;
2084 const double* Lambda_coeff = m_Lambda_nj_coeff.ptrColumn(n);
2085 switch (m_formPitzerTemp) {
2086 case PITZER_TEMP_CONSTANT:
2087 m_Lambda_nj(i,j) = Lambda_coeff[0];
2088 break;
2089 case PITZER_TEMP_LINEAR:
2090 m_Lambda_nj(i,j) = Lambda_coeff[0] + Lambda_coeff[1]*tlin;
2091 m_Lambda_nj_L(i,j) = Lambda_coeff[1];
2092 m_Lambda_nj_LL(i,j) = 0.0;
2093 break;
2094 case PITZER_TEMP_COMPLEX1:
2095 m_Lambda_nj(i,j) = Lambda_coeff[0]
2096 + Lambda_coeff[1]*tlin
2097 + Lambda_coeff[2]*tquad
2098 + Lambda_coeff[3]*tinv
2099 + Lambda_coeff[4]*tln;
2100
2101 m_Lambda_nj_L(i,j) = Lambda_coeff[1]
2102 + Lambda_coeff[2]*twoT
2103 - Lambda_coeff[3]*invT2
2104 + Lambda_coeff[4]*invT;
2105
2106 m_Lambda_nj_LL(i,j) =
2107 Lambda_coeff[2]*2.0
2108 + Lambda_coeff[3]*twoinvT3
2109 - Lambda_coeff[4]*invT2;
2110 }
2111
2112 if (j == i) {
2113 const double* Mu_coeff = m_Mu_nnn_coeff.ptrColumn(i);
2114 switch (m_formPitzerTemp) {
2115 case PITZER_TEMP_CONSTANT:
2116 m_Mu_nnn[i] = Mu_coeff[0];
2117 break;
2118 case PITZER_TEMP_LINEAR:
2119 m_Mu_nnn[i] = Mu_coeff[0] + Mu_coeff[1]*tlin;
2120 m_Mu_nnn_L[i] = Mu_coeff[1];
2121 m_Mu_nnn_LL[i] = 0.0;
2122 break;
2123 case PITZER_TEMP_COMPLEX1:
2124 m_Mu_nnn[i] = Mu_coeff[0]
2125 + Mu_coeff[1]*tlin
2126 + Mu_coeff[2]*tquad
2127 + Mu_coeff[3]*tinv
2128 + Mu_coeff[4]*tln;
2129 m_Mu_nnn_L[i] = Mu_coeff[1]
2130 + Mu_coeff[2]*twoT
2131 - Mu_coeff[3]*invT2
2132 + Mu_coeff[4]*invT;
2133 m_Mu_nnn_LL[i] =
2134 Mu_coeff[2]*2.0
2135 + Mu_coeff[3]*twoinvT3
2136 - Mu_coeff[4]*invT2;
2137 }
2138 }
2139 }
2140 }
2141 }
2142
2143 switch(m_formPitzerTemp) {
2144 case PITZER_TEMP_CONSTANT:
2145 for (size_t i = 1; i < m_kk; i++) {
2146 for (size_t j = 1; j < m_kk; j++) {
2147 for (size_t k = 1; k < m_kk; k++) {
2148 size_t n = i * m_kk *m_kk + j * m_kk + k;
2149 const double* Psi_coeff = m_Psi_ijk_coeff.ptrColumn(n);
2150 m_Psi_ijk[n] = Psi_coeff[0];
2151 }
2152 }
2153 }
2154 break;
2155 case PITZER_TEMP_LINEAR:
2156 for (size_t i = 1; i < m_kk; i++) {
2157 for (size_t j = 1; j < m_kk; j++) {
2158 for (size_t k = 1; k < m_kk; k++) {
2159 size_t n = i * m_kk *m_kk + j * m_kk + k;
2160 const double* Psi_coeff = m_Psi_ijk_coeff.ptrColumn(n);
2161 m_Psi_ijk[n] = Psi_coeff[0] + Psi_coeff[1]*tlin;
2162 m_Psi_ijk_L[n] = Psi_coeff[1];
2163 m_Psi_ijk_LL[n] = 0.0;
2164 }
2165 }
2166 }
2167 break;
2168 case PITZER_TEMP_COMPLEX1:
2169 for (size_t i = 1; i < m_kk; i++) {
2170 for (size_t j = 1; j < m_kk; j++) {
2171 for (size_t k = 1; k < m_kk; k++) {
2172 size_t n = i * m_kk *m_kk + j * m_kk + k;
2173 const double* Psi_coeff = m_Psi_ijk_coeff.ptrColumn(n);
2174 m_Psi_ijk[n] = Psi_coeff[0]
2175 + Psi_coeff[1]*tlin
2176 + Psi_coeff[2]*tquad
2177 + Psi_coeff[3]*tinv
2178 + Psi_coeff[4]*tln;
2179 m_Psi_ijk_L[n] = Psi_coeff[1]
2180 + Psi_coeff[2]*twoT
2181 - Psi_coeff[3]*invT2
2182 + Psi_coeff[4]*invT;
2183 m_Psi_ijk_LL[n] =
2184 Psi_coeff[2]*2.0
2185 + Psi_coeff[3]*twoinvT3
2186 - Psi_coeff[4]*invT2;
2187 }
2188 }
2189 }
2190 break;
2191 }
2192}
2193
2195{
2196 // Use the CROPPED molality of the species in solution.
2197 const vector_fp& molality = m_molalitiesCropped;
2198
2199 // These are data inputs about the Pitzer correlation. They come from the
2200 // input file for the Pitzer model.
2201 vector_fp& gamma_Unscaled = m_gamma_tmp;
2202
2203 // Local variables defined by Coltrin
2204 double etheta[5][5], etheta_prime[5][5], sqrtIs;
2205
2206 // Molality based ionic strength of the solution
2207 double Is = 0.0;
2208
2209 // Molar charge of the solution: In Pitzer's notation, this is his variable
2210 // called "Z".
2211 double molarcharge = 0.0;
2212
2213 // molalitysum is the sum of the molalities over all solutes, even those
2214 // with zero charge.
2215 double molalitysumUncropped = 0.0;
2216
2217 // Make sure the counter variables are setup
2219
2220 // ---------- Calculate common sums over solutes ---------------------
2221 for (size_t n = 1; n < m_kk; n++) {
2222 // ionic strength
2223 Is += charge(n) * charge(n) * molality[n];
2224 // total molar charge
2225 molarcharge += fabs(charge(n)) * molality[n];
2226 molalitysumUncropped += m_molalities[n];
2227 }
2228 Is *= 0.5;
2229
2230 // Store the ionic molality in the object for reference.
2231 m_IionicMolality = Is;
2232 sqrtIs = sqrt(Is);
2233
2234 // The following call to calc_lambdas() calculates all 16 elements of the
2235 // elambda and elambda1 arrays, given the value of the ionic strength (Is)
2236 calc_lambdas(Is);
2237
2238 // Step 2: Find the coefficients E-theta and E-thetaprime for all
2239 // combinations of positive unlike charges up to 4
2240 for (int z1 = 1; z1 <=4; z1++) {
2241 for (int z2 =1; z2 <=4; z2++) {
2242 calc_thetas(z1, z2, &etheta[z1][z2], &etheta_prime[z1][z2]);
2243 }
2244 }
2245
2246 // calculate g(x) and hfunc(x) for each cation-anion pair MX. In the
2247 // original literature, hfunc, was called gprime. However, it's not the
2248 // derivative of g(x), so I renamed it.
2249 for (size_t i = 1; i < (m_kk - 1); i++) {
2250 for (size_t j = (i+1); j < m_kk; j++) {
2251 // Find the counterIJ for the symmetric binary interaction
2252 size_t n = m_kk*i + j;
2253 size_t counterIJ = m_CounterIJ[n];
2254
2255 // Only loop over oppositely charge species
2256 if (charge(i)*charge(j) < 0) {
2257 // x is a reduced function variable
2258 double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
2259 if (x1 > 1.0E-100) {
2260 m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 * x1);
2261 m_hfunc_IJ[counterIJ] = -2.0 *
2262 (1.0-(1.0 + x1 + 0.5 * x1 * x1) * exp(-x1)) / (x1 * x1);
2263 } else {
2264 m_gfunc_IJ[counterIJ] = 0.0;
2265 m_hfunc_IJ[counterIJ] = 0.0;
2266 }
2267
2268 if (m_Beta2MX_ij[counterIJ] != 0.0) {
2269 double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
2270 if (x2 > 1.0E-100) {
2271 m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
2272 m_h2func_IJ[counterIJ] = -2.0 *
2273 (1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
2274 } else {
2275 m_g2func_IJ[counterIJ] = 0.0;
2276 m_h2func_IJ[counterIJ] = 0.0;
2277 }
2278 }
2279 } else {
2280 m_gfunc_IJ[counterIJ] = 0.0;
2281 m_hfunc_IJ[counterIJ] = 0.0;
2282 }
2283 }
2284 }
2285
2286 // SUBSECTION TO CALCULATE BMX, BprimeMX, BphiMX
2287 // Agrees with Pitzer, Eq. (49), (51), (55)
2288 for (size_t i = 1; i < m_kk - 1; i++) {
2289 for (size_t j = i+1; j < m_kk; j++) {
2290 // Find the counterIJ for the symmetric binary interaction
2291 size_t n = m_kk*i + j;
2292 size_t counterIJ = m_CounterIJ[n];
2293
2294 // both species have a non-zero charge, and one is positive and the
2295 // other is negative
2296 if (charge(i)*charge(j) < 0.0) {
2297 m_BMX_IJ[counterIJ] = m_Beta0MX_ij[counterIJ]
2298 + m_Beta1MX_ij[counterIJ] * m_gfunc_IJ[counterIJ]
2299 + m_Beta2MX_ij[counterIJ] * m_g2func_IJ[counterIJ];
2300
2301 if (Is > 1.0E-150) {
2302 m_BprimeMX_IJ[counterIJ] = (m_Beta1MX_ij[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
2303 m_Beta2MX_ij[counterIJ] * m_h2func_IJ[counterIJ]/Is);
2304 } else {
2305 m_BprimeMX_IJ[counterIJ] = 0.0;
2306 }
2307 m_BphiMX_IJ[counterIJ] = m_BMX_IJ[counterIJ] + Is*m_BprimeMX_IJ[counterIJ];
2308 } else {
2309 m_BMX_IJ[counterIJ] = 0.0;
2310 m_BprimeMX_IJ[counterIJ] = 0.0;
2311 m_BphiMX_IJ[counterIJ] = 0.0;
2312 }
2313 }
2314 }
2315
2316 // SUBSECTION TO CALCULATE CMX
2317 // Agrees with Pitzer, Eq. (53).
2318 for (size_t i = 1; i < m_kk-1; i++) {
2319 for (size_t j = i+1; j < m_kk; j++) {
2320 // Find the counterIJ for the symmetric binary interaction
2321 size_t n = m_kk*i + j;
2322 size_t counterIJ = m_CounterIJ[n];
2323
2324 // both species have a non-zero charge, and one is positive
2325 // and the other is negative
2326 if (charge(i)*charge(j) < 0.0) {
2327 m_CMX_IJ[counterIJ] = m_CphiMX_ij[counterIJ]/
2328 (2.0* sqrt(fabs(charge(i)*charge(j))));
2329 } else {
2330 m_CMX_IJ[counterIJ] = 0.0;
2331 }
2332 }
2333 }
2334
2335 // SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi
2336 // Agrees with Pitzer, Eq. 72, 73, 74
2337 for (size_t i = 1; i < m_kk-1; i++) {
2338 for (size_t j = i+1; j < m_kk; j++) {
2339 // Find the counterIJ for the symmetric binary interaction
2340 size_t n = m_kk*i + j;
2341 size_t counterIJ = m_CounterIJ[n];
2342
2343 // both species have a non-zero charge, and one is positive and the
2344 // other is negative
2345 if (charge(i)*charge(j) > 0) {
2346 int z1 = (int) fabs(charge(i));
2347 int z2 = (int) fabs(charge(j));
2348 m_Phi_IJ[counterIJ] = m_Theta_ij[counterIJ] + etheta[z1][z2];
2349 m_Phiprime_IJ[counterIJ] = etheta_prime[z1][z2];
2350 m_PhiPhi_IJ[counterIJ] = m_Phi_IJ[counterIJ] + Is * m_Phiprime_IJ[counterIJ];
2351 } else {
2352 m_Phi_IJ[counterIJ] = 0.0;
2353 m_Phiprime_IJ[counterIJ] = 0.0;
2354 m_PhiPhi_IJ[counterIJ] = 0.0;
2355 }
2356 }
2357 }
2358
2359 // SUBSECTION FOR CALCULATION OF F
2360 // Agrees with Pitzer Eqn. (65)
2361 double Aphi = A_Debye_TP() / 3.0;
2362 double F = -Aphi * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
2363 + (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
2364 for (size_t i = 1; i < m_kk-1; i++) {
2365 for (size_t j = i+1; j < m_kk; j++) {
2366 // Find the counterIJ for the symmetric binary interaction
2367 size_t n = m_kk*i + j;
2368 size_t counterIJ = m_CounterIJ[n];
2369
2370 // both species have a non-zero charge, and one is positive and the
2371 // other is negative
2372 if (charge(i)*charge(j) < 0) {
2373 F += molality[i]*molality[j] * m_BprimeMX_IJ[counterIJ];
2374 }
2375
2376 // Both species have a non-zero charge, and they
2377 // have the same sign
2378 if (charge(i)*charge(j) > 0) {
2379 F += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
2380 }
2381 }
2382 }
2383
2384 for (size_t i = 1; i < m_kk; i++) {
2385
2386 // SUBSECTION FOR CALCULATING THE ACTCOEFF FOR CATIONS
2387 // equations agree with my notes, Eqn. (118).
2388 // Equations agree with Pitzer, eqn.(63)
2389 if (charge(i) > 0.0) {
2390 // species i is the cation (positive) to calc the actcoeff
2391 double zsqF = charge(i)*charge(i)*F;
2392 double sum1 = 0.0;
2393 double sum2 = 0.0;
2394 double sum3 = 0.0;
2395 double sum4 = 0.0;
2396 double sum5 = 0.0;
2397 for (size_t j = 1; j < m_kk; j++) {
2398 // Find the counterIJ for the symmetric binary interaction
2399 size_t n = m_kk*i + j;
2400 size_t counterIJ = m_CounterIJ[n];
2401
2402 if (charge(j) < 0.0) {
2403 // sum over all anions
2404 sum1 += molality[j] *
2405 (2.0*m_BMX_IJ[counterIJ] + molarcharge*m_CMX_IJ[counterIJ]);
2406 if (j < m_kk-1) {
2407 // This term is the ternary interaction involving the
2408 // non-duplicate sum over double anions, j, k, with
2409 // respect to the cation, i.
2410 for (size_t k = j+1; k < m_kk; k++) {
2411 // an inner sum over all anions
2412 if (charge(k) < 0.0) {
2413 n = k + j * m_kk + i * m_kk * m_kk;
2414 sum3 += molality[j]*molality[k]*m_Psi_ijk[n];
2415 }
2416 }
2417 }
2418 }
2419
2420 if (charge(j) > 0.0) {
2421 // sum over all cations
2422 if (j != i) {
2423 sum2 += molality[j]*(2.0*m_Phi_IJ[counterIJ]);
2424 }
2425 for (size_t k = 1; k < m_kk; k++) {
2426 if (charge(k) < 0.0) {
2427 // two inner sums over anions
2428 n = k + j * m_kk + i * m_kk * m_kk;
2429 sum2 += molality[j]*molality[k]*m_Psi_ijk[n];
2430
2431 // Find the counterIJ for the j,k interaction
2432 n = m_kk*j + k;
2433 size_t counterIJ2 = m_CounterIJ[n];
2434 sum4 += (fabs(charge(i))*
2435 molality[j]*molality[k]*m_CMX_IJ[counterIJ2]);
2436 }
2437 }
2438 }
2439
2440 // Handle neutral j species
2441 if (charge(j) == 0) {
2442 sum5 += molality[j]*2.0*m_Lambda_nj(j,i);
2443
2444 // Zeta interaction term
2445 for (size_t k = 1; k < m_kk; k++) {
2446 if (charge(k) < 0.0) {
2447 size_t izeta = j;
2448 size_t jzeta = i;
2449 n = izeta * m_kk * m_kk + jzeta * m_kk + k;
2450 double zeta = m_Psi_ijk[n];
2451 if (zeta != 0.0) {
2452 sum5 += molality[j]*molality[k]*zeta;
2453 }
2454 }
2455 }
2456 }
2457 }
2458
2459 // Add all of the contributions up to yield the log of the solute
2460 // activity coefficients (molality scale)
2461 m_lnActCoeffMolal_Unscaled[i] = zsqF + sum1 + sum2 + sum3 + sum4 + sum5;
2462 gamma_Unscaled[i] = exp(m_lnActCoeffMolal_Unscaled[i]);
2463 }
2464
2465 // SUBSECTION FOR CALCULATING THE ACTCOEFF FOR ANIONS
2466 // equations agree with my notes, Eqn. (119).
2467 // Equations agree with Pitzer, eqn.(64)
2468 if (charge(i) < 0) {
2469 // species i is an anion (negative)
2470 double zsqF = charge(i)*charge(i)*F;
2471 double sum1 = 0.0;
2472 double sum2 = 0.0;
2473 double sum3 = 0.0;
2474 double sum4 = 0.0;
2475 double sum5 = 0.0;
2476 for (size_t j = 1; j < m_kk; j++) {
2477 // Find the counterIJ for the symmetric binary interaction
2478 size_t n = m_kk*i + j;
2479 size_t counterIJ = m_CounterIJ[n];
2480
2481 // For Anions, do the cation interactions.
2482 if (charge(j) > 0) {
2483 sum1 += molality[j]*
2484 (2.0*m_BMX_IJ[counterIJ]+molarcharge*m_CMX_IJ[counterIJ]);
2485 if (j < m_kk-1) {
2486 for (size_t k = j+1; k < m_kk; k++) {
2487 // an inner sum over all cations
2488 if (charge(k) > 0) {
2489 n = k + j * m_kk + i * m_kk * m_kk;
2490 sum3 += molality[j]*molality[k]*m_Psi_ijk[n];
2491 }
2492 }
2493 }
2494 }
2495
2496 // For Anions, do the other anion interactions.
2497 if (charge(j) < 0.0) {
2498 // sum over all anions
2499 if (j != i) {
2500 sum2 += molality[j]*(2.0*m_Phi_IJ[counterIJ]);
2501 }
2502 for (size_t k = 1; k < m_kk; k++) {
2503 if (charge(k) > 0.0) {
2504 // two inner sums over cations
2505 n = k + j * m_kk + i * m_kk * m_kk;
2506 sum2 += molality[j]*molality[k]*m_Psi_ijk[n];
2507 // Find the counterIJ for the symmetric binary interaction
2508 n = m_kk*j + k;
2509 size_t counterIJ2 = m_CounterIJ[n];
2510 sum4 += fabs(charge(i))*
2511 molality[j]*molality[k]*m_CMX_IJ[counterIJ2];
2512 }
2513 }
2514 }
2515
2516 // for Anions, do the neutral species interaction
2517 if (charge(j) == 0.0) {
2518 sum5 += molality[j]*2.0*m_Lambda_nj(j,i);
2519 // Zeta interaction term
2520 for (size_t k = 1; k < m_kk; k++) {
2521 if (charge(k) > 0.0) {
2522 size_t izeta = j;
2523 size_t jzeta = k;
2524 size_t kzeta = i;
2525 n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
2526 double zeta = m_Psi_ijk[n];
2527 if (zeta != 0.0) {
2528 sum5 += molality[j]*molality[k]*zeta;
2529 }
2530 }
2531 }
2532 }
2533 }
2534 m_lnActCoeffMolal_Unscaled[i] = zsqF + sum1 + sum2 + sum3 + sum4 + sum5;
2535 gamma_Unscaled[i] = exp(m_lnActCoeffMolal_Unscaled[i]);
2536 }
2537
2538 // SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF
2539 // equations agree with my notes,
2540 // Equations agree with Pitzer,
2541 if (charge(i) == 0.0) {
2542 double sum1 = 0.0;
2543 double sum3 = 0.0;
2544 for (size_t j = 1; j < m_kk; j++) {
2545 sum1 += molality[j]*2.0*m_Lambda_nj(i,j);
2546 // Zeta term -> we piggyback on the psi term
2547 if (charge(j) > 0.0) {
2548 for (size_t k = 1; k < m_kk; k++) {
2549 if (charge(k) < 0.0) {
2550 size_t n = k + j * m_kk + i * m_kk * m_kk;
2551 sum3 += molality[j]*molality[k]*m_Psi_ijk[n];
2552 }
2553 }
2554 }
2555 }
2556 double sum2 = 3.0 * molality[i]* molality[i] * m_Mu_nnn[i];
2557 m_lnActCoeffMolal_Unscaled[i] = sum1 + sum2 + sum3;
2558 gamma_Unscaled[i] = exp(m_lnActCoeffMolal_Unscaled[i]);
2559 }
2560 }
2561
2562 // SUBSECTION FOR CALCULATING THE OSMOTIC COEFF
2563 // equations agree with my notes, Eqn. (117).
2564 // Equations agree with Pitzer, eqn.(62)
2565 double sum1 = 0.0;
2566 double sum2 = 0.0;
2567 double sum3 = 0.0;
2568 double sum4 = 0.0;
2569 double sum5 = 0.0;
2570 double sum6 = 0.0;
2571 double sum7 = 0.0;
2572
2573 // term1 is the DH term in the osmotic coefficient expression
2574 // b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer
2575 // implementations.
2576 // Is = Ionic strength on the molality scale (units of (gmol/kg))
2577 // Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
2578 double term1 = -Aphi * pow(Is,1.5) / (1.0 + 1.2 * sqrt(Is));
2579
2580 for (size_t j = 1; j < m_kk; j++) {
2581 // Loop Over Cations
2582 if (charge(j) > 0.0) {
2583 for (size_t k = 1; k < m_kk; k++) {
2584 if (charge(k) < 0.0) {
2585 // Find the counterIJ for the symmetric j,k binary interaction
2586 size_t n = m_kk*j + k;
2587 size_t counterIJ = m_CounterIJ[n];
2588
2589 sum1 += molality[j]*molality[k]*
2590 (m_BphiMX_IJ[counterIJ] + molarcharge*m_CMX_IJ[counterIJ]);
2591 }
2592 }
2593
2594 for (size_t k = j+1; k < m_kk; k++) {
2595 if (j == (m_kk-1)) {
2596 // we should never reach this step
2597 throw CanteraError("HMWSoln::s_updatePitzer_lnMolalityActCoeff",
2598 "logic error 1 in Step 9 of hmw_act");
2599 }
2600 if (charge(k) > 0.0) {
2601 // Find the counterIJ for the symmetric j,k binary interaction
2602 // between 2 cations.
2603 size_t n = m_kk*j + k;
2604 size_t counterIJ = m_CounterIJ[n];
2605 sum2 += molality[j]*molality[k]*m_PhiPhi_IJ[counterIJ];
2606 for (size_t m = 1; m < m_kk; m++) {
2607 if (charge(m) < 0.0) {
2608 // species m is an anion
2609 n = m + k * m_kk + j * m_kk * m_kk;
2610 sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk[n];
2611 }
2612 }
2613 }
2614 }
2615 }
2616
2617 // Loop Over Anions
2618 if (charge(j) < 0) {
2619 for (size_t k = j+1; k < m_kk; k++) {
2620 if (j == m_kk-1) {
2621 // we should never reach this step
2622 throw CanteraError("HMWSoln::s_updatePitzer_lnMolalityActCoeff",
2623 "logic error 2 in Step 9 of hmw_act");
2624 }
2625 if (charge(k) < 0) {
2626 // Find the counterIJ for the symmetric j,k binary interaction
2627 // between two anions
2628 size_t n = m_kk*j + k;
2629 size_t counterIJ = m_CounterIJ[n];
2630 sum3 += molality[j]*molality[k]*m_PhiPhi_IJ[counterIJ];
2631 for (size_t m = 1; m < m_kk; m++) {
2632 if (charge(m) > 0.0) {
2633 n = m + k * m_kk + j * m_kk * m_kk;
2634 sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk[n];
2635 }
2636 }
2637 }
2638 }
2639 }
2640
2641 // Loop Over Neutral Species
2642 if (charge(j) == 0) {
2643 for (size_t k = 1; k < m_kk; k++) {
2644 if (charge(k) < 0.0) {
2645 sum4 += molality[j]*molality[k]*m_Lambda_nj(j,k);
2646 }
2647 if (charge(k) > 0.0) {
2648 sum5 += molality[j]*molality[k]*m_Lambda_nj(j,k);
2649 }
2650 if (charge(k) == 0.0) {
2651 if (k > j) {
2652 sum6 += molality[j]*molality[k]*m_Lambda_nj(j,k);
2653 } else if (k == j) {
2654 sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj(j,k);
2655 }
2656 }
2657 if (charge(k) < 0.0) {
2658 size_t izeta = j;
2659 for (size_t m = 1; m < m_kk; m++) {
2660 if (charge(m) > 0.0) {
2661 size_t jzeta = m;
2662 size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
2663 double zeta = m_Psi_ijk[n];
2664 if (zeta != 0.0) {
2665 sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta;
2666 }
2667 }
2668 }
2669 }
2670 }
2671 sum7 += molality[j]*molality[j]*molality[j]*m_Mu_nnn[j];
2672 }
2673 }
2674 double sum_m_phi_minus_1 = 2.0 *
2675 (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
2676 // Calculate the osmotic coefficient from
2677 // osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
2678 double osmotic_coef;
2679 if (molalitysumUncropped > 1.0E-150) {
2680 osmotic_coef = 1.0 + (sum_m_phi_minus_1 / molalitysumUncropped);
2681 } else {
2682 osmotic_coef = 1.0;
2683 }
2684 double lnwateract = -(m_weightSolvent/1000.0) * molalitysumUncropped * osmotic_coef;
2685
2686 // In Cantera, we define the activity coefficient of the solvent as
2687 //
2688 // act_0 = actcoeff_0 * Xmol_0
2689 //
2690 // We have just computed act_0. However, this routine returns
2691 // ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
2692 double xmolSolvent = moleFraction(0);
2693 double xx = std::max(m_xmolSolventMIN, xmolSolvent);
2694 m_lnActCoeffMolal_Unscaled[0] = lnwateract - log(xx);
2695}
2696
2698{
2699 static const int cacheId = m_cache.getId();
2700 CachedScalar cached = m_cache.getScalar(cacheId);
2701 if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
2702 return;
2703 }
2704
2705 // Zero the unscaled 2nd derivatives
2707
2708 // Do the actual calculation of the unscaled temperature derivatives
2710
2711 for (size_t k = 1; k < m_kk; k++) {
2712 if (CROP_speciesCropped_[k] == 2) {
2714 }
2715 }
2716
2717 if (CROP_speciesCropped_[0]) {
2719 }
2720
2721 // Do the pH scaling to the derivatives
2723}
2724
2726{
2727 // It may be assumed that the Pitzer activity coefficient routine is called
2728 // immediately preceding the calling of this routine. Therefore, some
2729 // quantities do not need to be recalculated in this routine.
2730
2731 const vector_fp& molality = m_molalitiesCropped;
2732 double* d_gamma_dT_Unscaled = m_gamma_tmp.data();
2733
2734 // Local variables defined by Coltrin
2735 double etheta[5][5], etheta_prime[5][5], sqrtIs;
2736
2737 // Molality based ionic strength of the solution
2738 double Is = 0.0;
2739
2740 // Molar charge of the solution: In Pitzer's notation, this is his variable
2741 // called "Z".
2742 double molarcharge = 0.0;
2743
2744 // molalitysum is the sum of the molalities over all solutes, even those
2745 // with zero charge.
2746 double molalitysum = 0.0;
2747
2748 // Make sure the counter variables are setup
2750
2751 // ---------- Calculate common sums over solutes ---------------------
2752 for (size_t n = 1; n < m_kk; n++) {
2753 // ionic strength
2754 Is += charge(n) * charge(n) * molality[n];
2755 // total molar charge
2756 molarcharge += fabs(charge(n)) * molality[n];
2757 molalitysum += molality[n];
2758 }
2759 Is *= 0.5;
2760
2761 // Store the ionic molality in the object for reference.
2762 m_IionicMolality = Is;
2763 sqrtIs = sqrt(Is);
2764
2765 // The following call to calc_lambdas() calculates all 16 elements of the
2766 // elambda and elambda1 arrays, given the value of the ionic strength (Is)
2767 calc_lambdas(Is);
2768
2769 // Step 2: Find the coefficients E-theta and E-thetaprime for all
2770 // combinations of positive unlike charges up to 4
2771 for (int z1 = 1; z1 <=4; z1++) {
2772 for (int z2 =1; z2 <=4; z2++) {
2773 calc_thetas(z1, z2, &etheta[z1][z2], &etheta_prime[z1][z2]);
2774 }
2775 }
2776
2777 // calculate g(x) and hfunc(x) for each cation-anion pair MX
2778 // In the original literature, hfunc, was called gprime. However,
2779 // it's not the derivative of g(x), so I renamed it.
2780 for (size_t i = 1; i < (m_kk - 1); i++) {
2781 for (size_t j = (i+1); j < m_kk; j++) {
2782 // Find the counterIJ for the symmetric binary interaction
2783 size_t n = m_kk*i + j;
2784 size_t counterIJ = m_CounterIJ[n];
2785
2786 // Only loop over oppositely charge species
2787 if (charge(i)*charge(j) < 0) {
2788 // x is a reduced function variable
2789 double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
2790 if (x1 > 1.0E-100) {
2791 m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 * x1);
2792 m_hfunc_IJ[counterIJ] = -2.0 *
2793 (1.0-(1.0 + x1 + 0.5 * x1 *x1) * exp(-x1)) / (x1 * x1);
2794 } else {
2795 m_gfunc_IJ[counterIJ] = 0.0;
2796 m_hfunc_IJ[counterIJ] = 0.0;
2797 }
2798
2799 if (m_Beta2MX_ij_L[counterIJ] != 0.0) {
2800 double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
2801 if (x2 > 1.0E-100) {
2802 m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
2803 m_h2func_IJ[counterIJ] = -2.0 *
2804 (1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
2805 } else {
2806 m_g2func_IJ[counterIJ] = 0.0;
2807 m_h2func_IJ[counterIJ] = 0.0;
2808 }
2809 }
2810 } else {
2811 m_gfunc_IJ[counterIJ] = 0.0;
2812 m_hfunc_IJ[counterIJ] = 0.0;
2813 }
2814 }
2815 }
2816
2817 // SUBSECTION TO CALCULATE BMX_L, BprimeMX_L, BphiMX_L
2818 // These are now temperature derivatives of the previously calculated
2819 // quantities.
2820 for (size_t i = 1; i < m_kk - 1; i++) {
2821 for (size_t j = i+1; j < m_kk; j++) {
2822 // Find the counterIJ for the symmetric binary interaction
2823 size_t n = m_kk*i + j;
2824 size_t counterIJ = m_CounterIJ[n];
2825
2826 // both species have a non-zero charge, and one is positive
2827 // and the other is negative
2828 if (charge(i)*charge(j) < 0.0) {
2829 m_BMX_IJ_L[counterIJ] = m_Beta0MX_ij_L[counterIJ]
2830 + m_Beta1MX_ij_L[counterIJ] * m_gfunc_IJ[counterIJ]
2831 + m_Beta2MX_ij_L[counterIJ] * m_gfunc_IJ[counterIJ];
2832 if (Is > 1.0E-150) {
2833 m_BprimeMX_IJ_L[counterIJ] = (m_Beta1MX_ij_L[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
2834 m_Beta2MX_ij_L[counterIJ] * m_h2func_IJ[counterIJ]/Is);
2835 } else {
2836 m_BprimeMX_IJ_L[counterIJ] = 0.0;
2837 }
2838 m_BphiMX_IJ_L[counterIJ] = m_BMX_IJ_L[counterIJ] + Is*m_BprimeMX_IJ_L[counterIJ];
2839 } else {
2840 m_BMX_IJ_L[counterIJ] = 0.0;
2841 m_BprimeMX_IJ_L[counterIJ] = 0.0;
2842 m_BphiMX_IJ_L[counterIJ] = 0.0;
2843 }
2844 }
2845 }
2846
2847 // --------- SUBSECTION TO CALCULATE CMX_L ----------
2848 for (size_t i = 1; i < m_kk-1; i++) {
2849 for (size_t j = i+1; j < m_kk; j++) {
2850 // Find the counterIJ for the symmetric binary interaction
2851 size_t n = m_kk*i + j;
2852 size_t counterIJ = m_CounterIJ[n];
2853
2854 // both species have a non-zero charge, and one is positive
2855 // and the other is negative
2856 if (charge(i)*charge(j) < 0.0) {
2857 m_CMX_IJ_L[counterIJ] = m_CphiMX_ij_L[counterIJ]/
2858 (2.0* sqrt(fabs(charge(i)*charge(j))));
2859 } else {
2860 m_CMX_IJ_L[counterIJ] = 0.0;
2861 }
2862 }
2863 }
2864
2865 // ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ----------
2866 for (size_t i = 1; i < m_kk-1; i++) {
2867 for (size_t j = i+1; j < m_kk; j++) {
2868 // Find the counterIJ for the symmetric binary interaction
2869 size_t n = m_kk*i + j;
2870 size_t counterIJ = m_CounterIJ[n];
2871
2872 // both species have a non-zero charge, and one is positive
2873 // and the other is negative
2874 if (charge(i)*charge(j) > 0) {
2875 m_Phi_IJ_L[counterIJ] = m_Theta_ij_L[counterIJ];
2876 m_Phiprime_IJ[counterIJ] = 0.0;
2877 m_PhiPhi_IJ_L[counterIJ] = m_Phi_IJ_L[counterIJ] + Is * m_Phiprime_IJ[counterIJ];
2878 } else {
2879 m_Phi_IJ_L[counterIJ] = 0.0;
2880 m_Phiprime_IJ[counterIJ] = 0.0;
2881 m_PhiPhi_IJ_L[counterIJ] = 0.0;
2882 }
2883 }
2884 }
2885
2886 // ----------- SUBSECTION FOR CALCULATION OF dFdT ---------------------
2887 double dA_DebyedT = dA_DebyedT_TP();
2888 double dAphidT = dA_DebyedT /3.0;
2889 double dFdT = -dAphidT * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
2890 + (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
2891 for (size_t i = 1; i < m_kk-1; i++) {
2892 for (size_t j = i+1; j < m_kk; j++) {
2893 // Find the counterIJ for the symmetric binary interaction
2894 size_t n = m_kk*i + j;
2895 size_t counterIJ = m_CounterIJ[n];
2896
2897 // both species have a non-zero charge, and one is positive
2898 // and the other is negative
2899 if (charge(i)*charge(j) < 0) {
2900 dFdT += molality[i]*molality[j] * m_BprimeMX_IJ_L[counterIJ];
2901 }
2902
2903 // Both species have a non-zero charge, and they
2904 // have the same sign, that is, both positive or both negative.
2905 if (charge(i)*charge(j) > 0) {
2906 dFdT += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
2907 }
2908 }
2909 }
2910
2911 for (size_t i = 1; i < m_kk; i++) {
2912 // -------- SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR CATIONS -----
2913 if (charge(i) > 0) {
2914 // species i is the cation (positive) to calc the actcoeff
2915 double zsqdFdT = charge(i)*charge(i)*dFdT;
2916 double sum1 = 0.0;
2917 double sum2 = 0.0;
2918 double sum3 = 0.0;
2919 double sum4 = 0.0;
2920 double sum5 = 0.0;
2921 for (size_t j = 1; j < m_kk; j++) {
2922 // Find the counterIJ for the symmetric binary interaction
2923 size_t n = m_kk*i + j;
2924 size_t counterIJ = m_CounterIJ[n];
2925
2926 if (charge(j) < 0.0) {
2927 // sum over all anions
2928 sum1 += molality[j]*
2929 (2.0*m_BMX_IJ_L[counterIJ] + molarcharge*m_CMX_IJ_L[counterIJ]);
2930 if (j < m_kk-1) {
2931 // This term is the ternary interaction involving the
2932 // non-duplicate sum over double anions, j, k, with
2933 // respect to the cation, i.
2934 for (size_t k = j+1; k < m_kk; k++) {
2935 // an inner sum over all anions
2936 if (charge(k) < 0.0) {
2937 n = k + j * m_kk + i * m_kk * m_kk;
2938 sum3 += molality[j]*molality[k]*m_Psi_ijk_L[n];
2939 }
2940 }
2941 }
2942 }
2943
2944 if (charge(j) > 0.0) {
2945 // sum over all cations
2946 if (j != i) {
2947 sum2 += molality[j]*(2.0*m_Phi_IJ_L[counterIJ]);
2948 }
2949 for (size_t k = 1; k < m_kk; k++) {
2950 if (charge(k) < 0.0) {
2951 // two inner sums over anions
2952 n = k + j * m_kk + i * m_kk * m_kk;
2953 sum2 += molality[j]*molality[k]*m_Psi_ijk_L[n];
2954
2955 // Find the counterIJ for the j,k interaction
2956 n = m_kk*j + k;
2957 size_t counterIJ2 = m_CounterIJ[n];
2958 sum4 += fabs(charge(i))*
2959 molality[j]*molality[k]*m_CMX_IJ_L[counterIJ2];
2960 }
2961 }
2962 }
2963
2964 // Handle neutral j species
2965 if (charge(j) == 0) {
2966 sum5 += molality[j]*2.0*m_Lambda_nj_L(j,i);
2967 }
2968
2969 // Zeta interaction term
2970 for (size_t k = 1; k < m_kk; k++) {
2971 if (charge(k) < 0.0) {
2972 size_t izeta = j;
2973 size_t jzeta = i;
2974 n = izeta * m_kk * m_kk + jzeta * m_kk + k;
2975 double zeta_L = m_Psi_ijk_L[n];
2976 if (zeta_L != 0.0) {
2977 sum5 += molality[j]*molality[k]*zeta_L;
2978 }
2979 }
2980 }
2981 }
2982
2983 // Add all of the contributions up to yield the log of the
2984 // solute activity coefficients (molality scale)
2986 zsqdFdT + sum1 + sum2 + sum3 + sum4 + sum5;
2987 d_gamma_dT_Unscaled[i] = exp(m_dlnActCoeffMolaldT_Unscaled[i]);
2988 }
2989
2990 // ------ SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR ANIONS ------
2991 if (charge(i) < 0) {
2992 // species i is an anion (negative)
2993 double zsqdFdT = charge(i)*charge(i)*dFdT;
2994 double sum1 = 0.0;
2995 double sum2 = 0.0;
2996 double sum3 = 0.0;
2997 double sum4 = 0.0;
2998 double sum5 = 0.0;
2999 for (size_t j = 1; j < m_kk; j++) {
3000 // Find the counterIJ for the symmetric binary interaction
3001 size_t n = m_kk*i + j;
3002 size_t counterIJ = m_CounterIJ[n];
3003
3004 // For Anions, do the cation interactions.
3005 if (charge(j) > 0) {
3006 sum1 += molality[j]*
3007 (2.0*m_BMX_IJ_L[counterIJ] + molarcharge*m_CMX_IJ_L[counterIJ]);
3008 if (j < m_kk-1) {
3009 for (size_t k = j+1; k < m_kk; k++) {
3010 // an inner sum over all cations
3011 if (charge(k) > 0) {
3012 n = k + j * m_kk + i * m_kk * m_kk;
3013 sum3 += molality[j]*molality[k]*m_Psi_ijk_L[n];
3014 }
3015 }
3016 }
3017 }
3018
3019 // For Anions, do the other anion interactions.
3020 if (charge(j) < 0.0) {
3021 // sum over all anions
3022 if (j != i) {
3023 sum2 += molality[j]*(2.0*m_Phi_IJ_L[counterIJ]);
3024 }
3025 for (size_t k = 1; k < m_kk; k++) {
3026 if (charge(k) > 0.0) {
3027 // two inner sums over cations
3028 n = k + j * m_kk + i * m_kk * m_kk;
3029 sum2 += molality[j]*molality[k]*m_Psi_ijk_L[n];
3030 // Find the counterIJ for the symmetric binary interaction
3031 n = m_kk*j + k;
3032 size_t counterIJ2 = m_CounterIJ[n];
3033 sum4 += fabs(charge(i)) *
3034 molality[j]*molality[k]*m_CMX_IJ_L[counterIJ2];
3035 }
3036 }
3037 }
3038
3039 // for Anions, do the neutral species interaction
3040 if (charge(j) == 0.0) {
3041 sum5 += molality[j]*2.0*m_Lambda_nj_L(j,i);
3042 for (size_t k = 1; k < m_kk; k++) {
3043 if (charge(k) > 0.0) {
3044 size_t izeta = j;
3045 size_t jzeta = k;
3046 size_t kzeta = i;
3047 n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
3048 double zeta_L = m_Psi_ijk_L[n];
3049 if (zeta_L != 0.0) {
3050 sum5 += molality[j]*molality[k]*zeta_L;
3051 }
3052 }
3053 }
3054 }
3055 }
3057 zsqdFdT + sum1 + sum2 + sum3 + sum4 + sum5;
3058 d_gamma_dT_Unscaled[i] = exp(m_dlnActCoeffMolaldT_Unscaled[i]);
3059 }
3060
3061 // SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF
3062 // equations agree with my notes,
3063 // Equations agree with Pitzer,
3064 if (charge(i) == 0.0) {
3065 double sum1 = 0.0;
3066 double sum3 = 0.0;
3067 for (size_t j = 1; j < m_kk; j++) {
3068 sum1 += molality[j]*2.0*m_Lambda_nj_L(i,j);
3069 // Zeta term -> we piggyback on the psi term
3070 if (charge(j) > 0.0) {
3071 for (size_t k = 1; k < m_kk; k++) {
3072 if (charge(k) < 0.0) {
3073 size_t n = k + j * m_kk + i * m_kk * m_kk;
3074 sum3 += molality[j]*molality[k]*m_Psi_ijk_L[n];
3075 }
3076 }
3077 }
3078 }
3079 double sum2 = 3.0 * molality[i] * molality[i] * m_Mu_nnn_L[i];
3080 m_dlnActCoeffMolaldT_Unscaled[i] = sum1 + sum2 + sum3;
3081 d_gamma_dT_Unscaled[i] = exp(m_dlnActCoeffMolaldT_Unscaled[i]);
3082 }
3083 }
3084
3085 // ------ SUBSECTION FOR CALCULATING THE d OSMOTIC COEFF dT ---------
3086 double sum1 = 0.0;
3087 double sum2 = 0.0;
3088 double sum3 = 0.0;
3089 double sum4 = 0.0;
3090 double sum5 = 0.0;
3091 double sum6 = 0.0;
3092 double sum7 = 0.0;
3093
3094 // term1 is the temperature derivative of the DH term in the osmotic
3095 // coefficient expression
3096 // b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer implementations.
3097 // Is = Ionic strength on the molality scale (units of (gmol/kg))
3098 // Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
3099 double term1 = -dAphidT * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is));
3100
3101 for (size_t j = 1; j < m_kk; j++) {
3102 // Loop Over Cations
3103 if (charge(j) > 0.0) {
3104 for (size_t k = 1; k < m_kk; k++) {
3105 if (charge(k) < 0.0) {
3106 // Find the counterIJ for the symmetric j,k binary interaction
3107 size_t n = m_kk*j + k;
3108 size_t counterIJ = m_CounterIJ[n];
3109 sum1 += molality[j]*molality[k]*
3110 (m_BphiMX_IJ_L[counterIJ] + molarcharge*m_CMX_IJ_L[counterIJ]);
3111 }
3112 }
3113
3114 for (size_t k = j+1; k < m_kk; k++) {
3115 if (j == (m_kk-1)) {
3116 // we should never reach this step
3117 throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dT",
3118 "logic error 1 in Step 9 of hmw_act");
3119 }
3120 if (charge(k) > 0.0) {
3121 // Find the counterIJ for the symmetric j,k binary interaction
3122 // between 2 cations.
3123 size_t n = m_kk*j + k;
3124 size_t counterIJ = m_CounterIJ[n];
3125 sum2 += molality[j]*molality[k]*m_PhiPhi_IJ_L[counterIJ];
3126 for (size_t m = 1; m < m_kk; m++) {
3127 if (charge(m) < 0.0) {
3128 // species m is an anion
3129 n = m + k * m_kk + j * m_kk * m_kk;
3130 sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_L[n];
3131 }
3132 }
3133 }
3134 }
3135 }
3136
3137 // Loop Over Anions
3138 if (charge(j) < 0) {
3139 for (size_t k = j+1; k < m_kk; k++) {
3140 if (j == m_kk-1) {
3141 // we should never reach this step
3142 throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dT",
3143 "logic error 2 in Step 9 of hmw_act");
3144 }
3145 if (charge(k) < 0) {
3146 // Find the counterIJ for the symmetric j,k binary interaction
3147 // between two anions
3148 size_t n = m_kk*j + k;
3149 size_t counterIJ = m_CounterIJ[n];
3150 sum3 += molality[j]*molality[k]*m_PhiPhi_IJ_L[counterIJ];
3151 for (size_t m = 1; m < m_kk; m++) {
3152 if (charge(m) > 0.0) {
3153 n = m + k * m_kk + j * m_kk * m_kk;
3154 sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_L[n];
3155 }
3156 }
3157 }
3158 }
3159 }
3160
3161 // Loop Over Neutral Species
3162 if (charge(j) == 0) {
3163 for (size_t k = 1; k < m_kk; k++) {
3164 if (charge(k) < 0.0) {
3165 sum4 += molality[j]*molality[k]*m_Lambda_nj_L(j,k);
3166 }
3167 if (charge(k) > 0.0) {
3168 sum5 += molality[j]*molality[k]*m_Lambda_nj_L(j,k);
3169 }
3170 if (charge(k) == 0.0) {
3171 if (k > j) {
3172 sum6 += molality[j]*molality[k]*m_Lambda_nj_L(j,k);
3173 } else if (k == j) {
3174 sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj_L(j,k);
3175 }
3176 }
3177 if (charge(k) < 0.0) {
3178 size_t izeta = j;
3179 for (size_t m = 1; m < m_kk; m++) {
3180 if (charge(m) > 0.0) {
3181 size_t jzeta = m;
3182 size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
3183 double zeta_L = m_Psi_ijk_L[n];
3184 if (zeta_L != 0.0) {
3185 sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta_L;
3186 }
3187 }
3188 }
3189 }
3190 }
3191 sum7 += molality[j]*molality[j]*molality[j]*m_Mu_nnn_L[j];
3192 }
3193 }
3194 double sum_m_phi_minus_1 = 2.0 *
3195 (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
3196 // Calculate the osmotic coefficient from
3197 // osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
3198 double d_osmotic_coef_dT;
3199 if (molalitysum > 1.0E-150) {
3200 d_osmotic_coef_dT = 0.0 + (sum_m_phi_minus_1 / molalitysum);
3201 } else {
3202 d_osmotic_coef_dT = 0.0;
3203 }
3204
3205 double d_lnwateract_dT = -(m_weightSolvent/1000.0) * molalitysum * d_osmotic_coef_dT;
3206
3207 // In Cantera, we define the activity coefficient of the solvent as
3208 //
3209 // act_0 = actcoeff_0 * Xmol_0
3210 //
3211 // We have just computed act_0. However, this routine returns
3212 // ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
3213 m_dlnActCoeffMolaldT_Unscaled[0] = d_lnwateract_dT;
3214}
3215
3217{
3218 static const int cacheId = m_cache.getId();
3219 CachedScalar cached = m_cache.getScalar(cacheId);
3220 if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
3221 return;
3222 }
3223
3224 // Zero the unscaled 2nd derivatives
3226
3227 //! Calculate the unscaled 2nd derivatives
3229
3230 for (size_t k = 1; k < m_kk; k++) {
3231 if (CROP_speciesCropped_[k] == 2) {
3233 }
3234 }
3235
3236 if (CROP_speciesCropped_[0]) {
3238 }
3239
3240 // Scale the 2nd derivatives
3242}
3243
3245{
3246 const double* molality = m_molalitiesCropped.data();
3247
3248 // Local variables defined by Coltrin
3249 double etheta[5][5], etheta_prime[5][5], sqrtIs;
3250
3251 // Molality based ionic strength of the solution
3252 double Is = 0.0;
3253
3254 // Molar charge of the solution: In Pitzer's notation, this is his variable
3255 // called "Z".
3256 double molarcharge = 0.0;
3257
3258 // molalitysum is the sum of the molalities over all solutes, even those
3259 // with zero charge.
3260 double molalitysum = 0.0;
3261
3262 // Make sure the counter variables are setup
3264
3265 // ---------- Calculate common sums over solutes ---------------------
3266 for (size_t n = 1; n < m_kk; n++) {
3267 // ionic strength
3268 Is += charge(n) * charge(n) * molality[n];
3269 // total molar charge
3270 molarcharge += fabs(charge(n)) * molality[n];
3271 molalitysum += molality[n];
3272 }
3273 Is *= 0.5;
3274
3275 // Store the ionic molality in the object for reference.
3276 m_IionicMolality = Is;
3277 sqrtIs = sqrt(Is);
3278
3279 // The following call to calc_lambdas() calculates all 16 elements of the
3280 // elambda and elambda1 arrays, given the value of the ionic strength (Is)
3281 calc_lambdas(Is);
3282
3283 // Step 2: Find the coefficients E-theta and E-thetaprime for all
3284 // combinations of positive unlike charges up to 4
3285 for (int z1 = 1; z1 <=4; z1++) {
3286 for (int z2 =1; z2 <=4; z2++) {
3287 calc_thetas(z1, z2, &etheta[z1][z2], &etheta_prime[z1][z2]);
3288 }
3289 }
3290
3291 // calculate gfunc(x) and hfunc(x) for each cation-anion pair MX. In the
3292 // original literature, hfunc, was called gprime. However, it's not the
3293 // derivative of gfunc(x), so I renamed it.
3294 for (size_t i = 1; i < (m_kk - 1); i++) {
3295 for (size_t j = (i+1); j < m_kk; j++) {
3296 // Find the counterIJ for the symmetric binary interaction
3297 size_t n = m_kk*i + j;
3298 size_t counterIJ = m_CounterIJ[n];
3299
3300 // Only loop over oppositely charge species
3301 if (charge(i)*charge(j) < 0) {
3302 // x is a reduced function variable
3303 double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
3304 if (x1 > 1.0E-100) {
3305 m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 *x1);
3306 m_hfunc_IJ[counterIJ] = -2.0*
3307 (1.0-(1.0 + x1 + 0.5*x1 * x1) * exp(-x1)) / (x1 * x1);
3308 } else {
3309 m_gfunc_IJ[counterIJ] = 0.0;
3310 m_hfunc_IJ[counterIJ] = 0.0;
3311 }
3312
3313 if (m_Beta2MX_ij_LL[counterIJ] != 0.0) {
3314 double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
3315 if (x2 > 1.0E-100) {
3316 m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
3317 m_h2func_IJ[counterIJ] = -2.0 *
3318 (1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
3319 } else {
3320 m_g2func_IJ[counterIJ] = 0.0;
3321 m_h2func_IJ[counterIJ] = 0.0;
3322 }
3323 }
3324 } else {
3325 m_gfunc_IJ[counterIJ] = 0.0;
3326 m_hfunc_IJ[counterIJ] = 0.0;
3327 }
3328 }
3329 }
3330
3331 // SUBSECTION TO CALCULATE BMX_L, BprimeMX_LL, BphiMX_L
3332 // These are now temperature derivatives of the previously calculated
3333 // quantities.
3334 for (size_t i = 1; i < m_kk - 1; i++) {
3335 for (size_t j = i+1; j < m_kk; j++) {
3336 // Find the counterIJ for the symmetric binary interaction
3337 size_t n = m_kk*i + j;
3338 size_t counterIJ = m_CounterIJ[n];
3339
3340 // both species have a non-zero charge, and one is positive
3341 // and the other is negative
3342 if (charge(i)*charge(j) < 0.0) {
3343 m_BMX_IJ_LL[counterIJ] = m_Beta0MX_ij_LL[counterIJ]
3344 + m_Beta1MX_ij_LL[counterIJ] * m_gfunc_IJ[counterIJ]
3345 + m_Beta2MX_ij_LL[counterIJ] * m_g2func_IJ[counterIJ];
3346 if (Is > 1.0E-150) {
3347 m_BprimeMX_IJ_LL[counterIJ] = (m_Beta1MX_ij_LL[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
3348 m_Beta2MX_ij_LL[counterIJ] * m_h2func_IJ[counterIJ]/Is);
3349 } else {
3350 m_BprimeMX_IJ_LL[counterIJ] = 0.0;
3351 }
3352 m_BphiMX_IJ_LL[counterIJ] = m_BMX_IJ_LL[counterIJ] + Is*m_BprimeMX_IJ_LL[counterIJ];
3353 } else {
3354 m_BMX_IJ_LL[counterIJ] = 0.0;
3355 m_BprimeMX_IJ_LL[counterIJ] = 0.0;
3356 m_BphiMX_IJ_LL[counterIJ] = 0.0;
3357 }
3358 }
3359 }
3360
3361 // --------- SUBSECTION TO CALCULATE CMX_LL ----------
3362 for (size_t i = 1; i < m_kk-1; i++) {
3363 for (size_t j = i+1; j < m_kk; j++) {
3364 // Find the counterIJ for the symmetric binary interaction
3365 size_t n = m_kk*i + j;
3366 size_t counterIJ = m_CounterIJ[n];
3367
3368 // both species have a non-zero charge, and one is positive
3369 // and the other is negative
3370 if (charge(i)*charge(j) < 0.0) {
3371 m_CMX_IJ_LL[counterIJ] = m_CphiMX_ij_LL[counterIJ]/
3372 (2.0* sqrt(fabs(charge(i)*charge(j))));
3373 } else {
3374 m_CMX_IJ_LL[counterIJ] = 0.0;
3375 }
3376 }
3377 }
3378
3379 // ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ----------
3380 for (size_t i = 1; i < m_kk-1; i++) {
3381 for (size_t j = i+1; j < m_kk; j++) {
3382 // Find the counterIJ for the symmetric binary interaction
3383 size_t n = m_kk*i + j;
3384 size_t counterIJ = m_CounterIJ[n];
3385
3386 // both species have a non-zero charge, and one is positive
3387 // and the other is negative
3388 if (charge(i)*charge(j) > 0) {
3389 m_Phi_IJ_LL[counterIJ] = m_Theta_ij_LL[counterIJ];
3390 m_Phiprime_IJ[counterIJ] = 0.0;
3391 m_PhiPhi_IJ_LL[counterIJ] = m_Phi_IJ_LL[counterIJ];
3392 } else {
3393 m_Phi_IJ_LL[counterIJ] = 0.0;
3394 m_Phiprime_IJ[counterIJ] = 0.0;
3395 m_PhiPhi_IJ_LL[counterIJ] = 0.0;
3396 }
3397 }
3398 }
3399
3400 // ----------- SUBSECTION FOR CALCULATION OF d2FdT2 ---------------------
3401 double d2AphidT2 = d2A_DebyedT2_TP() / 3.0;
3402 double d2FdT2 = -d2AphidT2 * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
3403 + (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
3404 for (size_t i = 1; i < m_kk-1; i++) {
3405 for (size_t j = i+1; j < m_kk; j++) {
3406 // Find the counterIJ for the symmetric binary interaction
3407 size_t n = m_kk*i + j;
3408 size_t counterIJ = m_CounterIJ[n];
3409
3410 // both species have a non-zero charge, and one is positive
3411 // and the other is negative
3412 if (charge(i)*charge(j) < 0) {
3413 d2FdT2 += molality[i]*molality[j] * m_BprimeMX_IJ_LL[counterIJ];
3414 }
3415
3416 // Both species have a non-zero charge, and they
3417 // have the same sign, that is, both positive or both negative.
3418 if (charge(i)*charge(j) > 0) {
3419 d2FdT2 += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
3420 }
3421 }
3422 }
3423
3424 for (size_t i = 1; i < m_kk; i++) {
3425 // -------- SUBSECTION FOR CALCULATING THE dACTCOEFFdT FOR CATIONS -----
3426 if (charge(i) > 0) {
3427 // species i is the cation (positive) to calc the actcoeff
3428 double zsqd2FdT2 = charge(i)*charge(i)*d2FdT2;
3429 double sum1 = 0.0;
3430 double sum2 = 0.0;
3431 double sum3 = 0.0;
3432 double sum4 = 0.0;
3433 double sum5 = 0.0;
3434 for (size_t j = 1; j < m_kk; j++) {
3435 // Find the counterIJ for the symmetric binary interaction
3436 size_t n = m_kk*i + j;
3437 size_t counterIJ = m_CounterIJ[n];
3438
3439 if (charge(j) < 0.0) {
3440 // sum over all anions
3441 sum1 += molality[j]*
3442 (2.0*m_BMX_IJ_LL[counterIJ] + molarcharge*m_CMX_IJ_LL[counterIJ]);
3443 if (j < m_kk-1) {
3444 // This term is the ternary interaction involving the
3445 // non-duplicate sum over double anions, j, k, with
3446 // respect to the cation, i.
3447 for (size_t k = j+1; k < m_kk; k++) {
3448 // an inner sum over all anions
3449 if (charge(k) < 0.0) {
3450 n = k + j * m_kk + i * m_kk * m_kk;
3451 sum3 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
3452 }
3453 }
3454 }
3455 }
3456
3457 if (charge(j) > 0.0) {
3458 // sum over all cations
3459 if (j != i) {
3460 sum2 += molality[j]*(2.0*m_Phi_IJ_LL[counterIJ]);
3461 }
3462 for (size_t k = 1; k < m_kk; k++) {
3463 if (charge(k) < 0.0) {
3464 // two inner sums over anions
3465 n = k + j * m_kk + i * m_kk * m_kk;
3466 sum2 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
3467
3468 // Find the counterIJ for the j,k interaction
3469 n = m_kk*j + k;
3470 size_t counterIJ2 = m_CounterIJ[n];
3471 sum4 += fabs(charge(i)) *
3472 molality[j]*molality[k]*m_CMX_IJ_LL[counterIJ2];
3473 }
3474 }
3475 }
3476
3477 // Handle neutral j species
3478 if (charge(j) == 0) {
3479 sum5 += molality[j]*2.0*m_Lambda_nj_LL(j,i);
3480 // Zeta interaction term
3481 for (size_t k = 1; k < m_kk; k++) {
3482 if (charge(k) < 0.0) {
3483 size_t izeta = j;
3484 size_t jzeta = i;
3485 n = izeta * m_kk * m_kk + jzeta * m_kk + k;
3486 double zeta_LL = m_Psi_ijk_LL[n];
3487 if (zeta_LL != 0.0) {
3488 sum5 += molality[j]*molality[k]*zeta_LL;
3489 }
3490 }
3491 }
3492 }
3493 }
3494 // Add all of the contributions up to yield the log of the
3495 // solute activity coefficients (molality scale)
3497 zsqd2FdT2 + sum1 + sum2 + sum3 + sum4 + sum5;
3498 }
3499
3500 // ------ SUBSECTION FOR CALCULATING THE d2ACTCOEFFdT2 FOR ANIONS ------
3501 if (charge(i) < 0) {
3502 // species i is an anion (negative)
3503 double zsqd2FdT2 = charge(i)*charge(i)*d2FdT2;
3504 double sum1 = 0.0;
3505 double sum2 = 0.0;
3506 double sum3 = 0.0;
3507 double sum4 = 0.0;
3508 double sum5 = 0.0;
3509 for (size_t j = 1; j < m_kk; j++) {
3510 // Find the counterIJ for the symmetric binary interaction
3511 size_t n = m_kk*i + j;
3512 size_t counterIJ = m_CounterIJ[n];
3513
3514 // For Anions, do the cation interactions.
3515 if (charge(j) > 0) {
3516 sum1 += molality[j]*
3517 (2.0*m_BMX_IJ_LL[counterIJ] + molarcharge*m_CMX_IJ_LL[counterIJ]);
3518 if (j < m_kk-1) {
3519 for (size_t k = j+1; k < m_kk; k++) {
3520 // an inner sum over all cations
3521 if (charge(k) > 0) {
3522 n = k + j * m_kk + i * m_kk * m_kk;
3523 sum3 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
3524 }
3525 }
3526 }
3527 }
3528
3529 // For Anions, do the other anion interactions.
3530 if (charge(j) < 0.0) {
3531 // sum over all anions
3532 if (j != i) {
3533 sum2 += molality[j]*(2.0*m_Phi_IJ_LL[counterIJ]);
3534 }
3535 for (size_t k = 1; k < m_kk; k++) {
3536 if (charge(k) > 0.0) {
3537 // two inner sums over cations
3538 n = k + j * m_kk + i * m_kk * m_kk;
3539 sum2 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
3540 // Find the counterIJ for the symmetric binary interaction
3541 n = m_kk*j + k;
3542 size_t counterIJ2 = m_CounterIJ[n];
3543 sum4 += fabs(charge(i)) *
3544 molality[j]*molality[k]*m_CMX_IJ_LL[counterIJ2];
3545 }
3546 }
3547 }
3548
3549 // for Anions, do the neutral species interaction
3550 if (charge(j) == 0.0) {
3551 sum5 += molality[j]*2.0*m_Lambda_nj_LL(j,i);
3552 // Zeta interaction term
3553 for (size_t k = 1; k < m_kk; k++) {
3554 if (charge(k) > 0.0) {
3555 size_t izeta = j;
3556 size_t jzeta = k;
3557 size_t kzeta = i;
3558 n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
3559 double zeta_LL = m_Psi_ijk_LL[n];
3560 if (zeta_LL != 0.0) {
3561 sum5 += molality[j]*molality[k]*zeta_LL;
3562 }
3563 }
3564 }
3565 }
3566 }
3568 zsqd2FdT2 + sum1 + sum2 + sum3 + sum4 + sum5;
3569 }
3570
3571 // SUBSECTION FOR CALCULATING NEUTRAL SOLUTE ACT COEFF
3572 // equations agree with my notes,
3573 // Equations agree with Pitzer,
3574 if (charge(i) == 0.0) {
3575 double sum1 = 0.0;
3576 double sum3 = 0.0;
3577 for (size_t j = 1; j < m_kk; j++) {
3578 sum1 += molality[j]*2.0*m_Lambda_nj_LL(i,j);
3579 // Zeta term -> we piggyback on the psi term
3580 if (charge(j) > 0.0) {
3581 for (size_t k = 1; k < m_kk; k++) {
3582 if (charge(k) < 0.0) {
3583 size_t n = k + j * m_kk + i * m_kk * m_kk;
3584 sum3 += molality[j]*molality[k]*m_Psi_ijk_LL[n];
3585 }
3586 }
3587 }
3588 }
3589 double sum2 = 3.0 * molality[i] * molality[i] * m_Mu_nnn_LL[i];
3590 m_d2lnActCoeffMolaldT2_Unscaled[i] = sum1 + sum2 + sum3;
3591 }
3592 }
3593
3594 // ------ SUBSECTION FOR CALCULATING THE d2 OSMOTIC COEFF dT2 ---------
3595 double sum1 = 0.0;
3596 double sum2 = 0.0;
3597 double sum3 = 0.0;
3598 double sum4 = 0.0;
3599 double sum5 = 0.0;
3600 double sum6 = 0.0;
3601 double sum7 = 0.0;
3602
3603 // term1 is the temperature derivative of the DH term in the osmotic
3604 // coefficient expression
3605 // b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer implementations.
3606 // Is = Ionic strength on the molality scale (units of (gmol/kg))
3607 // Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
3608 double term1 = -d2AphidT2 * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is));
3609
3610 for (size_t j = 1; j < m_kk; j++) {
3611 // Loop Over Cations
3612 if (charge(j) > 0.0) {
3613 for (size_t k = 1; k < m_kk; k++) {
3614 if (charge(k) < 0.0) {
3615 // Find the counterIJ for the symmetric j,k binary interaction
3616 size_t n = m_kk*j + k;
3617 size_t counterIJ = m_CounterIJ[n];
3618
3619 sum1 += molality[j]*molality[k] *
3620 (m_BphiMX_IJ_LL[counterIJ] + molarcharge*m_CMX_IJ_LL[counterIJ]);
3621 }
3622 }
3623
3624 for (size_t k = j+1; k < m_kk; k++) {
3625 if (j == (m_kk-1)) {
3626 // we should never reach this step
3627 throw CanteraError("HMWSoln::s_updatePitzer_d2lnMolalityActCoeff_dT2",
3628 "logic error 1 in Step 9 of hmw_act");
3629 }
3630 if (charge(k) > 0.0) {
3631 // Find the counterIJ for the symmetric j,k binary interaction
3632 // between 2 cations.
3633 size_t n = m_kk*j + k;
3634 size_t counterIJ = m_CounterIJ[n];
3635 sum2 += molality[j]*molality[k]*m_PhiPhi_IJ_LL[counterIJ];
3636 for (size_t m = 1; m < m_kk; m++) {
3637 if (charge(m) < 0.0) {
3638 // species m is an anion
3639 n = m + k * m_kk + j * m_kk * m_kk;
3640 sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_LL[n];
3641 }
3642 }
3643 }
3644 }
3645 }
3646
3647 // Loop Over Anions
3648 if (charge(j) < 0) {
3649 for (size_t k = j+1; k < m_kk; k++) {
3650 if (j == m_kk-1) {
3651 // we should never reach this step
3652 throw CanteraError("HMWSoln::s_updatePitzer_d2lnMolalityActCoeff_dT2",
3653 "logic error 2 in Step 9 of hmw_act");
3654 }
3655 if (charge(k) < 0) {
3656 // Find the counterIJ for the symmetric j,k binary interaction
3657 // between two anions
3658 size_t n = m_kk*j + k;
3659 size_t counterIJ = m_CounterIJ[n];
3660
3661 sum3 += molality[j]*molality[k]*m_PhiPhi_IJ_LL[counterIJ];
3662 for (size_t m = 1; m < m_kk; m++) {
3663 if (charge(m) > 0.0) {
3664 n = m + k * m_kk + j * m_kk * m_kk;
3665 sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_LL[n];
3666 }
3667 }
3668 }
3669 }
3670 }
3671
3672 // Loop Over Neutral Species
3673 if (charge(j) == 0) {
3674 for (size_t k = 1; k < m_kk; k++) {
3675 if (charge(k) < 0.0) {
3676 sum4 += molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
3677 }
3678 if (charge(k) > 0.0) {
3679 sum5 += molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
3680 }
3681 if (charge(k) == 0.0) {
3682 if (k > j) {
3683 sum6 += molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
3684 } else if (k == j) {
3685 sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj_LL(j,k);
3686 }
3687 }
3688 if (charge(k) < 0.0) {
3689 size_t izeta = j;
3690 for (size_t m = 1; m < m_kk; m++) {
3691 if (charge(m) > 0.0) {
3692 size_t jzeta = m;
3693 size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
3694 double zeta_LL = m_Psi_ijk_LL[n];
3695 if (zeta_LL != 0.0) {
3696 sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta_LL;
3697 }
3698 }
3699 }
3700 }
3701 }
3702
3703 sum7 += molality[j] * molality[j] * molality[j] * m_Mu_nnn_LL[j];
3704 }
3705 }
3706 double sum_m_phi_minus_1 = 2.0 *
3707 (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
3708 // Calculate the osmotic coefficient from
3709 // osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
3710 double d2_osmotic_coef_dT2;
3711 if (molalitysum > 1.0E-150) {
3712 d2_osmotic_coef_dT2 = 0.0 + (sum_m_phi_minus_1 / molalitysum);
3713 } else {
3714 d2_osmotic_coef_dT2 = 0.0;
3715 }
3716 double d2_lnwateract_dT2 = -(m_weightSolvent/1000.0) * molalitysum * d2_osmotic_coef_dT2;
3717
3718 // In Cantera, we define the activity coefficient of the solvent as
3719 //
3720 // act_0 = actcoeff_0 * Xmol_0
3721 //
3722 // We have just computed act_0. However, this routine returns
3723 // ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
3724 m_d2lnActCoeffMolaldT2_Unscaled[0] = d2_lnwateract_dT2;
3725}
3726
3728{
3729 static const int cacheId = m_cache.getId();
3730 CachedScalar cached = m_cache.getScalar(cacheId);
3731 if( cached.validate(temperature(), pressure(), stateMFNumber()) ) {
3732 return;
3733 }
3734
3737
3738 for (size_t k = 1; k < m_kk; k++) {
3739 if (CROP_speciesCropped_[k] == 2) {
3741 }
3742 }
3743
3744 if (CROP_speciesCropped_[0]) {
3746 }
3747
3749}
3750
3752{
3753 const double* molality = m_molalitiesCropped.data();
3754
3755 // Local variables defined by Coltrin
3756 double etheta[5][5], etheta_prime[5][5], sqrtIs;
3757
3758 // Molality based ionic strength of the solution
3759 double Is = 0.0;
3760
3761 // Molar charge of the solution: In Pitzer's notation, this is his variable
3762 // called "Z".
3763 double molarcharge = 0.0;
3764
3765 // molalitysum is the sum of the molalities over all solutes, even those
3766 // with zero charge.
3767 double molalitysum = 0.0;
3768 double currTemp = temperature();
3769 double currPres = pressure();
3770
3771 // Make sure the counter variables are setup
3773
3774 // ---------- Calculate common sums over solutes ---------------------
3775 for (size_t n = 1; n < m_kk; n++) {
3776 // ionic strength
3777 Is += charge(n) * charge(n) * molality[n];
3778 // total molar charge
3779 molarcharge += fabs(charge(n)) * molality[n];
3780 molalitysum += molality[n];
3781 }
3782 Is *= 0.5;
3783
3784 // Store the ionic molality in the object for reference.
3785 m_IionicMolality = Is;
3786 sqrtIs = sqrt(Is);
3787
3788 // The following call to calc_lambdas() calculates all 16 elements of the
3789 // elambda and elambda1 arrays, given the value of the ionic strength (Is)
3790 calc_lambdas(Is);
3791
3792 // Step 2: Find the coefficients E-theta and E-thetaprime for all
3793 // combinations of positive unlike charges up to 4
3794 for (int z1 = 1; z1 <=4; z1++) {
3795 for (int z2 =1; z2 <=4; z2++) {
3796 calc_thetas(z1, z2, &etheta[z1][z2], &etheta_prime[z1][z2]);
3797 }
3798 }
3799
3800 // calculate g(x) and hfunc(x) for each cation-anion pair MX
3801 // In the original literature, hfunc, was called gprime. However,
3802 // it's not the derivative of g(x), so I renamed it.
3803 for (size_t i = 1; i < (m_kk - 1); i++) {
3804 for (size_t j = (i+1); j < m_kk; j++) {
3805 // Find the counterIJ for the symmetric binary interaction
3806 size_t n = m_kk*i + j;
3807 size_t counterIJ = m_CounterIJ[n];
3808
3809 // Only loop over oppositely charge species
3810 if (charge(i)*charge(j) < 0) {
3811 // x is a reduced function variable
3812 double x1 = sqrtIs * m_Alpha1MX_ij[counterIJ];
3813 if (x1 > 1.0E-100) {
3814 m_gfunc_IJ[counterIJ] = 2.0*(1.0-(1.0 + x1) * exp(-x1)) / (x1 * x1);
3815 m_hfunc_IJ[counterIJ] = -2.0*
3816 (1.0-(1.0 + x1 + 0.5 * x1 * x1) * exp(-x1)) / (x1 * x1);
3817 } else {
3818 m_gfunc_IJ[counterIJ] = 0.0;
3819 m_hfunc_IJ[counterIJ] = 0.0;
3820 }
3821
3822 if (m_Beta2MX_ij_P[counterIJ] != 0.0) {
3823 double x2 = sqrtIs * m_Alpha2MX_ij[counterIJ];
3824 if (x2 > 1.0E-100) {
3825 m_g2func_IJ[counterIJ] = 2.0*(1.0-(1.0 + x2) * exp(-x2)) / (x2 * x2);
3826 m_h2func_IJ[counterIJ] = -2.0 *
3827 (1.0-(1.0 + x2 + 0.5 * x2 * x2) * exp(-x2)) / (x2 * x2);
3828 } else {
3829 m_g2func_IJ[counterIJ] = 0.0;
3830 m_h2func_IJ[counterIJ] = 0.0;
3831 }
3832 }
3833 } else {
3834 m_gfunc_IJ[counterIJ] = 0.0;
3835 m_hfunc_IJ[counterIJ] = 0.0;
3836 }
3837 }
3838 }
3839
3840 // SUBSECTION TO CALCULATE BMX_P, BprimeMX_P, BphiMX_P
3841 // These are now temperature derivatives of the previously calculated
3842 // quantities.
3843 for (size_t i = 1; i < m_kk - 1; i++) {
3844 for (size_t j = i+1; j < m_kk; j++) {
3845 // Find the counterIJ for the symmetric binary interaction
3846 size_t n = m_kk*i + j;
3847 size_t counterIJ = m_CounterIJ[n];
3848
3849 // both species have a non-zero charge, and one is positive
3850 // and the other is negative
3851 if (charge(i)*charge(j) < 0.0) {
3852 m_BMX_IJ_P[counterIJ] = m_Beta0MX_ij_P[counterIJ]
3853 + m_Beta1MX_ij_P[counterIJ] * m_gfunc_IJ[counterIJ]
3854 + m_Beta2MX_ij_P[counterIJ] * m_g2func_IJ[counterIJ];
3855 if (Is > 1.0E-150) {
3856 m_BprimeMX_IJ_P[counterIJ] = (m_Beta1MX_ij_P[counterIJ] * m_hfunc_IJ[counterIJ]/Is +
3857 m_Beta2MX_ij_P[counterIJ] * m_h2func_IJ[counterIJ]/Is);
3858 } else {
3859 m_BprimeMX_IJ_P[counterIJ] = 0.0;
3860 }
3861 m_BphiMX_IJ_P[counterIJ] = m_BMX_IJ_P[counterIJ] + Is*m_BprimeMX_IJ_P[counterIJ];
3862 } else {
3863 m_BMX_IJ_P[counterIJ] = 0.0;
3864 m_BprimeMX_IJ_P[counterIJ] = 0.0;
3865 m_BphiMX_IJ_P[counterIJ] = 0.0;
3866 }
3867 }
3868 }
3869
3870 // --------- SUBSECTION TO CALCULATE CMX_P ----------
3871 for (size_t i = 1; i < m_kk-1; i++) {
3872 for (size_t j = i+1; j < m_kk; j++) {
3873 // Find the counterIJ for the symmetric binary interaction
3874 size_t n = m_kk*i + j;
3875 size_t counterIJ = m_CounterIJ[n];
3876
3877 // both species have a non-zero charge, and one is positive
3878 // and the other is negative
3879 if (charge(i)*charge(j) < 0.0) {
3880 m_CMX_IJ_P[counterIJ] = m_CphiMX_ij_P[counterIJ]/
3881 (2.0* sqrt(fabs(charge(i)*charge(j))));
3882 } else {
3883 m_CMX_IJ_P[counterIJ] = 0.0;
3884 }
3885 }
3886 }
3887
3888 // ------- SUBSECTION TO CALCULATE Phi, PhiPrime, and PhiPhi ----------
3889 for (size_t i = 1; i < m_kk-1; i++) {
3890 for (size_t j = i+1; j < m_kk; j++) {
3891 // Find the counterIJ for the symmetric binary interaction
3892 size_t n = m_kk*i + j;
3893 size_t counterIJ = m_CounterIJ[n];
3894
3895 // both species have a non-zero charge, and one is positive
3896 // and the other is negative
3897 if (charge(i)*charge(j) > 0) {
3898 m_Phi_IJ_P[counterIJ] = m_Theta_ij_P[counterIJ];
3899 m_Phiprime_IJ[counterIJ] = 0.0;
3900 m_PhiPhi_IJ_P[counterIJ] = m_Phi_IJ_P[counterIJ] + Is * m_Phiprime_IJ[counterIJ];
3901 } else {
3902 m_Phi_IJ_P[counterIJ] = 0.0;
3903 m_Phiprime_IJ[counterIJ] = 0.0;
3904 m_PhiPhi_IJ_P[counterIJ] = 0.0;
3905 }
3906 }
3907 }
3908
3909 // ----------- SUBSECTION FOR CALCULATION OF dFdT ---------------------
3910 double dA_DebyedP = dA_DebyedP_TP(currTemp, currPres);
3911 double dAphidP = dA_DebyedP /3.0;
3912 double dFdP = -dAphidP * (sqrt(Is) / (1.0 + 1.2*sqrt(Is))
3913 + (2.0/1.2) * log(1.0+1.2*(sqrtIs)));
3914 for (size_t i = 1; i < m_kk-1; i++) {
3915 for (size_t j = i+1; j < m_kk; j++) {
3916 // Find the counterIJ for the symmetric binary interaction
3917 size_t n = m_kk*i + j;
3918 size_t counterIJ = m_CounterIJ[n];
3919
3920 // both species have a non-zero charge, and one is positive
3921 // and the other is negative
3922 if (charge(i)*charge(j) < 0) {
3923 dFdP += molality[i]*molality[j] * m_BprimeMX_IJ_P[counterIJ];
3924 }
3925
3926 // Both species have a non-zero charge, and they
3927 // have the same sign, that is, both positive or both negative.
3928 if (charge(i)*charge(j) > 0) {
3929 dFdP += molality[i]*molality[j] * m_Phiprime_IJ[counterIJ];
3930 }
3931 }
3932 }
3933
3934 for (size_t i = 1; i < m_kk; i++) {
3935 // -------- SUBSECTION FOR CALCULATING THE dACTCOEFFdP FOR CATIONS -----
3936 if (charge(i) > 0) {
3937 // species i is the cation (positive) to calc the actcoeff
3938 double zsqdFdP = charge(i)*charge(i)*dFdP;
3939 double sum1 = 0.0;
3940 double sum2 = 0.0;
3941 double sum3 = 0.0;
3942 double sum4 = 0.0;
3943 double sum5 = 0.0;
3944 for (size_t j = 1; j < m_kk; j++) {
3945 // Find the counterIJ for the symmetric binary interaction
3946 size_t n = m_kk*i + j;
3947 size_t counterIJ = m_CounterIJ[n];
3948
3949 if (charge(j) < 0.0) {
3950 // sum over all anions
3951 sum1 += molality[j]*
3952 (2.0*m_BMX_IJ_P[counterIJ] + molarcharge*m_CMX_IJ_P[counterIJ]);
3953 if (j < m_kk-1) {
3954 // This term is the ternary interaction involving the
3955 // non-duplicate sum over double anions, j, k, with
3956 // respect to the cation, i.
3957 for (size_t k = j+1; k < m_kk; k++) {
3958 // an inner sum over all anions
3959 if (charge(k) < 0.0) {
3960 n = k + j * m_kk + i * m_kk * m_kk;
3961 sum3 += molality[j]*molality[k]*m_Psi_ijk_P[n];
3962 }
3963 }
3964 }
3965 }
3966
3967 if (charge(j) > 0.0) {
3968 // sum over all cations
3969 if (j != i) {
3970 sum2 += molality[j]*(2.0*m_Phi_IJ_P[counterIJ]);
3971 }
3972 for (size_t k = 1; k < m_kk; k++) {
3973 if (charge(k) < 0.0) {
3974 // two inner sums over anions
3975 n = k + j * m_kk + i * m_kk * m_kk;
3976 sum2 += molality[j]*molality[k]*m_Psi_ijk_P[n];
3977
3978 // Find the counterIJ for the j,k interaction
3979 n = m_kk*j + k;
3980 size_t counterIJ2 = m_CounterIJ[n];
3981 sum4 += fabs(charge(i)) *
3982 molality[j]*molality[k]*m_CMX_IJ_P[counterIJ2];
3983 }
3984 }
3985 }
3986
3987 // for Anions, do the neutral species interaction
3988 if (charge(j) == 0) {
3989 sum5 += molality[j]*2.0*m_Lambda_nj_P(j,i);
3990 // Zeta interaction term
3991 for (size_t k = 1; k < m_kk; k++) {
3992 if (charge(k) < 0.0) {
3993 size_t izeta = j;
3994 size_t jzeta = i;
3995 n = izeta * m_kk * m_kk + jzeta * m_kk + k;
3996 double zeta_P = m_Psi_ijk_P[n];
3997 if (zeta_P != 0.0) {
3998 sum5 += molality[j]*molality[k]*zeta_P;
3999 }
4000 }
4001 }
4002 }
4003 }
4004
4005 // Add all of the contributions up to yield the log of the
4006 // solute activity coefficients (molality scale)
4008 zsqdFdP + sum1 + sum2 + sum3 + sum4 + sum5;
4009 }
4010
4011 // ------ SUBSECTION FOR CALCULATING THE dACTCOEFFdP FOR ANIONS ------
4012 if (charge(i) < 0) {
4013 // species i is an anion (negative)
4014 double zsqdFdP = charge(i)*charge(i)*dFdP;
4015 double sum1 = 0.0;
4016 double sum2 = 0.0;
4017 double sum3 = 0.0;
4018 double sum4 = 0.0;
4019 double sum5 = 0.0;
4020 for (size_t j = 1; j < m_kk; j++) {
4021 // Find the counterIJ for the symmetric binary interaction
4022 size_t n = m_kk*i + j;
4023 size_t counterIJ = m_CounterIJ[n];
4024
4025 // For Anions, do the cation interactions.
4026 if (charge(j) > 0) {
4027 sum1 += molality[j] *
4028 (2.0*m_BMX_IJ_P[counterIJ] + molarcharge*m_CMX_IJ_P[counterIJ]);
4029 if (j < m_kk-1) {
4030 for (size_t k = j+1; k < m_kk; k++) {
4031 // an inner sum over all cations
4032 if (charge(k) > 0) {
4033 n = k + j * m_kk + i * m_kk * m_kk;
4034 sum3 += molality[j]*molality[k]*m_Psi_ijk_P[n];
4035 }
4036 }
4037 }
4038 }
4039
4040 // For Anions, do the other anion interactions.
4041 if (charge(j) < 0.0) {
4042 // sum over all anions
4043 if (j != i) {
4044 sum2 += molality[j]*(2.0*m_Phi_IJ_P[counterIJ]);
4045 }
4046 for (size_t k = 1; k < m_kk; k++) {
4047 if (charge(k) > 0.0) {
4048 // two inner sums over cations
4049 n = k + j * m_kk + i * m_kk * m_kk;
4050 sum2 += molality[j]*molality[k]*m_Psi_ijk_P[n];
4051 // Find the counterIJ for the symmetric binary interaction
4052 n = m_kk*j + k;
4053 size_t counterIJ2 = m_CounterIJ[n];
4054 sum4 += fabs(charge(i))*
4055 molality[j]*molality[k]*m_CMX_IJ_P[counterIJ2];
4056 }
4057 }
4058 }
4059
4060 // for Anions, do the neutral species interaction
4061 if (charge(j) == 0.0) {
4062 sum5 += molality[j]*2.0*m_Lambda_nj_P(j,i);
4063 // Zeta interaction term
4064 for (size_t k = 1; k < m_kk; k++) {
4065 if (charge(k) > 0.0) {
4066 size_t izeta = j;
4067 size_t jzeta = k;
4068 size_t kzeta = i;
4069 n = izeta * m_kk * m_kk + jzeta * m_kk + kzeta;
4070 double zeta_P = m_Psi_ijk_P[n];
4071 if (zeta_P != 0.0) {
4072 sum5 += molality[j]*molality[k]*zeta_P;
4073 }
4074 }
4075 }
4076 }
4077 }
4079 zsqdFdP + sum1 + sum2 + sum3 + sum4 + sum5;
4080 }
4081
4082 // ------ SUBSECTION FOR CALCULATING d NEUTRAL SOLUTE ACT COEFF dP -----
4083 if (charge(i) == 0.0) {
4084 double sum1 = 0.0;
4085 double sum3 = 0.0;
4086 for (size_t j = 1; j < m_kk; j++) {
4087 sum1 += molality[j]*2.0*m_Lambda_nj_P(i,j);
4088 // Zeta term -> we piggyback on the psi term
4089 if (charge(j) > 0.0) {
4090 for (size_t k = 1; k < m_kk; k++) {
4091 if (charge(k) < 0.0) {
4092 size_t n = k + j * m_kk + i * m_kk * m_kk;
4093 sum3 += molality[j]*molality[k]*m_Psi_ijk_P[n];
4094 }
4095 }
4096 }
4097 }
4098 double sum2 = 3.0 * molality[i] * molality[i] * m_Mu_nnn_P[i];
4099 m_dlnActCoeffMolaldP_Unscaled[i] = sum1 + sum2 + sum3;
4100 }
4101 }
4102
4103 // ------ SUBSECTION FOR CALCULATING THE d OSMOTIC COEFF dP ---------
4104 double sum1 = 0.0;
4105 double sum2 = 0.0;
4106 double sum3 = 0.0;
4107 double sum4 = 0.0;
4108 double sum5 = 0.0;
4109 double sum6 = 0.0;
4110 double sum7 = 0.0;
4111
4112 // term1 is the temperature derivative of the DH term in the osmotic
4113 // coefficient expression
4114 // b = 1.2 sqrt(kg/gmol) <- arbitrarily set in all Pitzer implementations.
4115 // Is = Ionic strength on the molality scale (units of (gmol/kg))
4116 // Aphi = A_Debye / 3 (units of sqrt(kg/gmol))
4117 double term1 = -dAphidP * Is * sqrt(Is) / (1.0 + 1.2 * sqrt(Is));
4118
4119 for (size_t j = 1; j < m_kk; j++) {
4120 // Loop Over Cations
4121 if (charge(j) > 0.0) {
4122 for (size_t k = 1; k < m_kk; k++) {
4123 if (charge(k) < 0.0) {
4124 // Find the counterIJ for the symmetric j,k binary interaction
4125 size_t n = m_kk*j + k;
4126 size_t counterIJ = m_CounterIJ[n];
4127 sum1 += molality[j]*molality[k]*
4128 (m_BphiMX_IJ_P[counterIJ] + molarcharge*m_CMX_IJ_P[counterIJ]);
4129 }
4130 }
4131
4132 for (size_t k = j+1; k < m_kk; k++) {
4133 if (j == (m_kk-1)) {
4134 // we should never reach this step
4135 throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dP",
4136 "logic error 1 in Step 9 of hmw_act");
4137 }
4138 if (charge(k) > 0.0) {
4139 // Find the counterIJ for the symmetric j,k binary interaction
4140 // between 2 cations.
4141 size_t n = m_kk*j + k;
4142 size_t counterIJ = m_CounterIJ[n];
4143 sum2 += molality[j]*molality[k]*m_PhiPhi_IJ_P[counterIJ];
4144 for (size_t m = 1; m < m_kk; m++) {
4145 if (charge(m) < 0.0) {
4146 // species m is an anion
4147 n = m + k * m_kk + j * m_kk * m_kk;
4148 sum2 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_P[n];
4149 }
4150 }
4151 }
4152 }
4153 }
4154
4155 // Loop Over Anions
4156 if (charge(j) < 0) {
4157 for (size_t k = j+1; k < m_kk; k++) {
4158 if (j == m_kk-1) {
4159 // we should never reach this step
4160 throw CanteraError("HMWSoln::s_updatePitzer_dlnMolalityActCoeff_dP",
4161 "logic error 2 in Step 9 of hmw_act");
4162 }
4163 if (charge(k) < 0) {
4164 // Find the counterIJ for the symmetric j,k binary interaction
4165 // between two anions
4166 size_t n = m_kk*j + k;
4167 size_t counterIJ = m_CounterIJ[n];
4168
4169 sum3 += molality[j]*molality[k]*m_PhiPhi_IJ_P[counterIJ];
4170 for (size_t m = 1; m < m_kk; m++) {
4171 if (charge(m) > 0.0) {
4172 n = m + k * m_kk + j * m_kk * m_kk;
4173 sum3 += molality[j]*molality[k]*molality[m]*m_Psi_ijk_P[n];
4174 }
4175 }
4176 }
4177 }
4178 }
4179
4180 // Loop Over Neutral Species
4181 if (charge(j) == 0) {
4182 for (size_t k = 1; k < m_kk; k++) {
4183 if (charge(k) < 0.0) {
4184 sum4 += molality[j]*molality[k]*m_Lambda_nj_P(j,k);
4185 }
4186 if (charge(k) > 0.0) {
4187 sum5 += molality[j]*molality[k]*m_Lambda_nj_P(j,k);
4188 }
4189 if (charge(k) == 0.0) {
4190 if (k > j) {
4191 sum6 += molality[j]*molality[k]*m_Lambda_nj_P(j,k);
4192 } else if (k == j) {
4193 sum6 += 0.5 * molality[j]*molality[k]*m_Lambda_nj_P(j,k);
4194 }
4195 }
4196 if (charge(k) < 0.0) {
4197 size_t izeta = j;
4198 for (size_t m = 1; m < m_kk; m++) {
4199 if (charge(m) > 0.0) {
4200 size_t jzeta = m;
4201 size_t n = k + jzeta * m_kk + izeta * m_kk * m_kk;
4202 double zeta_P = m_Psi_ijk_P[n];
4203 if (zeta_P != 0.0) {
4204 sum7 += molality[izeta]*molality[jzeta]*molality[k]*zeta_P;
4205 }
4206 }
4207 }
4208 }
4209 }
4210
4211 sum7 += molality[j] * molality[j] * molality[j] * m_Mu_nnn_P[j];
4212 }
4213 }
4214 double sum_m_phi_minus_1 = 2.0 *
4215 (term1 + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7);
4216
4217 // Calculate the osmotic coefficient from
4218 // osmotic_coeff = 1 + dGex/d(M0noRT) / sum(molality_i)
4219 double d_osmotic_coef_dP;
4220 if (molalitysum > 1.0E-150) {
4221 d_osmotic_coef_dP = 0.0 + (sum_m_phi_minus_1 / molalitysum);
4222 } else {
4223 d_osmotic_coef_dP = 0.0;
4224 }
4225 double d_lnwateract_dP = -(m_weightSolvent/1000.0) * molalitysum * d_osmotic_coef_dP;
4226
4227 // In Cantera, we define the activity coefficient of the solvent as
4228 //
4229 // act_0 = actcoeff_0 * Xmol_0
4230 //
4231 // We have just computed act_0. However, this routine returns
4232 // ln(actcoeff[]). Therefore, we must calculate ln(actcoeff_0).
4233 m_dlnActCoeffMolaldP_Unscaled[0] = d_lnwateract_dP;
4234}
4235
4236void HMWSoln::calc_lambdas(double is) const
4237{
4238 if( m_last_is == is ) {
4239 return;
4240 }
4241 m_last_is = is;
4242
4243 // Coefficients c1-c4 are used to approximate the integral function "J";
4244 // aphi is the Debye-Huckel constant at 25 C
4245 double c1 = 4.581, c2 = 0.7237, c3 = 0.0120, c4 = 0.528;
4246 double aphi = 0.392; /* Value at 25 C */
4247 if (is < 1.0E-150) {
4248 for (int i = 0; i < 17; i++) {
4249 elambda[i] = 0.0;
4250 elambda1[i] = 0.0;
4251 }
4252 return;
4253 }
4254
4255 // Calculate E-lambda terms for charge combinations of like sign,
4256 // using method of Pitzer (1975). Charges up to 4 are calculated.
4257 for (int i=1; i<=4; i++) {
4258 for (int j=i; j<=4; j++) {
4259 int ij = i*j;
4260
4261 // calculate the product of the charges
4262 double zprod = (double)ij;
4263
4264 // calculate Xmn (A1) from Harvie, Weare (1980).
4265 double x = 6.0* zprod * aphi * sqrt(is); // eqn 23
4266
4267 double jfunc = x / (4.0 + c1*pow(x,-c2)*exp(-c3*pow(x,c4))); // eqn 47
4268
4269 double t = c3 * c4 * pow(x,c4);
4270 double dj = c1* pow(x,(-c2-1.0)) * (c2+t) * exp(-c3*pow(x,c4));
4271 double jprime = (jfunc/x)*(1.0 + jfunc*dj);
4272
4273 elambda[ij] = zprod*jfunc / (4.0*is); // eqn 14
4274 elambda1[ij] = (3.0*zprod*zprod*aphi*jprime/(4.0*sqrt(is))
4275 - elambda[ij])/is;
4276 }
4277 }
4278}
4279
4280void HMWSoln::calc_thetas(int z1, int z2,
4281 double* etheta, double* etheta_prime) const
4282{
4283 // Calculate E-theta(i) and E-theta'(I) using method of Pitzer (1987)
4284 int i = abs(z1);
4285 int j = abs(z2);
4286
4287 AssertThrowMsg(i <= 4 && j <= 4, "HMWSoln::calc_thetas",
4288 "we shouldn't be here");
4289 AssertThrowMsg(i != 0 && j != 0, "HMWSoln::calc_thetas",
4290 "called with one species being neutral");
4291
4292 // Check to see if the charges are of opposite sign. If they are of opposite
4293 // sign then their etheta interaction is zero.
4294 if (z1*z2 < 0) {
4295 *etheta = 0.0;
4296 *etheta_prime = 0.0;
4297 } else {
4298 // Actually calculate the interaction.
4299 double f1 = (double)i / (2.0 * j);
4300 double f2 = (double)j / (2.0 * i);
4301 *etheta = elambda[i*j] - f1*elambda[j*j] - f2*elambda[i*i];
4302 *etheta_prime = elambda1[i*j] - f1*elambda1[j*j] - f2*elambda1[i*i];
4303 }
4304}
4305
4307{
4308 // Calculate the molalities. Currently, the molalities may not be current
4309 // with respect to the contents of the State objects' data.
4311 double xmolSolvent = moleFraction(0);
4312 double xx = std::max(m_xmolSolventMIN, xmolSolvent);
4313 // Exponentials - trial 2
4314 if (xmolSolvent > IMS_X_o_cutoff_) {
4315 for (size_t k = 1; k < m_kk; k++) {
4316 IMS_lnActCoeffMolal_[k]= 0.0;
4317 }
4318 IMS_lnActCoeffMolal_[0] = - log(xx) + (xx - 1.0)/xx;
4319 return;
4320 } else {
4321 double xoverc = xmolSolvent/IMS_cCut_;
4322 double eterm = std::exp(-xoverc);
4323
4324 double fptmp = IMS_bfCut_ - IMS_afCut_ / IMS_cCut_ - IMS_bfCut_*xoverc
4325 + 2.0*IMS_dfCut_*xmolSolvent - IMS_dfCut_*xmolSolvent*xoverc;
4326 double f_prime = 1.0 + eterm*fptmp;
4327 double f = xmolSolvent + IMS_efCut_
4328 + eterm * (IMS_afCut_ + xmolSolvent * (IMS_bfCut_ + IMS_dfCut_*xmolSolvent));
4329
4330 double gptmp = IMS_bgCut_ - IMS_agCut_ / IMS_cCut_ - IMS_bgCut_*xoverc
4331 + 2.0*IMS_dgCut_*xmolSolvent - IMS_dgCut_*xmolSolvent*xoverc;
4332 double g_prime = 1.0 + eterm*gptmp;
4333 double g = xmolSolvent + IMS_egCut_
4334 + eterm * (IMS_agCut_ + xmolSolvent * (IMS_bgCut_ + IMS_dgCut_*xmolSolvent));
4335
4336 double tmp = (xmolSolvent / g * g_prime + (1.0 - xmolSolvent) / f * f_prime);
4337 double lngammak = -1.0 - log(f) + tmp * xmolSolvent;
4338 double lngammao =-log(g) - tmp * (1.0-xmolSolvent);
4339
4340 tmp = log(xx) + lngammak;
4341 for (size_t k = 1; k < m_kk; k++) {
4342 IMS_lnActCoeffMolal_[k]= tmp;
4343 }
4344 IMS_lnActCoeffMolal_[0] = lngammao;
4345 }
4346 return;
4347}
4348
4350{
4352 vector_fp& moleF = m_tmpV;
4353
4354 // Update the coefficients wrt Temperature. Calculate the derivatives as well
4356 getMoleFractions(moleF.data());
4357
4358 writelog("Index Name MoleF MolalityCropped Charge\n");
4359 for (size_t k = 0; k < m_kk; k++) {
4360 writelogf("%2d %-16s %14.7le %14.7le %5.1f \n",
4361 k, speciesName(k), moleF[k], m_molalitiesCropped[k], charge(k));
4362 }
4363
4364 writelog("\n Species Species beta0MX "
4365 "beta1MX beta2MX CphiMX alphaMX thetaij\n");
4366 for (size_t i = 1; i < m_kk - 1; i++) {
4367 for (size_t j = i+1; j < m_kk; j++) {
4368 size_t n = i * m_kk + j;
4369 size_t ct = m_CounterIJ[n];
4370 writelogf(" %-16s %-16s %9.5f %9.5f %9.5f %9.5f %9.5f %9.5f \n",
4371 speciesName(i), speciesName(j),
4372 m_Beta0MX_ij[ct], m_Beta1MX_ij[ct],
4373 m_Beta2MX_ij[ct], m_CphiMX_ij[ct],
4374 m_Alpha1MX_ij[ct], m_Theta_ij[ct]);
4375 }
4376 }
4377
4378 writelog("\n Species Species Species psi \n");
4379 for (size_t i = 1; i < m_kk; i++) {
4380 for (size_t j = 1; j < m_kk; j++) {
4381 for (size_t k = 1; k < m_kk; k++) {
4382 size_t n = k + j * m_kk + i * m_kk * m_kk;
4383 if (m_Psi_ijk[n] != 0.0) {
4384 writelogf(" %-16s %-16s %-16s %9.5f \n",
4385 speciesName(i), speciesName(j),
4386 speciesName(k), m_Psi_ijk[n]);
4387 }
4388 }
4389 }
4390 }
4391}
4392
4393void HMWSoln::applyphScale(doublereal* acMolality) const
4394{
4396 return;
4397 }
4399 doublereal lnGammaClMs2 = s_NBS_CLM_lnMolalityActCoeff();
4400 doublereal lnGammaCLMs1 = m_lnActCoeffMolal_Unscaled[m_indexCLM];
4401 doublereal afac = -1.0 *(lnGammaClMs2 - lnGammaCLMs1);
4402 for (size_t k = 0; k < m_kk; k++) {
4403 acMolality[k] *= exp(charge(k) * afac);
4404 }
4405}
4406
4408{
4411 return;
4412 }
4414 doublereal lnGammaClMs2 = s_NBS_CLM_lnMolalityActCoeff();
4415 doublereal lnGammaCLMs1 = m_lnActCoeffMolal_Unscaled[m_indexCLM];
4416 doublereal afac = -1.0 *(lnGammaClMs2 - lnGammaCLMs1);
4417 for (size_t k = 0; k < m_kk; k++) {
4419 }
4420}
4421
4423{
4426 return;
4427 }
4429 doublereal dlnGammaClM_dT_s2 = s_NBS_CLM_dlnMolalityActCoeff_dT();
4430 doublereal dlnGammaCLM_dT_s1 = m_dlnActCoeffMolaldT_Unscaled[m_indexCLM];
4431 doublereal afac = -1.0 *(dlnGammaClM_dT_s2 - dlnGammaCLM_dT_s1);
4432 for (size_t k = 0; k < m_kk; k++) {
4434 }
4435}
4436
4438{
4441 return;
4442 }
4444 doublereal d2lnGammaClM_dT2_s2 = s_NBS_CLM_d2lnMolalityActCoeff_dT2();
4445 doublereal d2lnGammaCLM_dT2_s1 = m_d2lnActCoeffMolaldT2_Unscaled[m_indexCLM];
4446 doublereal afac = -1.0 *(d2lnGammaClM_dT2_s2 - d2lnGammaCLM_dT2_s1);
4447 for (size_t k = 0; k < m_kk; k++) {
4449 }
4450}
4451
4453{
4456 return;
4457 }
4459 doublereal dlnGammaClM_dP_s2 = s_NBS_CLM_dlnMolalityActCoeff_dP();
4460 doublereal dlnGammaCLM_dP_s1 = m_dlnActCoeffMolaldP_Unscaled[m_indexCLM];
4461 doublereal afac = -1.0 *(dlnGammaClM_dP_s2 - dlnGammaCLM_dP_s1);
4462 for (size_t k = 0; k < m_kk; k++) {
4464 }
4465}
4466
4468{
4469 doublereal sqrtIs = sqrt(m_IionicMolality);
4470 doublereal A = A_Debye_TP();
4471 doublereal lnGammaClMs2 = - A * sqrtIs /(1.0 + 1.5 * sqrtIs);
4472 return lnGammaClMs2;
4473}
4474
4476{
4477 doublereal sqrtIs = sqrt(m_IionicMolality);
4478 doublereal dAdT = dA_DebyedT_TP();
4479 return - dAdT * sqrtIs /(1.0 + 1.5 * sqrtIs);
4480}
4481
4483{
4484 doublereal sqrtIs = sqrt(m_IionicMolality);
4485 doublereal d2AdT2 = d2A_DebyedT2_TP();
4486 return - d2AdT2 * sqrtIs /(1.0 + 1.5 * sqrtIs);
4487}
4488
4490{
4491 doublereal sqrtIs = sqrt(m_IionicMolality);
4492 doublereal dAdP = dA_DebyedP_TP();
4493 return - dAdP * sqrtIs /(1.0 + 1.5 * sqrtIs);
4494}
4495
4496}
Headers for the HMWSoln ThermoPhase object, which models concentrated electrolyte solutions (see Ther...
Implementation of a pressure dependent standard state virtual function for a Pure Water Phase (see Sp...
Headers for the factory class that can create known ThermoPhase objects (see Thermodynamic Properties...
A map of string keys to values whose type can vary at runtime.
Definition: AnyMap.h:399
size_t size() const
Returns the number of elements in this map.
Definition: AnyMap.h:590
bool hasKey(const std::string &key) const
Returns true if the map contains an item named key.
Definition: AnyMap.cpp:1406
doublereal * ptrColumn(size_t j)
Return a pointer to the top of column j, columns are contiguous in memory.
Definition: Array.h:233
void resize(size_t n, size_t m, double v=0.0)
Resize the array, and fill the new entries with 'v'.
Definition: Array.cpp:52
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
vector_fp m_CMX_IJ_P
Derivative of m_CMX_IJ wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:2172
void calcIMSCutoffParams_()
Precalculate the IMS Cutoff parameters for typeCutoff = 2.
Definition: HMWSoln.cpp:1859
Array2D m_Theta_ij_coeff
Array of coefficients for Theta_ij[i][j] in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1893
vector_fp m_BphiMX_IJ_P
Derivative of BphiMX_IJ wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:2129
doublereal MC_X_o_cutoff_
value of the solvent mole fraction that centers the cutoff polynomials for the cutoff =1 process;
Definition: HMWSoln.h:2210
virtual void applyphScale(doublereal *acMolality) const
Apply the current phScale to a set of activity Coefficients or activities.
Definition: HMWSoln.cpp:4393
Array2D m_Beta0MX_ij_coeff
Array of coefficients for Beta0, a variable in Pitzer's papers.
Definition: HMWSoln.h:1770
vector_fp m_PhiPhi_IJ
Intermediate variable called PhiPhi in Pitzer's paper.
Definition: HMWSoln.h:2150
vector_fp m_Theta_ij_L
Derivative of Theta_ij[i][j] wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:1875
vector_fp m_BMX_IJ_L
Derivative of BMX_IJ wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:2097
vector_fp m_BMX_IJ_P
Derivative of BMX_IJ wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:2103
double m_A_Debye
A_Debye: this expression appears on the top of the ln actCoeff term in the general Debye-Huckel expre...
Definition: HMWSoln.h:1732
void readXMLLambdaNeutral(XML_Node &BinSalt)
Process an XML node called "lambdaNeutral".
Definition: HMWSoln.cpp:1769
virtual double d2A_DebyedT2_TP(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...
Definition: HMWSoln.cpp:1265
vector_fp m_Phi_IJ_LL
Derivative of m_Phi_IJ wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:2139
virtual void getParameters(AnyMap &phaseNode) const
Store the parameters of a ThermoPhase object such that an identical one could be reconstructed using ...
Definition: HMWSoln.cpp:814
vector_fp m_Beta0MX_ij_P
Derivative of Beta0_ij[i][j] wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:1762
Array2D m_Lambda_nj_P
Derivative of Lambda_nj[i][j] wrt P.
Definition: HMWSoln.h:1953
void s_updateScaling_pHScaling_dT() const
Apply the current phScale to a set of derivatives of the activity Coefficients wrt temperature.
Definition: HMWSoln.cpp:4422
double m_IionicMolality
Current value of the ionic strength on the molality scale Associated Salts, if present in the mechani...
Definition: HMWSoln.h:1676
vector_fp m_PhiPhi_IJ_L
Derivative of m_PhiPhi_IJ wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:2153
void calcMCCutoffParams_()
Calculate molality cut-off parameters.
Definition: HMWSoln.cpp:1912
vector_fp m_dlnActCoeffMolaldT_Scaled
Derivative of the Logarithm of the activity coefficients on the molality scale wrt T.
Definition: HMWSoln.h:2027
Array2D m_Beta2MX_ij_coeff
Array of coefficients for Beta2, a variable in Pitzer's papers.
Definition: HMWSoln.h:1820
vector_fp m_BphiMX_IJ_LL
Derivative of BphiMX_IJ wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:2126
HMWSoln(const std::string &inputFile="", const std::string &id="")
Construct and initialize an HMWSoln ThermoPhase object directly from an ASCII input file.
Definition: HMWSoln.cpp:43
void s_update_d2lnMolalityActCoeff_dT2() const
This function calculates the temperature second derivative of the natural logarithm of the molality a...
Definition: HMWSoln.cpp:3216
doublereal IMS_cCut_
Parameter in the polyExp cutoff treatment having to do with rate of exp decay.
Definition: HMWSoln.h:2187
doublereal IMS_slopegCut_
Parameter in the polyExp cutoff treatment.
Definition: HMWSoln.h:2194
int m_form_A_Debye
Form of the constant outside the Debye-Huckel term called A.
Definition: HMWSoln.h:1700
void getUnscaledMolalityActivityCoefficients(doublereal *acMolality) const
Get the array of unscaled non-dimensional molality based activity coefficients at the current solutio...
Definition: HMWSoln.cpp:259
vector_int CROP_speciesCropped_
This is a boolean-type vector indicating whether a species's activity coefficient is in the cropped r...
Definition: HMWSoln.h:2233
vector_fp m_Psi_ijk_L
Derivative of Psi_ijk[n] wrt T.
Definition: HMWSoln.h:1910
virtual doublereal cp_mole() const
Molar heat capacity at constant pressure. Units: J/kmol/K.
Definition: HMWSoln.cpp:185
vector_fp m_Phiprime_IJ
Intermediate variable called Phiprime in Pitzer's paper.
Definition: HMWSoln.h:2146
doublereal s_NBS_CLM_dlnMolalityActCoeff_dP() const
Calculate the pressure derivative of the Chlorine activity coefficient.
Definition: HMWSoln.cpp:4489
virtual void getPartialMolarEnthalpies(doublereal *hbar) const
Returns an array of partial molar enthalpies for the species in the mixture.
Definition: HMWSoln.cpp:292
virtual void getPartialMolarEntropies(doublereal *sbar) const
Returns an array of partial molar entropies of the species in the solution.
Definition: HMWSoln.cpp:311
vector_fp m_Beta1MX_ij
Array of 2D data used in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1777
vector_fp m_lnActCoeffMolal_Unscaled
Logarithm of the activity coefficients on the molality scale.
Definition: HMWSoln.h:2023
Array2D m_Lambda_nj_L
Derivative of Lambda_nj[i][j] wrt T. see m_Lambda_ij.
Definition: HMWSoln.h:1947
virtual void initThermoXML(XML_Node &phaseNode, const std::string &id)
Initialize the phase parameters from an XML file.
Definition: HMWSoln.cpp:1044
vector_fp m_BprimeMX_IJ_P
Derivative of BprimeMX wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:2116
virtual doublereal enthalpy_mole() const
Molar enthalpy. Units: J/kmol.
Definition: HMWSoln.cpp:115
vector_fp m_h2func_IJ
hfunc2, was called gprime in Pitzer's paper.
Definition: HMWSoln.h:2090
vector_fp m_Alpha2MX_ij
Array of 2D data used in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1835
doublereal IMS_X_o_cutoff_
value of the solute mole fraction that centers the cutoff polynomials for the cutoff =1 process;
Definition: HMWSoln.h:2184
vector_fp m_Psi_ijk_P
Derivative of Psi_ijk[n] wrt P.
Definition: HMWSoln.h:1918
virtual void getPartialMolarVolumes(doublereal *vbar) const
Return an array of partial molar volumes for the species in the mixture.
Definition: HMWSoln.cpp:346
vector_fp m_dlnActCoeffMolaldT_Unscaled
Derivative of the Logarithm of the activity coefficients on the molality scale wrt T.
Definition: HMWSoln.h:2031
doublereal s_NBS_CLM_dlnMolalityActCoeff_dT() const
Calculate the temperature derivative of the Chlorine activity coefficient on the NBS scale.
Definition: HMWSoln.cpp:4475
vector_fp m_PhiPhi_IJ_LL
Derivative of m_PhiPhi_IJ wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:2156
vector_fp m_CMX_IJ_L
Derivative of m_CMX_IJ wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:2166
void s_updateScaling_pHScaling_dP() const
Apply the current phScale to a set of derivatives of the activity Coefficients wrt pressure.
Definition: HMWSoln.cpp:4452
void printCoeffs() const
Print out all of the input Pitzer coefficients.
Definition: HMWSoln.cpp:4349
virtual doublereal cv_mole() const
Molar heat capacity at constant volume. Units: J/kmol/K.
Definition: HMWSoln.cpp:191
virtual void getActivityConcentrations(doublereal *c) const
This method returns an array of generalized activity concentrations.
Definition: HMWSoln.cpp:220
vector_fp m_BphiMX_IJ
Intermediate variable called BphiMX in Pitzer's paper.
Definition: HMWSoln.h:2120
void s_update_dlnMolalityActCoeff_dT() const
This function calculates the temperature derivative of the natural logarithm of the molality activity...
Definition: HMWSoln.cpp:2697
vector_fp m_CphiMX_ij
Array of 2D data used in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1842
vector_fp m_CphiMX_ij_L
Derivative of Cphi_ij[i][j] wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:1845
double ADebye_V(double temperature=-1.0, double pressure=-1.0) const
Return Pitzer's definition of A_V.
Definition: HMWSoln.cpp:1242
vector_fp m_Beta0MX_ij_L
Derivative of Beta0_ij[i][j] wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:1756
virtual void getPartialMolarCp(doublereal *cpbar) const
Return an array of partial molar heat capacities for the species in the mixture.
Definition: HMWSoln.cpp:359
vector_fp m_Phi_IJ_P
Derivative of m_Phi_IJ wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:2142
void counterIJ_setup() const
Set up a counter variable for keeping track of symmetric binary interactions amongst the solute speci...
Definition: HMWSoln.cpp:1611
vector_fp m_hfunc_IJ
hfunc, was called gprime in Pitzer's paper.
Definition: HMWSoln.h:2086
vector_fp m_Theta_ij_P
Derivative of Theta_ij[i][j] wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:1881
vector_fp m_Beta1MX_ij_LL
Derivative of Beta1_ij[i][j] wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:1783
vector_fp m_Mu_nnn
Mu coefficient for the self-ternary neutral coefficient.
Definition: HMWSoln.h:1976
vector_fp m_d2lnActCoeffMolaldT2_Scaled
Derivative of the Logarithm of the activity coefficients on the molality scale wrt TT.
Definition: HMWSoln.h:2035
Array2D m_Psi_ijk_coeff
Array of coefficients for Psi_ijk[n] in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1934
void readXMLZetaCation(const XML_Node &BinSalt)
Process an XML node called "zetaCation".
Definition: HMWSoln.cpp:1824
vector_fp m_CphiMX_ij_LL
Derivative of Cphi_ij[i][j] wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:1848
vector_fp m_Beta2MX_ij_L
Derivative of Beta2_ij[i][j] wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:1804
void readXMLPsi(XML_Node &BinSalt)
Process an XML node called "psiCommonAnion" or "psiCommonCation".
Definition: HMWSoln.cpp:1720
void setA_Debye(double A)
Set the A_Debye parameter.
Definition: HMWSoln.cpp:601
int m_formPitzerTemp
This is the form of the temperature dependence of Pitzer parameterization used in the model.
Definition: HMWSoln.h:1671
virtual doublereal entropy_mole() const
Molar entropy. Units: J/kmol/K.
Definition: HMWSoln.cpp:173
vector_fp m_Mu_nnn_LL
Mu coefficient 2nd temperature derivative for the self-ternary neutral coefficient.
Definition: HMWSoln.h:1996
vector_fp m_BMX_IJ_LL
Derivative of BMX_IJ wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:2100
void s_updatePitzer_CoeffWRTemp(int doDerivs=2) const
Calculates the Pitzer coefficients' dependence on the temperature.
Definition: HMWSoln.cpp:1948
vector_fp m_molalitiesCropped
Cropped and modified values of the molalities used in activity coefficient calculations.
Definition: HMWSoln.h:2053
vector_fp m_Psi_ijk_LL
Derivative of Psi_ijk[n] wrt TT.
Definition: HMWSoln.h:1914
doublereal s_NBS_CLM_d2lnMolalityActCoeff_dT2() const
Calculate the second temperature derivative of the Chlorine activity coefficient on the NBS scale.
Definition: HMWSoln.cpp:4482
virtual void initThermo()
Definition: HMWSoln.cpp:638
void s_updatePitzer_dlnMolalityActCoeff_dP() const
Calculates the Pressure derivative of the natural logarithm of the molality activity coefficients.
Definition: HMWSoln.cpp:3751
vector_fp m_Beta2MX_ij_P
Derivative of Beta2_ij[i][j] wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:1810
Array2D m_Lambda_nj
Lambda coefficient for the ij interaction.
Definition: HMWSoln.h:1944
vector_fp m_Phi_IJ
Intermediate variable called Phi in Pitzer's paper.
Definition: HMWSoln.h:2133
vector_fp m_Beta2MX_ij
Array of 2D data used in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1801
vector_fp m_BprimeMX_IJ_L
Derivative of BprimeMX wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:2110
vector_fp m_PhiPhi_IJ_P
Derivative of m_PhiPhi_IJ wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:2159
void calc_thetas(int z1, int z2, double *etheta, double *etheta_prime) const
Calculate etheta and etheta_prime.
Definition: HMWSoln.cpp:4280
vector_fp m_Beta2MX_ij_LL
Derivative of Beta2_ij[i][j] wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:1807
vector_fp m_d2lnActCoeffMolaldT2_Unscaled
Derivative of the Logarithm of the activity coefficients on the molality scale wrt TT.
Definition: HMWSoln.h:2039
Array2D m_Mu_nnn_coeff
Array of coefficients form_Mu_nnn term.
Definition: HMWSoln.h:2009
double ADebye_L(double temperature=-1.0, double pressure=-1.0) const
Return Pitzer's definition of A_L.
Definition: HMWSoln.cpp:1231
vector_fp m_Mu_nnn_P
Mu coefficient pressure derivative for the self-ternary neutral coefficient.
Definition: HMWSoln.h:2006
vector_fp m_CMX_IJ
Intermediate variable called CMX in Pitzer's paper.
Definition: HMWSoln.h:2163
PDSS * m_waterSS
Water standard state calculator.
Definition: HMWSoln.h:1738
void calc_lambdas(double is) const
Calculate the lambda interactions.
Definition: HMWSoln.cpp:4236
double elambda1[17]
This is elambda1, MEC.
Definition: HMWSoln.h:2070
virtual doublereal relative_molal_enthalpy() const
Excess molar enthalpy of the solution from the mixing process on a molality basis.
Definition: HMWSoln.cpp:133
vector_fp m_Phi_IJ_L
Derivative of m_Phi_IJ wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:2136
vector_fp m_Theta_ij_LL
Derivative of Theta_ij[i][j] wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:1878
double m_maxIionicStrength
Maximum value of the ionic strength allowed in the calculation of the activity coefficients.
Definition: HMWSoln.h:1680
vector_fp m_BphiMX_IJ_L
Derivative of BphiMX_IJ wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:2123
virtual doublereal gibbs_mole() const
Molar Gibbs function. Units: J/kmol.
Definition: HMWSoln.cpp:179
vector_fp m_dlnActCoeffMolaldP_Unscaled
Derivative of the Logarithm of the activity coefficients on the molality scale wrt P.
Definition: HMWSoln.h:2047
vector_fp m_BprimeMX_IJ_LL
Derivative of BprimeMX wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:2113
vector_fp IMS_lnActCoeffMolal_
Logarithm of the molal activity coefficients.
Definition: HMWSoln.h:2180
void s_updatePitzer_dlnMolalityActCoeff_dT() const
Calculates the temperature derivative of the natural logarithm of the molality activity coefficients.
Definition: HMWSoln.cpp:2725
void s_updatePitzer_lnMolalityActCoeff() const
Calculate the Pitzer portion of the activity coefficients.
Definition: HMWSoln.cpp:2194
vector_fp m_CMX_IJ_LL
Derivative of m_CMX_IJ wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:2169
vector_fp m_gamma_tmp
Intermediate storage of the activity coefficient itself.
Definition: HMWSoln.h:2176
vector_fp m_Psi_ijk
Array of 3D data used in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1906
void readXMLMunnnNeutral(XML_Node &BinSalt)
Process an XML node called "MunnnNeutral".
Definition: HMWSoln.cpp:1799
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 temperature as a function of tem...
Definition: HMWSoln.cpp:1175
virtual doublereal relative_enthalpy() const
Excess molar enthalpy of the solution from the mixing process.
Definition: HMWSoln.cpp:121
vector_fp m_Beta1MX_ij_P
Derivative of Beta1_ij[i][j] wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:1786
vector_fp m_Mu_nnn_L
Mu coefficient temperature derivative for the self-ternary neutral coefficient.
Definition: HMWSoln.h:1986
Array2D m_Lambda_nj_LL
Derivative of Lambda_nj[i][j] wrt TT.
Definition: HMWSoln.h:1950
void s_updatePitzer_d2lnMolalityActCoeff_dT2() const
This function calculates the temperature second derivative of the natural logarithm of the molality a...
Definition: HMWSoln.cpp:3244
void readXMLTheta(XML_Node &BinSalt)
Process an XML node called "thetaAnion" or "thetaCation".
Definition: HMWSoln.cpp:1680
Array2D m_Lambda_nj_coeff
Array of coefficients for Lambda_nj[i][j] in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1968
vector_fp m_Theta_ij
Array of 2D data for Theta_ij[i][j] in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1872
void initLengths()
Initialize all of the species-dependent lengths in the object.
Definition: HMWSoln.cpp:1293
vector_fp m_Beta0MX_ij
Array of 2D data used in the Pitzer/HMW formulation.
Definition: HMWSoln.h:1753
vector_fp m_BMX_IJ
Intermediate variable called BMX in Pitzer's paper.
Definition: HMWSoln.h:2094
vector_fp m_Beta0MX_ij_LL
Derivative of Beta0_ij[i][j] wrt TT. Vector index is counterIJ.
Definition: HMWSoln.h:1759
vector_fp m_gfunc_IJ
Various temporary arrays used in the calculation of the Pitzer activity coefficients.
Definition: HMWSoln.h:2079
void s_update_dlnMolalityActCoeff_dP() const
This function calculates the pressure derivative of the natural logarithm of the molality activity co...
Definition: HMWSoln.cpp:3727
double m_TempPitzerRef
Reference Temperature for the Pitzer formulations.
Definition: HMWSoln.h:1683
virtual void getChemPotentials(doublereal *mu) const
Get the species chemical potentials. Units: J/kmol.
Definition: HMWSoln.cpp:272
vector_fp m_Beta1MX_ij_L
Derivative of Beta1_ij[i][j] wrt T. Vector index is counterIJ.
Definition: HMWSoln.h:1780
void readXMLBinarySalt(XML_Node &BinSalt)
Process an XML node called "binarySaltParameters".
Definition: HMWSoln.cpp:1634
virtual doublereal satPressure(doublereal T)
Get the saturation pressure for a given temperature.
Definition: HMWSoln.cpp:379
double elambda[17]
This is elambda, MEC.
Definition: HMWSoln.h:2067
std::unique_ptr< WaterProps > m_waterProps
Pointer to the water property calculator.
Definition: HMWSoln.h:1741
vector_fp m_BprimeMX_IJ
Intermediate variable called BprimeMX in Pitzer's paper.
Definition: HMWSoln.h:2107
void s_updateIMS_lnMolalityActCoeff() const
This function will be called to update the internally stored natural logarithm of the molality activi...
Definition: HMWSoln.cpp:4306
void calcMolalitiesCropped() const
Calculate the cropped molalities.
Definition: HMWSoln.cpp:1470
virtual void getActivities(doublereal *ac) const
Get the array of non-dimensional activities at the current solution temperature, pressure,...
Definition: HMWSoln.cpp:243
vector_fp m_CphiMX_ij_P
Derivative of Cphi_ij[i][j] wrt P. Vector index is counterIJ.
Definition: HMWSoln.h:1851
vector_fp m_tmpV
vector of size m_kk, used as a temporary holding area.
Definition: HMWSoln.h:1744
double ADebye_J(double temperature=-1.0, double pressure=-1.0) const
Return Pitzer's definition of A_J.
Definition: HMWSoln.cpp:1253
void s_updateScaling_pHScaling_dT2() const
Apply the current phScale to a set of 2nd derivatives of the activity Coefficients wrt temperature.
Definition: HMWSoln.cpp:4437
Array2D m_Beta1MX_ij_coeff
Array of coefficients for Beta1, a variable in Pitzer's papers.
Definition: HMWSoln.h:1794
doublereal s_NBS_CLM_lnMolalityActCoeff() const
Calculate the Chlorine activity coefficient on the NBS scale.
Definition: HMWSoln.cpp:4467
void s_updateScaling_pHScaling() const
Apply the current phScale to a set of activity Coefficients.
Definition: HMWSoln.cpp:4407
Array2D m_CphiMX_ij_coeff
Array of coefficients for CphiMX, a parameter in the activity coefficient formulation.
Definition: HMWSoln.h:1860
vector_fp m_g2func_IJ
This is the value of g2(x2) in Pitzer's papers. Vector index is counterIJ.
Definition: HMWSoln.h:2082
virtual double A_Debye_TP(double temperature=-1.0, double pressure=-1.0) const
Value of the Debye Huckel constant as a function of temperature and pressure.
Definition: HMWSoln.cpp:1143
virtual double dA_DebyedP_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 tempe...
Definition: HMWSoln.cpp:1199
void calcDensity()
Calculate the density of the mixture using the partial molar volumes and mole fractions as input.
Definition: HMWSoln.cpp:203
vector_fp m_dlnActCoeffMolaldP_Scaled
Derivative of the Logarithm of the activity coefficients on the molality scale wrt P.
Definition: HMWSoln.h:2043
vector_int m_CounterIJ
a counter variable for keeping track of symmetric binary interactions amongst the solute species.
Definition: HMWSoln.h:2064
vector_fp m_lnActCoeffMolal_Scaled
Logarithm of the activity coefficients on the molality scale.
Definition: HMWSoln.h:2016
bool m_molalitiesAreCropped
Boolean indicating whether the molalities are cropped or are modified.
Definition: HMWSoln.h:2056
virtual doublereal standardConcentration(size_t k=0) const
Return the standard concentration for the kth species.
Definition: HMWSoln.cpp:233
Error thrown for problems processing information contained in an AnyMap or AnyValue.
Definition: AnyMap.h:702
size_t m_indexCLM
Index of the phScale species.
void setMoleFSolventMin(doublereal xmolSolventMIN)
Sets the minimum mole fraction in the molality formulation.
int m_pHScalingType
Scaling to be used for output of single-ion species activity coefficients.
doublereal m_Mnaught
This is the multiplication factor that goes inside log expressions involving the molalities of specie...
void calcMolalities() const
Calculates the molality of all species and stores the result internally.
vector_fp m_molalities
Current value of the molalities of the species in the phase.
doublereal m_weightSolvent
Molecular weight of the Solvent.
Class for the liquid water pressure dependent standard state.
Definition: PDSS_Water.h:50
virtual void setState_TP(doublereal temp, doublereal pres)
Set the internal temperature and pressure.
Definition: PDSS.cpp:181
virtual doublereal satPressure(doublereal T)
saturation pressure
Definition: PDSS.cpp:191
void assignDensity(const double density_)
Set the internally stored constant density (kg/m^3) of the phase.
Definition: Phase.cpp:698
virtual void setMoleFractions(const double *const x)
Set the mole fractions to the specified values.
Definition: Phase.cpp:339
doublereal mean_X(const doublereal *const Q) const
Evaluate the mole-fraction-weighted mean of an array Q.
Definition: Phase.cpp:717
ValueCache m_cache
Cached for saved calculations within each ThermoPhase.
Definition: Phase.h:923
doublereal charge(size_t k) const
Dimensionless electrical charge of a single molecule of species k The charge is normalized by the the...
Definition: Phase.h:630
size_t m_kk
Number of species in the phase.
Definition: Phase.h:943
std::string speciesName(size_t k) const
Name of the species with index k.
Definition: Phase.cpp:200
doublereal meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition: Phase.h:751
void getMoleFractions(double *const x) const
Get the species mole fraction vector.
Definition: Phase.cpp:543
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition: Phase.cpp:548
int stateMFNumber() const
Return the State Mole Fraction Number.
Definition: Phase.h:858
doublereal temperature() const
Temperature (K).
Definition: Phase.h:654
size_t speciesIndex(const std::string &name) const
Returns the index of a species named 'name' within the Phase object.
Definition: Phase.cpp:187
double molarVolume() const
Molar volume (m^3/kmol).
Definition: Phase.cpp:682
shared_ptr< Species > species(const std::string &name) const
Return the Species object for the named species.
Definition: Phase.cpp:950
doublereal RT() const
Return the Gas Constant multiplied by the current temperature.
Definition: ThermoPhase.h:782
virtual doublereal isothermalCompressibility() const
Returns the isothermal compressibility. Units: 1/Pa.
Definition: ThermoPhase.h:283
void initThermoFile(const std::string &inputFile, const std::string &id)
virtual void initThermoXML(XML_Node &phaseNode, const std::string &id)
Import and initialize a ThermoPhase object using an XML tree.
virtual doublereal thermalExpansionCoeff() const
Return the volumetric thermal expansion coefficient. Units: 1/K.
Definition: ThermoPhase.h:294
AnyMap m_input
Data supplied via setParameters.
Definition: ThermoPhase.h:1898
virtual void getParameters(int &n, doublereal *const c) const
Get the equation of state parameters in a vector.
virtual void getStandardVolumes(doublereal *vol) const
Get the molar volumes of the species standard states at the current T and P of the solution.
virtual void getCp_R(doublereal *cpr) const
Get the nondimensional Heat Capacities at constant pressure for the species standard states at the cu...
virtual void getEntropy_R(doublereal *sr) const
Get the array of nondimensional Entropy functions for the standard state species at the current T and...
virtual doublereal pressure() const
Returns the current pressure of the phase.
virtual void getStandardChemPotentials(doublereal *mu) const
Get the array of chemical potentials at unit activity for the species at their standard states at the...
virtual void updateStandardStateThermo() const
Updates the standard state thermodynamic functions at the current T and P of the solution.
virtual void getEnthalpy_RT(doublereal *hrt) const
Get the nondimensional Enthalpy functions for the species at their standard states at the current T a...
CachedScalar getScalar(int id)
Get a reference to a CachedValue object representing a scalar (doublereal) with the given id.
Definition: ValueCache.h:165
int getId()
Get a unique id for a cached value.
Definition: ValueCache.cpp:21
The WaterProps class is used to house several approximation routines for properties of water.
Definition: WaterProps.h:100
Class XML_Node is a tree-based representation of the contents of an XML file.
Definition: xml.h:103
std::string attrib(const std::string &attr) const
Function returns the value of an attribute.
Definition: xml.cpp:493
std::string name() const
Returns the name of the XML node.
Definition: xml.h:371
bool hasChild(const std::string &ch) const
Tests whether the current node has a child node with a particular name.
Definition: xml.cpp:529
std::string id() const
Return the id attribute, if present.
Definition: xml.cpp:539
const std::vector< XML_Node * > & children() const
Return an unchangeable reference to the vector of children of the current node.
Definition: xml.cpp:552
XML_Node & child(const size_t n) const
Return a changeable reference to the n'th child of the current node.
Definition: xml.cpp:547
CTML ("Cantera Markup Language") is the variant of XML that Cantera uses to store data.
Header file for a common definitions used in electrolytes thermodynamics.
#define AssertTrace(expr)
Assertion must be true or an error is thrown.
Definition: ctexceptions.h:240
#define AssertThrowMsg(expr, procedure,...)
Assertion must be true or an error is thrown.
Definition: ctexceptions.h:271
void writelog(const std::string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:164
void writelogf(const char *fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:185
void importPhase(XML_Node &phase, ThermoPhase *th)
Import a phase information into an empty ThermoPhase object.
Namespace for the Cantera kernel.
Definition: AnyMap.h:29
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:192
doublereal getFloat(const XML_Node &parent, const std::string &name, const std::string &type="")
Get a floating-point value from a child element.
Definition: ctml.cpp:166
bool getOptionalFloat(const XML_Node &parent, const std::string &name, doublereal &fltRtn, const std::string &type="")
Get an optional floating-point value from a child element.
Definition: ctml.cpp:214
bool caseInsensitiveEquals(const std::string &input, const std::string &test)
Case insensitive equality predicate.
const int PHSCALE_PITZER
Scale to be used for the output of single-ion activity coefficients is that used by Pitzer.
const double SmallNumber
smallest number to compare to zero.
Definition: ct_defs.h:153
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
Definition: ct_defs.h:184
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition: ct_defs.h:113
size_t getFloatArray(const XML_Node &node, vector_fp &v, const bool convert=true, const std::string &unitsString="", const std::string &nodeName="floatArray")
This function reads the current node or a child node of the current node with the default name,...
Definition: ctml.cpp:258
doublereal fpValueCheck(const std::string &val)
Translate a string into one doublereal value, with error checking.
const int PHSCALE_NBS
Scale to be used for evaluation of single-ion activity coefficients is that used by the NBS standard ...
int sign(T x)
Sign of a number. Returns -1 if x < 0, 1 if x > 0 and 0 if x == 0.
Definition: global.h:362
Contains declarations for string manipulation functions within Cantera.
T value
The value of the cached property.
Definition: ValueCache.h:118
bool validate(double state1New)
Check whether the currently cached value is valid based on a single state variable.
Definition: ValueCache.h:44