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