Cantera 2.6.0
DebyeHuckel.cpp
Go to the documentation of this file.
1/**
2 * @file DebyeHuckel.cpp
3 * Declarations for the DebyeHuckel ThermoPhase object, which models dilute
4 * electrolyte solutions
5 * (see \ref thermoprops and \link Cantera::DebyeHuckel DebyeHuckel \endlink).
6 *
7 * Class DebyeHuckel represents a dilute liquid electrolyte phase which
8 * obeys the Debye Huckel formulation for nonideality.
9 */
10
11// This file is part of Cantera. See License.txt in the top-level directory or
12// at https://cantera.org/license.txt for license and copyright information.
13
21#include "cantera/base/ctml.h"
22
23#include <cstdio>
24
25using namespace std;
26
27namespace Cantera
28{
29
30namespace {
31double A_Debye_default = 1.172576; // units = sqrt(kg/gmol)
32double B_Debye_default = 3.28640E9; // units = sqrt(kg/gmol) / m
33double maxIionicStrength_default = 30.0;
34}
35
36DebyeHuckel::DebyeHuckel(const std::string& inputFile,
37 const std::string& id_) :
38 m_formDH(DHFORM_DILUTE_LIMIT),
39 m_Aionic_default(NAN),
40 m_IionicMolality(0.0),
41 m_maxIionicStrength(maxIionicStrength_default),
42 m_useHelgesonFixedForm(false),
43 m_IionicMolalityStoich(0.0),
44 m_form_A_Debye(A_DEBYE_CONST),
45 m_A_Debye(A_Debye_default),
46 m_B_Debye(B_Debye_default),
47 m_waterSS(0),
48 m_densWaterSS(1000.)
49{
50 initThermoFile(inputFile, id_);
51}
52
53DebyeHuckel::DebyeHuckel(XML_Node& phaseRoot, const std::string& id_) :
54 m_formDH(DHFORM_DILUTE_LIMIT),
55 m_Aionic_default(NAN),
56 m_IionicMolality(0.0),
57 m_maxIionicStrength(maxIionicStrength_default),
58 m_useHelgesonFixedForm(false),
59 m_IionicMolalityStoich(0.0),
60 m_form_A_Debye(A_DEBYE_CONST),
61 m_A_Debye(A_Debye_default),
62 m_B_Debye(B_Debye_default),
63 m_waterSS(0),
64 m_densWaterSS(1000.)
65{
66 importPhase(phaseRoot, this);
67}
68
69DebyeHuckel::~DebyeHuckel()
70{
71}
72
73// -------- Molar Thermodynamic Properties of the Solution ---------------
74
76{
78 return mean_X(m_tmpV);
79}
80
81doublereal DebyeHuckel::entropy_mole() const
82{
84 return mean_X(m_tmpV);
85}
86
87doublereal DebyeHuckel::gibbs_mole() const
88{
90 return mean_X(m_tmpV);
91}
92
93doublereal DebyeHuckel::cp_mole() const
94{
96 return mean_X(m_tmpV);
97}
98
99// ------- Mechanical Equation of State Properties ------------------------
100
102{
103 if (m_waterSS) {
104 // Store the internal density of the water SS. Note, we would have to do
105 // this for all other species if they had pressure dependent properties.
107 }
109 double dd = meanMolecularWeight() / mean_X(m_tmpV);
111}
112
113// ------- Activities and Activity Concentrations
114
116{
117 double c_solvent = standardConcentration();
118 getActivities(c);
119 for (size_t k = 0; k < m_kk; k++) {
120 c[k] *= c_solvent;
121 }
122}
123
124doublereal DebyeHuckel::standardConcentration(size_t k) const
125{
126 double mvSolvent = providePDSS(0)->molarVolume();
127 return 1.0 / mvSolvent;
128}
129
130void DebyeHuckel::getActivities(doublereal* ac) const
131{
133
134 // Update the molality array, m_molalities(). This requires an update due to
135 // mole fractions
137 for (size_t k = 1; k < m_kk; k++) {
138 ac[k] = m_molalities[k] * exp(m_lnActCoeffMolal[k]);
139 }
140 double xmolSolvent = moleFraction(0);
141 ac[0] = exp(m_lnActCoeffMolal[0]) * xmolSolvent;
142}
143
144void DebyeHuckel::getMolalityActivityCoefficients(doublereal* acMolality) const
145{
147 A_Debye_TP(-1.0, -1.0);
149 copy(m_lnActCoeffMolal.begin(), m_lnActCoeffMolal.end(), acMolality);
150 for (size_t k = 0; k < m_kk; k++) {
151 acMolality[k] = exp(acMolality[k]);
152 }
153}
154
155// ------ Partial Molar Properties of the Solution -----------------
156
157void DebyeHuckel::getChemPotentials(doublereal* mu) const
158{
159 double xx;
160
161 // First get the standard chemical potentials in molar form. This requires
162 // updates of standard state as a function of T and P
164
165 // Update the activity coefficients. This also updates the internal molality
166 // array.
168 double xmolSolvent = moleFraction(0);
169 for (size_t k = 1; k < m_kk; k++) {
170 xx = std::max(m_molalities[k], SmallNumber);
171 mu[k] += RT() * (log(xx) + m_lnActCoeffMolal[k]);
172 }
173 xx = std::max(xmolSolvent, SmallNumber);
174 mu[0] += RT() * (log(xx) + m_lnActCoeffMolal[0]);
175}
176
177void DebyeHuckel::getPartialMolarEnthalpies(doublereal* hbar) const
178{
179 // Get the nondimensional standard state enthalpies
180 getEnthalpy_RT(hbar);
181
182 // Dimensionalize it.
183 for (size_t k = 0; k < m_kk; k++) {
184 hbar[k] *= RT();
185 }
186
187 // Check to see whether activity coefficients are temperature
188 // dependent. If they are, then calculate the their temperature
189 // derivatives and add them into the result.
190 double dAdT = dA_DebyedT_TP();
191 if (dAdT != 0.0) {
192 // Update the activity coefficients, This also update the
193 // internally stored molalities.
196 for (size_t k = 0; k < m_kk; k++) {
197 hbar[k] -= RT() * temperature() * m_dlnActCoeffMolaldT[k];
198 }
199 }
200}
201
202void DebyeHuckel::getPartialMolarEntropies(doublereal* sbar) const
203{
204 // Get the standard state entropies at the temperature and pressure of the
205 // solution.
206 getEntropy_R(sbar);
207
208 // Dimensionalize the entropies
209 for (size_t k = 0; k < m_kk; k++) {
210 sbar[k] *= GasConstant;
211 }
212
213 // Update the activity coefficients, This also update the internally stored
214 // molalities.
216
217 // First we will add in the obvious dependence on the T term out front of
218 // the log activity term
219 doublereal mm;
220 for (size_t k = 1; k < m_kk; k++) {
221 mm = std::max(SmallNumber, m_molalities[k]);
222 sbar[k] -= GasConstant * (log(mm) + m_lnActCoeffMolal[k]);
223 }
224 double xmolSolvent = moleFraction(0);
225 mm = std::max(SmallNumber, xmolSolvent);
226 sbar[0] -= GasConstant *(log(mm) + m_lnActCoeffMolal[0]);
227
228 // Check to see whether activity coefficients are temperature dependent. If
229 // they are, then calculate the their temperature derivatives and add them
230 // into the result.
231 double dAdT = dA_DebyedT_TP();
232 if (dAdT != 0.0) {
234 for (size_t k = 0; k < m_kk; k++) {
235 sbar[k] -= RT() * m_dlnActCoeffMolaldT[k];
236 }
237 }
238}
239
240void DebyeHuckel::getPartialMolarVolumes(doublereal* vbar) const
241{
242 getStandardVolumes(vbar);
243
244 // Update the derivatives wrt the activity coefficients.
247 for (size_t k = 0; k < m_kk; k++) {
248 vbar[k] += RT() * m_dlnActCoeffMolaldP[k];
249 }
250}
251
252void DebyeHuckel::getPartialMolarCp(doublereal* cpbar) const
253{
254 getCp_R(cpbar);
255 for (size_t k = 0; k < m_kk; k++) {
256 cpbar[k] *= GasConstant;
257 }
258
259 // Check to see whether activity coefficients are temperature dependent. If
260 // they are, then calculate the their temperature derivatives and add them
261 // into the result.
262 double dAdT = dA_DebyedT_TP();
263 if (dAdT != 0.0) {
264 // Update the activity coefficients, This also update the internally
265 // stored molalities.
269 for (size_t k = 0; k < m_kk; k++) {
270 cpbar[k] -= (2.0 * RT() * m_dlnActCoeffMolaldT[k] +
272 }
273 }
274}
275
276// -------------- Utilities -------------------------------
277
278//! Utility function to assign an integer value from a string for the
279//! ElectrolyteSpeciesType field.
280/*!
281 * @param estString input string that will be interpreted
282 */
283static int interp_est(const std::string& estString)
284{
285 if (caseInsensitiveEquals(estString, "solvent")) {
286 return cEST_solvent;
287 } else if (estString == "charged-species"
288 || caseInsensitiveEquals(estString, "chargedspecies")) {
289 return cEST_chargedSpecies;
290 } else if (estString == "weak-acid-associated"
291 || caseInsensitiveEquals(estString, "weakacidassociated")) {
292 return cEST_weakAcidAssociated;
293 } else if (estString == "strong-acid-associated"
294 || caseInsensitiveEquals(estString, "strongacidassociated")) {
295 return cEST_strongAcidAssociated;
296 } else if (estString == "polar-neutral"
297 || caseInsensitiveEquals(estString, "polarneutral")) {
298 return cEST_polarNeutral;
299 } else if (estString == "nonpolar-neutral"
300 || caseInsensitiveEquals(estString, "nonpolarneutral")) {
301 return cEST_nonpolarNeutral;
302 } else {
303 throw CanteraError("interp_est (DebyeHuckel)",
304 "Invalid electrolyte species type '{}'", estString);
305 }
306}
307
308void DebyeHuckel::setDebyeHuckelModel(const std::string& model) {
309 if (model == ""
310 || model == "dilute-limit"
311 || caseInsensitiveEquals(model, "Dilute_limit")) {
312 m_formDH = DHFORM_DILUTE_LIMIT;
313 } else if (model == "B-dot-with-variable-a"
314 || caseInsensitiveEquals(model, "Bdot_with_variable_a")) {
315 m_formDH = DHFORM_BDOT_AK;
316 } else if (model == "B-dot-with-common-a"
317 || caseInsensitiveEquals(model, "Bdot_with_common_a")) {
318 m_formDH = DHFORM_BDOT_ACOMMON;
319 } else if (caseInsensitiveEquals(model, "beta_ij")) {
320 m_formDH = DHFORM_BETAIJ;
321 m_Beta_ij.resize(m_kk, m_kk, 0.0);
322 } else if (model == "Pitzer-with-beta_ij"
323 || caseInsensitiveEquals(model, "Pitzer_with_Beta_ij")) {
324 m_formDH = DHFORM_PITZER_BETAIJ;
325 m_Beta_ij.resize(m_kk, m_kk, 0.0);
326 } else {
327 throw CanteraError("DebyeHuckel::setDebyeHuckelModel",
328 "Unknown model '{}'", model);
329 }
330}
331
333{
334 if (A < 0) {
335 m_form_A_Debye = A_DEBYE_WATER;
336 } else {
337 m_form_A_Debye = A_DEBYE_CONST;
338 m_A_Debye = A;
339 }
340}
341
342void DebyeHuckel::setB_dot(double bdot)
343{
344 if (m_formDH == DHFORM_BETAIJ || m_formDH == DHFORM_DILUTE_LIMIT ||
345 m_formDH == DHFORM_PITZER_BETAIJ) {
346 throw CanteraError("DebyeHuckel::setB_dot",
347 "B_dot entry in the wrong DH form");
348 }
349 // Set B_dot parameters for charged species
350 for (size_t k = 0; k < nSpecies(); k++) {
351 if (fabs(charge(k)) > 0.0001) {
352 m_B_Dot[k] = bdot;
353 } else {
354 m_B_Dot[k] = 0.0;
355 }
356 }
357}
358
360{
361 m_Aionic_default = value;
362 for (size_t k = 0; k < m_kk; k++) {
363 if (std::isnan(m_Aionic[k])) {
364 m_Aionic[k] = value;
365 }
366 }
367}
368
369void DebyeHuckel::setBeta(const std::string& sp1, const std::string& sp2,
370 double value)
371{
372 size_t k1 = speciesIndex(sp1);
373 if (k1 == npos) {
374 throw CanteraError("DebyeHuckel::setBeta", "Species '{}' not found", sp1);
375 }
376 size_t k2 = speciesIndex(sp2);
377 if (k2 == npos) {
378 throw CanteraError("DebyeHuckel::setBeta", "Species '{}' not found", sp2);
379 }
380 m_Beta_ij(k1, k2) = value;
381 m_Beta_ij(k2, k1) = value;
382}
383
384void DebyeHuckel::initThermoXML(XML_Node& phaseNode, const std::string& id_)
385{
386 if (id_.size() > 0) {
387 std::string idp = phaseNode.id();
388 if (idp != id_) {
389 throw CanteraError("DebyeHuckel::initThermoXML",
390 "phasenode and Id are incompatible");
391 }
392 }
393
394 // Find the Thermo XML node
395 if (!phaseNode.hasChild("thermo")) {
396 throw CanteraError("DebyeHuckel::initThermoXML",
397 "no thermo XML node");
398 }
399 XML_Node& thermoNode = phaseNode.child("thermo");
400
401 // Determine the form of the Debye-Huckel model, m_formDH. We will use
402 // this information to size arrays below. If there is no XML node named
403 // "activityCoefficients", assume that we are doing the extreme dilute
404 // limit assumption
405 if (thermoNode.hasChild("activityCoefficients")) {
406 setDebyeHuckelModel(thermoNode.child("activityCoefficients")["model"]);
407 } else {
408 setDebyeHuckelModel("Dilute_limit");
409 }
410
411 // Go get all of the coefficients and factors in the activityCoefficients
412 // XML block
413 XML_Node* acNodePtr = 0;
414 if (thermoNode.hasChild("activityCoefficients")) {
415 XML_Node& acNode = thermoNode.child("activityCoefficients");
416 acNodePtr = &acNode;
417
418 // Look for parameters for A_Debye
419 if (acNode.hasChild("A_Debye")) {
420 XML_Node* ss = acNode.findByName("A_Debye");
421 string modelString = ss->attrib("model");
422 if (modelString != "") {
423 if (caseInsensitiveEquals(modelString, "water")) {
424 setA_Debye(-1);
425 } else {
426 throw CanteraError("DebyeHuckel::initThermoXML",
427 "A_Debye Model \"" + modelString +
428 "\" is not known");
429 }
430 } else {
431 setA_Debye(getFloat(acNode, "A_Debye"));
432 }
433 }
434
435 // Look for parameters for B_Debye
436 if (acNode.hasChild("B_Debye")) {
437 setB_Debye(getFloat(acNode, "B_Debye"));
438 }
439
440 // Look for parameters for B_dot
441 if (acNode.hasChild("B_dot")) {
442 setB_dot(getFloat(acNode, "B_dot"));
443 }
444
445 // Look for Parameters for the Maximum Ionic Strength
446 if (acNode.hasChild("maxIonicStrength")) {
447 setMaxIonicStrength(getFloat(acNode, "maxIonicStrength"));
448 }
449
450 // Look for Helgeson Parameters
451 useHelgesonFixedForm(acNode.hasChild("UseHelgesonFixedForm"));
452
453 // Look for parameters for the Ionic radius
454 if (acNode.hasChild("ionicRadius")) {
455 XML_Node& irNode = acNode.child("ionicRadius");
456
457 double Afactor = 1.0;
458 if (irNode.hasAttrib("units")) {
459 std::string Aunits = irNode.attrib("units");
460 Afactor = toSI(Aunits);
461 }
462
463 if (irNode.hasAttrib("default")) {
464 setDefaultIonicRadius(Afactor * fpValue(irNode.attrib("default")));
465 }
466
467 // If the Debye-Huckel form is BDOT_AK, we can have separate values
468 // for the denominator's ionic size. -> That's how the activity
469 // coefficient is parameterized. In this case only do we allow the
470 // code to read in these parameters.
471 if (m_formDH == DHFORM_BDOT_AK) {
472 // Define a string-string map, and interpret the value of the
473 // XML element as binary pairs separated by colons, for example:
474 // Na+:3.0
475 // Cl-:4.0
476 // H+:9.0
477 // OH-:3.5
478 // Read them into the map.
479 map<string, string> m;
480 getMap(irNode, m);
481
482 // Iterate over the map pairs, interpreting the first string as
483 // a species in the current phase. If no match is made, silently
484 // ignore the lack of agreement (HKM -> may be changed in the
485 // future).
486 for (const auto& b : m) {
487 size_t k = speciesIndex(b.first);
488 if (k != npos) {
489 m_Aionic[k] = fpValue(b.second) * Afactor;
490 }
491 }
492 }
493 }
494
495 // Get the matrix of coefficients for the Beta binary interaction
496 // parameters. We assume here that this matrix is symmetric, so that we
497 // only have to input 1/2 of the values.
498 if (acNode.hasChild("DHBetaMatrix")) {
499 if (m_formDH == DHFORM_BETAIJ ||
500 m_formDH == DHFORM_PITZER_BETAIJ) {
501 XML_Node& irNode = acNode.child("DHBetaMatrix");
502 const vector<string>& sn = speciesNames();
503 getMatrixValues(irNode, sn, sn, m_Beta_ij, true, true);
504 } else {
505 throw CanteraError("DebyeHuckel::initThermoXML:",
506 "DHBetaMatrix found for wrong type");
507 }
508 }
509
510 // Override stoichiometric Ionic Strength based on the phase definition
511 if (acNodePtr && acNodePtr->hasChild("stoichIsMods")) {
512 XML_Node& sIsNode = acNodePtr->child("stoichIsMods");
513 map<std::string, std::string> msIs;
514 getMap(sIsNode, msIs);
515 for (const auto& b : msIs) {
516 size_t kk = speciesIndex(b.first);
517 double val = fpValue(b.second);
518 m_speciesCharge_Stoich[kk] = val;
519 }
520 }
521 }
522
523 // Override electrolyte species type based on the phase definition
524 if (acNodePtr && acNodePtr->hasChild("electrolyteSpeciesType")) {
525 XML_Node& ESTNode = acNodePtr->child("electrolyteSpeciesType");
526 map<std::string, std::string> msEST;
527 getMap(ESTNode, msEST);
528 for (const auto& b : msEST) {
529 size_t kk = speciesIndex(b.first);
530 std::string est = b.second;
531 if ((m_electrolyteSpeciesType[kk] = interp_est(est)) == -1) {
532 throw CanteraError("DebyeHuckel:initThermoXML",
533 "Bad electrolyte type: " + est);
534 }
535 }
536 }
537
538 // Lastly set the state
539 if (phaseNode.hasChild("state")) {
540 XML_Node& stateNode = phaseNode.child("state");
541 setStateFromXML(stateNode);
542 }
543}
544
546{
548 if (m_input.hasKey("activity-data")) {
549 auto& node = m_input["activity-data"].as<AnyMap>();
550 setDebyeHuckelModel(node["model"].asString());
551 if (node.hasKey("A_Debye")) {
552 if (node["A_Debye"] == "variable") {
553 setA_Debye(-1);
554 } else {
555 setA_Debye(node.convert("A_Debye", "kg^0.5/gmol^0.5"));
556 }
557 }
558 if (node.hasKey("B_Debye")) {
559 setB_Debye(node.convert("B_Debye", "kg^0.5/gmol^0.5/m"));
560 }
561 if (node.hasKey("max-ionic-strength")) {
562 setMaxIonicStrength(node["max-ionic-strength"].asDouble());
563 }
564 if (node.hasKey("use-Helgeson-fixed-form")) {
565 useHelgesonFixedForm(node["use-Helgeson-fixed-form"].asBool());
566 }
567 if (node.hasKey("default-ionic-radius")) {
568 setDefaultIonicRadius(node.convert("default-ionic-radius", "m"));
569 }
570 if (node.hasKey("B-dot")) {
571 setB_dot(node["B-dot"].asDouble());
572 }
573 if (node.hasKey("beta")) {
574 for (auto& item : node["beta"].asVector<AnyMap>()) {
575 auto& species = item["species"].asVector<string>(2);
576 setBeta(species[0], species[1], item["beta"].asDouble());
577 }
578 }
579 }
580
581 // Solvent
582 m_waterSS = dynamic_cast<PDSS_Water*>(providePDSS(0));
583 if (m_waterSS) {
584 // Initialize the water property calculator. It will share the internal
585 // eos water calculator.
586 if (m_form_A_Debye == A_DEBYE_WATER) {
588 }
589 } else if (dynamic_cast<PDSS_ConstVol*>(providePDSS(0)) == 0) {
590 throw CanteraError("DebyeHuckel::initThermo", "Solvent standard state"
591 " model must be WaterIAPWS or constant_incompressible.");
592 }
593
594 // Solutes
595 for (size_t k = 1; k < nSpecies(); k++) {
596 if (dynamic_cast<PDSS_ConstVol*>(providePDSS(k)) == 0) {
597 throw CanteraError("DebyeHuckel::initThermo", "Solute standard"
598 " state model must be constant_incompressible.");
599 }
600 }
601}
602
603void DebyeHuckel::getParameters(AnyMap& phaseNode) const
604{
606 AnyMap activityNode;
607
608 switch (m_formDH) {
609 case DHFORM_DILUTE_LIMIT:
610 activityNode["model"] = "dilute-limit";
611 break;
612 case DHFORM_BDOT_AK:
613 activityNode["model"] = "B-dot-with-variable-a";
614 break;
615 case DHFORM_BDOT_ACOMMON:
616 activityNode["model"] = "B-dot-with-common-a";
617 break;
618 case DHFORM_BETAIJ:
619 activityNode["model"] = "beta_ij";
620 break;
621 case DHFORM_PITZER_BETAIJ:
622 activityNode["model"] = "Pitzer-with-beta_ij";
623 break;
624 }
625
626 if (m_form_A_Debye == A_DEBYE_WATER) {
627 activityNode["A_Debye"] = "variable";
628 } else if (m_A_Debye != A_Debye_default) {
629 activityNode["A_Debye"].setQuantity(m_A_Debye, "kg^0.5/gmol^0.5");
630 }
631
632 if (m_B_Debye != B_Debye_default) {
633 activityNode["B_Debye"].setQuantity(m_B_Debye, "kg^0.5/gmol^0.5/m");
634 }
635 if (m_maxIionicStrength != maxIionicStrength_default) {
636 activityNode["max-ionic-strength"] = m_maxIionicStrength;
637 }
639 activityNode["use-Helgeson-fixed-form"] = true;
640 }
641 if (!isnan(m_Aionic_default)) {
642 activityNode["default-ionic-radius"].setQuantity(m_Aionic_default, "m");
643 }
644 for (double B_dot : m_B_Dot) {
645 if (B_dot != 0.0) {
646 activityNode["B-dot"] = B_dot;
647 break;
648 }
649 }
650 if (m_Beta_ij.nRows() && m_Beta_ij.nColumns()) {
651 std::vector<AnyMap> beta;
652 for (size_t i = 0; i < m_kk; i++) {
653 for (size_t j = i; j < m_kk; j++) {
654 if (m_Beta_ij(i, j) != 0) {
655 AnyMap entry;
656 entry["species"] = vector<std::string>{
657 speciesName(i), speciesName(j)};
658 entry["beta"] = m_Beta_ij(i, j);
659 beta.push_back(std::move(entry));
660 }
661 }
662 }
663 activityNode["beta"] = std::move(beta);
664 }
665 phaseNode["activity-data"] = std::move(activityNode);
666}
667
668void DebyeHuckel::getSpeciesParameters(const std::string& name,
669 AnyMap& speciesNode) const
670{
672 size_t k = speciesIndex(name);
674 AnyMap dhNode;
675 if (m_Aionic[k] != m_Aionic_default) {
676 dhNode["ionic-radius"].setQuantity(m_Aionic[k], "m");
677 }
678
679 int estDefault = cEST_nonpolarNeutral;
680 if (k == 0) {
681 estDefault = cEST_solvent;
682 }
683
684 if (m_speciesCharge_Stoich[k] != charge(k)) {
685 dhNode["weak-acid-charge"] = m_speciesCharge_Stoich[k];
686 estDefault = cEST_weakAcidAssociated;
687 } else if (fabs(charge(k)) > 0.0001) {
688 estDefault = cEST_chargedSpecies;
689 }
690
691 if (m_electrolyteSpeciesType[k] != estDefault) {
692 string estType;
693 switch (m_electrolyteSpeciesType[k]) {
694 case cEST_solvent:
695 estType = "solvent";
696 break;
697 case cEST_chargedSpecies:
698 estType = "charged-species";
699 break;
700 case cEST_weakAcidAssociated:
701 estType = "weak-acid-associated";
702 break;
703 case cEST_strongAcidAssociated:
704 estType = "strong-acid-associated";
705 break;
706 case cEST_polarNeutral:
707 estType = "polar-neutral";
708 break;
709 case cEST_nonpolarNeutral:
710 estType = "nonpolar-neutral";
711 break;
712 default:
713 throw CanteraError("DebyeHuckel::getSpeciesParameters",
714 "Unknown electrolyte species type ({}) for species '{}'",
716 }
717 dhNode["electrolyte-species-type"] = estType;
718 }
719
720 if (dhNode.size()) {
721 speciesNode["Debye-Huckel"] = std::move(dhNode);
722 }
723}
724
725
726double DebyeHuckel::A_Debye_TP(double tempArg, double presArg) const
727{
728 double T = temperature();
729 double A;
730 if (tempArg != -1.0) {
731 T = tempArg;
732 }
733 double P = pressure();
734 if (presArg != -1.0) {
735 P = presArg;
736 }
737
738 switch (m_form_A_Debye) {
739 case A_DEBYE_CONST:
740 A = m_A_Debye;
741 break;
742 case A_DEBYE_WATER:
743 A = m_waterProps->ADebye(T, P, 0);
744 m_A_Debye = A;
745 break;
746 default:
747 throw CanteraError("DebyeHuckel::A_Debye_TP", "shouldn't be here");
748 }
749 return A;
750}
751
752double DebyeHuckel::dA_DebyedT_TP(double tempArg, double presArg) const
753{
754 double T = temperature();
755 if (tempArg != -1.0) {
756 T = tempArg;
757 }
758 double P = pressure();
759 if (presArg != -1.0) {
760 P = presArg;
761 }
762 double dAdT;
763 switch (m_form_A_Debye) {
764 case A_DEBYE_CONST:
765 dAdT = 0.0;
766 break;
767 case A_DEBYE_WATER:
768 dAdT = m_waterProps->ADebye(T, P, 1);
769 break;
770 default:
771 throw CanteraError("DebyeHuckel::dA_DebyedT_TP", "shouldn't be here");
772 }
773 return dAdT;
774}
775
776double DebyeHuckel::d2A_DebyedT2_TP(double tempArg, double presArg) const
777{
778 double T = temperature();
779 if (tempArg != -1.0) {
780 T = tempArg;
781 }
782 double P = pressure();
783 if (presArg != -1.0) {
784 P = presArg;
785 }
786 double d2AdT2;
787 switch (m_form_A_Debye) {
788 case A_DEBYE_CONST:
789 d2AdT2 = 0.0;
790 break;
791 case A_DEBYE_WATER:
792 d2AdT2 = m_waterProps->ADebye(T, P, 2);
793 break;
794 default:
795 throw CanteraError("DebyeHuckel::d2A_DebyedT2_TP", "shouldn't be here");
796 }
797 return d2AdT2;
798}
799
800double DebyeHuckel::dA_DebyedP_TP(double tempArg, double presArg) const
801{
802 double T = temperature();
803 if (tempArg != -1.0) {
804 T = tempArg;
805 }
806 double P = pressure();
807 if (presArg != -1.0) {
808 P = presArg;
809 }
810 double dAdP;
811 switch (m_form_A_Debye) {
812 case A_DEBYE_CONST:
813 dAdP = 0.0;
814 break;
815 case A_DEBYE_WATER:
816 dAdP = m_waterProps->ADebye(T, P, 3);
817 break;
818 default:
819 throw CanteraError("DebyeHuckel::dA_DebyedP_TP", "shouldn't be here");
820 }
821 return dAdP;
822}
823
824// ---------- Other Property Functions
825
826double DebyeHuckel::AionicRadius(int k) const
827{
828 return m_Aionic[k];
829}
830
831// ------------ Private and Restricted Functions ------------------
832
833bool DebyeHuckel::addSpecies(shared_ptr<Species> spec)
834{
835 bool added = MolalityVPSSTP::addSpecies(spec);
836 if (added) {
837 m_lnActCoeffMolal.push_back(0.0);
838 m_dlnActCoeffMolaldT.push_back(0.0);
839 m_d2lnActCoeffMolaldT2.push_back(0.0);
840 m_dlnActCoeffMolaldP.push_back(0.0);
841 m_B_Dot.push_back(0.0);
842 m_tmpV.push_back(0.0);
843
844 // NAN will be replaced with default value
845 double Aionic = NAN;
846
847 // Guess electrolyte species type based on charge properties
848 int est = cEST_nonpolarNeutral;
849 double stoichCharge = spec->charge;
850 if (fabs(spec->charge) > 0.0001) {
851 est = cEST_chargedSpecies;
852 }
853
854 if (spec->input.hasKey("Debye-Huckel")) {
855 auto& dhNode = spec->input["Debye-Huckel"].as<AnyMap>();
856 Aionic = dhNode.convert("ionic-radius", "m", NAN);
857 if (dhNode.hasKey("weak-acid-charge")) {
858 stoichCharge = dhNode["weak-acid-charge"].asDouble();
859 if (fabs(stoichCharge - spec->charge) > 0.0001) {
860 est = cEST_weakAcidAssociated;
861 }
862 }
863 // Apply override of the electrolyte species type
864 if (dhNode.hasKey("electrolyte-species-type")) {
865 est = interp_est(dhNode["electrolyte-species-type"].asString());
866 }
867 }
868
869 if (m_electrolyteSpeciesType.size() == 0) {
870 est = cEST_solvent; // species 0 is the solvent
871 }
872
873 m_Aionic.push_back(Aionic);
874 m_speciesCharge_Stoich.push_back(stoichCharge);
875 m_electrolyteSpeciesType.push_back(est);
876 }
877 return added;
878}
879
880double DebyeHuckel::_nonpolarActCoeff(double IionicMolality)
881{
882 // These are coefficients to describe the increase in activity coeff for
883 // non-polar molecules due to the electrolyte becoming stronger (the so-
884 // called salt-out effect)
885 const static double npActCoeff[] = {0.1127, -0.01049, 1.545E-3};
886 double I2 = IionicMolality * IionicMolality;
887 double l10actCoeff =
888 npActCoeff[0] * IionicMolality +
889 npActCoeff[1] * I2 +
890 npActCoeff[2] * I2 * IionicMolality;
891 return pow(10.0 , l10actCoeff);
892}
893
895{
896 const double a0 = 1.454;
897 const double b0 = 0.02236;
898 const double c0 = 9.380E-3;
899 const double d0 = -5.362E-4;
900 double Is = m_IionicMolalityStoich;
901 if (Is <= 0.0) {
902 return 0.0;
903 }
904 double Is2 = Is * Is;
905 double bhat = 1.0 + a0 * sqrt(Is);
906 double func = bhat - 2.0 * log(bhat) - 1.0/bhat;
907 double v1 = m_A_Debye / (a0 * a0 * a0 * Is) * func;
908 double oc = 1.0 - v1 + b0 * Is / 2.0 + 2.0 * c0 * Is2 / 3.0
909 + 3.0 * d0 * Is2 * Is / 4.0;
910 return oc;
911}
912
914{
915 // Update the internally stored vector of molalities
917 double oc = _osmoticCoeffHelgesonFixedForm();
918 double sum = 0.0;
919 for (size_t k = 1; k < m_kk; k++) {
920 sum += std::max(m_molalities[k], 0.0);
921 }
922 if (sum > 2.0 * m_maxIionicStrength) {
923 sum = 2.0 * m_maxIionicStrength;
924 };
925 return - m_Mnaught * sum * oc;
926}
927
929{
930 double z_k, zs_k1, zs_k2;
931
932 // Update the internally stored vector of molalities
934
935 // Calculate the apparent (real) ionic strength.
936 //
937 // Note this is not the stoichiometric ionic strengh, where reactions of
938 // ions forming neutral salts are ignorred in calculating the ionic
939 // strength.
940 m_IionicMolality = 0.0;
941 for (size_t k = 0; k < m_kk; k++) {
942 z_k = m_speciesCharge[k];
943 m_IionicMolality += m_molalities[k] * z_k * z_k;
944 }
945 m_IionicMolality /= 2.0;
947
948 // Calculate the stoichiometric ionic charge
950 for (size_t k = 0; k < m_kk; k++) {
951 z_k = m_speciesCharge[k];
952 zs_k1 = m_speciesCharge_Stoich[k];
953 if (z_k == zs_k1) {
954 m_IionicMolalityStoich += m_molalities[k] * z_k * z_k;
955 } else {
956 zs_k2 = z_k - zs_k1;
957 m_IionicMolalityStoich += m_molalities[k] * (zs_k1 * zs_k1 + zs_k2 * zs_k2);
958 }
959 }
962
963 // Possibly update the stored value of the Debye-Huckel parameter A_Debye
964 // This parameter appears on the top of the activity coefficient expression.
965 // It depends on T (and P), as it depends explicitly on the temperature.
966 // Also, the dielectric constant is usually a fairly strong function of T,
967 // also.
969
970 // Calculate a safe value for the mole fraction of the solvent
971 double xmolSolvent = moleFraction(0);
972 xmolSolvent = std::max(8.689E-3, xmolSolvent);
973
974 int est;
975 double ac_nonPolar = 1.0;
976 double numTmp = m_A_Debye * sqrt(m_IionicMolality);
977 double denomTmp = m_B_Debye * sqrt(m_IionicMolality);
978 double coeff;
979 double lnActivitySolvent = 0.0;
980 double tmp;
981 double tmpLn;
982 double y, yp1, sigma;
983 switch (m_formDH) {
984 case DHFORM_DILUTE_LIMIT:
985 for (size_t k = 0; k < m_kk; k++) {
986 z_k = m_speciesCharge[k];
987 m_lnActCoeffMolal[k] = - z_k * z_k * numTmp;
988 }
989 lnActivitySolvent =
990 (xmolSolvent - 1.0)/xmolSolvent +
991 2.0 / 3.0 * m_A_Debye * m_Mnaught *
993 break;
994
995 case DHFORM_BDOT_AK:
996 ac_nonPolar = _nonpolarActCoeff(m_IionicMolality);
997 for (size_t k = 0; k < m_kk; k++) {
999 if (est == cEST_nonpolarNeutral) {
1000 m_lnActCoeffMolal[k] = log(ac_nonPolar);
1001 } else {
1002 z_k = m_speciesCharge[k];
1004 - z_k * z_k * numTmp / (1.0 + denomTmp * m_Aionic[k])
1005 + log(10.0) * m_B_Dot[k] * m_IionicMolality;
1006 }
1007 }
1008
1009 lnActivitySolvent = (xmolSolvent - 1.0)/xmolSolvent;
1010 coeff = 2.0 / 3.0 * m_A_Debye * m_Mnaught
1011 * sqrt(m_IionicMolality);
1012 tmp = 0.0;
1013 if (denomTmp > 0.0) {
1014 for (size_t k = 0; k < m_kk; k++) {
1015 if (k != 0 || m_Aionic[k] != 0.0) {
1016 y = denomTmp * m_Aionic[k];
1017 yp1 = y + 1.0;
1018 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1019 z_k = m_speciesCharge[k];
1020 tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
1021 }
1022 }
1023 }
1024 lnActivitySolvent += coeff * tmp;
1025 tmp = 0.0;
1026 for (size_t k = 1; k < m_kk; k++) {
1027 z_k = m_speciesCharge[k];
1028 if (z_k != 0.0) {
1029 tmp += m_B_Dot[k] * m_molalities[k];
1030 }
1031 }
1032 lnActivitySolvent -=
1033 m_Mnaught * log(10.0) * m_IionicMolality * tmp / 2.0;
1034
1035 // Special section to implement the Helgeson fixed form for the water
1036 // brine activity coefficient.
1038 lnActivitySolvent = _lnactivityWaterHelgesonFixedForm();
1039 }
1040 break;
1041
1042 case DHFORM_BDOT_ACOMMON:
1043 denomTmp *= m_Aionic[0];
1044 for (size_t k = 0; k < m_kk; k++) {
1045 z_k = m_speciesCharge[k];
1047 - z_k * z_k * numTmp / (1.0 + denomTmp)
1048 + log(10.0) * m_B_Dot[k] * m_IionicMolality;
1049 }
1050 if (denomTmp > 0.0) {
1051 y = denomTmp;
1052 yp1 = y + 1.0;
1053 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1054 } else {
1055 sigma = 0.0;
1056 }
1057 lnActivitySolvent =
1058 (xmolSolvent - 1.0)/xmolSolvent +
1059 2.0 /3.0 * m_A_Debye * m_Mnaught *
1060 m_IionicMolality * sqrt(m_IionicMolality) * sigma;
1061 tmp = 0.0;
1062 for (size_t k = 1; k < m_kk; k++) {
1063 z_k = m_speciesCharge[k];
1064 if (z_k != 0.0) {
1065 tmp += m_B_Dot[k] * m_molalities[k];
1066 }
1067 }
1068 lnActivitySolvent -=
1069 m_Mnaught * log(10.0) * m_IionicMolality * tmp / 2.0;
1070 break;
1071
1072 case DHFORM_BETAIJ:
1073 denomTmp = m_B_Debye * m_Aionic[0];
1074 denomTmp *= sqrt(m_IionicMolality);
1075 lnActivitySolvent =
1076 (xmolSolvent - 1.0)/xmolSolvent;
1077
1078 for (size_t k = 1; k < m_kk; k++) {
1079 z_k = m_speciesCharge[k];
1081 - z_k * z_k * numTmp / (1.0 + denomTmp);
1082 for (size_t j = 0; j < m_kk; j++) {
1083 double beta = m_Beta_ij.value(k, j);
1084 m_lnActCoeffMolal[k] += 2.0 * m_molalities[j] * beta;
1085 }
1086 }
1087 if (denomTmp > 0.0) {
1088 y = denomTmp;
1089 yp1 = y + 1.0;
1090 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 -2.0*log(yp1));
1091 } else {
1092 sigma = 0.0;
1093 }
1094 lnActivitySolvent =
1095 (xmolSolvent - 1.0)/xmolSolvent +
1096 2.0 /3.0 * m_A_Debye * m_Mnaught *
1097 m_IionicMolality * sqrt(m_IionicMolality) * sigma;
1098 tmp = 0.0;
1099 for (size_t k = 0; k < m_kk; k++) {
1100 for (size_t j = 0; j < m_kk; j++) {
1101 tmp +=
1102 m_Beta_ij.value(k, j) * m_molalities[k] * m_molalities[j];
1103 }
1104 }
1105 lnActivitySolvent -= m_Mnaught * tmp;
1106 break;
1107
1108 case DHFORM_PITZER_BETAIJ:
1109 denomTmp = m_B_Debye * sqrt(m_IionicMolality);
1110 denomTmp *= m_Aionic[0];
1111 numTmp = m_A_Debye * sqrt(m_IionicMolality);
1112 tmpLn = log(1.0 + denomTmp);
1113 for (size_t k = 1; k < m_kk; k++) {
1114 z_k = m_speciesCharge[k];
1116 - z_k * z_k * numTmp / 3.0 / (1.0 + denomTmp);
1117 m_lnActCoeffMolal[k] +=
1118 - 2.0 * z_k * z_k * m_A_Debye * tmpLn /
1119 (3.0 * m_B_Debye * m_Aionic[0]);
1120 for (size_t j = 0; j < m_kk; j++) {
1121 m_lnActCoeffMolal[k] += 2.0 * m_molalities[j] *
1122 m_Beta_ij.value(k, j);
1123 }
1124 }
1125 sigma = 1.0 / (1.0 + denomTmp);
1126 lnActivitySolvent =
1127 (xmolSolvent - 1.0)/xmolSolvent +
1128 2.0 /3.0 * m_A_Debye * m_Mnaught *
1129 m_IionicMolality * sqrt(m_IionicMolality) * sigma;
1130 tmp = 0.0;
1131 for (size_t k = 0; k < m_kk; k++) {
1132 for (size_t j = 0; j < m_kk; j++) {
1133 tmp +=
1134 m_Beta_ij.value(k, j) * m_molalities[k] * m_molalities[j];
1135 }
1136 }
1137 lnActivitySolvent -= m_Mnaught * tmp;
1138 break;
1139
1140 default:
1141 throw CanteraError("DebyeHuckel::s_update_lnMolalityActCoeff", "ERROR");
1142 }
1143
1144 // Above, we calculated the ln(activitySolvent). Translate that into the
1145 // molar-based activity coefficient by dividing by the solvent mole
1146 // fraction. Solvents are not on the molality scale.
1147 xmolSolvent = moleFraction(0);
1148 m_lnActCoeffMolal[0] = lnActivitySolvent - log(xmolSolvent);
1149}
1150
1152{
1153 double z_k, coeff, tmp, y, yp1, sigma, tmpLn;
1154 // First we store dAdT explicitly here
1155 double dAdT = dA_DebyedT_TP();
1156 if (dAdT == 0.0) {
1157 for (size_t k = 0; k < m_kk; k++) {
1158 m_dlnActCoeffMolaldT[k] = 0.0;
1159 }
1160 return;
1161 }
1162
1163 // Calculate a safe value for the mole fraction of the solvent
1164 double xmolSolvent = moleFraction(0);
1165 xmolSolvent = std::max(8.689E-3, xmolSolvent);
1166 double sqrtI = sqrt(m_IionicMolality);
1167 double numdAdTTmp = dAdT * sqrtI;
1168 double denomTmp = m_B_Debye * sqrtI;
1169 double d_lnActivitySolvent_dT = 0;
1170
1171 switch (m_formDH) {
1172 case DHFORM_DILUTE_LIMIT:
1173 for (size_t k = 1; k < m_kk; k++) {
1175 m_lnActCoeffMolal[k] * dAdT / m_A_Debye;
1176 }
1177 d_lnActivitySolvent_dT = 2.0 / 3.0 * dAdT * m_Mnaught *
1179 m_dlnActCoeffMolaldT[0] = d_lnActivitySolvent_dT;
1180 break;
1181
1182 case DHFORM_BDOT_AK:
1183 for (size_t k = 0; k < m_kk; k++) {
1184 z_k = m_speciesCharge[k];
1186 - z_k * z_k * numdAdTTmp / (1.0 + denomTmp * m_Aionic[k]);
1187 }
1188
1189 m_dlnActCoeffMolaldT[0] = 0.0;
1190 coeff = 2.0 / 3.0 * dAdT * m_Mnaught * sqrtI;
1191 tmp = 0.0;
1192 if (denomTmp > 0.0) {
1193 for (size_t k = 0; k < m_kk; k++) {
1194 y = denomTmp * m_Aionic[k];
1195 yp1 = y + 1.0;
1196 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1197 z_k = m_speciesCharge[k];
1198 tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
1199 }
1200 }
1201 m_dlnActCoeffMolaldT[0] += coeff * tmp;
1202 break;
1203
1204 case DHFORM_BDOT_ACOMMON:
1205 denomTmp *= m_Aionic[0];
1206 for (size_t k = 0; k < m_kk; k++) {
1207 z_k = m_speciesCharge[k];
1209 - z_k * z_k * numdAdTTmp / (1.0 + denomTmp);
1210 }
1211 if (denomTmp > 0.0) {
1212 y = denomTmp;
1213 yp1 = y + 1.0;
1214 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1215 } else {
1216 sigma = 0.0;
1217 }
1218 m_dlnActCoeffMolaldT[0] = 2.0 /3.0 * dAdT * m_Mnaught *
1219 m_IionicMolality * sqrtI * sigma;
1220 break;
1221
1222 case DHFORM_BETAIJ:
1223 denomTmp *= m_Aionic[0];
1224 for (size_t k = 1; k < m_kk; k++) {
1225 z_k = m_speciesCharge[k];
1226 m_dlnActCoeffMolaldT[k] = -z_k*z_k * numdAdTTmp / (1.0 + denomTmp);
1227 }
1228 if (denomTmp > 0.0) {
1229 y = denomTmp;
1230 yp1 = y + 1.0;
1231 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1232 } else {
1233 sigma = 0.0;
1234 }
1235 m_dlnActCoeffMolaldT[0] = 2.0 /3.0 * dAdT * m_Mnaught *
1236 m_IionicMolality * sqrtI * sigma;
1237 break;
1238
1239 case DHFORM_PITZER_BETAIJ:
1240 denomTmp *= m_Aionic[0];
1241 tmpLn = log(1.0 + denomTmp);
1242 for (size_t k = 1; k < m_kk; k++) {
1243 z_k = m_speciesCharge[k];
1245 - z_k * z_k * numdAdTTmp / (1.0 + denomTmp)
1246 - 2.0 * z_k * z_k * dAdT * tmpLn / (m_B_Debye * m_Aionic[0]);
1247 m_dlnActCoeffMolaldT[k] /= 3.0;
1248 }
1249
1250 sigma = 1.0 / (1.0 + denomTmp);
1251 m_dlnActCoeffMolaldT[0] = 2.0 /3.0 * dAdT * m_Mnaught *
1252 m_IionicMolality * sqrtI * sigma;
1253 break;
1254
1255 default:
1256 throw CanteraError("DebyeHuckel::s_update_dlnMolalityActCoeff_dT",
1257 "ERROR");
1258 }
1259}
1260
1262{
1263 double z_k, coeff, tmp, y, yp1, sigma, tmpLn;
1264 double dAdT = dA_DebyedT_TP();
1265 double d2AdT2 = d2A_DebyedT2_TP();
1266 if (d2AdT2 == 0.0 && dAdT == 0.0) {
1267 for (size_t k = 0; k < m_kk; k++) {
1268 m_d2lnActCoeffMolaldT2[k] = 0.0;
1269 }
1270 return;
1271 }
1272
1273 // Calculate a safe value for the mole fraction of the solvent
1274 double xmolSolvent = moleFraction(0);
1275 xmolSolvent = std::max(8.689E-3, xmolSolvent);
1276 double sqrtI = sqrt(m_IionicMolality);
1277 double numd2AdT2Tmp = d2AdT2 * sqrtI;
1278 double denomTmp = m_B_Debye * sqrtI;
1279
1280 switch (m_formDH) {
1281 case DHFORM_DILUTE_LIMIT:
1282 for (size_t k = 0; k < m_kk; k++) {
1284 m_lnActCoeffMolal[k] * d2AdT2 / m_A_Debye;
1285 }
1286 break;
1287
1288 case DHFORM_BDOT_AK:
1289 for (size_t k = 0; k < m_kk; k++) {
1290 z_k = m_speciesCharge[k];
1292 - z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp * m_Aionic[k]);
1293 }
1294
1295 m_d2lnActCoeffMolaldT2[0] = 0.0;
1296 coeff = 2.0 / 3.0 * d2AdT2 * m_Mnaught * sqrtI;
1297 tmp = 0.0;
1298 if (denomTmp > 0.0) {
1299 for (size_t k = 0; k < m_kk; k++) {
1300 y = denomTmp * m_Aionic[k];
1301 yp1 = y + 1.0;
1302 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1303 z_k = m_speciesCharge[k];
1304 tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
1305 }
1306 }
1307 m_d2lnActCoeffMolaldT2[0] += coeff * tmp;
1308 break;
1309
1310 case DHFORM_BDOT_ACOMMON:
1311 denomTmp *= m_Aionic[0];
1312 for (size_t k = 0; k < m_kk; k++) {
1313 z_k = m_speciesCharge[k];
1315 - z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp);
1316 }
1317 if (denomTmp > 0.0) {
1318 y = denomTmp;
1319 yp1 = y + 1.0;
1320 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1321 } else {
1322 sigma = 0.0;
1323 }
1324 m_d2lnActCoeffMolaldT2[0] = 2.0 /3.0 * d2AdT2 * m_Mnaught *
1325 m_IionicMolality * sqrtI * sigma;
1326 break;
1327
1328 case DHFORM_BETAIJ:
1329 denomTmp *= m_Aionic[0];
1330 for (size_t k = 1; k < m_kk; k++) {
1331 z_k = m_speciesCharge[k];
1332 m_d2lnActCoeffMolaldT2[k] = -z_k*z_k * numd2AdT2Tmp / (1.0 + denomTmp);
1333 }
1334 if (denomTmp > 0.0) {
1335 y = denomTmp;
1336 yp1 = y + 1.0;
1337 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 -2.0*log(yp1));
1338 } else {
1339 sigma = 0.0;
1340 }
1341 m_d2lnActCoeffMolaldT2[0] = 2.0 /3.0 * d2AdT2 * m_Mnaught *
1342 m_IionicMolality * sqrtI * sigma;
1343 break;
1344
1345 case DHFORM_PITZER_BETAIJ:
1346 denomTmp *= m_Aionic[0];
1347 tmpLn = log(1.0 + denomTmp);
1348 for (size_t k = 1; k < m_kk; k++) {
1349 z_k = m_speciesCharge[k];
1351 - z_k * z_k * numd2AdT2Tmp / (1.0 + denomTmp)
1352 - 2.0 * z_k * z_k * d2AdT2 * tmpLn / (m_B_Debye * m_Aionic[0]);
1353 m_d2lnActCoeffMolaldT2[k] /= 3.0;
1354 }
1355
1356 sigma = 1.0 / (1.0 + denomTmp);
1357 m_d2lnActCoeffMolaldT2[0] = 2.0 /3.0 * d2AdT2 * m_Mnaught *
1358 m_IionicMolality * sqrtI * sigma;
1359 break;
1360
1361 default:
1362 throw CanteraError("DebyeHuckel::s_update_d2lnMolalityActCoeff_dT2",
1363 "ERROR");
1364 }
1365}
1366
1368{
1369 double z_k, coeff, tmp, y, yp1, sigma, tmpLn;
1370 int est;
1371 double dAdP = dA_DebyedP_TP();
1372 if (dAdP == 0.0) {
1373 for (size_t k = 0; k < m_kk; k++) {
1374 m_dlnActCoeffMolaldP[k] = 0.0;
1375 }
1376 return;
1377 }
1378
1379 // Calculate a safe value for the mole fraction of the solvent
1380 double xmolSolvent = moleFraction(0);
1381 xmolSolvent = std::max(8.689E-3, xmolSolvent);
1382 double sqrtI = sqrt(m_IionicMolality);
1383 double numdAdPTmp = dAdP * sqrtI;
1384 double denomTmp = m_B_Debye * sqrtI;
1385
1386 switch (m_formDH) {
1387 case DHFORM_DILUTE_LIMIT:
1388 for (size_t k = 0; k < m_kk; k++) {
1390 m_lnActCoeffMolal[k] * dAdP / m_A_Debye;
1391 }
1392 break;
1393
1394 case DHFORM_BDOT_AK:
1395 for (size_t k = 0; k < m_kk; k++) {
1396 est = m_electrolyteSpeciesType[k];
1397 if (est == cEST_nonpolarNeutral) {
1398 m_lnActCoeffMolal[k] = 0.0;
1399 } else {
1400 z_k = m_speciesCharge[k];
1402 - z_k * z_k * numdAdPTmp / (1.0 + denomTmp * m_Aionic[k]);
1403 }
1404 }
1405
1406 m_dlnActCoeffMolaldP[0] = 0.0;
1407 coeff = 2.0 / 3.0 * dAdP * m_Mnaught * sqrtI;
1408 tmp = 0.0;
1409 if (denomTmp > 0.0) {
1410 for (size_t k = 0; k < m_kk; k++) {
1411 y = denomTmp * m_Aionic[k];
1412 yp1 = y + 1.0;
1413 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1414 z_k = m_speciesCharge[k];
1415 tmp += m_molalities[k] * z_k * z_k * sigma / 2.0;
1416 }
1417 }
1418 m_dlnActCoeffMolaldP[0] += coeff * tmp;
1419 break;
1420
1421 case DHFORM_BDOT_ACOMMON:
1422 denomTmp *= m_Aionic[0];
1423 for (size_t k = 0; k < m_kk; k++) {
1424 z_k = m_speciesCharge[k];
1426 - z_k * z_k * numdAdPTmp / (1.0 + denomTmp);
1427 }
1428 if (denomTmp > 0.0) {
1429 y = denomTmp;
1430 yp1 = y + 1.0;
1431 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1432 } else {
1433 sigma = 0.0;
1434 }
1436 2.0 /3.0 * dAdP * m_Mnaught *
1437 m_IionicMolality * sqrtI * sigma;
1438 break;
1439
1440 case DHFORM_BETAIJ:
1441 denomTmp *= m_Aionic[0];
1442 for (size_t k = 1; k < m_kk; k++) {
1443 z_k = m_speciesCharge[k];
1444 m_dlnActCoeffMolaldP[k] = - z_k*z_k * numdAdPTmp / (1.0 + denomTmp);
1445 }
1446 if (denomTmp > 0.0) {
1447 y = denomTmp;
1448 yp1 = y + 1.0;
1449 sigma = 3.0 / (y * y * y) * (yp1 - 1.0/yp1 - 2.0*log(yp1));
1450 } else {
1451 sigma = 0.0;
1452 }
1453 m_dlnActCoeffMolaldP[0] = 2.0 /3.0 * dAdP * m_Mnaught *
1454 m_IionicMolality * sqrtI * sigma;
1455 break;
1456
1457 case DHFORM_PITZER_BETAIJ:
1458 denomTmp *= m_Aionic[0];
1459 tmpLn = log(1.0 + denomTmp);
1460 for (size_t k = 1; k < m_kk; k++) {
1461 z_k = m_speciesCharge[k];
1463 - z_k * z_k * numdAdPTmp / (1.0 + denomTmp)
1464 - 2.0 * z_k * z_k * dAdP * tmpLn
1465 / (m_B_Debye * m_Aionic[0]);
1466 m_dlnActCoeffMolaldP[k] /= 3.0;
1467 }
1468
1469 sigma = 1.0 / (1.0 + denomTmp);
1470 m_dlnActCoeffMolaldP[0] = 2.0 /3.0 * dAdP * m_Mnaught *
1471 m_IionicMolality * sqrtI * sigma;
1472 break;
1473
1474 default:
1475 throw CanteraError("DebyeHuckel::s_update_dlnMolalityActCoeff_dP",
1476 "ERROR");
1477 }
1478}
1479
1480}
Headers for the DebyeHuckel ThermoPhase object, which models dilute electrolyte solutions (see Thermo...
Declarations for the class PDSS_ConstVol (pressure dependent standard state) which handles calculatio...
Implementation of a pressure dependent standard state virtual function for a Pure Water Phase (see Sp...
Declaration for class Cantera::Species.
Headers for the factory class that can create known ThermoPhase objects (see Thermodynamic Properties...
A map of string keys to values whose type can vary at runtime.
Definition: AnyMap.h:399
size_t size() const
Returns the number of elements in this map.
Definition: AnyMap.h:590
double convert(const std::string &key, const std::string &units) const
Convert the item stored by the given key to the units specified in units.
Definition: AnyMap.cpp:1508
bool hasKey(const std::string &key) const
Returns true if the map contains an item named key.
Definition: AnyMap.cpp:1406
size_t nRows() const
Number of rows.
Definition: Array.h:188
size_t nColumns() const
Number of columns.
Definition: Array.h:193
doublereal & value(size_t i, size_t j)
Returns a changeable reference to position in the matrix.
Definition: Array.h:172
void resize(size_t n, size_t m, double v=0.0)
Resize the array, and fill the new entries with 'v'.
Definition: Array.cpp:52
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
Array2D m_Beta_ij
Array of 2D data used in the DHFORM_BETAIJ formulation Beta_ij.value(i,j) is the coefficient of the j...
Definition: DebyeHuckel.h:1082
int m_formDH
form of the Debye-Huckel parameterization used in the model.
Definition: DebyeHuckel.h:939
virtual bool addSpecies(shared_ptr< Species > spec)
double m_A_Debye
Current value of the Debye Constant, A_Debye.
Definition: DebyeHuckel.h:1016
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...
virtual void getParameters(AnyMap &phaseNode) const
Store the parameters of a ThermoPhase object such that an identical one could be reconstructed using ...
vector_fp m_d2lnActCoeffMolaldT2
2nd Derivative of log act coeff wrt T
Definition: DebyeHuckel.h:1095
double m_IionicMolality
Current value of the ionic strength on the molality scale.
Definition: DebyeHuckel.h:961
double _osmoticCoeffHelgesonFixedForm() const
Formula for the osmotic coefficient that occurs in the GWB.
double m_densWaterSS
Storage for the density of water's standard state.
Definition: DebyeHuckel.h:1054
void s_update_d2lnMolalityActCoeff_dT2() const
Calculate the temperature 2nd derivative of the activity coefficient.
vector_fp m_speciesCharge_Stoich
Stoichiometric species charge -> This is for calculations of the ionic strength which ignore ion-ion ...
Definition: DebyeHuckel.h:1074
int m_form_A_Debye
Form of the constant outside the Debye-Huckel term called A.
Definition: DebyeHuckel.h:994
bool m_useHelgesonFixedForm
If true, then the fixed for of Helgeson's activity for water is used instead of the rigorous form obt...
Definition: DebyeHuckel.h:972
static double _nonpolarActCoeff(double IionicMolality)
Static function that implements the non-polar species salt-out modifications.
virtual doublereal cp_mole() const
Molar heat capacity at constant pressure. Units: J/kmol/K.
Definition: DebyeHuckel.cpp:93
virtual void getMolalityActivityCoefficients(doublereal *acMolality) const
Get the array of non-dimensional molality-based activity coefficients at the current solution tempera...
virtual void getPartialMolarEnthalpies(doublereal *hbar) const
Returns an array of partial molar enthalpies for the species in the mixture.
virtual void getPartialMolarEntropies(doublereal *sbar) const
Returns an array of partial molar entropies of the species in the solution.
virtual void initThermoXML(XML_Node &phaseNode, const std::string &id)
Import and initialize a ThermoPhase object using an XML tree.
double m_B_Debye
Current value of the constant that appears in the denominator.
Definition: DebyeHuckel.h:1034
virtual doublereal enthalpy_mole() const
Molar enthalpy. Units: J/kmol.
Definition: DebyeHuckel.cpp:75
virtual void getPartialMolarVolumes(doublereal *vbar) const
Return an array of partial molar volumes for the species in the mixture.
double AionicRadius(int k=0) const
Reports the ionic radius of the kth species.
virtual void getActivityConcentrations(doublereal *c) const
This method returns an array of generalized concentrations.
void s_update_dlnMolalityActCoeff_dT() const
Calculation of temperature derivative of activity coefficient.
void s_update_lnMolalityActCoeff() const
Calculate the log activity coefficients.
virtual void getPartialMolarCp(doublereal *cpbar) const
Return an array of partial molar heat capacities for the species in the mixture.
void setBeta(const std::string &sp1, const std::string &sp2, double value)
Set the value for the beta interaction between species sp1 and sp2.
vector_fp m_dlnActCoeffMolaldP
Derivative of log act coeff wrt P.
Definition: DebyeHuckel.h:1098
void setDebyeHuckelModel(const std::string &form)
Set the DebyeHuckel parameterization form.
void setA_Debye(double A)
Set the A_Debye parameter.
vector_fp m_dlnActCoeffMolaldT
Derivative of log act coeff wrt T.
Definition: DebyeHuckel.h:1092
virtual doublereal entropy_mole() const
Molar entropy. Units: J/kmol/K.
Definition: DebyeHuckel.cpp:81
vector_int m_electrolyteSpeciesType
Vector containing the electrolyte species type.
Definition: DebyeHuckel.h:952
virtual void initThermo()
double m_Aionic_default
Default ionic radius for species where it is not specified.
Definition: DebyeHuckel.h:958
vector_fp m_B_Dot
Array of B_Dot values.
Definition: DebyeHuckel.h:1042
double m_maxIionicStrength
Maximum value of the ionic strength allowed in the calculation of the activity coefficients.
Definition: DebyeHuckel.h:965
virtual doublereal gibbs_mole() const
Molar Gibbs function. Units: J/kmol.
Definition: DebyeHuckel.cpp:87
DebyeHuckel(const std::string &inputFile="", const std::string &id="")
Full constructor for creating the phase.
Definition: DebyeHuckel.cpp:36
double _lnactivityWaterHelgesonFixedForm() const
Formula for the log of the water activity that occurs in the GWB.
vector_fp m_Aionic
a_k = Size of the ionic species in the DH formulation. units = meters
Definition: DebyeHuckel.h:955
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.
virtual void getSpeciesParameters(const std::string &name, AnyMap &speciesNode) const
Get phase-specific parameters of a Species object such that an identical one could be reconstructed a...
void setDefaultIonicRadius(double value)
Set the default ionic radius [m] for each species.
void s_update_dlnMolalityActCoeff_dP() const
Calculate the pressure derivative of the activity coefficient.
PDSS_Water * m_waterSS
Pointer to the Water standard state object.
Definition: DebyeHuckel.h:1048
virtual void getChemPotentials(doublereal *mu) const
Get the species chemical potentials. Units: J/kmol.
double m_IionicMolalityStoich
Stoichiometric ionic strength on the molality scale.
Definition: DebyeHuckel.h:975
std::unique_ptr< WaterProps > m_waterProps
Pointer to the water property calculator.
Definition: DebyeHuckel.h:1057
virtual void getActivities(doublereal *ac) const
Get the array of non-dimensional activities at the current solution temperature, pressure,...
vector_fp m_tmpV
vector of size m_kk, used as a temporary holding area.
Definition: DebyeHuckel.h:1060
vector_fp m_lnActCoeffMolal
Logarithm of the activity coefficients on the molality scale.
Definition: DebyeHuckel.h:1089
virtual double A_Debye_TP(double temperature=-1.0, double pressure=-1.0) const
Return the Debye Huckel constant as a function of temperature and pressure (Units = sqrt(kg/gmol))
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...
virtual void calcDensity()
Calculate the density of the mixture using the partial molar volumes and mole fractions as input.
virtual doublereal standardConcentration(size_t k=0) const
Return the standard concentration for the kth species.
virtual void setStateFromXML(const XML_Node &state)
Set equation of state parameter values from XML entries.
virtual bool addSpecies(shared_ptr< Species > spec)
doublereal m_Mnaught
This is the multiplication factor that goes inside log expressions involving the molalities of specie...
void calcMolalities() const
Calculates the molality of all species and stores the result internally.
vector_fp m_molalities
Current value of the molalities of the species in the phase.
Class for pressure dependent standard states that use a constant volume model.
Definition: PDSS_ConstVol.h:23
Class for the liquid water pressure dependent standard state.
Definition: PDSS_Water.h:50
virtual doublereal density() const
Return the standard state density at standard state.
Definition: PDSS_Water.cpp:217
virtual doublereal molarVolume() const
Return the molar volume at standard state.
Definition: PDSS.cpp:72
void assignDensity(const double density_)
Set the internally stored constant density (kg/m^3) of the phase.
Definition: Phase.cpp:698
doublereal mean_X(const doublereal *const Q) const
Evaluate the mole-fraction-weighted mean of an array Q.
Definition: Phase.cpp:717
vector_fp m_speciesCharge
Vector of species charges. length m_kk.
Definition: Phase.h:954
void checkSpeciesIndex(size_t k) const
Check that the specified species index is in range.
Definition: Phase.cpp:211
std::string name() const
Return the name of the phase.
Definition: Phase.cpp:70
size_t nSpecies() const
Returns the number of species in the phase.
Definition: Phase.h:273
doublereal charge(size_t k) const
Dimensionless electrical charge of a single molecule of species k The charge is normalized by the the...
Definition: Phase.h:630
size_t m_kk
Number of species in the phase.
Definition: Phase.h:943
std::string speciesName(size_t k) const
Name of the species with index k.
Definition: Phase.cpp:200
doublereal meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition: Phase.h:751
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition: Phase.cpp:548
doublereal temperature() const
Temperature (K).
Definition: Phase.h:654
const std::vector< std::string > & speciesNames() const
Return a const reference to the vector of species names.
Definition: Phase.cpp:206
size_t speciesIndex(const std::string &name) const
Returns the index of a species named 'name' within the Phase object.
Definition: Phase.cpp:187
shared_ptr< Species > species(const std::string &name) const
Return the Species object for the named species.
Definition: Phase.cpp:950
doublereal RT() const
Return the Gas Constant multiplied by the current temperature.
Definition: ThermoPhase.h:782
void initThermoFile(const std::string &inputFile, const std::string &id)
AnyMap m_input
Data supplied via setParameters.
Definition: ThermoPhase.h:1898
virtual void getParameters(int &n, doublereal *const c) const
Get the equation of state parameters in a vector.
virtual void _updateStandardStateThermo() const
Updates the standard state thermodynamic functions at the current T and P of the solution.
virtual void getStandardVolumes(doublereal *vol) const
Get the molar volumes of the species standard states at the current T and P of the solution.
virtual void getCp_R(doublereal *cpr) const
Get the nondimensional Heat Capacities at constant pressure for the species standard states at the cu...
virtual void getEntropy_R(doublereal *sr) const
Get the array of nondimensional Entropy functions for the standard state species at the current T and...
virtual doublereal pressure() const
Returns the current pressure of the phase.
virtual void getSpeciesParameters(const std::string &name, AnyMap &speciesNode) const
Get phase-specific parameters of a Species object such that an identical one could be reconstructed a...
virtual void getStandardChemPotentials(doublereal *mu) const
Get the array of chemical potentials at unit activity for the species at their standard states at the...
virtual void getEnthalpy_RT(doublereal *hrt) const
Get the nondimensional Enthalpy functions for the species at their standard states at the current T a...
The WaterProps class is used to house several approximation routines for properties of water.
Definition: WaterProps.h:100
Class XML_Node is a tree-based representation of the contents of an XML file.
Definition: xml.h:103
std::string attrib(const std::string &attr) const
Function returns the value of an attribute.
Definition: xml.cpp:493
bool hasChild(const std::string &ch) const
Tests whether the current node has a child node with a particular name.
Definition: xml.cpp:529
std::string id() const
Return the id attribute, if present.
Definition: xml.cpp:539
const XML_Node * findByName(const std::string &nm, int depth=100000) const
This routine carries out a recursive search for an XML node based on the name of the node.
Definition: xml.cpp:680
XML_Node & child(const size_t n) const
Return a changeable reference to the n'th child of the current node.
Definition: xml.cpp:547
bool hasAttrib(const std::string &a) const
Tests whether the current node has an attribute with a particular name.
Definition: xml.cpp:534
CTML ("Cantera Markup Language") is the variant of XML that Cantera uses to store data.
Header file for a common definitions used in electrolytes thermodynamics.
void importPhase(XML_Node &phase, ThermoPhase *th)
Import a phase information into an empty ThermoPhase object.
Namespace for the Cantera kernel.
Definition: AnyMap.h:29
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:192
doublereal getFloat(const XML_Node &parent, const std::string &name, const std::string &type="")
Get a floating-point value from a child element.
Definition: ctml.cpp:166
doublereal fpValue(const std::string &val)
Translate a string into one doublereal value.
bool caseInsensitiveEquals(const std::string &input, const std::string &test)
Case insensitive equality predicate.
const double SmallNumber
smallest number to compare to zero.
Definition: ct_defs.h:153
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition: ct_defs.h:113
void getMatrixValues(const XML_Node &node, const std::vector< std::string > &keyStringRow, const std::vector< std::string > &keyStringCol, Array2D &returnValues, const bool convert=true, const bool matrixSymmetric=false)
This function interprets the value portion of an XML element as a series of "Matrix ids and entries" ...
Definition: ctml.cpp:364
doublereal toSI(const std::string &unit)
Return the conversion factor to convert unit std::string 'unit' to SI units.
Definition: global.cpp:170
static int interp_est(const std::string &estString)
Utility function to assign an integer value from a string for the ElectrolyteSpeciesType field.
const int cEST_solvent
Electrolyte species type.
Definition: electrolytes.h:18
void getMap(const XML_Node &node, std::map< std::string, std::string > &m)
This routine is used to interpret the value portions of XML elements that contain colon separated pai...
Definition: ctml.cpp:332
Contains declarations for string manipulation functions within Cantera.