1 /**
2  *  @file IdealSolidSolnPhase.cpp Implementation file for an ideal solid
3  *      solution model with incompressible thermodynamics (see \ref
4  *      thermoprops and \link Cantera::IdealSolidSolnPhase
5  *      IdealSolidSolnPhase\endlink).
6  */
7 
8 // This file is part of Cantera. See License.txt in the top-level directory or
9 // at https://cantera.org/license.txt for license and copyright information.
10 
11 #include "cantera/thermo/IdealSolidSolnPhase.h"
12 #include "cantera/thermo/ThermoFactory.h"
13 #include "cantera/thermo/Species.h"
14 #include "cantera/base/stringUtils.h"
15 #include "cantera/base/ctml.h"
16 #include "cantera/base/utilities.h"
17 
18 using namespace std;
19 
20 namespace Cantera
21 {
22 
IdealSolidSolnPhase(int formGC)23 IdealSolidSolnPhase::IdealSolidSolnPhase(int formGC) :
24     m_formGC(formGC),
25     m_Pref(OneAtm),
26     m_Pcurrent(OneAtm)
27 {
28     // @TODO: After Cantera 2.6, this constructor can be deleted and the default
29     // construction option can be provided by adding "" as the default argument
30     // to the constructor from input file name and phase id.
31     if (formGC == -1) {
32         formGC = 0;
33     } else {
34         warn_deprecated("IdealSolidSolnPhase(int formGC)",
35             "The formGC constructor argument is deprecated and will be removed"
36             " after Cantera 2.6. Use the setStandardConcentrationModel"
37             " method instead.");
38     }
39     m_formGC = formGC;
40     if (formGC < 0 || formGC > 2) {
41         throw CanteraError("IdealSolidSolnPhase::IdealSolidSolnPhase",
42                            "Illegal value of formGC");
43     }
44 }
45 
IdealSolidSolnPhase(const std::string & inputFile,const std::string & id_,int formGC)46 IdealSolidSolnPhase::IdealSolidSolnPhase(const std::string& inputFile,
47         const std::string& id_, int formGC) :
48     m_formGC(formGC),
49     m_Pref(OneAtm),
50     m_Pcurrent(OneAtm)
51 {
52     if (formGC == -1) {
53         formGC = 0;
54     } else {
55         warn_deprecated("IdealSolidSolnPhase(string inputFile, string id_, int formGC)",
56             "The formGC constructor argument is deprecated and will be removed"
57             " after Cantera 2.6. Use the setStandardConcentrationModel"
58             " method instead.");
59     }
60     m_formGC = formGC;
61     if (formGC < 0 || formGC > 2) {
62         throw CanteraError("IdealSolidSolnPhase::IdealSolidSolnPhase",
63                            "Illegal value of formGC");
64     }
65     initThermoFile(inputFile, id_);
66 }
67 
IdealSolidSolnPhase(XML_Node & root,const std::string & id_,int formGC)68 IdealSolidSolnPhase::IdealSolidSolnPhase(XML_Node& root, const std::string& id_,
69         int formGC) :
70     m_formGC(formGC),
71     m_Pref(OneAtm),
72     m_Pcurrent(OneAtm)
73 {
74     if (formGC == -1) {
75         formGC = 0;
76     } else {
77         warn_deprecated("IdealSolidSolnPhase(XML_Node root, string id_, int formGC)",
78             "The formGC constructor argument is deprecated and will be removed"
79             " after Cantera 2.6. Use the setStandardConcentrationModel"
80             " method instead.");
81     }
82     m_formGC = formGC;
83     if (formGC < 0 || formGC > 2) {
84         throw CanteraError("IdealSolidSolnPhase::IdealSolidSolnPhase",
85                            "Illegal value of formGC");
86     }
87     importPhase(root, this);
88 }
89 
90 // Molar Thermodynamic Properties of the Solution
91 
enthalpy_mole() const92 doublereal IdealSolidSolnPhase::enthalpy_mole() const
93 {
94     doublereal htp = RT() * mean_X(enthalpy_RT_ref());
95     return htp + (pressure() - m_Pref)/molarDensity();
96 }
97 
entropy_mole() const98 doublereal IdealSolidSolnPhase::entropy_mole() const
99 {
100     return GasConstant * (mean_X(entropy_R_ref()) - sum_xlogx());
101 }
102 
gibbs_mole() const103 doublereal IdealSolidSolnPhase::gibbs_mole() const
104 {
105     return RT() * (mean_X(gibbs_RT_ref()) + sum_xlogx());
106 }
107 
cp_mole() const108 doublereal IdealSolidSolnPhase::cp_mole() const
109 {
110     return GasConstant * mean_X(cp_R_ref());
111 }
112 
113 // Mechanical Equation of State
114 
calcDensity()115 void IdealSolidSolnPhase::calcDensity()
116 {
117     // Calculate the molarVolume of the solution (m**3 kmol-1)
118     const doublereal* const dtmp = moleFractdivMMW();
119     double invDens = dot(m_speciesMolarVolume.begin(),
120                          m_speciesMolarVolume.end(), dtmp);
121 
122     // Set the density in the parent State object directly, by calling the
123     // Phase::assignDensity() function.
124     Phase::assignDensity(1.0/invDens);
125 }
126 
setPressure(doublereal p)127 void IdealSolidSolnPhase::setPressure(doublereal p)
128 {
129     m_Pcurrent = p;
130     calcDensity();
131 }
132 
compositionChanged()133 void IdealSolidSolnPhase::compositionChanged()
134 {
135     Phase::compositionChanged();
136     calcDensity();
137 }
138 
139 // Chemical Potentials and Activities
140 
standardConcentrationUnits() const141 Units IdealSolidSolnPhase::standardConcentrationUnits() const
142 {
143     if (m_formGC == 0) {
144         return Units(1.0); // dimensionless
145     } else {
146         // kmol/m^3 for bulk phases
147         return Units(1.0, 0, -static_cast<double>(nDim()), 0, 0, 0, 1);
148     }
149 }
150 
getActivityConcentrations(doublereal * c) const151 void IdealSolidSolnPhase::getActivityConcentrations(doublereal* c) const
152 {
153     const doublereal* const dtmp = moleFractdivMMW();
154     const double mmw = meanMolecularWeight();
155     switch (m_formGC) {
156     case 0:
157         for (size_t k = 0; k < m_kk; k++) {
158             c[k] = dtmp[k] * mmw;
159         }
160         break;
161     case 1:
162         for (size_t k = 0; k < m_kk; k++) {
163             c[k] = dtmp[k] * mmw / m_speciesMolarVolume[k];
164         }
165         break;
166     case 2:
167         double atmp = mmw / m_speciesMolarVolume[m_kk-1];
168         for (size_t k = 0; k < m_kk; k++) {
169             c[k] = dtmp[k] * atmp;
170         }
171         break;
172     }
173 }
174 
standardConcentration(size_t k) const175 doublereal IdealSolidSolnPhase::standardConcentration(size_t k) const
176 {
177     switch (m_formGC) {
178     case 0:
179         return 1.0;
180     case 1:
181         return 1.0 / m_speciesMolarVolume[k];
182     case 2:
183         return 1.0/m_speciesMolarVolume[m_kk-1];
184     }
185     return 0.0;
186 }
187 
getActivityCoefficients(doublereal * ac) const188 void IdealSolidSolnPhase::getActivityCoefficients(doublereal* ac) const
189 {
190     for (size_t k = 0; k < m_kk; k++) {
191         ac[k] = 1.0;
192     }
193 }
194 
getChemPotentials(doublereal * mu) const195 void IdealSolidSolnPhase::getChemPotentials(doublereal* mu) const
196 {
197     double delta_p = m_Pcurrent - m_Pref;
198     const vector_fp& g_RT = gibbs_RT_ref();
199     for (size_t k = 0; k < m_kk; k++) {
200         double xx = std::max(SmallNumber, moleFraction(k));
201         mu[k] = RT() * (g_RT[k] + log(xx))
202                 + delta_p * m_speciesMolarVolume[k];
203     }
204 }
205 
getChemPotentials_RT(doublereal * mu) const206 void IdealSolidSolnPhase::getChemPotentials_RT(doublereal* mu) const
207 {
208     double delta_pdRT = (m_Pcurrent - m_Pref) / (temperature() * GasConstant);
209     const vector_fp& g_RT = gibbs_RT_ref();
210     for (size_t k = 0; k < m_kk; k++) {
211         double xx = std::max(SmallNumber, moleFraction(k));
212         mu[k] = (g_RT[k] + log(xx))
213                 + delta_pdRT * m_speciesMolarVolume[k];
214     }
215 }
216 
217 // Partial Molar Properties
218 
getPartialMolarEnthalpies(doublereal * hbar) const219 void IdealSolidSolnPhase::getPartialMolarEnthalpies(doublereal* hbar) const
220 {
221     const vector_fp& _h = enthalpy_RT_ref();
222     double delta_p = m_Pcurrent - m_Pref;
223     for (size_t k = 0; k < m_kk; k++) {
224         hbar[k] = _h[k]*RT() + delta_p * m_speciesMolarVolume[k];
225     }
226     // scale(_h.begin(), _h.end(), hbar, RT());
227 }
228 
getPartialMolarEntropies(doublereal * sbar) const229 void IdealSolidSolnPhase::getPartialMolarEntropies(doublereal* sbar) const
230 {
231     const vector_fp& _s = entropy_R_ref();
232     for (size_t k = 0; k < m_kk; k++) {
233         double xx = std::max(SmallNumber, moleFraction(k));
234         sbar[k] = GasConstant * (_s[k] - log(xx));
235     }
236 }
237 
getPartialMolarCp(doublereal * cpbar) const238 void IdealSolidSolnPhase::getPartialMolarCp(doublereal* cpbar) const
239 {
240     getCp_R(cpbar);
241     for (size_t k = 0; k < m_kk; k++) {
242         cpbar[k] *= GasConstant;
243     }
244 }
245 
getPartialMolarVolumes(doublereal * vbar) const246 void IdealSolidSolnPhase::getPartialMolarVolumes(doublereal* vbar) const
247 {
248     getStandardVolumes(vbar);
249 }
250 
251 // Properties of the Standard State of the Species in the Solution
252 
getPureGibbs(doublereal * gpure) const253 void IdealSolidSolnPhase::getPureGibbs(doublereal* gpure) const
254 {
255     const vector_fp& gibbsrt = gibbs_RT_ref();
256     double delta_p = (m_Pcurrent - m_Pref);
257     for (size_t k = 0; k < m_kk; k++) {
258         gpure[k] = RT() * gibbsrt[k] + delta_p * m_speciesMolarVolume[k];
259     }
260 }
261 
getGibbs_RT(doublereal * grt) const262 void IdealSolidSolnPhase::getGibbs_RT(doublereal* grt) const
263 {
264     const vector_fp& gibbsrt = gibbs_RT_ref();
265     doublereal delta_prt = (m_Pcurrent - m_Pref)/ RT();
266     for (size_t k = 0; k < m_kk; k++) {
267         grt[k] = gibbsrt[k] + delta_prt * m_speciesMolarVolume[k];
268     }
269 }
270 
getEnthalpy_RT(doublereal * hrt) const271 void IdealSolidSolnPhase::getEnthalpy_RT(doublereal* hrt) const
272 {
273     const vector_fp& _h = enthalpy_RT_ref();
274     doublereal delta_prt = (m_Pcurrent - m_Pref) / RT();
275     for (size_t k = 0; k < m_kk; k++) {
276         hrt[k] = _h[k] + delta_prt * m_speciesMolarVolume[k];
277     }
278 }
279 
getEntropy_R(doublereal * sr) const280 void IdealSolidSolnPhase::getEntropy_R(doublereal* sr) const
281 {
282     const vector_fp& _s = entropy_R_ref();
283     copy(_s.begin(), _s.end(), sr);
284 }
285 
getIntEnergy_RT(doublereal * urt) const286 void IdealSolidSolnPhase::getIntEnergy_RT(doublereal* urt) const
287 {
288     const vector_fp& _h = enthalpy_RT_ref();
289     doublereal prefrt = m_Pref / RT();
290     for (size_t k = 0; k < m_kk; k++) {
291         urt[k] = _h[k] - prefrt * m_speciesMolarVolume[k];
292     }
293 }
294 
getCp_R(doublereal * cpr) const295 void IdealSolidSolnPhase::getCp_R(doublereal* cpr) const
296 {
297     const vector_fp& _cpr = cp_R_ref();
298     copy(_cpr.begin(), _cpr.end(), cpr);
299 }
300 
getStandardVolumes(doublereal * vol) const301 void IdealSolidSolnPhase::getStandardVolumes(doublereal* vol) const
302 {
303     copy(m_speciesMolarVolume.begin(), m_speciesMolarVolume.end(), vol);
304 }
305 
306 // Thermodynamic Values for the Species Reference States
307 
getEnthalpy_RT_ref(doublereal * hrt) const308 void IdealSolidSolnPhase::getEnthalpy_RT_ref(doublereal* hrt) const
309 {
310     _updateThermo();
311     for (size_t k = 0; k != m_kk; k++) {
312         hrt[k] = m_h0_RT[k];
313     }
314 }
315 
getGibbs_RT_ref(doublereal * grt) const316 void IdealSolidSolnPhase::getGibbs_RT_ref(doublereal* grt) const
317 {
318     _updateThermo();
319     for (size_t k = 0; k != m_kk; k++) {
320         grt[k] = m_g0_RT[k];
321     }
322 }
323 
getGibbs_ref(doublereal * g) const324 void IdealSolidSolnPhase::getGibbs_ref(doublereal* g) const
325 {
326     _updateThermo();
327     double tmp = RT();
328     for (size_t k = 0; k != m_kk; k++) {
329         g[k] = tmp * m_g0_RT[k];
330     }
331 }
332 
getIntEnergy_RT_ref(doublereal * urt) const333 void IdealSolidSolnPhase::getIntEnergy_RT_ref(doublereal* urt) const
334 {
335     const vector_fp& _h = enthalpy_RT_ref();
336     doublereal prefrt = m_Pref / RT();
337     for (size_t k = 0; k < m_kk; k++) {
338         urt[k] = _h[k] - prefrt * m_speciesMolarVolume[k];
339     }
340 }
341 
getEntropy_R_ref(doublereal * er) const342 void IdealSolidSolnPhase::getEntropy_R_ref(doublereal* er) const
343 {
344     _updateThermo();
345     for (size_t k = 0; k != m_kk; k++) {
346         er[k] = m_s0_R[k];
347     }
348 }
349 
getCp_R_ref(doublereal * cpr) const350 void IdealSolidSolnPhase::getCp_R_ref(doublereal* cpr) const
351 {
352     _updateThermo();
353     for (size_t k = 0; k != m_kk; k++) {
354         cpr[k] = m_cp0_R[k];
355     }
356 }
357 
enthalpy_RT_ref() const358 const vector_fp& IdealSolidSolnPhase::enthalpy_RT_ref() const
359 {
360     _updateThermo();
361     return m_h0_RT;
362 }
363 
entropy_R_ref() const364 const vector_fp& IdealSolidSolnPhase::entropy_R_ref() const
365 {
366     _updateThermo();
367     return m_s0_R;
368 }
369 
370 // Utility Functions
371 
addSpecies(shared_ptr<Species> spec)372 bool IdealSolidSolnPhase::addSpecies(shared_ptr<Species> spec)
373 {
374     bool added = ThermoPhase::addSpecies(spec);
375     if (added) {
376         if (m_kk == 1) {
377             // Obtain the reference pressure by calling the ThermoPhase function
378             // refPressure, which in turn calls the species thermo reference
379             // pressure function of the same name.
380             m_Pref = refPressure();
381         }
382 
383         m_h0_RT.push_back(0.0);
384         m_g0_RT.push_back(0.0);
385         m_expg0_RT.push_back(0.0);
386         m_cp0_R.push_back(0.0);
387         m_s0_R.push_back(0.0);
388         m_pp.push_back(0.0);
389         if (spec->input.hasKey("equation-of-state")) {
390             auto& eos = spec->input["equation-of-state"].getMapWhere("model", "constant-volume");
391             double mv;
392             if (eos.hasKey("density")) {
393                 mv = molecularWeight(m_kk-1) / eos.convert("density", "kg/m^3");
394             } else if (eos.hasKey("molar-density")) {
395                 mv = 1.0 / eos.convert("molar-density", "kmol/m^3");
396             } else if (eos.hasKey("molar-volume")) {
397                 mv = eos.convert("molar-volume", "m^3/kmol");
398             } else {
399                 throw CanteraError("IdealSolidSolnPhase::addSpecies",
400                     "equation-of-state entry for species '{}' is missing "
401                     "'density', 'molar-volume', or 'molar-density' "
402                     "specification", spec->name);
403             }
404             m_speciesMolarVolume.push_back(mv);
405         } else if (spec->input.hasKey("molar_volume")) {
406             // @Deprecated - remove this case for Cantera 3.0 with removal of the XML format
407             m_speciesMolarVolume.push_back(spec->input["molar_volume"].asDouble());
408         } else {
409             throw CanteraError("IdealSolidSolnPhase::addSpecies",
410                 "Molar volume not specified for species '{}'", spec->name);
411         }
412         calcDensity();
413     }
414     return added;
415 }
416 
initThermo()417 void IdealSolidSolnPhase::initThermo()
418 {
419    if (m_input.hasKey("standard-concentration-basis")) {
420         setStandardConcentrationModel(m_input["standard-concentration-basis"].asString());
421     }
422     ThermoPhase::initThermo();
423 }
424 
getParameters(AnyMap & phaseNode) const425 void IdealSolidSolnPhase::getParameters(AnyMap& phaseNode) const
426 {
427     ThermoPhase::getParameters(phaseNode);
428     if (m_formGC == 1) {
429         phaseNode["standard-concentration-basis"] = "species-molar-volume";
430     } else if (m_formGC == 2) {
431         phaseNode["standard-concentration-basis"] = "solvent-molar-volume";
432     }
433 }
434 
getSpeciesParameters(const std::string & name,AnyMap & speciesNode) const435 void IdealSolidSolnPhase::getSpeciesParameters(const std::string &name,
436                                                AnyMap& speciesNode) const
437 {
438     ThermoPhase::getSpeciesParameters(name, speciesNode);
439     size_t k = speciesIndex(name);
440     const auto S = species(k);
441     auto& eosNode = speciesNode["equation-of-state"].getMapWhere(
442         "model", "constant-volume", true);
443     // Output volume information in a form consistent with the input
444     if (S->input.hasKey("equation-of-state")) {
445         auto& eosIn = S->input["equation-of-state"];
446         if (eosIn.hasKey("density")) {
447             eosNode["density"].setQuantity(
448                 molecularWeight(k) / m_speciesMolarVolume[k], "kg/m^3");
449         } else if (eosIn.hasKey("molar-density")) {
450             eosNode["molar-density"].setQuantity(1.0 / m_speciesMolarVolume[k],
451                                                  "kmol/m^3");
452         } else {
453             eosNode["molar-volume"].setQuantity(m_speciesMolarVolume[k],
454                                                 "m^3/kmol");
455         }
456     } else {
457         eosNode["molar-volume"].setQuantity(m_speciesMolarVolume[k],
458                                             "m^3/kmol");
459     }
460 }
461 
initThermoXML(XML_Node & phaseNode,const std::string & id_)462 void IdealSolidSolnPhase::initThermoXML(XML_Node& phaseNode, const std::string& id_)
463 {
464     if (id_.size() > 0 && phaseNode.id() != id_) {
465         throw CanteraError("IdealSolidSolnPhase::initThermoXML",
466                            "phasenode and Id are incompatible");
467     }
468 
469     // Check on the thermo field. Must have:
470     // <thermo model="IdealSolidSolution" />
471     if (phaseNode.hasChild("thermo")) {
472         XML_Node& thNode = phaseNode.child("thermo");
473         if (!caseInsensitiveEquals(thNode["model"], "idealsolidsolution")) {
474             throw CanteraError("IdealSolidSolnPhase::initThermoXML",
475                                "Unknown thermo model: " + thNode["model"]);
476         }
477     } else {
478         throw CanteraError("IdealSolidSolnPhase::initThermoXML",
479                            "Unspecified thermo model");
480     }
481 
482     // Form of the standard concentrations. Must have one of:
483     //
484     //     <standardConc model="unity" />
485     //     <standardConc model="molar_volume" />
486     //     <standardConc model="solvent_volume" />
487     if (phaseNode.hasChild("standardConc")) {
488         setStandardConcentrationModel(phaseNode.child("standardConc")["model"]);
489     } else {
490         throw CanteraError("IdealSolidSolnPhase::initThermoXML",
491                            "Unspecified standardConc model");
492     }
493 
494     // Call the base initThermo, which handles setting the initial state.
495     ThermoPhase::initThermoXML(phaseNode, id_);
496 }
497 
setToEquilState(const doublereal * mu_RT)498 void IdealSolidSolnPhase::setToEquilState(const doublereal* mu_RT)
499 {
500     const vector_fp& grt = gibbs_RT_ref();
501 
502     // Within the method, we protect against inf results if the exponent is too
503     // high.
504     //
505     // If it is too low, we set the partial pressure to zero. This capability is
506     // needed by the elemental potential method.
507     doublereal pres = 0.0;
508     double m_p0 = refPressure();
509     for (size_t k = 0; k < m_kk; k++) {
510         double tmp = -grt[k] + mu_RT[k];
511         if (tmp < -600.) {
512             m_pp[k] = 0.0;
513         } else if (tmp > 500.0) {
514             // Protect against inf results if the exponent is too high
515             double tmp2 = tmp / 500.;
516             tmp2 *= tmp2;
517             m_pp[k] = m_p0 * exp(500.) * tmp2;
518         } else {
519             m_pp[k] = m_p0 * exp(tmp);
520         }
521         pres += m_pp[k];
522     }
523     // set state
524     setState_PX(pres, m_pp.data());
525 }
526 
setStandardConcentrationModel(const std::string & model)527 void IdealSolidSolnPhase::setStandardConcentrationModel(const std::string& model)
528 {
529     if (caseInsensitiveEquals(model, "unity")) {
530         m_formGC = 0;
531     } else if (caseInsensitiveEquals(model, "species-molar-volume")
532                || caseInsensitiveEquals(model, "molar_volume")) {
533         m_formGC = 1;
534     } else if (caseInsensitiveEquals(model, "solvent-molar-volume")
535                || caseInsensitiveEquals(model, "solvent_volume")) {
536         m_formGC = 2;
537     } else {
538         throw CanteraError("IdealSolidSolnPhase::setStandardConcentrationModel",
539                            "Unknown standard concentration model '{}'", model);
540     }
541 }
542 
speciesMolarVolume(int k) const543 double IdealSolidSolnPhase::speciesMolarVolume(int k) const
544 {
545     return m_speciesMolarVolume[k];
546 }
547 
getSpeciesMolarVolumes(doublereal * smv) const548 void IdealSolidSolnPhase::getSpeciesMolarVolumes(doublereal* smv) const
549 {
550     copy(m_speciesMolarVolume.begin(), m_speciesMolarVolume.end(), smv);
551 }
552 
_updateThermo() const553 void IdealSolidSolnPhase::_updateThermo() const
554 {
555     doublereal tnow = temperature();
556     if (m_tlast != tnow) {
557 
558         // Update the thermodynamic functions of the reference state.
559         m_spthermo.update(tnow, m_cp0_R.data(), m_h0_RT.data(), m_s0_R.data());
560         m_tlast = tnow;
561         for (size_t k = 0; k < m_kk; k++) {
562             m_g0_RT[k] = m_h0_RT[k] - m_s0_R[k];
563         }
564         m_tlast = tnow;
565     }
566 }
567 
568 } // end namespace Cantera
569