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