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