Cantera  2.0
IdealMolalSoln.cpp
Go to the documentation of this file.
1 /**
2  * @file IdealMolalSoln.cpp
3  * ThermoPhase object for the ideal molal equation of
4  * state (see \ref thermoprops
5  * and class \link Cantera::IdealMolalSoln IdealMolalSoln\endlink).
6  *
7  * Definition file for a derived class of ThermoPhase that handles
8  * variable pressure standard state methods for calculating
9  * thermodynamic properties that are further based upon
10  * activities on the molality scale. The Ideal molal
11  * solution assumes that all molality-based activity
12  * coefficients are equal to one. This turns out, actually, to be
13  * highly nonlinear when the solvent densities get low.
14  */
15 /*
16  * Copyright (2006) Sandia Corporation. Under the terms of
17  * Contract DE-AC04-94AL85000 with Sandia Corporation, the
18  * U.S. Government retains certain rights in this software.
19  */
22 
23 #include <cmath>
24 #include <fstream>
25 
26 using namespace ctml;
27 
28 namespace Cantera
29 {
30 
31 //! Small value to be used in cutoff expressions with logs
32 static double xxSmall = 1.0E-150;
33 
34 /*
35  * Default constructor
36  */
37 IdealMolalSoln::IdealMolalSoln() :
39  m_formGC(2),
40  IMS_typeCutoff_(0),
41  IMS_X_o_cutoff_(0.20),
42  IMS_gamma_o_min_(0.00001),
43  IMS_gamma_k_min_(10.0),
44  IMS_cCut_(.05),
45  IMS_slopefCut_(0.6),
46  IMS_dfCut_(0.0),
47  IMS_efCut_(0.0),
48  IMS_afCut_(0.0),
49  IMS_bfCut_(0.0),
50  IMS_slopegCut_(0.0),
51  IMS_dgCut_(0.0),
52  IMS_egCut_(0.0),
53  IMS_agCut_(0.0),
54  IMS_bgCut_(0.0)
55 {
56 }
57 
58 /*
59  * Copy Constructor:
60  *
61  * Note this stuff will not work until the underlying phase
62  * has a working copy constructor
63  */
66 {
67  /*
68  * Use the assignment operator to do the brunt
69  * of the work for the copy constructor.
70  */
71  *this = b;
72 }
73 
74 /*
75  * operator=()
76  *
77  * Note this stuff will not work until the underlying phase
78  * has a working assignment operator
79  */
82 {
83  if (&b != this) {
86  m_formGC = b.m_formGC;
91  IMS_cCut_ = b.IMS_cCut_;
103  m_pe = b.m_pe;
104  m_pp = b.m_pp;
105  m_tmpV = b.m_tmpV;
107  }
108  return *this;
109 }
110 
111 IdealMolalSoln::IdealMolalSoln(std::string inputFile, std::string id) :
112  MolalityVPSSTP(),
113  m_formGC(2),
114  IMS_typeCutoff_(0),
115  IMS_X_o_cutoff_(0.2),
116  IMS_gamma_o_min_(0.00001),
117  IMS_gamma_k_min_(10.0),
118  IMS_cCut_(.05),
119  IMS_slopefCut_(0.6),
120  IMS_dfCut_(0.0),
121  IMS_efCut_(0.0),
122  IMS_afCut_(0.0),
123  IMS_bfCut_(0.0),
124  IMS_slopegCut_(0.0),
125  IMS_dgCut_(0.0),
126  IMS_egCut_(0.0),
127  IMS_agCut_(0.0),
128  IMS_bgCut_(0.0)
129 {
130  constructPhaseFile(inputFile, id);
131 }
132 
133 IdealMolalSoln::IdealMolalSoln(XML_Node& root, std::string id) :
134  MolalityVPSSTP(),
135  m_formGC(2),
136  IMS_typeCutoff_(0),
137  IMS_X_o_cutoff_(0.2),
138  IMS_gamma_o_min_(0.00001),
139  IMS_gamma_k_min_(10.0),
140  IMS_cCut_(.05),
141  IMS_slopefCut_(0.6),
142  IMS_dfCut_(0.0),
143  IMS_efCut_(0.0),
144  IMS_afCut_(0.0),
145  IMS_bfCut_(0.0),
146  IMS_slopegCut_(0.0),
147  IMS_dgCut_(0.0),
148  IMS_egCut_(0.0),
149  IMS_agCut_(0.0),
150  IMS_bgCut_(0.0)
151 {
152  constructPhaseXML(root, id);
153 }
154 
155 /*
156  *
157  * ~IdealMolalSoln(): (virtual)
158  *
159  * Destructor: does nothing:
160  *
161  */
163 {
164 }
165 
166 /**
167  *
168  */
170 {
171  IdealMolalSoln* mtp = new IdealMolalSoln(*this);
172  return (ThermoPhase*) mtp;
173 }
174 
175 //
176 // -------- Molar Thermodynamic Properties of the Solution ---------------
177 //
178 /*
179  * Molar enthalpy of the solution: Units: J/kmol.
180  *
181  * Returns the amount of enthalpy per mole of solution.
182  * For an ideal molal solution,
183  * \f[
184  * \bar{h}(T, P, X_k) = \sum_k X_k \bar{h}_k(T)
185  * \f]
186  * The formula is written in terms of the partial molar enthalpies.
187  * \f$ \bar{h}_k(T, p, m_k) \f$.
188  * See the partial molar enthalpy function, getPartialMolarEnthalpies(),
189  * for details.
190  *
191  * Units: J/kmol
192  */
194 {
197  double val = mean_X(DATA_PTR(m_tmpV));
198  return val;
199 }
200 
201 /*
202  * Molar internal energy of the solution: Units: J/kmol.
203  *
204  * Returns the amount of internal energy per mole of solution.
205  * For an ideal molal solution,
206  * \f[
207  * \bar{u}(T, P, X_k) = \sum_k X_k \bar{u}_k(T)
208  * \f]
209  * The formula is written in terms of the partial molar internal energy.
210  * \f$ \bar{u}_k(T, p, m_k) \f$.
211  */
213 {
215  return mean_X(DATA_PTR(m_tmpV));
216 }
217 
218 /*
219  * Molar entropy of the solution: Units J/kmol/K.
220  *
221  * Returns the amount of entropy per mole of solution.
222  * For an ideal molal solution,
223  * \f[
224  * \bar{s}(T, P, X_k) = \sum_k X_k \bar{s}_k(T)
225  * \f]
226  * The formula is written in terms of the partial molar entropies.
227  * \f$ \bar{s}_k(T, p, m_k) \f$.
228  * See the partial molar entropies function, getPartialMolarEntropies(),
229  * for details.
230  *
231  * Units: J/kmol/K.
232  */
234 {
236  return mean_X(DATA_PTR(m_tmpV));
237 }
238 
239 /*
240  * Molar Gibbs function for the solution: Units J/kmol.
241  *
242  * Returns the gibbs free energy of the solution per mole
243  * of the solution.
244  *
245  * \f[
246  * \bar{g}(T, P, X_k) = \sum_k X_k \mu_k(T)
247  * \f]
248  *
249  * Units: J/kmol
250  */
251 doublereal IdealMolalSoln::gibbs_mole() const
252 {
254  return mean_X(DATA_PTR(m_tmpV));
255 }
256 
257 /*
258  * Molar heat capacity at constant pressure: Units: J/kmol/K.
259  * * \f[
260  * \bar{c}_p(T, P, X_k) = \sum_k X_k \bar{c}_{p,k}(T)
261  * \f]
262  *
263  * Units: J/kmol/K
264  */
265 doublereal IdealMolalSoln::cp_mole() const
266 {
268  double val = mean_X(DATA_PTR(m_tmpV));
269  return val;
270 }
271 
272 /*
273  * Molar heat capacity at constant volume: Units: J/kmol/K.
274  * NOT IMPLEMENTED.
275  * Units: J/kmol/K
276  */
277 doublereal IdealMolalSoln::cv_mole() const
278 {
279  return err("not implemented");
280 }
281 
282 //
283 // ------- Mechanical Equation of State Properties ------------------------
284 //
285 
286 
287 
288 /*
289  * Set the pressure at constant temperature. Units: Pa.
290  * This method sets a constant within the object.
291  * The mass density is not a function of pressure.
292  */
293 void IdealMolalSoln::setPressure(doublereal p)
294 {
295  setState_TP(temperature(), p);
296 }
297 
299 {
300  double* vbar = &m_pp[0];
302  double* x = &m_tmpV[0];
303  getMoleFractions(x);
304  doublereal vtotal = 0.0;
305  for (size_t i = 0; i < m_kk; i++) {
306  vtotal += vbar[i] * x[i];
307  }
308  doublereal dd = meanMolecularWeight() / vtotal;
309  Phase::setDensity(dd);
310 }
311 
312 /*
313  * The isothermal compressibility. Units: 1/Pa.
314  * The isothermal compressibility is defined as
315  * \f[
316  * \kappa_T = -\frac{1}{v}\left(\frac{\partial v}{\partial P}\right)_T
317  * \f]
318  *
319  * It's equal to zero for this model, since the molar volume
320  * doesn't change with pressure or temperature.
321  */
323 {
324  return 0.0;
325 }
326 
327 /*
328  * The thermal expansion coefficient. Units: 1/K.
329  * The thermal expansion coefficient is defined as
330  *
331  * \f[
332  * \beta = \frac{1}{v}\left(\frac{\partial v}{\partial T}\right)_P
333  * \f]
334  *
335  * It's equal to zero for this model, since the molar volume
336  * doesn't change with pressure or temperature.
337  */
339 {
340  return 0.0;
341 }
342 
343 /*
344  * Overwritten setDensity() function is necessary because the
345  * density is not an independent variable.
346  *
347  * This function will now throw an error condition
348  *
349  * @internal May have to adjust the strategy here to make
350  * the eos for these materials slightly compressible, in order
351  * to create a condition where the density is a function of
352  * the pressure.
353  *
354  * This function will now throw an error condition.
355  *
356  * NOTE: This is an overwritten function from the State.h
357  * class
358  */
359 void IdealMolalSoln::setDensity(const doublereal rho)
360 {
361  double dens = density();
362  if (rho != dens) {
363  throw CanteraError("Idea;MolalSoln::setDensity",
364  "Density is not an independent variable");
365  }
366 }
367 
368 /*
369  * Overwritten setMolarDensity() function is necessary because the
370  * density is not an independent variable.
371  *
372  * This function will now throw an error condition.
373  *
374  * NOTE: This is a virtual function, overwritten function from the State.h
375  * class
376  */
377 void IdealMolalSoln::setMolarDensity(const doublereal conc)
378 {
379  double concI = Phase::molarDensity();
380  if (conc != concI) {
381  throw CanteraError("IdealMolalSoln::setMolarDensity",
382  "molarDensity/denisty is not an independent variable");
383  }
384 }
385 
386 void IdealMolalSoln::setState_TP(doublereal temp, doublereal pres)
387 {
388  Phase::setTemperature(temp);
389  m_Pcurrent = pres;
391  //m_densWaterSS = m_waterSS->density();
392  calcDensity();
393 }
394 
395 //
396 // ------- Activities and Activity Concentrations
397 //
398 
399 /*
400  * This method returns an array of activity concentrations \f$ C^a_k\f$.
401  * \f$ C^a_k\f$ are defined such that
402  * \f$ a_k = C^a_k / C^s_k, \f$ where \f$ C^s_k \f$
403  * is a standard concentration
404  * defined below. These activity concentrations are used
405  * by kinetics manager classes to compute the forward and
406  * reverse rates of elementary reactions.
407  *
408  * @param c Array of activity concentrations. The
409  * units depend upon the implementation of the
410  * reaction rate expressions within the phase.
411  */
413 {
414  if (m_formGC != 1) {
415  double c_solvent = standardConcentration();
416  getActivities(c);
417  for (size_t k = 0; k < m_kk; k++) {
418  c[k] *= c_solvent;
419  }
420  } else {
421  getActivities(c);
422  for (size_t k = 0; k < m_kk; k++) {
423  double c0 = standardConcentration(k);
424  c[k] *= c0;
425  }
426  }
427 }
428 
429 /*
430  * The standard concentration \f$ C^s_k \f$ used to normalize
431  * the activity concentration. In many cases, this quantity
432  * will be the same for all species in a phase - for example,
433  * for an ideal gas \f$ C^s_k = P/\hat R T \f$. For this
434  * reason, this method returns a single value, instead of an
435  * array. However, for phases in which the standard
436  * concentration is species-specific (e.g. surface species of
437  * different sizes), this method may be called with an
438  * optional parameter indicating the species.
439  *
440  */
441 doublereal IdealMolalSoln::standardConcentration(size_t k) const
442 {
443  double c0 = 1.0, mvSolvent;
444  switch (m_formGC) {
445  case 0:
446  break;
447  case 1:
449  break;
450  case 2:
452  c0 = 1.0 / mvSolvent;
453  break;
454  }
455  return c0;
456 }
457 
458 /*
459  * Returns the natural logarithm of the standard
460  * concentration of the kth species
461  */
462 doublereal IdealMolalSoln::logStandardConc(size_t k) const
463 {
464  double c0 = standardConcentration(k);
465  return log(c0);
466 }
467 
468 /*
469  * Returns the units of the standard and general concentrations
470  * Note they have the same units, as their divisor is
471  * defined to be equal to the activity of the kth species
472  * in the solution, which is unitless.
473  *
474  * This routine is used in print out applications where the
475  * units are needed. Usually, MKS units are assumed throughout
476  * the program and in the XML input files.
477  *
478  * On return uA contains the powers of the units (MKS assumed)
479  * of the standard concentrations and generalized concentrations
480  * for the kth species.
481  *
482  * uA[0] = kmol units - default = 1
483  * uA[1] = m units - default = -nDim(), the number of spatial
484  * dimensions in the Phase class.
485  * uA[2] = kg units - default = 0;
486  * uA[3] = Pa(pressure) units - default = 0;
487  * uA[4] = Temperature units - default = 0;
488  * uA[5] = time units - default = 0
489  */
490 void IdealMolalSoln::getUnitsStandardConc(double* uA, int k, int sizeUA) const
491 {
492  int eos = eosType();
493  if (eos == 0) {
494  for (int i = 0; i < sizeUA; i++) {
495  uA[i] = 0.0;
496  }
497  } else {
498  for (int i = 0; i < sizeUA; i++) {
499  if (i == 0) {
500  uA[0] = 1.0;
501  }
502  if (i == 1) {
503  uA[1] = -int(nDim());
504  }
505  if (i == 2) {
506  uA[2] = 0.0;
507  }
508  if (i == 3) {
509  uA[3] = 0.0;
510  }
511  if (i == 4) {
512  uA[4] = 0.0;
513  }
514  if (i == 5) {
515  uA[5] = 0.0;
516  }
517  }
518  }
519 }
520 
521 /*
522  * Get the array of non-dimensional molality-based
523  * activities at the current solution temperature,
524  * pressure, and solution concentration.
525  *
526  * The max against xmolSolventMIN is to limit the activity
527  * coefficient to be finite as the solvent mf goes to zero.
528  */
529 void IdealMolalSoln::getActivities(doublereal* ac) const
530 {
532  /*
533  * Update the molality array, m_molalities()
534  * This requires an update due to mole fractions
535  */
536  if (IMS_typeCutoff_ == 0) {
537  calcMolalities();
538  for (size_t k = 0; k < m_kk; k++) {
539  ac[k] = m_molalities[k];
540  }
541  double xmolSolvent = moleFraction(m_indexSolvent);
542  xmolSolvent = std::max(m_xmolSolventMIN, xmolSolvent);
543  ac[m_indexSolvent] =
544  exp((xmolSolvent - 1.0)/xmolSolvent);
545  } else {
546 
548  /*
549  * Now calculate the array of activities.
550  */
551  for (size_t k = 1; k < m_kk; k++) {
552  ac[k] = m_molalities[k] * exp(IMS_lnActCoeffMolal_[k]);
553  }
554  double xmolSolvent = moleFraction(m_indexSolvent);
555  ac[m_indexSolvent] =
556  exp(IMS_lnActCoeffMolal_[m_indexSolvent]) * xmolSolvent;
557 
558  }
559 }
560 
561 /*
562  * Get the array of non-dimensional Molality based
563  * activity coefficients at
564  * the current solution temperature, pressure, and
565  * solution concentration.
566  * See Denbigh
567  * (note solvent activity coefficient is on the molar scale).
568  *
569  * The max against xmolSolventMIN is to limit the activity
570  * coefficient to be finite as the solvent mf goes to zero.
571  */
572 void IdealMolalSoln::
573 getMolalityActivityCoefficients(doublereal* acMolality) const
574 {
575  if (IMS_typeCutoff_ == 0) {
576  for (size_t k = 0; k < m_kk; k++) {
577  acMolality[k] = 1.0;
578  }
579  double xmolSolvent = moleFraction(m_indexSolvent);
580  xmolSolvent = std::max(m_xmolSolventMIN, xmolSolvent);
581  acMolality[m_indexSolvent] =
582  exp((xmolSolvent - 1.0)/xmolSolvent) / xmolSolvent;
583  } else {
585  std::copy(IMS_lnActCoeffMolal_.begin(), IMS_lnActCoeffMolal_.end(), acMolality);
586  for (size_t k = 0; k < m_kk; k++) {
587  acMolality[k] = exp(acMolality[k]);
588  }
589  }
590 }
591 
592 //
593 // ------ Partial Molar Properties of the Solution -----------------
594 //
595 
596 /*
597  * Get the species chemical potentials: Units: J/kmol.
598  *
599  * This function returns a vector of chemical potentials of the
600  * species in solution.
601  *
602  * \f[
603  * \mu_k = \mu^{o}_k(T,P) + R T \ln(\frac{m_k}{m^\Delta})
604  * \f]
605  * \f[
606  * \mu_w = \mu^{o}_w(T,P) +
607  * R T ((X_w - 1.0) / X_w)
608  * \f]
609  *
610  * \f$ w \f$ refers to the solvent species.
611  * \f$ X_w \f$ is the mole fraction of the solvent.
612  * \f$ m_k \f$ is the molality of the kth solute.
613  * \f$ m^\Delta is 1 gmol solute per kg solvent. \f$
614  *
615  * Units: J/kmol.
616  */
617 void IdealMolalSoln::getChemPotentials(doublereal* mu) const
618 {
619  double xx;
620  //const double xxSmall = 1.0E-150;
621 
622  // Assertion is made for speed
623  AssertThrow(m_indexSolvent == 0, "solvent not the first species");
624 
625  /*
626  * First get the standard chemical potentials
627  * -> this requires updates of standard state as a function
628  * of T and P
629  * These are defined at unit molality.
630  */
632  /*
633  * Update the molality array, m_molalities()
634  * This requires an update due to mole fractions
635  */
636  calcMolalities();
637  /*
638  * get the solvent mole fraction
639  */
640  double xmolSolvent = moleFraction(m_indexSolvent);
641  doublereal RT = GasConstant * temperature();
642 
643  if (IMS_typeCutoff_ == 0 || xmolSolvent > 3.* IMS_X_o_cutoff_/2.0) {
644 
645  for (size_t k = 1; k < m_kk; k++) {
646  xx = std::max(m_molalities[k], xxSmall);
647  mu[k] += RT * log(xx);
648  }
649  /*
650  * Do the solvent
651  * -> see my notes
652  */
653 
654  xx = std::max(xmolSolvent, xxSmall);
655  mu[m_indexSolvent] +=
656  (RT * (xmolSolvent - 1.0) / xx);
657  } else {
658  /*
659  * Update the activity coefficients
660  * This also updates the internal molality array.
661  */
663 
664 
665  for (size_t k = 1; k < m_kk; k++) {
666  xx = std::max(m_molalities[k], xxSmall);
667  mu[k] += RT * (log(xx) + IMS_lnActCoeffMolal_[k]);
668  }
669  xx = std::max(xmolSolvent, xxSmall);
670  mu[m_indexSolvent] +=
671  RT * (log(xx) + IMS_lnActCoeffMolal_[m_indexSolvent]);
672  }
673 
674 }
675 
676 /*
677  * Returns an array of partial molar enthalpies for the species
678  * in the mixture: Units (J/kmol).
679  *
680  */
681 void IdealMolalSoln::getPartialMolarEnthalpies(doublereal* hbar) const
682 {
683  getEnthalpy_RT(hbar);
684  doublereal RT = _RT();
685  for (size_t k = 0; k < m_kk; k++) {
686  hbar[k] *= RT;
687  }
688 }
689 
690 /*
691  * Returns an array of partial molar entropies of the species in the
692  * solution: Units: J/kmol.
693  *
694  * Maxwell's equations provide an insight in how to calculate this
695  * (p.215 Smith and Van Ness)
696  * \f[
697  * \frac{d(\mu_k)}{dT} = -\bar{s}_i
698  * \f]
699  * For this phase, the partial molar entropies are equal to the
700  * standard state species entropies plus the ideal molal solution contribution.
701  *
702  * \f[
703  * \bar{s}_k(T,P) = s^0_k(T) - R log( m_k )
704  * \f]
705  * \f[
706  * \bar{s}_w(T,P) = s^0_w(T) - R ((X_w - 1.0) / X_w)
707  * \f]
708  *
709  * The subscript, w, refers to the solvent species. \f$ X_w \f$ is
710  * the mole fraction of solvent.
711  * The reference-state pure-species entropies,\f$ s^0_k(T) \f$,
712  * at the reference pressure, \f$ P_{ref} \f$, are computed by the
713  * species thermodynamic
714  * property manager. They are polynomial functions of temperature.
715  * @see SpeciesThermo
716  */
717 void IdealMolalSoln::getPartialMolarEntropies(doublereal* sbar) const
718 {
719  getEntropy_R(sbar);
720  doublereal R = GasConstant;
721  doublereal mm;
722  calcMolalities();
723  if (IMS_typeCutoff_ == 0) {
724  for (size_t k = 0; k < m_kk; k++) {
725  if (k != m_indexSolvent) {
727  sbar[k] -= R * log(mm);
728  }
729  }
730  double xmolSolvent = moleFraction(m_indexSolvent);
731  sbar[m_indexSolvent] -= (R * (xmolSolvent - 1.0) / xmolSolvent);
732  } else {
733  /*
734  * Update the activity coefficients, This also update the
735  * internally stored molalities.
736  */
738  /*
739  * First we will add in the obvious dependence on the T
740  * term out front of the log activity term
741  */
742  doublereal mm;
743  for (size_t k = 0; k < m_kk; k++) {
744  if (k != m_indexSolvent) {
746  sbar[k] -= R * (log(mm) + IMS_lnActCoeffMolal_[k]);
747  }
748  }
749  double xmolSolvent = moleFraction(m_indexSolvent);
750  mm = std::max(SmallNumber, xmolSolvent);
751  sbar[m_indexSolvent] -= R *(log(mm) + IMS_lnActCoeffMolal_[m_indexSolvent]);
752 
753  }
754 }
755 
756 /*
757  * Returns an array of partial molar volumes of the species
758  * in the solution: Units: m^3 kmol-1.
759  *
760  * For this solution, the partial molar volumes are equal to the
761  * constant species molar volumes.
762  *
763  * Units: m^3 kmol-1.
764  */
765 void IdealMolalSoln::getPartialMolarVolumes(doublereal* vbar) const
766 {
767  getStandardVolumes(vbar);
768 }
769 
770 /*
771  * Partial molar heat capacity of the solution: Units: J/kmol/K.
772  *
773  * The kth partial molar heat capacity is equal to
774  * the temperature derivative of the partial molar
775  * enthalpy of the kth species in the solution at constant
776  * P and composition (p. 220 Smith and Van Ness).
777  * \f[
778  * \bar{Cp}_k(T,P) = {Cp}^0_k(T)
779  * \f]
780  *
781  * For this solution, this is equal to the reference state
782  * heat capacities.
783  *
784  * Units: J/kmol/K
785  */
786 void IdealMolalSoln::getPartialMolarCp(doublereal* cpbar) const
787 {
788  /*
789  * Get the nondimensional gibbs standard state of the
790  * species at the T and P of the solution.
791  */
792  getCp_R(cpbar);
793 
794  for (size_t k = 0; k < m_kk; k++) {
795  cpbar[k] *= GasConstant;
796  }
797 }
798 
799 /*
800  * -------- Properties of the Standard State of the Species
801  * in the Solution ------------------
802  */
803 
804 
805 
806 /*
807  * ------ Thermodynamic Values for the Species Reference States ---
808  */
809 
810 // -> This is handled by VPStandardStatesTP
811 
812 /*
813  * -------------- Utilities -------------------------------
814  */
815 
816 /*
817  * Initialization routine for an IdealMolalSoln phase.
818  *
819  * This is a virtual routine. This routine will call initThermo()
820  * for the parent class as well.
821  */
823 {
824  initLengths();
826 }
827 
828 /*
829  * Initialization of an IdealMolalSoln phase using an
830  * xml file
831  *
832  * This routine is a precursor to constructPhaseFile(XML_Node*)
833  * routine, which does most of the work.
834  *
835  * @param inputFile XML file containing the description of the
836  * phase
837  *
838  * @param id Optional parameter identifying the name of the
839  * phase. If none is given, the first XML
840  * phase element will be used.
841  */
842 void IdealMolalSoln::constructPhaseFile(std::string inputFile,
843  std::string id)
844 {
845 
846  if (inputFile.size() == 0) {
847  throw CanteraError("IdealMolalSoln::constructPhaseFile",
848  "input file is null");
849  }
850  std::string path = findInputFile(inputFile);
851  std::ifstream fin(path.c_str());
852  if (!fin) {
853  throw CanteraError("IdealMolalSoln::constructPhaseFile",
854  "could not open "
855  +path+" for reading.");
856  }
857  /*
858  * The phase object automatically constructs an XML object.
859  * Use this object to store information.
860  */
861  XML_Node& phaseNode_XML = xml();
862  XML_Node* fxml = new XML_Node();
863  fxml->build(fin);
864  XML_Node* fxml_phase = findXMLPhase(fxml, id);
865  if (!fxml_phase) {
866  throw CanteraError("IdealMolalSoln::constructPhaseFile",
867  "ERROR: Can not find phase named " +
868  id + " in file named " + inputFile);
869  }
870  fxml_phase->copy(&phaseNode_XML);
871  constructPhaseXML(*fxml_phase, id);
872  delete fxml;
873 }
874 
875 /*
876  * Import and initialize an IdealMolalSoln phase
877  * specification in an XML tree into the current object.
878  * Here we read an XML description of the phase.
879  * We import descriptions of the elements that make up the
880  * species in a phase.
881  * We import information about the species, including their
882  * reference state thermodynamic polynomials. We then freeze
883  * the state of the species.
884  *
885  * Then, we read the species molar volumes from the xml
886  * tree to finish the initialization.
887  *
888  * @param phaseNode This object must be the phase node of a
889  * complete XML tree
890  * description of the phase, including all of the
891  * species data. In other words while "phase" must
892  * point to an XML phase object, it must have
893  * sibling nodes "speciesData" that describe
894  * the species in the phase.
895  * @param id ID of the phase. If nonnull, a check is done
896  * to see if phaseNode is pointing to the phase
897  * with the correct id.
898  */
900  std::string id)
901 {
902  if (id.size() > 0) {
903  std::string idp = phaseNode.id();
904  if (idp != id) {
905  throw CanteraError("IdealMolalSoln::constructPhaseXML",
906  "phasenode and Id are incompatible");
907  }
908  }
909 
910  /*
911  * Find the Thermo XML node
912  */
913  if (!phaseNode.hasChild("thermo")) {
914  throw CanteraError("IdealMolalSoln::constructPhaseXML",
915  "no thermo XML node");
916  }
917 
918  /*
919  * Call the Cantera importPhase() function. This will import
920  * all of the species into the phase. Then, it will call
921  * initThermoXML() below.
922  */
923  bool m_ok = importPhase(phaseNode, this);
924  if (!m_ok) {
925  throw CanteraError("IdealMolalSoln::constructPhaseXML","importPhase failed ");
926  }
927 }
928 
929 /*
930  * Import and initialize an IdealMolalSoln phase
931  * specification in an XML tree into the current object.
932  *
933  * This routine is called from importPhase() to finish
934  * up the initialization of the thermo object. It reads in the
935  * species molar volumes.
936  *
937  * @param phaseNode This object must be the phase node of a
938  * complete XML tree
939  * description of the phase, including all of the
940  * species data. In other words while "phase" must
941  * point to an XML phase object, it must have
942  * sibling nodes "speciesData" that describe
943  * the species in the phase.
944  * @param id ID of the phase. If nonnull, a check is done
945  * to see if phaseNode is pointing to the phase
946  * with the correct id.
947  */
948 void IdealMolalSoln::initThermoXML(XML_Node& phaseNode, std::string id)
949 {
950 
951  /*
952  * Initialize the whole thermo object, using a virtual function.
953  */
954  initThermo();
955 
956  if (id.size() > 0) {
957  std::string idp = phaseNode.id();
958  if (idp != id) {
959  throw CanteraError("IdealMolalSoln::initThermo",
960  "phasenode and Id are incompatible");
961  }
962  }
963 
964  /*
965  * Find the Thermo XML node
966  */
967  if (!phaseNode.hasChild("thermo")) {
968  throw CanteraError("IdealMolalSoln::initThermo",
969  "no thermo XML node");
970  }
971  XML_Node& thermoNode = phaseNode.child("thermo");
972 
973  /*
974  * Possible change the form of the standard concentrations
975  */
976  if (thermoNode.hasChild("standardConc")) {
977  XML_Node& scNode = thermoNode.child("standardConc");
978  m_formGC = 2;
979  std::string formString = scNode.attrib("model");
980  if (formString != "") {
981  if (formString == "unity") {
982  m_formGC = 0;
983  } else if (formString == "molar_volume") {
984  m_formGC = 1;
985  } else if (formString == "solvent_volume") {
986  m_formGC = 2;
987  } else {
988  throw CanteraError("IdealMolalSoln::initThermo",
989  "Unknown standardConc model: " + formString);
990  }
991  }
992  }
993 
994  /*
995  * Get the Name of the Solvent:
996  * <solvent> solventName </solvent>
997  */
998  std::string solventName = "";
999  if (thermoNode.hasChild("solvent")) {
1000  XML_Node& scNode = thermoNode.child("solvent");
1001  std::vector<std::string> nameSolventa;
1002  getStringArray(scNode, nameSolventa);
1003  int nsp = static_cast<int>(nameSolventa.size());
1004  if (nsp != 1) {
1005  throw CanteraError("IdealMolalSoln::initThermoXML",
1006  "badly formed solvent XML node");
1007  }
1008  solventName = nameSolventa[0];
1009  }
1010 
1011  if (thermoNode.hasChild("activityCoefficients")) {
1012  XML_Node& acNode = thermoNode.child("activityCoefficients");
1013  std::string modelString = acNode.attrib("model");
1014  IMS_typeCutoff_ = 0;
1015  if (modelString != "IdealMolalSoln") {
1016  throw CanteraError("IdealMolalSoln::initThermoXML",
1017  "unknown ActivityCoefficient model: " + modelString);
1018  }
1019  if (acNode.hasChild("idealMolalSolnCutoff")) {
1020  XML_Node& ccNode = acNode.child("idealMolalSolnCutoff");
1021  modelString = ccNode.attrib("model");
1022  if (modelString != "") {
1023  if (modelString == "polyExp") {
1024  IMS_typeCutoff_ = 2;
1025  } else if (modelString == "poly") {
1026  IMS_typeCutoff_ = 1;
1027  } else {
1028  throw CanteraError("IdealMolalSoln::initThermoXML",
1029  "Unknown idealMolalSolnCutoff form: " + modelString);
1030  }
1031 
1032  if (ccNode.hasChild("gamma_o_limit")) {
1033  IMS_gamma_o_min_ = getFloat(ccNode, "gamma_o_limit");
1034  }
1035  if (ccNode.hasChild("gamma_k_limit")) {
1036  IMS_gamma_k_min_ = getFloat(ccNode, "gamma_k_limit");
1037  }
1038  if (ccNode.hasChild("X_o_cutoff")) {
1039  IMS_X_o_cutoff_ = getFloat(ccNode, "X_o_cutoff");
1040  }
1041  if (ccNode.hasChild("c_0_param")) {
1042  IMS_cCut_ = getFloat(ccNode, "c_0_param");
1043  }
1044  if (ccNode.hasChild("slope_f_limit")) {
1045  IMS_slopefCut_ = getFloat(ccNode, "slope_f_limit");
1046  }
1047  if (ccNode.hasChild("slope_g_limit")) {
1048  IMS_slopegCut_ = getFloat(ccNode, "slope_g_limit");
1049  }
1050 
1051  }
1052  }
1053  }
1054 
1055 
1056  /*
1057  * Reconcile the solvent name and index.
1058  */
1059  for (size_t k = 0; k < m_kk; k++) {
1060  std::string sname = speciesName(k);
1061  if (solventName == sname) {
1062  m_indexSolvent = k;
1063  break;
1064  }
1065  }
1066  if (m_indexSolvent == npos) {
1067  std::cout << "IdealMolalSoln::initThermo: Solvent Name not found"
1068  << std::endl;
1069  throw CanteraError("IdealMolalSoln::initThermo",
1070  "Solvent name not found");
1071  }
1072  if (m_indexSolvent != 0) {
1073  throw CanteraError("IdealMolalSoln::initThermo",
1074  "Solvent " + solventName +
1075  " should be first species");
1076  }
1077 
1078 
1079  /*
1080  * Now go get the molar volumes
1081  */
1082  XML_Node& speciesList = phaseNode.child("speciesArray");
1083  XML_Node* speciesDB =
1084  get_XML_NameID("speciesData", speciesList["datasrc"],
1085  &phaseNode.root());
1086  const std::vector<std::string> &sss = speciesNames();
1087 
1088  for (size_t k = 0; k < m_kk; k++) {
1089  XML_Node* s = speciesDB->findByAttr("name", sss[k]);
1090  XML_Node* ss = s->findByName("standardState");
1091  m_speciesMolarVolume[k] = getFloat(*ss, "molarVolume", "toSI");
1092  }
1093 
1094  IMS_typeCutoff_ = 2;
1095  if (IMS_typeCutoff_ == 2) {
1097  }
1098 
1099  MolalityVPSSTP::initThermoXML(phaseNode, id);
1100 
1101 
1102  setMoleFSolventMin(1.0E-5);
1103  /*
1104  * Set the state
1105  */
1106  if (phaseNode.hasChild("state")) {
1107  XML_Node& stateNode = phaseNode.child("state");
1108  setStateFromXML(stateNode);
1109  }
1110 
1111 }
1112 
1113 /*
1114  * @internal
1115  * Set equation of state parameters. The number and meaning of
1116  * these depends on the subclass.
1117  * @param n number of parameters
1118  * @param c array of \i n coefficients
1119  *
1120  */
1121 void IdealMolalSoln::setParameters(int n, doublereal* const c)
1122 {
1123 }
1124 
1125 void IdealMolalSoln::getParameters(int& n, doublereal* const c) const
1126 {
1127 }
1128 
1129 /*
1130  * Set equation of state parameter values from XML
1131  * entries. This method is called by function importPhase in
1132  * file importCTML.cpp when processing a phase definition in
1133  * an input file. It should be overloaded in subclasses to set
1134  * any parameters that are specific to that particular phase
1135  * model.
1136  *
1137  * @param eosdata An XML_Node object corresponding to
1138  * the "thermo" entry for this phase in the input file.
1139  *
1140  * HKM -> Right now, the parameters are set elsewhere (initThermo)
1141  * It just didn't seem to fit.
1142  */
1144 {
1145 }
1146 
1147 /*
1148  * ----------- Critical State Properties --------------------------
1149  */
1150 
1151 /*
1152  * ------------ Private and Restricted Functions ------------------
1153  */
1154 
1155 /*
1156  * Bail out of functions with an error exit if they are not
1157  * implemented.
1158  */
1159 doublereal IdealMolalSoln::err(std::string msg) const
1160 {
1161  throw CanteraError("IdealMolalSoln",
1162  "Unfinished func called: " + msg);
1163  return 0.0;
1164 }
1165 
1166 
1167 
1168 // This function will be called to update the internally stored
1169 // natural logarithm of the molality activity coefficients
1170 /*
1171  * Normally they are all one. However, sometimes they are not,
1172  * due to stability schemes
1173  *
1174  * gamma_k_molar = gamma_k_molal / Xmol_solvent
1175  *
1176  * gamma_o_molar = gamma_o_molal
1177  */
1179 {
1180  double tmp;
1181  /*
1182  * Calculate the molalities. Currently, the molalities
1183  * may not be current with respect to the contents of the
1184  * State objects' data.
1185  */
1186  calcMolalities();
1187 
1188  double xmolSolvent = moleFraction(m_indexSolvent);
1189  double xx = std::max(m_xmolSolventMIN, xmolSolvent);
1190 
1191  if (IMS_typeCutoff_ == 0) {
1192  for (size_t k = 1; k < m_kk; k++) {
1193  IMS_lnActCoeffMolal_[k]= 0.0;
1194  }
1195  IMS_lnActCoeffMolal_[m_indexSolvent] = - log(xx) + (xx - 1.0)/xx;
1196  return;
1197  } else if (IMS_typeCutoff_ == 1) {
1198  if (xmolSolvent > 3.0 * IMS_X_o_cutoff_/2.0) {
1199  for (size_t k = 1; k < m_kk; k++) {
1200  IMS_lnActCoeffMolal_[k]= 0.0;
1201  }
1202  IMS_lnActCoeffMolal_[m_indexSolvent] = - log(xx) + (xx - 1.0)/xx;
1203  return;
1204  } else if (xmolSolvent < IMS_X_o_cutoff_/2.0) {
1205  tmp = log(xx * IMS_gamma_k_min_);
1206  for (size_t k = 1; k < m_kk; k++) {
1207  IMS_lnActCoeffMolal_[k]= tmp;
1208  }
1210  return;
1211  } else {
1212 
1213  /*
1214  * If we are in the middle region, calculate the connecting polynomials
1215  */
1216  double xminus = xmolSolvent - IMS_X_o_cutoff_/2.0;
1217  double xminus2 = xminus * xminus;
1218  double xminus3 = xminus2 * xminus;
1219  double x_o_cut2 = IMS_X_o_cutoff_ * IMS_X_o_cutoff_;
1220  double x_o_cut3 = x_o_cut2 * IMS_X_o_cutoff_;
1221 
1222  double h2 = 3.5 * xminus2 / IMS_X_o_cutoff_ - 2.0 * xminus3 / x_o_cut2;
1223  double h2_prime = 7.0 * xminus / IMS_X_o_cutoff_ - 6.0 * xminus2 / x_o_cut2;
1224 
1225  double h1 = (1.0 - 3.0 * xminus2 / x_o_cut2 + 2.0 * xminus3/ x_o_cut3);
1226  double h1_prime = (- 6.0 * xminus / x_o_cut2 + 6.0 * xminus2/ x_o_cut3);
1227 
1228  double h1_g = h1 / IMS_gamma_o_min_;
1229  double h1_g_prime = h1_prime / IMS_gamma_o_min_;
1230 
1231  double alpha = 1.0 / (exp(1.0) * IMS_gamma_k_min_);
1232  double h1_f = h1 * alpha;
1233  double h1_f_prime = h1_prime * alpha;
1234 
1235  double f = h2 + h1_f;
1236  double f_prime = h2_prime + h1_f_prime;
1237 
1238  double g = h2 + h1_g;
1239  double g_prime = h2_prime + h1_g_prime;
1240 
1241  tmp = (xmolSolvent/ g * g_prime + (1.0-xmolSolvent) / f * f_prime);
1242  double lngammak = -1.0 - log(f) + tmp * xmolSolvent;
1243  double lngammao =-log(g) - tmp * (1.0-xmolSolvent);
1244 
1245  tmp = log(xmolSolvent) + lngammak;
1246  for (size_t k = 1; k < m_kk; k++) {
1247  IMS_lnActCoeffMolal_[k]= tmp;
1248  }
1250  }
1251  }
1252 
1253  // Exponentials - trial 2
1254  else if (IMS_typeCutoff_ == 2) {
1255  if (xmolSolvent > IMS_X_o_cutoff_) {
1256  for (size_t k = 1; k < m_kk; k++) {
1257  IMS_lnActCoeffMolal_[k]= 0.0;
1258  }
1259  IMS_lnActCoeffMolal_[m_indexSolvent] = - log(xx) + (xx - 1.0)/xx;
1260  return;
1261  } else {
1262 
1263  double xoverc = xmolSolvent/IMS_cCut_;
1264  double eterm = std::exp(-xoverc);
1265 
1266  double fptmp = IMS_bfCut_ - IMS_afCut_ / IMS_cCut_ - IMS_bfCut_*xoverc
1267  + 2.0*IMS_dfCut_*xmolSolvent - IMS_dfCut_*xmolSolvent*xoverc;
1268  double f_prime = 1.0 + eterm*fptmp;
1269  double f = xmolSolvent + IMS_efCut_ + eterm * (IMS_afCut_ + xmolSolvent * (IMS_bfCut_ + IMS_dfCut_*xmolSolvent));
1270 
1271  double gptmp = IMS_bgCut_ - IMS_agCut_ / IMS_cCut_ - IMS_bgCut_*xoverc
1272  + 2.0*IMS_dgCut_*xmolSolvent - IMS_dgCut_*xmolSolvent*xoverc;
1273  double g_prime = 1.0 + eterm*gptmp;
1274  double g = xmolSolvent + IMS_egCut_ + eterm * (IMS_agCut_ + xmolSolvent * (IMS_bgCut_ + IMS_dgCut_*xmolSolvent));
1275 
1276  tmp = (xmolSolvent / g * g_prime + (1.0 - xmolSolvent) / f * f_prime);
1277  double lngammak = -1.0 - log(f) + tmp * xmolSolvent;
1278  double lngammao =-log(g) - tmp * (1.0-xmolSolvent);
1279 
1280  tmp = log(xx) + lngammak;
1281  for (size_t k = 1; k < m_kk; k++) {
1282  IMS_lnActCoeffMolal_[k]= tmp;
1283  }
1285  }
1286  }
1287  return;
1288 }
1289 
1290 /*
1291  * This internal function adjusts the lengths of arrays.
1292  *
1293  * This function is not virtual nor is it inherited
1294  */
1296 {
1297  m_kk = nSpecies();
1298  /*
1299  * Obtain the limits of the temperature from the species
1300  * thermo handler's limits.
1301  */
1302  m_expg0_RT.resize(m_kk);
1303  m_pe.resize(m_kk, 0.0);
1304  m_pp.resize(m_kk);
1305  m_speciesMolarVolume.resize(m_kk);
1306  m_tmpV.resize(m_kk);
1307  IMS_lnActCoeffMolal_.resize(m_kk);
1308 }
1309 
1310 
1312 {
1313  IMS_afCut_ = 1.0 / (std::exp(1.0) * IMS_gamma_k_min_);
1314  IMS_efCut_ = 0.0;
1315  bool converged = false;
1316  double oldV = 0.0;
1317  int its;
1318  for (its = 0; its < 100 && !converged; its++) {
1319  oldV = IMS_efCut_;
1320  IMS_afCut_ = 1.0 / (std::exp(1.0) * IMS_gamma_k_min_) - IMS_efCut_;
1323  /
1326  double eterm = std::exp(-IMS_X_o_cutoff_/IMS_cCut_);
1327  IMS_efCut_ = - eterm * (tmp);
1328  if (fabs(IMS_efCut_ - oldV) < 1.0E-14) {
1329  converged = true;
1330  }
1331  }
1332  if (!converged) {
1333  throw CanteraError(" IdealMolalSoln::calcCutoffParams_()",
1334  " failed to converge on the f polynomial");
1335  }
1336  converged = false;
1337  double f_0 = IMS_afCut_ + IMS_efCut_;
1338  double f_prime_0 = 1.0 - IMS_afCut_ / IMS_cCut_ + IMS_bfCut_;
1339  IMS_egCut_ = 0.0;
1340  for (its = 0; its < 100 && !converged; its++) {
1341  oldV = IMS_egCut_;
1342  double lng_0 = -log(IMS_gamma_o_min_) - f_prime_0 / f_0;
1343  IMS_agCut_ = exp(lng_0) - IMS_egCut_;
1346  /
1349  double eterm = std::exp(-IMS_X_o_cutoff_/IMS_cCut_);
1350  IMS_egCut_ = - eterm * (tmp);
1351  if (fabs(IMS_egCut_ - oldV) < 1.0E-14) {
1352  converged = true;
1353  }
1354  }
1355  if (!converged) {
1356  throw CanteraError(" IdealMolalSoln::calcCutoffParams_()",
1357  " failed to converge on the g polynomial");
1358  }
1359 }
1360 
1361 }
1362