Cantera  4.0.0a1
Loading...
Searching...
No Matches
ThermoPhase.cpp
Go to the documentation of this file.
1/**
2 * @file ThermoPhase.cpp
3 * Definition file for class ThermoPhase, the base class for phases with
4 * thermodynamic properties
5 * (see class @link Cantera::ThermoPhase ThermoPhase@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
18
19#include <iomanip>
20#include <fstream>
21#include <numeric>
22
23using namespace std;
24
25namespace Cantera
26{
27
28shared_ptr<ThermoPhase> ThermoPhase::clone() const
29{
30 AnyMap phase = parameters();
32 vector<AnyMap> speciesDefs;
33 speciesDefs.reserve(nSpecies());
34 for (const auto& name : speciesNames()) {
35 speciesDefs.emplace_back(species(name)->parameters(this));
36 }
37 root["species"] = std::move(speciesDefs);
38 phase.applyUnits();
39 root.applyUnits();
40 return newThermo(phase, root);
41}
42
43namespace {
44
45struct PhaseEquilGuard
46{
47 explicit PhaseEquilGuard(ThermoPhase& phase) : m_phase(phase)
48 {
49 m_phase.beginEquilibrate();
50 }
51
52 ~PhaseEquilGuard()
53 {
54 m_phase.endEquilibrate();
55 }
56
57private:
58 ThermoPhase& m_phase;
59};
60
61} // namespace
62
64 if (k != npos) {
66 } else {
67 for (size_t k = 0; k < nSpecies(); k++) {
69 }
70 }
72}
73
75{
77}
78
80{
81 return m_ssConvention;
82}
83
85{
86 // kmol/m^3 for bulk phases, kmol/m^2 for surface phases, etc.
87 return Units(1.0, 0, -static_cast<double>(nDim()), 0, 0, 0, 1);
88}
89
90double ThermoPhase::logStandardConc(size_t k) const
91{
92 return log(standardConcentration(k));
93}
94
95void ThermoPhase::getActivities(span<double> a) const
96{
98 for (size_t k = 0; k < nSpecies(); k++) {
99 a[k] /= standardConcentration(k);
100 }
101}
102
103void ThermoPhase::getLnActivityCoefficients(span<double> lnac) const
104{
106 for (size_t k = 0; k < m_kk; k++) {
107 lnac[k] = std::log(lnac[k]);
108 }
109}
110
111void ThermoPhase::getElectrochemPotentials(span<double> mu) const
112{
114 double ve = Faraday * electricPotential();
115 for (size_t k = 0; k < m_kk; k++) {
116 mu[k] += ve*charge(k);
117 }
118}
119
121{
122 vector<double> vbar(m_kk);
125 double pi_t = internalPressure();
126 for (size_t k = 0; k < m_kk; k++) {
127 ubar[k] -= pi_t * vbar[k];
128 }
129}
130
131void ThermoPhase::setState_TPX(double t, double p, span<const double> x)
132{
134 setState_TP(t,p);
135}
136
137void ThermoPhase::setState_TPX(double t, double p, const Composition& x)
138{
140 setState_TP(t,p);
141}
142
143void ThermoPhase::setState_TPX(double t, double p, const string& x)
144{
146 setState_TP(t,p);
147}
148
149void ThermoPhase::setState_TPY(double t, double p, span<const double> y)
150{
152 setState_TP(t,p);
153}
154
155void ThermoPhase::setState_TPY(double t, double p, const Composition& y)
156{
158 setState_TP(t,p);
159}
160
161void ThermoPhase::setState_TPY(double t, double p, const string& y)
162{
164 setState_TP(t,p);
165}
166
167void ThermoPhase::setState_TP(double t, double p)
168{
169 vector<double> state(partialStateSize());
170 savePartialState(state);
171 try {
173 setPressure(p);
174 } catch (std::exception&) {
175 restorePartialState(state);
176 throw;
177 }
178}
179
180void ThermoPhase::setState_HP(double Htarget, double p, double rtol)
181{
182 vector<double> state(partialStateSize());
183 savePartialState(state);
184 try {
185 setState_HPorUV(Htarget, p, rtol, false);
186 } catch (std::exception&) {
187 restorePartialState(state);
188 throw;
189 }
190}
191
192void ThermoPhase::setState_UV(double u, double v, double rtol)
193{
194 assertCompressible("setState_UV");
195 vector<double> state(partialStateSize());
196 savePartialState(state);
197 try {
198 setState_HPorUV(u, v, rtol, true);
199 } catch (std::exception&) {
200 restorePartialState(state);
201 throw;
202 }
203}
204
205void ThermoPhase::setState(const AnyMap& input_state)
206{
207 AnyMap state = input_state;
208
209 // Remap allowable synonyms
210 if (state.hasKey("mass-fractions")) {
211 state["Y"] = state["mass-fractions"];
212 state.erase("mass-fractions");
213 }
214 if (state.hasKey("mole-fractions")) {
215 state["X"] = state["mole-fractions"];
216 state.erase("mole-fractions");
217 }
218 if (state.hasKey("temperature")) {
219 state["T"] = state["temperature"];
220 }
221 if (state.hasKey("pressure")) {
222 state["P"] = state["pressure"];
223 }
224 if (state.hasKey("enthalpy")) {
225 state["H"] = state["enthalpy"];
226 }
227 if (state.hasKey("int-energy")) {
228 state["U"] = state["int-energy"];
229 }
230 if (state.hasKey("internal-energy")) {
231 state["U"] = state["internal-energy"];
232 }
233 if (state.hasKey("specific-volume")) {
234 state["V"] = state["specific-volume"];
235 }
236 if (state.hasKey("entropy")) {
237 state["S"] = state["entropy"];
238 }
239 if (state.hasKey("density")) {
240 state["D"] = state["density"];
241 }
242 if (state.hasKey("vapor-fraction")) {
243 state["Q"] = state["vapor-fraction"];
244 }
245
246 // Set composition
247 if (state.hasKey("X")) {
248 if (state["X"].is<string>()) {
249 setMoleFractionsByName(state["X"].asString());
250 } else {
251 setMoleFractionsByName(state["X"].asMap<double>());
252 }
253 state.erase("X");
254 } else if (state.hasKey("Y")) {
255 if (state["Y"].is<string>()) {
256 setMassFractionsByName(state["Y"].asString());
257 } else {
258 setMassFractionsByName(state["Y"].asMap<double>());
259 }
260 state.erase("Y");
261 }
262 // set thermodynamic state using whichever property set is found
263 if (state.size() == 0) {
264 setState_TP(298.15, OneAtm);
265 } else if (state.hasKey("T") && state.hasKey("P")) {
266 double T = state.convert("T", "K");
267 double P = state.convert("P", "Pa");
268 if (state.hasKey("Q")) {
269 setState_TPQ(T, P, state["Q"].asDouble());
270 } else {
271 setState_TP(T, P);
272 }
273 } else if (state.hasKey("T") && state.hasKey("D")) {
274 setState_TD(state.convert("T", "K"), state.convert("D", "kg/m^3"));
275 } else if (state.hasKey("T") && state.hasKey("V")) {
276 setState_TV(state.convert("T", "K"), state.convert("V", "m^3/kg"));
277 } else if (state.hasKey("H") && state.hasKey("P")) {
278 setState_HP(state.convert("H", "J/kg"), state.convert("P", "Pa"));
279 } else if (state.hasKey("U") && state.hasKey("V")) {
280 setState_UV(state.convert("U", "J/kg"), state.convert("V", "m^3/kg"));
281 } else if (state.hasKey("S") && state.hasKey("P")) {
282 setState_SP(state.convert("S", "J/kg/K"), state.convert("P", "Pa"));
283 } else if (state.hasKey("S") && state.hasKey("V")) {
284 setState_SV(state.convert("S", "J/kg/K"), state.convert("V", "m^3/kg"));
285 } else if (state.hasKey("S") && state.hasKey("T")) {
286 setState_ST(state.convert("S", "J/kg/K"), state.convert("T", "K"));
287 } else if (state.hasKey("P") && state.hasKey("V")) {
288 setState_PV(state.convert("P", "Pa"), state.convert("V", "m^3/kg"));
289 } else if (state.hasKey("U") && state.hasKey("P")) {
290 setState_UP(state.convert("U", "J/kg"), state.convert("P", "Pa"));
291 } else if (state.hasKey("V") && state.hasKey("H")) {
292 setState_VH(state.convert("V", "m^3/kg"), state.convert("H", "J/kg"));
293 } else if (state.hasKey("T") && state.hasKey("H")) {
294 setState_TH(state.convert("T", "K"), state.convert("H", "J/kg"));
295 } else if (state.hasKey("S") && state.hasKey("H")) {
296 setState_SH(state.convert("S", "J/kg/K"), state.convert("H", "J/kg"));
297 } else if (state.hasKey("D") && state.hasKey("P")) {
298 setState_DP(state.convert("D", "kg/m^3"), state.convert("P", "Pa"));
299 } else if (state.hasKey("P") && state.hasKey("Q")) {
300 setState_Psat(state.convert("P", "Pa"), state["Q"].asDouble());
301 } else if (state.hasKey("T") && state.hasKey("Q")) {
302 setState_Tsat(state.convert("T", "K"), state["Q"].asDouble());
303 } else if (state.hasKey("T")) {
304 setState_TP(state.convert("T", "K"), OneAtm);
305 } else if (state.hasKey("P")) {
306 setState_TP(298.15, state.convert("P", "Pa"));
307 } else {
308 throw CanteraError("ThermoPhase::setState",
309 "'state' did not specify a recognized set of properties.\n"
310 "Keys provided were: {}", input_state.keys_str());
311 }
312}
313
314void ThermoPhase::setState_conditional_TP(double t, double p, bool set_p)
315{
317 if (set_p) {
318 setPressure(p);
319 }
320}
321
322void ThermoPhase::setState_HPorUV(double Htarget, double p,
323 double rtol, bool doUV)
324{
325 double dt;
326 double v = 0.0;
327
328 // Assign the specific volume or pressure and make sure it's positive
329 if (doUV) {
330 double v = p;
331 if (v < 1.0E-300) {
332 throw CanteraError("ThermoPhase::setState_HPorUV (UV)",
333 "Input specific volume is too small or negative. v = {}", v);
334 }
335 setDensity(1.0/v);
336 } else {
337 if (p < 1.0E-300) {
338 throw CanteraError("ThermoPhase::setState_HPorUV (HP)",
339 "Input pressure is too small or negative. p = {}", p);
340 }
341 setPressure(p);
342 }
343 double Tmax = maxTemp() + 0.1;
344 double Tmin = minTemp() - 0.1;
345
346 // Make sure we are within the temperature bounds at the start
347 // of the iteration
348 double Tnew = temperature();
349 double Tinit = Tnew;
350 if (Tnew > Tmax) {
351 Tnew = Tmax - 1.0;
352 } else if (Tnew < Tmin) {
353 Tnew = Tmin + 1.0;
354 }
355 if (Tnew != Tinit) {
356 setState_conditional_TP(Tnew, p, !doUV);
357 }
358
359 double Hnew = (doUV) ? intEnergy_mass() : enthalpy_mass();
360 double Cpnew = (doUV) ? cv_mass() : cp_mass();
361 double Htop = Hnew;
362 double Ttop = Tnew;
363 double Hbot = Hnew;
364 double Tbot = Tnew;
365
366 bool ignoreBounds = false;
367 // Unstable phases are those for which cp < 0.0. These are possible for
368 // cases where we have passed the spinodal curve.
369 bool unstablePhase = false;
370 // Counter indicating the last temperature point where the
371 // phase was unstable
372 double Tunstable = -1.0;
373 bool unstablePhaseNew = false;
374
375 // Newton iteration
376 for (int n = 0; n < 500; n++) {
377 double Told = Tnew;
378 double Hold = Hnew;
379 double cpd = Cpnew;
380 if (cpd < 0.0) {
381 unstablePhase = true;
382 Tunstable = Tnew;
383 }
384 // limit step size to 100 K
385 dt = clip((Htarget - Hold)/cpd, -100.0, 100.0);
386
387 // Calculate the new T
388 Tnew = Told + dt;
389
390 // Limit the step size so that we are convergent This is the step that
391 // makes it different from a Newton's algorithm
392 if ((dt > 0.0 && unstablePhase) || (dt <= 0.0 && !unstablePhase)) {
393 if (Hbot < Htarget && Tnew < (0.75 * Tbot + 0.25 * Told)) {
394 dt = 0.75 * (Tbot - Told);
395 Tnew = Told + dt;
396 }
397 } else if (Htop > Htarget && Tnew > (0.75 * Ttop + 0.25 * Told)) {
398 dt = 0.75 * (Ttop - Told);
399 Tnew = Told + dt;
400 }
401
402 // Check Max and Min values
403 if (Tnew > Tmax && !ignoreBounds) {
404 setState_conditional_TP(Tmax, p, !doUV);
405 double Hmax = (doUV) ? intEnergy_mass() : enthalpy_mass();
406 if (Hmax >= Htarget) {
407 if (Htop < Htarget) {
408 Ttop = Tmax;
409 Htop = Hmax;
410 }
411 } else {
412 Tnew = Tmax + 1.0;
413 ignoreBounds = true;
414 }
415 }
416 if (Tnew < Tmin && !ignoreBounds) {
417 setState_conditional_TP(Tmin, p, !doUV);
418 double Hmin = (doUV) ? intEnergy_mass() : enthalpy_mass();
419 if (Hmin <= Htarget) {
420 if (Hbot > Htarget) {
421 Tbot = Tmin;
422 Hbot = Hmin;
423 }
424 } else {
425 Tnew = Tmin - 1.0;
426 ignoreBounds = true;
427 }
428 }
429
430 // Try to keep phase within its region of stability
431 // -> Could do a lot better if I calculate the
432 // spinodal value of H.
433 for (int its = 0; its < 10; its++) {
434 Tnew = Told + dt;
435 if (Tnew < Told / 3.0) {
436 Tnew = Told / 3.0;
437 dt = -2.0 * Told / 3.0;
438 }
439 setState_conditional_TP(Tnew, p, !doUV);
440 if (doUV) {
441 Hnew = intEnergy_mass();
442 Cpnew = cv_mass();
443 } else {
444 Hnew = enthalpy_mass();
445 Cpnew = cp_mass();
446 }
447 if (Cpnew < 0.0) {
448 unstablePhaseNew = true;
449 Tunstable = Tnew;
450 } else {
451 unstablePhaseNew = false;
452 break;
453 }
454 if (unstablePhase == false && unstablePhaseNew == true) {
455 dt *= 0.25;
456 }
457 }
458
459 if (Hnew == Htarget) {
460 return;
461 } else if (Hnew > Htarget && (Htop < Htarget || Hnew < Htop)) {
462 Htop = Hnew;
463 Ttop = Tnew;
464 } else if (Hnew < Htarget && (Hbot > Htarget || Hnew > Hbot)) {
465 Hbot = Hnew;
466 Tbot = Tnew;
467 }
468 // Convergence in H
469 double Herr = Htarget - Hnew;
470 double acpd = std::max(fabs(cpd), 1.0E-5);
471 double denom = std::max(fabs(Htarget), acpd * Tnew);
472 double HConvErr = fabs((Herr)/denom);
473 if (HConvErr < rtol || fabs(dt/Tnew) < rtol) {
474 return;
475 }
476 }
477 // We are here when there hasn't been convergence
478
479 // Formulate a detailed error message, since questions seem to arise often
480 // about the lack of convergence.
481 string ErrString = "No convergence in 500 iterations\n";
482 if (doUV) {
483 ErrString += fmt::format(
484 "\tTarget Internal Energy = {}\n"
485 "\tCurrent Specific Volume = {}\n"
486 "\tStarting Temperature = {}\n"
487 "\tCurrent Temperature = {}\n"
488 "\tCurrent Internal Energy = {}\n"
489 "\tCurrent Delta T = {}\n",
490 Htarget, v, Tinit, Tnew, Hnew, dt);
491 } else {
492 ErrString += fmt::format(
493 "\tTarget Enthalpy = {}\n"
494 "\tCurrent Pressure = {}\n"
495 "\tStarting Temperature = {}\n"
496 "\tCurrent Temperature = {}\n"
497 "\tCurrent Enthalpy = {}\n"
498 "\tCurrent Delta T = {}\n",
499 Htarget, p, Tinit, Tnew, Hnew, dt);
500 }
501 if (unstablePhase) {
502 ErrString += fmt::format(
503 "\t - The phase became unstable (Cp < 0) T_unstable_last = {}\n",
504 Tunstable);
505 }
506 if (doUV) {
507 throw CanteraError("ThermoPhase::setState_HPorUV (UV)", ErrString);
508 } else {
509 throw CanteraError("ThermoPhase::setState_HPorUV (HP)", ErrString);
510 }
511}
512
513void ThermoPhase::setState_SP(double Starget, double p, double rtol)
514{
515 vector<double> state(partialStateSize());
516 savePartialState(state);
517 try {
518 setState_SPorSV(Starget, p, rtol, false);
519 } catch (std::exception&) {
520 restorePartialState(state);
521 throw;
522 }
523}
524
525void ThermoPhase::setState_SV(double Starget, double v, double rtol)
526{
527 assertCompressible("setState_SV");
528 vector<double> state(partialStateSize());
529 savePartialState(state);
530 try {
531 setState_SPorSV(Starget, v, rtol, true);
532 } catch (std::exception&) {
533 restorePartialState(state);
534 throw;
535 }
536}
537
538void ThermoPhase::setState_SPorSV(double Starget, double p,
539 double rtol, bool doSV)
540{
541 double v = 0.0;
542 double dt;
543 if (doSV) {
544 v = p;
545 if (v < 1.0E-300) {
546 throw CanteraError("ThermoPhase::setState_SPorSV (SV)",
547 "Input specific volume is too small or negative. v = {}", v);
548 }
549 setDensity(1.0/v);
550 } else {
551 if (p < 1.0E-300) {
552 throw CanteraError("ThermoPhase::setState_SPorSV (SP)",
553 "Input pressure is too small or negative. p = {}", p);
554 }
555 setPressure(p);
556 }
557 double Tmax = maxTemp() + 0.1;
558 double Tmin = minTemp() - 0.1;
559
560 // Make sure we are within the temperature bounds at the start
561 // of the iteration
562 double Tnew = temperature();
563 double Tinit = Tnew;
564 if (Tnew > Tmax) {
565 Tnew = Tmax - 1.0;
566 } else if (Tnew < Tmin) {
567 Tnew = Tmin + 1.0;
568 }
569 if (Tnew != Tinit) {
570 setState_conditional_TP(Tnew, p, !doSV);
571 }
572
573 double Snew = entropy_mass();
574 double Cpnew = (doSV) ? cv_mass() : cp_mass();
575 double Stop = Snew;
576 double Ttop = Tnew;
577 double Sbot = Snew;
578 double Tbot = Tnew;
579
580 bool ignoreBounds = false;
581 // Unstable phases are those for which Cp < 0.0. These are possible for
582 // cases where we have passed the spinodal curve.
583 bool unstablePhase = false;
584 double Tunstable = -1.0;
585 bool unstablePhaseNew = false;
586
587 // Newton iteration
588 for (int n = 0; n < 500; n++) {
589 double Told = Tnew;
590 double Sold = Snew;
591 double cpd = Cpnew;
592 if (cpd < 0.0) {
593 unstablePhase = true;
594 Tunstable = Tnew;
595 }
596 // limit step size to 100 K
597 dt = clip((Starget - Sold)*Told/cpd, -100.0, 100.0);
598 Tnew = Told + dt;
599
600 // Limit the step size so that we are convergent
601 if ((dt > 0.0 && unstablePhase) || (dt <= 0.0 && !unstablePhase)) {
602 if (Sbot < Starget && Tnew < Tbot) {
603 dt = 0.75 * (Tbot - Told);
604 Tnew = Told + dt;
605 }
606 } else if (Stop > Starget && Tnew > Ttop) {
607 dt = 0.75 * (Ttop - Told);
608 Tnew = Told + dt;
609 }
610
611 // Check Max and Min values
612 if (Tnew > Tmax && !ignoreBounds) {
613 setState_conditional_TP(Tmax, p, !doSV);
614 double Smax = entropy_mass();
615 if (Smax >= Starget) {
616 if (Stop < Starget) {
617 Ttop = Tmax;
618 Stop = Smax;
619 }
620 } else {
621 Tnew = Tmax + 1.0;
622 ignoreBounds = true;
623 }
624 } else if (Tnew < Tmin && !ignoreBounds) {
625 setState_conditional_TP(Tmin, p, !doSV);
626 double Smin = entropy_mass();
627 if (Smin <= Starget) {
628 if (Sbot > Starget) {
629 Tbot = Tmin;
630 Sbot = Smin;
631 }
632 } else {
633 Tnew = Tmin - 1.0;
634 ignoreBounds = true;
635 }
636 }
637
638 // Try to keep phase within its region of stability
639 // -> Could do a lot better if I calculate the
640 // spinodal value of H.
641 for (int its = 0; its < 10; its++) {
642 Tnew = Told + dt;
643 setState_conditional_TP(Tnew, p, !doSV);
644 Cpnew = (doSV) ? cv_mass() : cp_mass();
645 Snew = entropy_mass();
646 if (Cpnew < 0.0) {
647 unstablePhaseNew = true;
648 Tunstable = Tnew;
649 } else {
650 unstablePhaseNew = false;
651 break;
652 }
653 if (unstablePhase == false && unstablePhaseNew == true) {
654 dt *= 0.25;
655 }
656 }
657
658 if (Snew == Starget) {
659 return;
660 } else if (Snew > Starget && (Stop < Starget || Snew < Stop)) {
661 Stop = Snew;
662 Ttop = Tnew;
663 } else if (Snew < Starget && (Sbot > Starget || Snew > Sbot)) {
664 Sbot = Snew;
665 Tbot = Tnew;
666 }
667 // Convergence in S
668 double Serr = Starget - Snew;
669 double acpd = std::max(fabs(cpd), 1.0E-5);
670 double denom = std::max(fabs(Starget), acpd * Tnew);
671 double SConvErr = fabs((Serr * Tnew)/denom);
672 if (SConvErr < rtol || fabs(dt/Tnew) < rtol) {
673 return;
674 }
675 }
676 // We are here when there hasn't been convergence
677
678 // Formulate a detailed error message, since questions seem to arise often
679 // about the lack of convergence.
680 string ErrString = "No convergence in 500 iterations\n";
681 if (doSV) {
682 ErrString += fmt::format(
683 "\tTarget Entropy = {}\n"
684 "\tCurrent Specific Volume = {}\n"
685 "\tStarting Temperature = {}\n"
686 "\tCurrent Temperature = {}\n"
687 "\tCurrent Entropy = {}\n"
688 "\tCurrent Delta T = {}\n",
689 Starget, v, Tinit, Tnew, Snew, dt);
690 } else {
691 ErrString += fmt::format(
692 "\tTarget Entropy = {}\n"
693 "\tCurrent Pressure = {}\n"
694 "\tStarting Temperature = {}\n"
695 "\tCurrent Temperature = {}\n"
696 "\tCurrent Entropy = {}\n"
697 "\tCurrent Delta T = {}\n",
698 Starget, p, Tinit, Tnew, Snew, dt);
699 }
700 if (unstablePhase) {
701 ErrString += fmt::format("\t - The phase became unstable (Cp < 0) T_unstable_last = {}\n",
702 Tunstable);
703 }
704 if (doSV) {
705 throw CanteraError("ThermoPhase::setState_SPorSV (SV)", ErrString);
706 } else {
707 throw CanteraError("ThermoPhase::setState_SPorSV (SP)", ErrString);
708 }
709}
710
711double ThermoPhase::o2Required(span<const double> y) const
712{
713 checkArraySize("ThermoPhase::o2Required", y.size(), m_kk);
714 // indices of fuel elements
715 size_t iC = elementIndex("C", false);
716 size_t iS = elementIndex("S", false);
717 size_t iH = elementIndex("H", false);
718
719 double o2req = 0.0;
720 double sum = 0.0;
721 for (size_t k = 0; k != m_kk; ++k) {
722 sum += y[k];
723 double x = y[k] / molecularWeights()[k];
724 if (iC != npos) {
725 o2req += x * nAtoms(k, iC);
726 }
727 if (iS != npos) {
728 o2req += x * nAtoms(k, iS);
729 }
730 if (iH != npos) {
731 o2req += x * 0.25 * nAtoms(k, iH);
732 }
733 }
734 if (sum == 0.0) {
735 throw CanteraError("ThermoPhase::o2Required",
736 "No composition specified");
737 }
738 return o2req/sum;
739}
740
741double ThermoPhase::o2Present(span<const double> y) const
742{
743 checkArraySize("ThermoPhase::o2Present", y.size(), m_kk);
744 size_t iO = elementIndex("O", true);
745 double o2pres = 0.0;
746 double sum = 0.0;
747 for (size_t k = 0; k != m_kk; ++k) {
748 sum += y[k];
749 o2pres += y[k] / molecularWeights()[k] * nAtoms(k, iO);
750 }
751 if (sum == 0.0) {
752 throw CanteraError("ThermoPhase::o2Present",
753 "No composition specified");
754 }
755 return 0.5 * o2pres / sum;
756}
757
759 const Composition& oxComp,
760 ThermoBasis basis) const
761{
762 vector<double> fuel(getCompositionFromMap(fuelComp));
763 vector<double> ox(getCompositionFromMap(oxComp));
764 return stoichAirFuelRatio(fuel, ox, basis);
765}
766
767double ThermoPhase::stoichAirFuelRatio(const string& fuelComp, const string& oxComp,
768 ThermoBasis basis) const
769{
770 return stoichAirFuelRatio(
771 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
772 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
773 basis);
774}
775
776double ThermoPhase::stoichAirFuelRatio(span<const double> fuelComp,
777 span<const double> oxComp,
778 ThermoBasis basis) const
779{
780 vector<double> fuel, ox;
781 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
782 fuel.resize(m_kk);
783 ox.resize(m_kk);
784 moleFractionsToMassFractions(fuelComp, fuel);
786 fuelComp = fuel;
787 oxComp = ox;
788 }
789
790 double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp);
791 double o2_required_ox = o2Required(oxComp) - o2Present(oxComp);
792
793 if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) {
794 throw CanteraError("ThermoPhase::stoichAirFuelRatio",
795 "Fuel composition contains too much oxygen or "
796 "oxidizer contains not enough oxygen. "
797 "Fuel and oxidizer composition mixed up?");
798 }
799
800 if (o2_required_ox == 0.0) {
801 return std::numeric_limits<double>::infinity();
802 }
803
804 return o2_required_fuel / (-o2_required_ox);
805}
806
807void ThermoPhase::setEquivalenceRatio(double phi, span<const double> fuelComp,
808 span<const double> oxComp, ThermoBasis basis)
809{
810 if (phi < 0.0) {
811 throw CanteraError("ThermoPhase::setEquivalenceRatio",
812 "Equivalence ratio phi must be >= 0");
813 }
814
815 double p = pressure();
816
817 vector<double> fuel, ox;
818 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
819 fuel.resize(m_kk);
820 ox.resize(m_kk);
821 moleFractionsToMassFractions(fuelComp, fuel);
823 fuelComp = fuel;
824 oxComp = ox;
825 }
826
827 double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass);
828 double sum_f = std::accumulate(fuelComp.begin(), fuelComp.end(), 0.0);
829 double sum_o = std::accumulate(oxComp.begin(), oxComp.end(), 0.0);
830
831 vector<double> y(m_kk);
832 for (size_t k = 0; k != m_kk; ++k) {
833 y[k] = phi * fuelComp[k]/sum_f + AFR_st * oxComp[k]/sum_o;
834 }
835
837 setPressure(p);
838}
839
840void ThermoPhase::setEquivalenceRatio(double phi, const string& fuelComp,
841 const string& oxComp, ThermoBasis basis)
842{
844 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
845 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
846 basis);
847}
848
849void ThermoPhase::setEquivalenceRatio(double phi, const Composition& fuelComp,
850 const Composition& oxComp, ThermoBasis basis)
851{
852 vector<double> fuel = getCompositionFromMap(fuelComp);
853 vector<double> ox = getCompositionFromMap(oxComp);
854 setEquivalenceRatio(phi, fuel, ox, basis);
855}
856
858{
859 span<const double> y = massFractions();
860 double o2_required = o2Required(y);
861 double o2_present = o2Present(y);
862
863 if (o2_present == 0.0) { // pure fuel
864 return std::numeric_limits<double>::infinity();
865 }
866
867 return o2_required / o2_present;
868}
869
871 const Composition& oxComp,
872 ThermoBasis basis) const
873{
874 vector<double> fuel(getCompositionFromMap(fuelComp));
875 vector<double> ox(getCompositionFromMap(oxComp));
876 return equivalenceRatio(fuel, ox, basis);
877}
878
879double ThermoPhase::equivalenceRatio(const string& fuelComp, const string& oxComp,
880 ThermoBasis basis) const
881{
882 return equivalenceRatio(
883 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
884 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
885 basis);
886}
887
888double ThermoPhase::equivalenceRatio(span<const double> fuelComp,
889 span<const double> oxComp, ThermoBasis basis) const
890{
891 double Z = mixtureFraction(fuelComp, oxComp, basis);
892
893 if (Z == 0.0) {
894 return 0.0; // pure oxidizer
895 }
896
897 if (Z == 1.0) {
898 return std::numeric_limits<double>::infinity(); // pure fuel
899 }
900
901 vector<double> fuel, ox;
902 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
903 fuel.resize(m_kk);
904 ox.resize(m_kk);
905 moleFractionsToMassFractions(fuelComp, fuel);
907 fuelComp = fuel;
908 oxComp = ox;
909 }
910
911 double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass);
912
913 return std::max(Z / (1.0 - Z) * AFR_st, 0.0);
914}
915
916void ThermoPhase::setMixtureFraction(double mixFrac, const Composition& fuelComp,
917 const Composition& oxComp, ThermoBasis basis)
918{
919 vector<double> fuel(getCompositionFromMap(fuelComp));
920 vector<double> ox(getCompositionFromMap(oxComp));
921 setMixtureFraction(mixFrac, fuel, ox, basis);
922}
923
924void ThermoPhase::setMixtureFraction(double mixFrac, const string& fuelComp,
925 const string& oxComp, ThermoBasis basis)
926{
927 setMixtureFraction(mixFrac,
928 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
929 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
930 basis);
931}
932
933void ThermoPhase::setMixtureFraction(double mixFrac, span<const double> fuelComp,
934 span<const double> oxComp, ThermoBasis basis)
935{
936 checkArraySize("ThermoPhase::setMixtureFraction", fuelComp.size(), m_kk);
937 checkArraySize("ThermoPhase::setMixtureFraction", oxComp.size(), m_kk);
938 if (mixFrac < 0.0 || mixFrac > 1.0) {
939 throw CanteraError("ThermoPhase::setMixtureFraction",
940 "Mixture fraction must be between 0 and 1");
941 }
942
943 vector<double> fuel, ox;
944 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
945 fuel.resize(m_kk);
946 ox.resize(m_kk);
947 moleFractionsToMassFractions(fuelComp, fuel);
949 fuelComp = fuel;
950 oxComp = ox;
951 }
952
953 double sum_yf = std::accumulate(fuelComp.begin(), fuelComp.end(), 0.0);
954 double sum_yo = std::accumulate(oxComp.begin(), oxComp.end(), 0.0);
955
956 if (sum_yf == 0.0 || sum_yo == 0.0) {
957 throw CanteraError("ThermoPhase::setMixtureFraction",
958 "No fuel and/or oxidizer composition specified");
959 }
960
961 double p = pressure();
962
963 vector<double> y(m_kk);
964
965 for (size_t k = 0; k != m_kk; ++k) {
966 y[k] = mixFrac * fuelComp[k]/sum_yf + (1.0-mixFrac) * oxComp[k]/sum_yo;
967 }
968
970 setPressure(p);
971}
972
974 const Composition& oxComp,
975 ThermoBasis basis,
976 const string& element) const
977{
978 vector<double> fuel(getCompositionFromMap(fuelComp));
979 vector<double> ox(getCompositionFromMap(oxComp));
980 return mixtureFraction(fuel, ox, basis, element);
981}
982
983double ThermoPhase::mixtureFraction(const string& fuelComp, const string& oxComp,
984 ThermoBasis basis, const string& element) const
985{
986 return mixtureFraction(
987 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
988 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
989 basis, element);
990}
991
992double ThermoPhase::mixtureFraction(span<const double> fuelComp,
993 span<const double> oxComp,
994 ThermoBasis basis, const string& element) const
995{
996 checkArraySize("ThermoPhase::mixtureFraction", fuelComp.size(), m_kk);
997 checkArraySize("ThermoPhase::mixtureFraction", oxComp.size(), m_kk);
998 vector<double> fuel, ox;
999 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
1000 fuel.resize(m_kk);
1001 ox.resize(m_kk);
1002 moleFractionsToMassFractions(fuelComp, fuel);
1003 moleFractionsToMassFractions(oxComp, ox);
1004 fuelComp = fuel;
1005 oxComp = ox;
1006 }
1007
1008 if (element == "Bilger") // compute the mixture fraction based on the Bilger mixture fraction
1009 {
1010 double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp);
1011 double o2_required_ox = o2Required(oxComp) - o2Present(oxComp);
1012 span<const double> y = massFractions();
1013 double o2_required_mix = o2Required(y) - o2Present(y);
1014
1015 if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) {
1016 throw CanteraError("ThermoPhase::mixtureFraction",
1017 "Fuel composition contains too much oxygen or "
1018 "oxidizer contains not enough oxygen. "
1019 "Fuel and oxidizer composition mixed up?");
1020 }
1021
1022 double denominator = o2_required_fuel - o2_required_ox;
1023
1024 if (denominator == 0.0) {
1025 throw CanteraError("ThermoPhase::mixtureFraction",
1026 "Fuel and oxidizer have the same composition");
1027 }
1028
1029 double Z = (o2_required_mix - o2_required_ox) / denominator;
1030
1031 return std::min(std::max(Z, 0.0), 1.0);
1032 } else {
1033 // compute the mixture fraction from a single element
1034 double sum_yf = std::accumulate(fuelComp.begin(), fuelComp.end(), 0.0);
1035 double sum_yo = std::accumulate(oxComp.begin(), oxComp.end(), 0.0);
1036
1037 if (sum_yf == 0.0 || sum_yo == 0.0) {
1038 throw CanteraError("ThermoPhase::mixtureFraction",
1039 "No fuel and/or oxidizer composition specified");
1040 }
1041
1042 auto elementalFraction = [this](size_t m, span<const double> y) {
1043 double Z_m = 0.0;
1044 for (size_t k = 0; k != m_kk; ++k) {
1045 Z_m += y[k] / molecularWeight(k) * nAtoms(k, m);
1046 }
1047 return Z_m;
1048 };
1049
1050 size_t m = elementIndex(element, true);
1051 double Z_m_fuel = elementalFraction(m, fuelComp)/sum_yf;
1052 double Z_m_ox = elementalFraction(m, oxComp)/sum_yo;
1053 double Z_m_mix = elementalFraction(m, massFractions());
1054
1055 if (Z_m_fuel == Z_m_ox) {
1056 throw CanteraError("ThermoPhase::mixtureFraction",
1057 "Fuel and oxidizer have the same composition for element {}",
1058 element);
1059 }
1060 double Z = (Z_m_mix - Z_m_ox) / (Z_m_fuel - Z_m_ox);
1061 return std::min(std::max(Z, 0.0), 1.0);
1062 }
1063}
1064
1066{
1067 return m_spthermo;
1068}
1069
1071{
1072 return m_spthermo;
1073}
1074
1075
1076void ThermoPhase::initThermoFile(const string& inputFile, const string& id)
1077{
1078 if (inputFile.empty()) {
1079 // No input file specified - nothing to set up
1080 return;
1081 }
1082 size_t dot = inputFile.find_last_of(".");
1083 string extension;
1084 if (dot != npos) {
1085 extension = inputFile.substr(dot+1);
1086 }
1087 if (extension == "xml" || extension == "cti") {
1088 throw CanteraError("ThermoPhase::initThermoFile",
1089 "The CTI and XML formats are no longer supported.");
1090 }
1091
1092 AnyMap root = AnyMap::fromYamlFile(inputFile);
1093 auto& phase = root["phases"].getMapWhere("name", id);
1094 setupPhase(*this, phase, root);
1095}
1096
1098{
1099 // Check to see that all of the species thermo objects have been initialized
1100 if (!m_spthermo.ready(m_kk)) {
1101 throw CanteraError("ThermoPhase::initThermo()",
1102 "Missing species thermo data");
1103 }
1104}
1105
1106void ThermoPhase::setState_TPQ(double T, double P, double Q)
1107{
1108 if (T > critTemperature()) {
1109 if (P > critPressure() || Q == 1) {
1110 setState_TP(T, P);
1111 return;
1112 } else {
1113 throw CanteraError("ThermoPhase::setState_TPQ",
1114 "Temperature ({}), pressure ({}) and vapor fraction ({}) "
1115 "are inconsistent, above the critical temperature.",
1116 T, P, Q);
1117 }
1118 }
1119
1120 double Psat = satPressure(T);
1121 if (std::abs(Psat / P - 1) < 1e-6) {
1122 setState_Tsat(T, Q);
1123 } else if ((Q == 0 && P >= Psat) || (Q == 1 && P <= Psat)) {
1124 setState_TP(T, P);
1125 } else {
1126 throw CanteraError("ThermoPhase::setState_TPQ",
1127 "Temperature ({}), pressure ({}) and vapor fraction ({}) "
1128 "are inconsistent.\nPsat at this T: {}\n"
1129 "Consider specifying the state using two fully independent "
1130 "properties (for example, temperature and density)",
1131 T, P, Q, Psat);
1132 }
1133}
1134
1135bool ThermoPhase::addSpecies(shared_ptr<Species> spec)
1136{
1137 if (!spec->thermo) {
1138 throw CanteraError("ThermoPhase::addSpecies",
1139 "Species {} has no thermo data", spec->name);
1140 }
1141 bool added = Phase::addSpecies(spec);
1142 if (added) {
1143 spec->thermo->validate(spec->name);
1144 m_spthermo.install_STIT(m_kk-1, spec->thermo);
1145 }
1146 return added;
1147}
1148
1149void ThermoPhase::modifySpecies(size_t k, shared_ptr<Species> spec)
1150{
1151 if (!spec->thermo) {
1152 throw CanteraError("ThermoPhase::modifySpecies",
1153 "Species {} has no thermo data", spec->name);
1154 }
1155 Phase::modifySpecies(k, spec);
1156 if (speciesName(k) != spec->name) {
1157 throw CanteraError("ThermoPhase::modifySpecies",
1158 "New species '{}' does not match existing species '{}' at index {}",
1159 spec->name, speciesName(k), k);
1160 }
1161 spec->thermo->validate(spec->name);
1162 m_spthermo.modifySpecies(k, spec->thermo);
1163}
1164
1165void ThermoPhase::setParameters(const AnyMap& phaseNode, const AnyMap& rootNode)
1166{
1167 m_input = phaseNode;
1168}
1169
1170AnyMap ThermoPhase::parameters(bool withInput) const
1171{
1172 AnyMap out;
1173 getParameters(out);
1174 if (withInput) {
1175 out.update(m_input);
1176 }
1177 return out;
1178}
1179
1181{
1182 phaseNode["name"] = name();
1183 phaseNode["thermo"] = ThermoFactory::factory()->canonicalize(type());
1184 vector<string> elementNames;
1185 for (size_t i = 0; i < nElements(); i++) {
1186 elementNames.push_back(elementName(i));
1187 }
1188 phaseNode["elements"] = elementNames;
1189 phaseNode["species"] = speciesNames();
1190
1191 AnyMap state;
1192 auto stateVars = nativeState();
1193 if (stateVars.count("T")) {
1194 state["T"].setQuantity(temperature(), "K");
1195 }
1196
1197 if (stateVars.count("D")) {
1198 state["density"].setQuantity(density(), "kg/m^3");
1199 } else if (stateVars.count("P")) {
1200 state["P"].setQuantity(pressure(), "Pa");
1201 }
1202
1203 if (stateVars.count("Y")) {
1204 map<string, double> Y;
1205 for (size_t k = 0; k < m_kk; k++) {
1206 double Yk = massFraction(k);
1207 if (Yk > 0) {
1208 Y[speciesName(k)] = Yk;
1209 }
1210 }
1211 state["Y"] = Y;
1212 state["Y"].setFlowStyle();
1213 } else if (stateVars.count("X")) {
1214 map<string, double> X;
1215 for (size_t k = 0; k < m_kk; k++) {
1216 double Xk = moleFraction(k);
1217 if (Xk > 0) {
1218 X[speciesName(k)] = Xk;
1219 }
1220 }
1221 state["X"] = X;
1222 state["X"].setFlowStyle();
1223 }
1224
1225 phaseNode["state"] = std::move(state);
1226
1227 static bool reg = AnyMap::addOrderingRules("Phase", {{"tail", "state"}});
1228 if (reg) {
1229 phaseNode["__type__"] = "Phase";
1230 }
1231}
1232
1234{
1235 return m_input;
1236}
1237
1239{
1240 return m_input;
1241}
1242
1245 m_tlast += 0.1234;
1246}
1247
1248void ThermoPhase::equilibrate(const string& XY, const string& solver,
1249 double rtol, int max_steps, int max_iter,
1250 int estimate_equil, int log_level)
1251{
1252 if (solver == "auto" || solver == "element_potential") {
1253 vector<double> initial_state(stateSize());
1254 saveState(initial_state);
1255 debuglog("Trying ChemEquil solver\n", log_level);
1256 try {
1257 PhaseEquilGuard guard(*this);
1258 ChemEquil E;
1259 E.options.maxIterations = max_steps;
1260 E.options.relTolerance = rtol;
1261 int ret = E.equilibrate(*this, XY.c_str(), log_level-1);
1262 if (ret < 0) {
1263 throw CanteraError("ThermoPhase::equilibrate",
1264 "ChemEquil solver failed. Return code: {}", ret);
1265 }
1266 debuglog("ChemEquil solver succeeded\n", log_level);
1267 return;
1268 } catch (std::exception& err) {
1269 debuglog("ChemEquil solver failed.\n", log_level);
1270 debuglog(err.what(), log_level);
1271 restoreState(initial_state);
1272 if (solver == "auto") {
1273 } else {
1274 throw;
1275 }
1276 }
1277 }
1278
1279 if (solver == "auto" || solver == "vcs" || solver == "gibbs") {
1280 MultiPhase M;
1281 // Create a non-owning shared_ptr, since this ThermoPhase object is guaranteed
1282 // to outlive the MultiPhase object.
1283 M.addPhase(shared_ptr<ThermoPhase>(this, [](ThermoPhase*) {}), 1.0);
1284 M.init();
1285 M.equilibrate(XY, solver, rtol, max_steps, max_iter,
1286 estimate_equil, log_level);
1287 return;
1288 }
1289
1290 if (solver != "auto") {
1291 throw CanteraError("ThermoPhase::equilibrate",
1292 "Invalid solver specified: '{}'", solver);
1293 }
1294}
1295
1296void ThermoPhase::getdlnActCoeffdlnN(const size_t ld, span<double> dlnActCoeffdlnN)
1297{
1298 checkArraySize("ThermoPhase::getdlnActCoeffdlnN", dlnActCoeffdlnN.size(), ld*m_kk);
1299 for (size_t m = 0; m < m_kk; m++) {
1300 for (size_t k = 0; k < m_kk; k++) {
1301 dlnActCoeffdlnN[ld * k + m] = 0.0;
1302 }
1303 }
1304 return;
1305}
1306
1307void ThermoPhase::getdlnActCoeffdlnN_numderiv(const size_t ld,
1308 span<double> dlnActCoeffdlnN)
1309{
1310 checkArraySize("ThermoPhase::getdlnActCoeffdlnN_numderiv",
1311 dlnActCoeffdlnN.size(), ld*m_kk);
1312 double deltaMoles_j = 0.0;
1313 double pres = pressure();
1314
1315 // Evaluate the current base activity coefficients if necessary
1316 vector<double> ActCoeff_Base(m_kk);
1317 getActivityCoefficients(ActCoeff_Base);
1318 vector<double> Xmol_Base(m_kk);
1319 getMoleFractions(Xmol_Base);
1320
1321 // Make copies of ActCoeff and Xmol_ for use in taking differences
1322 vector<double> ActCoeff(m_kk);
1323 vector<double> Xmol(m_kk);
1324 double v_totalMoles = 1.0;
1325 double TMoles_base = v_totalMoles;
1326
1327 // Loop over the columns species to be deltad
1328 for (size_t j = 0; j < m_kk; j++) {
1329 // Calculate a value for the delta moles of species j
1330 // -> Note Xmol_[] and Tmoles are always positive or zero quantities.
1331 // -> experience has shown that you always need to make the deltas
1332 // greater than needed to change the other mole fractions in order
1333 // to capture some effects.
1334 double moles_j_base = v_totalMoles * Xmol_Base[j];
1335 deltaMoles_j = 1.0E-7 * moles_j_base + v_totalMoles * 1.0E-13 + 1.0E-150;
1336
1337 // Now, update the total moles in the phase and all of the mole
1338 // fractions based on this.
1339 v_totalMoles = TMoles_base + deltaMoles_j;
1340 for (size_t k = 0; k < m_kk; k++) {
1341 Xmol[k] = Xmol_Base[k] * TMoles_base / v_totalMoles;
1342 }
1343 Xmol[j] = (moles_j_base + deltaMoles_j) / v_totalMoles;
1344
1345 // Go get new values for the activity coefficients.
1346 setMoleFractions(Xmol);
1347 setPressure(pres);
1348 getActivityCoefficients(ActCoeff);
1349
1350 // Calculate the column of the matrix
1351 span<double> lnActCoeffCol = dlnActCoeffdlnN.subspan(ld * j, m_kk);
1352 for (size_t k = 0; k < m_kk; k++) {
1353 lnActCoeffCol[k] = (2*moles_j_base + deltaMoles_j) *(ActCoeff[k] - ActCoeff_Base[k]) /
1354 ((ActCoeff[k] + ActCoeff_Base[k]) * deltaMoles_j);
1355 }
1356 // Revert to the base case Xmol_, v_totalMoles
1357 v_totalMoles = TMoles_base;
1358 Xmol = Xmol_Base;
1359 }
1360 setMoleFractions(Xmol_Base);
1361 setPressure(pres);
1362}
1363
1364string ThermoPhase::report(bool show_thermo, double threshold) const
1365{
1366 if (type() == "none") {
1367 throw NotImplementedError("ThermoPhase::report",
1368 "Not implemented for thermo model 'none'");
1369 }
1370
1371 fmt::memory_buffer b;
1372 // This is the width of the first column of names in the report.
1373 int name_width = 18;
1374
1375 string blank_leader = fmt::format("{:{}}", "", name_width);
1376
1377 string string_property = fmt::format("{{:>{}}} {{}}\n", name_width);
1378
1379 string one_property = fmt::format("{{:>{}}} {{:<.5g}} {{}}\n", name_width);
1380
1381 constexpr auto two_prop_header = "{} {:^15} {:^15}\n";
1382 string kg_kmol_header = fmt::format(
1383 two_prop_header, blank_leader, "1 kg", "1 kmol"
1384 );
1385 string Y_X_header = fmt::format(
1386 two_prop_header, blank_leader, "mass frac. Y", "mole frac. X"
1387 );
1388 string two_prop_sep = fmt::format(
1389 "{} {:-^15} {:-^15}\n", blank_leader, "", ""
1390 );
1391 string two_property = fmt::format(
1392 "{{:>{}}} {{:15.5g}} {{:15.5g}} {{}}\n", name_width
1393 );
1394
1395 string three_prop_header = fmt::format(
1396 "{} {:^15} {:^15} {:^15}\n", blank_leader, "mass frac. Y",
1397 "mole frac. X", "chem. pot. / RT"
1398 );
1399 string three_prop_sep = fmt::format(
1400 "{} {:-^15} {:-^15} {:-^15}\n", blank_leader, "", "", ""
1401 );
1402 string three_property = fmt::format(
1403 "{{:>{}}} {{:15.5g}} {{:15.5g}} {{:15.5g}}\n", name_width
1404 );
1405
1406 try {
1407 if (name() != "") {
1408 fmt_append(b, "\n {}:\n", name());
1409 }
1410 fmt_append(b, "\n");
1411 fmt_append(b, one_property, "temperature", temperature(), "K");
1412 fmt_append(b, one_property, "pressure", pressure(), "Pa");
1413 fmt_append(b, one_property, "density", density(), "kg/m^3");
1414 fmt_append(b, one_property,
1415 "mean mol. weight", meanMolecularWeight(), "kg/kmol");
1416
1417 double phi = electricPotential();
1418 if (phi != 0.0) {
1419 fmt_append(b, one_property, "potential", phi, "V");
1420 }
1421
1422 fmt_append(b, string_property, "phase of matter", phaseOfMatter());
1423
1424 if (show_thermo) {
1425 fmt_append(b, "\n");
1426 fmt_append(b, kg_kmol_header);
1427 fmt_append(b, two_prop_sep);
1428 fmt_append(b, two_property,
1429 "enthalpy", enthalpy_mass(), enthalpy_mole(), "J");
1430 fmt_append(b, two_property,
1431 "internal energy", intEnergy_mass(), intEnergy_mole(), "J");
1432 fmt_append(b, two_property,
1433 "entropy", entropy_mass(), entropy_mole(), "J/K");
1434 fmt_append(b, two_property,
1435 "Gibbs function", gibbs_mass(), gibbs_mole(), "J");
1436 fmt_append(b, two_property,
1437 "heat capacity c_p", cp_mass(), cp_mole(), "J/K");
1438 try {
1439 fmt_append(b, two_property,
1440 "heat capacity c_v", cv_mass(), cv_mole(), "J/K");
1441 } catch (NotImplementedError&) {
1442 fmt_append(b, string_property,
1443 "heat capacity c_v", "<not implemented>");
1444 }
1445 }
1446
1447 vector<double> x(m_kk);
1448 vector<double> y(m_kk);
1449 vector<double> mu(m_kk);
1453 int nMinor = 0;
1454 double xMinor = 0.0;
1455 double yMinor = 0.0;
1456 fmt_append(b, "\n");
1457 if (show_thermo) {
1458 fmt_append(b, three_prop_header);
1459 fmt_append(b, three_prop_sep);
1460 for (size_t k = 0; k < m_kk; k++) {
1461 if (abs(x[k]) >= threshold) {
1462 if (abs(x[k]) > SmallNumber) {
1463 fmt_append(b, three_property,
1464 speciesName(k), y[k], x[k], mu[k]/RT());
1465 } else {
1466 fmt_append(b, two_property, speciesName(k), y[k], x[k], "");
1467 }
1468 } else {
1469 nMinor++;
1470 xMinor += x[k];
1471 yMinor += y[k];
1472 }
1473 }
1474 } else {
1475 fmt_append(b, Y_X_header);
1476 fmt_append(b, two_prop_sep);
1477 for (size_t k = 0; k < m_kk; k++) {
1478 if (abs(x[k]) >= threshold) {
1479 fmt_append(b, two_property, speciesName(k), y[k], x[k], "");
1480 } else {
1481 nMinor++;
1482 xMinor += x[k];
1483 yMinor += y[k];
1484 }
1485 }
1486 }
1487 if (nMinor) {
1488 string minor = fmt::format("[{:+5d} minor]", nMinor);
1489 fmt_append(b, two_property, minor, yMinor, xMinor, "");
1490 }
1491 } catch (CanteraError& err) {
1492 return to_string(b) + err.what();
1493 }
1494 return to_string(b);
1495}
1496
1497}
Chemical equilibrium.
Headers for the MultiPhase object that is used to set up multiphase equilibrium problems (see Chemica...
Pure Virtual Base class for individual species reference state thermodynamic managers and text for th...
Declaration for class Cantera::Species.
Headers for the factory class that can create known ThermoPhase objects (see Thermodynamic Properties...
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
A map of string keys to values whose type can vary at runtime.
Definition AnyMap.h:431
size_t size() const
Returns the number of elements in this map.
Definition AnyMap.cpp:1728
bool hasKey(const string &key) const
Returns true if the map contains an item named key.
Definition AnyMap.cpp:1477
void applyUnits()
Use the supplied UnitSystem to set the default units, and recursively process overrides from nodes na...
Definition AnyMap.cpp:1761
double convert(const string &key, const string &units) const
Convert the item stored by the given key to the units specified in units.
Definition AnyMap.cpp:1595
void setFlowStyle(bool flow=true)
Use "flow" style when outputting this AnyMap to YAML.
Definition AnyMap.cpp:1794
void erase(const string &key)
Erase the value held by key.
Definition AnyMap.cpp:1483
static AnyMap fromYamlFile(const string &name, const string &parent_name="")
Create an AnyMap from a YAML file.
Definition AnyMap.cpp:1841
void update(const AnyMap &other, bool keepExisting=true)
Add items from other to this AnyMap.
Definition AnyMap.cpp:1493
static bool addOrderingRules(const string &objectType, const vector< vector< string > > &specs)
Add global rules for setting the order of elements when outputting AnyMap objects to YAML.
Definition AnyMap.cpp:1798
string keys_str() const
Return a string listing the keys in this AnyMap, for use in error messages, for example.
Definition AnyMap.cpp:1507
Base class for exceptions thrown by Cantera classes.
const char * what() const override
Get a description of the error.
Class ChemEquil implements a chemical equilibrium solver for single-phase solutions.
Definition ChemEquil.h:79
int equilibrate(ThermoPhase &s, const char *XY, int loglevel=0)
Equilibrate a phase, holding the elemental composition fixed at the initial value found within the Th...
EquilOpt options
Options controlling how the calculation is carried out.
Definition ChemEquil.h:127
double relTolerance
Relative tolerance.
Definition ChemEquil.h:29
int maxIterations
Maximum number of iterations.
Definition ChemEquil.h:31
string canonicalize(const string &name)
Get the canonical name registered for a type.
Definition FactoryBase.h:94
A class for multiphase mixtures.
Definition MultiPhase.h:62
void init()
Process phases and build atomic composition array.
void addPhase(shared_ptr< ThermoPhase > p, double moles)
Add a phase to the mixture.
A species thermodynamic property manager for a phase.
bool ready(size_t nSpecies)
Check if data for all species (0 through nSpecies-1) has been installed.
virtual void install_STIT(size_t index, shared_ptr< SpeciesThermoInterpType > stit)
Install a new species thermodynamic property parameterization for one species.
virtual void modifySpecies(size_t index, shared_ptr< SpeciesThermoInterpType > spec)
Modify the species thermodynamic property parameterization for a species.
virtual void resetHf298(const size_t k)
Restore the original heat of formation of one or more species.
An error indicating that an unimplemented function has been called.
void getMoleFractions(span< double > x) const
Get the species mole fraction vector.
Definition Phase.cpp:441
void getMassFractions(span< double > y) const
Get the species mass fractions.
Definition Phase.cpp:479
double massFraction(size_t k) const
Return the mass fraction of a single species.
Definition Phase.cpp:463
virtual bool addSpecies(shared_ptr< Species > spec)
Add a Species to this Phase.
Definition Phase.cpp:708
void assertCompressible(const string &setter) const
Ensure that phase is compressible.
Definition Phase.h:835
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:246
virtual map< string, size_t > nativeState() const
Return a map of properties defining the native state of a substance.
Definition Phase.cpp:164
size_t m_kk
Number of species in the phase.
Definition Phase.h:875
virtual void modifySpecies(size_t k, shared_ptr< Species > spec)
Modify the thermodynamic data associated with a species.
Definition Phase.cpp:821
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
Definition Phase.h:569
span< const double > molecularWeights() const
Return a const reference to the internal vector of molecular weights.
Definition Phase.cpp:401
void setState_TD(double t, double rho)
Set the internally stored temperature (K) and density (kg/m^3)
Definition Phase.cpp:375
double temperature() const
Temperature (K).
Definition Phase.h:585
virtual void setPressure(double p)
Set the internally stored pressure (Pa) at constant temperature and composition.
Definition Phase.h:639
double meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition Phase.h:676
virtual void restorePartialState(span< const double > state)
Set the internal thermodynamic state of the phase, excluding composition.
Definition Phase.cpp:237
span< const double > massFractions() const
Return a view of the mass fraction array.
Definition Phase.h:465
void setMassFractionsByName(const Composition &yMap)
Set the species mass fractions by name.
Definition Phase.cpp:364
string speciesName(size_t k) const
Name of the species with index k.
Definition Phase.cpp:143
void moleFractionsToMassFractions(span< const double > X, span< double > Y) const
Converts a mixture composition from mass fractions to mole fractions.
Definition Phase.cpp:956
virtual void setDensity(const double density_)
Set the internally stored density (kg/m^3) of the phase.
Definition Phase.cpp:597
virtual size_t stateSize() const
Return size of vector defining internal state of the phase.
Definition Phase.cpp:249
vector< double > getCompositionFromMap(const Composition &comp) const
Converts a Composition to a vector with entries for each species Species that are not specified are s...
Definition Phase.cpp:929
void setMoleFractionsByName(const Composition &xMap)
Set the species mole fractions by name.
Definition Phase.cpp:326
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition Phase.cpp:447
const vector< string > & elementNames() const
Return a read-only reference to the vector of element names.
Definition Phase.cpp:64
virtual double density() const
Density (kg/m^3).
Definition Phase.h:610
double nAtoms(size_t k, size_t m) const
Number of atoms of element m in species k.
Definition Phase.cpp:101
virtual void setTemperature(double temp)
Set the internally stored temperature of the phase (K).
Definition Phase.h:646
size_t nElements() const
Number of elements.
Definition Phase.cpp:30
virtual void setMoleFractions(span< const double > x)
Set the mole fractions to the specified values.
Definition Phase.cpp:283
const vector< string > & speciesNames() const
Return a const reference to the vector of species names.
Definition Phase.cpp:151
size_t elementIndex(const string &name, bool raise=true) const
Return the index of element named 'name'.
Definition Phase.cpp:51
virtual void setMassFractions(span< const double > y)
Set the mass fractions to the specified values and normalize them.
Definition Phase.cpp:337
double molecularWeight(size_t k) const
Molecular weight of species k.
Definition Phase.cpp:388
shared_ptr< Species > species(const string &name) const
Return the Species object for the named species.
Definition Phase.cpp:881
virtual void invalidateCache()
Invalidate any cached values which are normally updated only when a change in state is detected.
Definition Phase.cpp:910
virtual void setMassFractions_NoNorm(span< const double > y)
Set the mass fractions to the specified values without normalizing.
Definition Phase.cpp:352
virtual size_t partialStateSize() const
Get the size of the partial state vector of the phase.
Definition Phase.h:317
virtual void savePartialState(span< double > state) const
Save the current thermodynamic state of the phase, excluding composition.
Definition Phase.cpp:225
virtual void restoreState(span< const double > state)
Restore the state of the phase from a previously saved state vector.
Definition Phase.cpp:269
virtual double pressure() const
Return the thermodynamic pressure (Pa).
Definition Phase.h:603
string elementName(size_t m) const
Name of the element with index m.
Definition Phase.cpp:43
double 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:561
string name() const
Return the name of the phase.
Definition Phase.cpp:20
virtual void saveState(span< double > state) const
Write to array 'state' the current internal state.
Definition Phase.cpp:257
static ThermoFactory * factory()
Static function that creates a static instance of the factory.
Base class for a phase with thermodynamic properties.
int m_ssConvention
Contains the standard state convention.
virtual double critTemperature() const
Critical temperature (K).
virtual void setState_HP(double h, double p, double tol=1e-9)
Set the internally stored specific enthalpy (J/kg) and pressure (Pa) of the phase.
virtual void getPartialMolarVolumes(span< double > vbar) const
Return an array of partial molar volumes for the species in the mixture.
double electricPotential() const
Returns the electric potential of this phase (V).
virtual void setState_UV(double u, double v, double tol=1e-9)
Set the specific internal energy (J/kg) and specific volume (m^3/kg).
virtual double cp_mole() const
Molar heat capacity at constant pressure and composition [J/kmol/K].
double equivalenceRatio() const
Compute the equivalence ratio for the current mixture from available oxygen and required oxygen.
virtual void setParameters(const AnyMap &phaseNode, const AnyMap &rootNode=AnyMap())
Set equation of state parameters from an AnyMap phase description.
virtual void getParameters(AnyMap &phaseNode) const
Store the parameters of a ThermoPhase object such that an identical one could be reconstructed using ...
virtual double enthalpy_mole() const
Molar enthalpy. Units: J/kmol.
virtual void setState_TP(double t, double p)
Set the temperature (K) and pressure (Pa)
virtual double standardConcentration(size_t k=0) const
Return the standard concentration for the kth species.
virtual void setState_TV(double t, double v, double tol=1e-9)
Set the temperature (K) and specific volume (m^3/kg).
virtual double logStandardConc(size_t k=0) const
Natural logarithm of the standard concentration of the kth species.
virtual void setState_PV(double p, double v, double tol=1e-9)
Set the pressure (Pa) and specific volume (m^3/kg).
virtual void setState(const AnyMap &state)
Set the state using an AnyMap containing any combination of properties supported by the thermodynamic...
virtual double minTemp(size_t k=npos) const
Minimum temperature for which the thermodynamic data for the species or phase are valid.
void setState_SPorSV(double s, double p, double tol=1e-9, bool doSV=false)
Carry out work in SP and SV calculations.
double RT() const
Return the Gas Constant multiplied by the current temperature.
double o2Required(span< const double > y) const
Helper function for computing the amount of oxygen required for complete oxidation.
virtual double critPressure() const
Critical pressure (Pa).
double m_tlast
last value of the temperature processed by reference state
virtual void setState_ST(double s, double t, double tol=1e-9)
Set the specific entropy (J/kg/K) and temperature (K).
virtual void getActivities(span< double > a) const
Get the array of non-dimensional activities at the current solution temperature, pressure,...
virtual void getdlnActCoeffdlnN(const size_t ld, span< double > dlnActCoeffdlnN)
Get the array of derivatives of the log activity coefficients with respect to the log of the species ...
void setState_HPorUV(double h, double p, double tol=1e-9, bool doUV=false)
Carry out work in HP and UV calculations.
double gibbs_mass() const
Specific Gibbs function. Units: J/kg.
string type() const override
String indicating the thermodynamic model implemented.
AnyMap parameters(bool withInput=true) const
Returns the parameters of a ThermoPhase object such that an identical one could be reconstructed usin...
virtual string report(bool show_thermo=true, double threshold=-1e-14) const
returns a summary of the state of the phase as a string
virtual void getActivityConcentrations(span< double > c) const
This method returns an array of generalized concentrations.
virtual double maxTemp(size_t k=npos) const
Maximum temperature for which the thermodynamic data for the species are valid.
double o2Present(span< const double > y) const
Helper function for computing the amount of oxygen available in the current mixture.
virtual void setState_TPY(double t, double p, span< const double > y)
Set the internally stored temperature (K), pressure (Pa), and mass fractions of the phase.
virtual void setState_TPX(double t, double p, span< const double > x)
Set the temperature (K), pressure (Pa), and mole fractions.
virtual string phaseOfMatter() const
String indicating the mechanical phase of the matter in this Phase.
virtual void setState_Tsat(double t, double x)
Set the state to a saturated system at a particular temperature.
virtual double entropy_mole() const
Molar entropy. Units: J/kmol/K.
double cv_mass() const
Specific heat at constant volume and composition [J/kg/K].
virtual void getLnActivityCoefficients(span< double > lnac) const
Get the array of non-dimensional molar-based ln activity coefficients at the current solution tempera...
virtual int activityConvention() const
This method returns the convention used in specification of the activities, of which there are curren...
virtual void initThermo()
Initialize the ThermoPhase object after all species have been set up.
double entropy_mass() const
Specific entropy. Units: J/kg/K.
void getElectrochemPotentials(span< double > mu) const
Get the species electrochemical potentials.
virtual MultiSpeciesThermo & speciesThermo(int k=-1)
Return a changeable reference to the calculation manager for species reference-state thermodynamic pr...
virtual void setState_UP(double u, double p, double tol=1e-9)
Set the specific internal energy (J/kg) and pressure (Pa).
void initThermoFile(const string &inputFile, const string &id)
Initialize a ThermoPhase object using an input file.
shared_ptr< Solution > root() const
Get the Solution object containing this ThermoPhase object and linked Kinetics and Transport objects.
virtual void setState_SP(double s, double p, double tol=1e-9)
Set the specific entropy (J/kg/K) and pressure (Pa).
virtual int standardStateConvention() const
This method returns the convention used in specification of the standard state, of which there are cu...
void modifySpecies(size_t k, shared_ptr< Species > spec) override
Modify the thermodynamic data associated with a species.
virtual void setState_SH(double s, double h, double tol=1e-9)
Set the specific entropy (J/kg/K) and the specific enthalpy (J/kg)
virtual void getPartialMolarIntEnergies(span< double > ubar) const
Return an array of partial molar internal energies for the species in the mixture.
void invalidateCache() override
Invalidate any cached values which are normally updated only when a change in state is detected.
double stoichAirFuelRatio(span< const double > fuelComp, span< const double > oxComp, ThermoBasis basis=ThermoBasis::molar) const
Compute the stoichiometric air to fuel ratio (kg oxidizer / kg fuel) given fuel and oxidizer composit...
virtual double internalPressure() const
Return the internal pressure [Pa].
virtual void resetHf298(const size_t k=npos)
Restore the original heat of formation of one or more species.
double cp_mass() const
Specific heat at constant pressure and composition [J/kg/K].
virtual void setState_TH(double t, double h, double tol=1e-9)
Set the temperature (K) and the specific enthalpy (J/kg)
double intEnergy_mass() const
Specific internal energy. Units: J/kg.
virtual void getPartialMolarIntEnergies_TV(span< double > utilde) const
Return an array of partial molar internal energies at constant temperature and volume [J/kmol].
virtual Units standardConcentrationUnits() const
Returns the units of the "standard concentration" for this phase.
void setMixtureFraction(double mixFrac, span< const double > fuelComp, span< const double > oxComp, ThermoBasis basis=ThermoBasis::molar)
Set the mixture composition according to the mixture fraction = kg fuel / (kg oxidizer + kg fuel)
double mixtureFraction(span< const double > fuelComp, span< const double > oxComp, ThermoBasis basis=ThermoBasis::molar, const string &element="Bilger") const
Compute the mixture fraction = kg fuel / (kg oxidizer + kg fuel) for the current mixture given fuel a...
void setEquivalenceRatio(double phi, span< const double > fuelComp, span< const double > oxComp, ThermoBasis basis=ThermoBasis::molar)
Set the mixture composition according to the equivalence ratio.
virtual double cv_mole() const
Molar heat capacity at constant volume and composition [J/kmol/K].
MultiSpeciesThermo m_spthermo
Pointer to the calculation manager for species reference-state thermodynamic properties.
virtual double satPressure(double t)
Return the saturation pressure given the temperature.
virtual void getChemPotentials(span< double > mu) const
Get the species chemical potentials. Units: J/kmol.
bool addSpecies(shared_ptr< Species > spec) override
Add a Species to this Phase.
AnyMap m_input
Data supplied via setParameters.
virtual double intEnergy_mole() const
Molar internal energy. Units: J/kmol.
virtual void setState_DP(double rho, double p)
Set the density (kg/m**3) and pressure (Pa) at constant composition.
void setState_TPQ(double T, double P, double Q)
Set the temperature, pressure, and vapor fraction (quality).
virtual void setState_VH(double v, double h, double tol=1e-9)
Set the specific volume (m^3/kg) and the specific enthalpy (J/kg)
virtual double gibbs_mole() const
Molar Gibbs function. Units: J/kmol.
virtual void setState_SV(double s, double v, double tol=1e-9)
Set the specific entropy (J/kg/K) and specific volume (m^3/kg).
const AnyMap & input() const
Access input data associated with the phase description.
virtual void setState_Psat(double p, double x)
Set the state to a saturated system at a particular pressure.
void setState_conditional_TP(double t, double p, bool set_p)
Helper function used by setState_HPorUV and setState_SPorSV.
virtual void getActivityCoefficients(span< double > ac) const
Get the array of non-dimensional molar-based activity coefficients at the current solution temperatur...
shared_ptr< ThermoPhase > clone() const
Create a new ThermoPhase object using the same species definitions, thermodynamic parameters,...
double enthalpy_mass() const
Specific enthalpy. Units: J/kg.
A representation of the units associated with a dimensional quantity.
Definition Units.h:35
Composition parseCompString(const string &ss, const vector< string > &names)
Parse a composition string into a map consisting of individual key:composition pairs.
void equilibrate(const string &XY, const string &solver="auto", double rtol=1e-9, int max_steps=50000, int max_iter=100, int estimate_equil=0, int log_level=0)
Equilibrate a ThermoPhase object.
void debuglog(const string &msg, int loglevel)
Write a message to the log only if loglevel > 0.
Definition global.h:154
double dot(InputIter x_begin, InputIter x_end, InputIter2 y_begin)
Function that calculates a templated inner product.
Definition utilities.h:96
T clip(const T &value, const T &lower, const T &upper)
Clip value such that lower <= value <= upper.
Definition global.h:326
const double Faraday
Faraday constant [C/kmol].
Definition ct_defs.h:134
const double OneAtm
One atmosphere [Pa].
Definition ct_defs.h:99
shared_ptr< ThermoPhase > newThermo(const AnyMap &phaseNode, const AnyMap &rootNode)
Create a new ThermoPhase object and initialize it.
void setupPhase(ThermoPhase &thermo, const AnyMap &phaseNode, const AnyMap &rootNode)
Initialize a ThermoPhase object.
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:183
const int cAC_CONVENTION_MOLAR
Standard state uses the molar convention.
const double SmallNumber
smallest number to compare to zero.
Definition ct_defs.h:161
ThermoBasis
Differentiate between mole fractions and mass fractions for input mixture composition.
map< string, double > Composition
Map from string names to doubles.
Definition ct_defs.h:180
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.
Contains declarations for string manipulation functions within Cantera.