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
120void ThermoPhase::setState_TPX(double t, double p, span<const double> x)
121{
123 setState_TP(t,p);
124}
125
126void ThermoPhase::setState_TPX(double t, double p, const Composition& x)
127{
129 setState_TP(t,p);
130}
131
132void ThermoPhase::setState_TPX(double t, double p, const string& x)
133{
135 setState_TP(t,p);
136}
137
138void ThermoPhase::setState_TPY(double t, double p, span<const double> y)
139{
141 setState_TP(t,p);
142}
143
144void ThermoPhase::setState_TPY(double t, double p, const Composition& y)
145{
147 setState_TP(t,p);
148}
149
150void ThermoPhase::setState_TPY(double t, double p, const string& y)
151{
153 setState_TP(t,p);
154}
155
156void ThermoPhase::setState_TP(double t, double p)
157{
158 vector<double> state(partialStateSize());
159 savePartialState(state);
160 try {
162 setPressure(p);
163 } catch (std::exception&) {
164 restorePartialState(state);
165 throw;
166 }
167}
168
169void ThermoPhase::setState_HP(double Htarget, double p, double rtol)
170{
171 vector<double> state(partialStateSize());
172 savePartialState(state);
173 try {
174 setState_HPorUV(Htarget, p, rtol, false);
175 } catch (std::exception&) {
176 restorePartialState(state);
177 throw;
178 }
179}
180
181void ThermoPhase::setState_UV(double u, double v, double rtol)
182{
183 assertCompressible("setState_UV");
184 vector<double> state(partialStateSize());
185 savePartialState(state);
186 try {
187 setState_HPorUV(u, v, rtol, true);
188 } catch (std::exception&) {
189 restorePartialState(state);
190 throw;
191 }
192}
193
194void ThermoPhase::setState(const AnyMap& input_state)
195{
196 AnyMap state = input_state;
197
198 // Remap allowable synonyms
199 if (state.hasKey("mass-fractions")) {
200 state["Y"] = state["mass-fractions"];
201 state.erase("mass-fractions");
202 }
203 if (state.hasKey("mole-fractions")) {
204 state["X"] = state["mole-fractions"];
205 state.erase("mole-fractions");
206 }
207 if (state.hasKey("temperature")) {
208 state["T"] = state["temperature"];
209 }
210 if (state.hasKey("pressure")) {
211 state["P"] = state["pressure"];
212 }
213 if (state.hasKey("enthalpy")) {
214 state["H"] = state["enthalpy"];
215 }
216 if (state.hasKey("int-energy")) {
217 state["U"] = state["int-energy"];
218 }
219 if (state.hasKey("internal-energy")) {
220 state["U"] = state["internal-energy"];
221 }
222 if (state.hasKey("specific-volume")) {
223 state["V"] = state["specific-volume"];
224 }
225 if (state.hasKey("entropy")) {
226 state["S"] = state["entropy"];
227 }
228 if (state.hasKey("density")) {
229 state["D"] = state["density"];
230 }
231 if (state.hasKey("vapor-fraction")) {
232 state["Q"] = state["vapor-fraction"];
233 }
234
235 // Set composition
236 if (state.hasKey("X")) {
237 if (state["X"].is<string>()) {
238 setMoleFractionsByName(state["X"].asString());
239 } else {
240 setMoleFractionsByName(state["X"].asMap<double>());
241 }
242 state.erase("X");
243 } else if (state.hasKey("Y")) {
244 if (state["Y"].is<string>()) {
245 setMassFractionsByName(state["Y"].asString());
246 } else {
247 setMassFractionsByName(state["Y"].asMap<double>());
248 }
249 state.erase("Y");
250 }
251 // set thermodynamic state using whichever property set is found
252 if (state.size() == 0) {
253 setState_TP(298.15, OneAtm);
254 } else if (state.hasKey("T") && state.hasKey("P")) {
255 double T = state.convert("T", "K");
256 double P = state.convert("P", "Pa");
257 if (state.hasKey("Q")) {
258 setState_TPQ(T, P, state["Q"].asDouble());
259 } else {
260 setState_TP(T, P);
261 }
262 } else if (state.hasKey("T") && state.hasKey("D")) {
263 setState_TD(state.convert("T", "K"), state.convert("D", "kg/m^3"));
264 } else if (state.hasKey("T") && state.hasKey("V")) {
265 setState_TV(state.convert("T", "K"), state.convert("V", "m^3/kg"));
266 } else if (state.hasKey("H") && state.hasKey("P")) {
267 setState_HP(state.convert("H", "J/kg"), state.convert("P", "Pa"));
268 } else if (state.hasKey("U") && state.hasKey("V")) {
269 setState_UV(state.convert("U", "J/kg"), state.convert("V", "m^3/kg"));
270 } else if (state.hasKey("S") && state.hasKey("P")) {
271 setState_SP(state.convert("S", "J/kg/K"), state.convert("P", "Pa"));
272 } else if (state.hasKey("S") && state.hasKey("V")) {
273 setState_SV(state.convert("S", "J/kg/K"), state.convert("V", "m^3/kg"));
274 } else if (state.hasKey("S") && state.hasKey("T")) {
275 setState_ST(state.convert("S", "J/kg/K"), state.convert("T", "K"));
276 } else if (state.hasKey("P") && state.hasKey("V")) {
277 setState_PV(state.convert("P", "Pa"), state.convert("V", "m^3/kg"));
278 } else if (state.hasKey("U") && state.hasKey("P")) {
279 setState_UP(state.convert("U", "J/kg"), state.convert("P", "Pa"));
280 } else if (state.hasKey("V") && state.hasKey("H")) {
281 setState_VH(state.convert("V", "m^3/kg"), state.convert("H", "J/kg"));
282 } else if (state.hasKey("T") && state.hasKey("H")) {
283 setState_TH(state.convert("T", "K"), state.convert("H", "J/kg"));
284 } else if (state.hasKey("S") && state.hasKey("H")) {
285 setState_SH(state.convert("S", "J/kg/K"), state.convert("H", "J/kg"));
286 } else if (state.hasKey("D") && state.hasKey("P")) {
287 setState_DP(state.convert("D", "kg/m^3"), state.convert("P", "Pa"));
288 } else if (state.hasKey("P") && state.hasKey("Q")) {
289 setState_Psat(state.convert("P", "Pa"), state["Q"].asDouble());
290 } else if (state.hasKey("T") && state.hasKey("Q")) {
291 setState_Tsat(state.convert("T", "K"), state["Q"].asDouble());
292 } else if (state.hasKey("T")) {
293 setState_TP(state.convert("T", "K"), OneAtm);
294 } else if (state.hasKey("P")) {
295 setState_TP(298.15, state.convert("P", "Pa"));
296 } else {
297 throw CanteraError("ThermoPhase::setState",
298 "'state' did not specify a recognized set of properties.\n"
299 "Keys provided were: {}", input_state.keys_str());
300 }
301}
302
303void ThermoPhase::setState_conditional_TP(double t, double p, bool set_p)
304{
306 if (set_p) {
307 setPressure(p);
308 }
309}
310
311void ThermoPhase::setState_HPorUV(double Htarget, double p,
312 double rtol, bool doUV)
313{
314 double dt;
315 double v = 0.0;
316
317 // Assign the specific volume or pressure and make sure it's positive
318 if (doUV) {
319 double v = p;
320 if (v < 1.0E-300) {
321 throw CanteraError("ThermoPhase::setState_HPorUV (UV)",
322 "Input specific volume is too small or negative. v = {}", v);
323 }
324 setDensity(1.0/v);
325 } else {
326 if (p < 1.0E-300) {
327 throw CanteraError("ThermoPhase::setState_HPorUV (HP)",
328 "Input pressure is too small or negative. p = {}", p);
329 }
330 setPressure(p);
331 }
332 double Tmax = maxTemp() + 0.1;
333 double Tmin = minTemp() - 0.1;
334
335 // Make sure we are within the temperature bounds at the start
336 // of the iteration
337 double Tnew = temperature();
338 double Tinit = Tnew;
339 if (Tnew > Tmax) {
340 Tnew = Tmax - 1.0;
341 } else if (Tnew < Tmin) {
342 Tnew = Tmin + 1.0;
343 }
344 if (Tnew != Tinit) {
345 setState_conditional_TP(Tnew, p, !doUV);
346 }
347
348 double Hnew = (doUV) ? intEnergy_mass() : enthalpy_mass();
349 double Cpnew = (doUV) ? cv_mass() : cp_mass();
350 double Htop = Hnew;
351 double Ttop = Tnew;
352 double Hbot = Hnew;
353 double Tbot = Tnew;
354
355 bool ignoreBounds = false;
356 // Unstable phases are those for which cp < 0.0. These are possible for
357 // cases where we have passed the spinodal curve.
358 bool unstablePhase = false;
359 // Counter indicating the last temperature point where the
360 // phase was unstable
361 double Tunstable = -1.0;
362 bool unstablePhaseNew = false;
363
364 // Newton iteration
365 for (int n = 0; n < 500; n++) {
366 double Told = Tnew;
367 double Hold = Hnew;
368 double cpd = Cpnew;
369 if (cpd < 0.0) {
370 unstablePhase = true;
371 Tunstable = Tnew;
372 }
373 // limit step size to 100 K
374 dt = clip((Htarget - Hold)/cpd, -100.0, 100.0);
375
376 // Calculate the new T
377 Tnew = Told + dt;
378
379 // Limit the step size so that we are convergent This is the step that
380 // makes it different from a Newton's algorithm
381 if ((dt > 0.0 && unstablePhase) || (dt <= 0.0 && !unstablePhase)) {
382 if (Hbot < Htarget && Tnew < (0.75 * Tbot + 0.25 * Told)) {
383 dt = 0.75 * (Tbot - Told);
384 Tnew = Told + dt;
385 }
386 } else if (Htop > Htarget && Tnew > (0.75 * Ttop + 0.25 * Told)) {
387 dt = 0.75 * (Ttop - Told);
388 Tnew = Told + dt;
389 }
390
391 // Check Max and Min values
392 if (Tnew > Tmax && !ignoreBounds) {
393 setState_conditional_TP(Tmax, p, !doUV);
394 double Hmax = (doUV) ? intEnergy_mass() : enthalpy_mass();
395 if (Hmax >= Htarget) {
396 if (Htop < Htarget) {
397 Ttop = Tmax;
398 Htop = Hmax;
399 }
400 } else {
401 Tnew = Tmax + 1.0;
402 ignoreBounds = true;
403 }
404 }
405 if (Tnew < Tmin && !ignoreBounds) {
406 setState_conditional_TP(Tmin, p, !doUV);
407 double Hmin = (doUV) ? intEnergy_mass() : enthalpy_mass();
408 if (Hmin <= Htarget) {
409 if (Hbot > Htarget) {
410 Tbot = Tmin;
411 Hbot = Hmin;
412 }
413 } else {
414 Tnew = Tmin - 1.0;
415 ignoreBounds = true;
416 }
417 }
418
419 // Try to keep phase within its region of stability
420 // -> Could do a lot better if I calculate the
421 // spinodal value of H.
422 for (int its = 0; its < 10; its++) {
423 Tnew = Told + dt;
424 if (Tnew < Told / 3.0) {
425 Tnew = Told / 3.0;
426 dt = -2.0 * Told / 3.0;
427 }
428 setState_conditional_TP(Tnew, p, !doUV);
429 if (doUV) {
430 Hnew = intEnergy_mass();
431 Cpnew = cv_mass();
432 } else {
433 Hnew = enthalpy_mass();
434 Cpnew = cp_mass();
435 }
436 if (Cpnew < 0.0) {
437 unstablePhaseNew = true;
438 Tunstable = Tnew;
439 } else {
440 unstablePhaseNew = false;
441 break;
442 }
443 if (unstablePhase == false && unstablePhaseNew == true) {
444 dt *= 0.25;
445 }
446 }
447
448 if (Hnew == Htarget) {
449 return;
450 } else if (Hnew > Htarget && (Htop < Htarget || Hnew < Htop)) {
451 Htop = Hnew;
452 Ttop = Tnew;
453 } else if (Hnew < Htarget && (Hbot > Htarget || Hnew > Hbot)) {
454 Hbot = Hnew;
455 Tbot = Tnew;
456 }
457 // Convergence in H
458 double Herr = Htarget - Hnew;
459 double acpd = std::max(fabs(cpd), 1.0E-5);
460 double denom = std::max(fabs(Htarget), acpd * Tnew);
461 double HConvErr = fabs((Herr)/denom);
462 if (HConvErr < rtol || fabs(dt/Tnew) < rtol) {
463 return;
464 }
465 }
466 // We are here when there hasn't been convergence
467
468 // Formulate a detailed error message, since questions seem to arise often
469 // about the lack of convergence.
470 string ErrString = "No convergence in 500 iterations\n";
471 if (doUV) {
472 ErrString += fmt::format(
473 "\tTarget Internal Energy = {}\n"
474 "\tCurrent Specific Volume = {}\n"
475 "\tStarting Temperature = {}\n"
476 "\tCurrent Temperature = {}\n"
477 "\tCurrent Internal Energy = {}\n"
478 "\tCurrent Delta T = {}\n",
479 Htarget, v, Tinit, Tnew, Hnew, dt);
480 } else {
481 ErrString += fmt::format(
482 "\tTarget Enthalpy = {}\n"
483 "\tCurrent Pressure = {}\n"
484 "\tStarting Temperature = {}\n"
485 "\tCurrent Temperature = {}\n"
486 "\tCurrent Enthalpy = {}\n"
487 "\tCurrent Delta T = {}\n",
488 Htarget, p, Tinit, Tnew, Hnew, dt);
489 }
490 if (unstablePhase) {
491 ErrString += fmt::format(
492 "\t - The phase became unstable (Cp < 0) T_unstable_last = {}\n",
493 Tunstable);
494 }
495 if (doUV) {
496 throw CanteraError("ThermoPhase::setState_HPorUV (UV)", ErrString);
497 } else {
498 throw CanteraError("ThermoPhase::setState_HPorUV (HP)", ErrString);
499 }
500}
501
502void ThermoPhase::setState_SP(double Starget, double p, double rtol)
503{
504 vector<double> state(partialStateSize());
505 savePartialState(state);
506 try {
507 setState_SPorSV(Starget, p, rtol, false);
508 } catch (std::exception&) {
509 restorePartialState(state);
510 throw;
511 }
512}
513
514void ThermoPhase::setState_SV(double Starget, double v, double rtol)
515{
516 assertCompressible("setState_SV");
517 vector<double> state(partialStateSize());
518 savePartialState(state);
519 try {
520 setState_SPorSV(Starget, v, rtol, true);
521 } catch (std::exception&) {
522 restorePartialState(state);
523 throw;
524 }
525}
526
527void ThermoPhase::setState_SPorSV(double Starget, double p,
528 double rtol, bool doSV)
529{
530 double v = 0.0;
531 double dt;
532 if (doSV) {
533 v = p;
534 if (v < 1.0E-300) {
535 throw CanteraError("ThermoPhase::setState_SPorSV (SV)",
536 "Input specific volume is too small or negative. v = {}", v);
537 }
538 setDensity(1.0/v);
539 } else {
540 if (p < 1.0E-300) {
541 throw CanteraError("ThermoPhase::setState_SPorSV (SP)",
542 "Input pressure is too small or negative. p = {}", p);
543 }
544 setPressure(p);
545 }
546 double Tmax = maxTemp() + 0.1;
547 double Tmin = minTemp() - 0.1;
548
549 // Make sure we are within the temperature bounds at the start
550 // of the iteration
551 double Tnew = temperature();
552 double Tinit = Tnew;
553 if (Tnew > Tmax) {
554 Tnew = Tmax - 1.0;
555 } else if (Tnew < Tmin) {
556 Tnew = Tmin + 1.0;
557 }
558 if (Tnew != Tinit) {
559 setState_conditional_TP(Tnew, p, !doSV);
560 }
561
562 double Snew = entropy_mass();
563 double Cpnew = (doSV) ? cv_mass() : cp_mass();
564 double Stop = Snew;
565 double Ttop = Tnew;
566 double Sbot = Snew;
567 double Tbot = Tnew;
568
569 bool ignoreBounds = false;
570 // Unstable phases are those for which Cp < 0.0. These are possible for
571 // cases where we have passed the spinodal curve.
572 bool unstablePhase = false;
573 double Tunstable = -1.0;
574 bool unstablePhaseNew = false;
575
576 // Newton iteration
577 for (int n = 0; n < 500; n++) {
578 double Told = Tnew;
579 double Sold = Snew;
580 double cpd = Cpnew;
581 if (cpd < 0.0) {
582 unstablePhase = true;
583 Tunstable = Tnew;
584 }
585 // limit step size to 100 K
586 dt = clip((Starget - Sold)*Told/cpd, -100.0, 100.0);
587 Tnew = Told + dt;
588
589 // Limit the step size so that we are convergent
590 if ((dt > 0.0 && unstablePhase) || (dt <= 0.0 && !unstablePhase)) {
591 if (Sbot < Starget && Tnew < Tbot) {
592 dt = 0.75 * (Tbot - Told);
593 Tnew = Told + dt;
594 }
595 } else if (Stop > Starget && Tnew > Ttop) {
596 dt = 0.75 * (Ttop - Told);
597 Tnew = Told + dt;
598 }
599
600 // Check Max and Min values
601 if (Tnew > Tmax && !ignoreBounds) {
602 setState_conditional_TP(Tmax, p, !doSV);
603 double Smax = entropy_mass();
604 if (Smax >= Starget) {
605 if (Stop < Starget) {
606 Ttop = Tmax;
607 Stop = Smax;
608 }
609 } else {
610 Tnew = Tmax + 1.0;
611 ignoreBounds = true;
612 }
613 } else if (Tnew < Tmin && !ignoreBounds) {
614 setState_conditional_TP(Tmin, p, !doSV);
615 double Smin = entropy_mass();
616 if (Smin <= Starget) {
617 if (Sbot > Starget) {
618 Tbot = Tmin;
619 Sbot = Smin;
620 }
621 } else {
622 Tnew = Tmin - 1.0;
623 ignoreBounds = true;
624 }
625 }
626
627 // Try to keep phase within its region of stability
628 // -> Could do a lot better if I calculate the
629 // spinodal value of H.
630 for (int its = 0; its < 10; its++) {
631 Tnew = Told + dt;
632 setState_conditional_TP(Tnew, p, !doSV);
633 Cpnew = (doSV) ? cv_mass() : cp_mass();
634 Snew = entropy_mass();
635 if (Cpnew < 0.0) {
636 unstablePhaseNew = true;
637 Tunstable = Tnew;
638 } else {
639 unstablePhaseNew = false;
640 break;
641 }
642 if (unstablePhase == false && unstablePhaseNew == true) {
643 dt *= 0.25;
644 }
645 }
646
647 if (Snew == Starget) {
648 return;
649 } else if (Snew > Starget && (Stop < Starget || Snew < Stop)) {
650 Stop = Snew;
651 Ttop = Tnew;
652 } else if (Snew < Starget && (Sbot > Starget || Snew > Sbot)) {
653 Sbot = Snew;
654 Tbot = Tnew;
655 }
656 // Convergence in S
657 double Serr = Starget - Snew;
658 double acpd = std::max(fabs(cpd), 1.0E-5);
659 double denom = std::max(fabs(Starget), acpd * Tnew);
660 double SConvErr = fabs((Serr * Tnew)/denom);
661 if (SConvErr < rtol || fabs(dt/Tnew) < rtol) {
662 return;
663 }
664 }
665 // We are here when there hasn't been convergence
666
667 // Formulate a detailed error message, since questions seem to arise often
668 // about the lack of convergence.
669 string ErrString = "No convergence in 500 iterations\n";
670 if (doSV) {
671 ErrString += fmt::format(
672 "\tTarget Entropy = {}\n"
673 "\tCurrent Specific Volume = {}\n"
674 "\tStarting Temperature = {}\n"
675 "\tCurrent Temperature = {}\n"
676 "\tCurrent Entropy = {}\n"
677 "\tCurrent Delta T = {}\n",
678 Starget, v, Tinit, Tnew, Snew, dt);
679 } else {
680 ErrString += fmt::format(
681 "\tTarget Entropy = {}\n"
682 "\tCurrent Pressure = {}\n"
683 "\tStarting Temperature = {}\n"
684 "\tCurrent Temperature = {}\n"
685 "\tCurrent Entropy = {}\n"
686 "\tCurrent Delta T = {}\n",
687 Starget, p, Tinit, Tnew, Snew, dt);
688 }
689 if (unstablePhase) {
690 ErrString += fmt::format("\t - The phase became unstable (Cp < 0) T_unstable_last = {}\n",
691 Tunstable);
692 }
693 if (doSV) {
694 throw CanteraError("ThermoPhase::setState_SPorSV (SV)", ErrString);
695 } else {
696 throw CanteraError("ThermoPhase::setState_SPorSV (SP)", ErrString);
697 }
698}
699
700double ThermoPhase::o2Required(span<const double> y) const
701{
702 checkArraySize("ThermoPhase::o2Required", y.size(), m_kk);
703 // indices of fuel elements
704 size_t iC = elementIndex("C", false);
705 size_t iS = elementIndex("S", false);
706 size_t iH = elementIndex("H", false);
707
708 double o2req = 0.0;
709 double sum = 0.0;
710 for (size_t k = 0; k != m_kk; ++k) {
711 sum += y[k];
712 double x = y[k] / molecularWeights()[k];
713 if (iC != npos) {
714 o2req += x * nAtoms(k, iC);
715 }
716 if (iS != npos) {
717 o2req += x * nAtoms(k, iS);
718 }
719 if (iH != npos) {
720 o2req += x * 0.25 * nAtoms(k, iH);
721 }
722 }
723 if (sum == 0.0) {
724 throw CanteraError("ThermoPhase::o2Required",
725 "No composition specified");
726 }
727 return o2req/sum;
728}
729
730double ThermoPhase::o2Present(span<const double> y) const
731{
732 checkArraySize("ThermoPhase::o2Present", y.size(), m_kk);
733 size_t iO = elementIndex("O", true);
734 double o2pres = 0.0;
735 double sum = 0.0;
736 for (size_t k = 0; k != m_kk; ++k) {
737 sum += y[k];
738 o2pres += y[k] / molecularWeights()[k] * nAtoms(k, iO);
739 }
740 if (sum == 0.0) {
741 throw CanteraError("ThermoPhase::o2Present",
742 "No composition specified");
743 }
744 return 0.5 * o2pres / sum;
745}
746
748 const Composition& oxComp,
749 ThermoBasis basis) const
750{
751 vector<double> fuel(getCompositionFromMap(fuelComp));
752 vector<double> ox(getCompositionFromMap(oxComp));
753 return stoichAirFuelRatio(fuel, ox, basis);
754}
755
756double ThermoPhase::stoichAirFuelRatio(const string& fuelComp, const string& oxComp,
757 ThermoBasis basis) const
758{
759 return stoichAirFuelRatio(
760 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
761 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
762 basis);
763}
764
765double ThermoPhase::stoichAirFuelRatio(span<const double> fuelComp,
766 span<const double> oxComp,
767 ThermoBasis basis) const
768{
769 vector<double> fuel, ox;
770 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
771 fuel.resize(m_kk);
772 ox.resize(m_kk);
773 moleFractionsToMassFractions(fuelComp, fuel);
775 fuelComp = fuel;
776 oxComp = ox;
777 }
778
779 double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp);
780 double o2_required_ox = o2Required(oxComp) - o2Present(oxComp);
781
782 if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) {
783 throw CanteraError("ThermoPhase::stoichAirFuelRatio",
784 "Fuel composition contains too much oxygen or "
785 "oxidizer contains not enough oxygen. "
786 "Fuel and oxidizer composition mixed up?");
787 }
788
789 if (o2_required_ox == 0.0) {
790 return std::numeric_limits<double>::infinity();
791 }
792
793 return o2_required_fuel / (-o2_required_ox);
794}
795
796void ThermoPhase::setEquivalenceRatio(double phi, span<const double> fuelComp,
797 span<const double> oxComp, ThermoBasis basis)
798{
799 if (phi < 0.0) {
800 throw CanteraError("ThermoPhase::setEquivalenceRatio",
801 "Equivalence ratio phi must be >= 0");
802 }
803
804 double p = pressure();
805
806 vector<double> fuel, ox;
807 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
808 fuel.resize(m_kk);
809 ox.resize(m_kk);
810 moleFractionsToMassFractions(fuelComp, fuel);
812 fuelComp = fuel;
813 oxComp = ox;
814 }
815
816 double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass);
817 double sum_f = std::accumulate(fuelComp.begin(), fuelComp.end(), 0.0);
818 double sum_o = std::accumulate(oxComp.begin(), oxComp.end(), 0.0);
819
820 vector<double> y(m_kk);
821 for (size_t k = 0; k != m_kk; ++k) {
822 y[k] = phi * fuelComp[k]/sum_f + AFR_st * oxComp[k]/sum_o;
823 }
824
826 setPressure(p);
827}
828
829void ThermoPhase::setEquivalenceRatio(double phi, const string& fuelComp,
830 const string& oxComp, ThermoBasis basis)
831{
833 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
834 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
835 basis);
836}
837
838void ThermoPhase::setEquivalenceRatio(double phi, const Composition& fuelComp,
839 const Composition& oxComp, ThermoBasis basis)
840{
841 vector<double> fuel = getCompositionFromMap(fuelComp);
842 vector<double> ox = getCompositionFromMap(oxComp);
843 setEquivalenceRatio(phi, fuel, ox, basis);
844}
845
847{
848 span<const double> y = massFractions();
849 double o2_required = o2Required(y);
850 double o2_present = o2Present(y);
851
852 if (o2_present == 0.0) { // pure fuel
853 return std::numeric_limits<double>::infinity();
854 }
855
856 return o2_required / o2_present;
857}
858
860 const Composition& oxComp,
861 ThermoBasis basis) const
862{
863 vector<double> fuel(getCompositionFromMap(fuelComp));
864 vector<double> ox(getCompositionFromMap(oxComp));
865 return equivalenceRatio(fuel, ox, basis);
866}
867
868double ThermoPhase::equivalenceRatio(const string& fuelComp, const string& oxComp,
869 ThermoBasis basis) const
870{
871 return equivalenceRatio(
872 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
873 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
874 basis);
875}
876
877double ThermoPhase::equivalenceRatio(span<const double> fuelComp,
878 span<const double> oxComp, ThermoBasis basis) const
879{
880 double Z = mixtureFraction(fuelComp, oxComp, basis);
881
882 if (Z == 0.0) {
883 return 0.0; // pure oxidizer
884 }
885
886 if (Z == 1.0) {
887 return std::numeric_limits<double>::infinity(); // pure fuel
888 }
889
890 vector<double> fuel, ox;
891 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
892 fuel.resize(m_kk);
893 ox.resize(m_kk);
894 moleFractionsToMassFractions(fuelComp, fuel);
896 fuelComp = fuel;
897 oxComp = ox;
898 }
899
900 double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass);
901
902 return std::max(Z / (1.0 - Z) * AFR_st, 0.0);
903}
904
905void ThermoPhase::setMixtureFraction(double mixFrac, const Composition& fuelComp,
906 const Composition& oxComp, ThermoBasis basis)
907{
908 vector<double> fuel(getCompositionFromMap(fuelComp));
909 vector<double> ox(getCompositionFromMap(oxComp));
910 setMixtureFraction(mixFrac, fuel, ox, basis);
911}
912
913void ThermoPhase::setMixtureFraction(double mixFrac, const string& fuelComp,
914 const string& oxComp, ThermoBasis basis)
915{
916 setMixtureFraction(mixFrac,
917 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
918 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
919 basis);
920}
921
922void ThermoPhase::setMixtureFraction(double mixFrac, span<const double> fuelComp,
923 span<const double> oxComp, ThermoBasis basis)
924{
925 checkArraySize("ThermoPhase::setMixtureFraction", fuelComp.size(), m_kk);
926 checkArraySize("ThermoPhase::setMixtureFraction", oxComp.size(), m_kk);
927 if (mixFrac < 0.0 || mixFrac > 1.0) {
928 throw CanteraError("ThermoPhase::setMixtureFraction",
929 "Mixture fraction must be between 0 and 1");
930 }
931
932 vector<double> fuel, ox;
933 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
934 fuel.resize(m_kk);
935 ox.resize(m_kk);
936 moleFractionsToMassFractions(fuelComp, fuel);
938 fuelComp = fuel;
939 oxComp = ox;
940 }
941
942 double sum_yf = std::accumulate(fuelComp.begin(), fuelComp.end(), 0.0);
943 double sum_yo = std::accumulate(oxComp.begin(), oxComp.end(), 0.0);
944
945 if (sum_yf == 0.0 || sum_yo == 0.0) {
946 throw CanteraError("ThermoPhase::setMixtureFraction",
947 "No fuel and/or oxidizer composition specified");
948 }
949
950 double p = pressure();
951
952 vector<double> y(m_kk);
953
954 for (size_t k = 0; k != m_kk; ++k) {
955 y[k] = mixFrac * fuelComp[k]/sum_yf + (1.0-mixFrac) * oxComp[k]/sum_yo;
956 }
957
959 setPressure(p);
960}
961
963 const Composition& oxComp,
964 ThermoBasis basis,
965 const string& element) const
966{
967 vector<double> fuel(getCompositionFromMap(fuelComp));
968 vector<double> ox(getCompositionFromMap(oxComp));
969 return mixtureFraction(fuel, ox, basis, element);
970}
971
972double ThermoPhase::mixtureFraction(const string& fuelComp, const string& oxComp,
973 ThermoBasis basis, const string& element) const
974{
975 return mixtureFraction(
976 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
977 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
978 basis, element);
979}
980
981double ThermoPhase::mixtureFraction(span<const double> fuelComp,
982 span<const double> oxComp,
983 ThermoBasis basis, const string& element) const
984{
985 checkArraySize("ThermoPhase::mixtureFraction", fuelComp.size(), m_kk);
986 checkArraySize("ThermoPhase::mixtureFraction", oxComp.size(), m_kk);
987 vector<double> fuel, ox;
988 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
989 fuel.resize(m_kk);
990 ox.resize(m_kk);
991 moleFractionsToMassFractions(fuelComp, fuel);
993 fuelComp = fuel;
994 oxComp = ox;
995 }
996
997 if (element == "Bilger") // compute the mixture fraction based on the Bilger mixture fraction
998 {
999 double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp);
1000 double o2_required_ox = o2Required(oxComp) - o2Present(oxComp);
1001 span<const double> y = massFractions();
1002 double o2_required_mix = o2Required(y) - o2Present(y);
1003
1004 if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) {
1005 throw CanteraError("ThermoPhase::mixtureFraction",
1006 "Fuel composition contains too much oxygen or "
1007 "oxidizer contains not enough oxygen. "
1008 "Fuel and oxidizer composition mixed up?");
1009 }
1010
1011 double denominator = o2_required_fuel - o2_required_ox;
1012
1013 if (denominator == 0.0) {
1014 throw CanteraError("ThermoPhase::mixtureFraction",
1015 "Fuel and oxidizer have the same composition");
1016 }
1017
1018 double Z = (o2_required_mix - o2_required_ox) / denominator;
1019
1020 return std::min(std::max(Z, 0.0), 1.0);
1021 } else {
1022 // compute the mixture fraction from a single element
1023 double sum_yf = std::accumulate(fuelComp.begin(), fuelComp.end(), 0.0);
1024 double sum_yo = std::accumulate(oxComp.begin(), oxComp.end(), 0.0);
1025
1026 if (sum_yf == 0.0 || sum_yo == 0.0) {
1027 throw CanteraError("ThermoPhase::mixtureFraction",
1028 "No fuel and/or oxidizer composition specified");
1029 }
1030
1031 auto elementalFraction = [this](size_t m, span<const double> y) {
1032 double Z_m = 0.0;
1033 for (size_t k = 0; k != m_kk; ++k) {
1034 Z_m += y[k] / molecularWeight(k) * nAtoms(k, m);
1035 }
1036 return Z_m;
1037 };
1038
1039 size_t m = elementIndex(element, true);
1040 double Z_m_fuel = elementalFraction(m, fuelComp)/sum_yf;
1041 double Z_m_ox = elementalFraction(m, oxComp)/sum_yo;
1042 double Z_m_mix = elementalFraction(m, massFractions());
1043
1044 if (Z_m_fuel == Z_m_ox) {
1045 throw CanteraError("ThermoPhase::mixtureFraction",
1046 "Fuel and oxidizer have the same composition for element {}",
1047 element);
1048 }
1049 double Z = (Z_m_mix - Z_m_ox) / (Z_m_fuel - Z_m_ox);
1050 return std::min(std::max(Z, 0.0), 1.0);
1051 }
1052}
1053
1055{
1056 return m_spthermo;
1057}
1058
1060{
1061 return m_spthermo;
1062}
1063
1064
1065void ThermoPhase::initThermoFile(const string& inputFile, const string& id)
1066{
1067 if (inputFile.empty()) {
1068 // No input file specified - nothing to set up
1069 return;
1070 }
1071 size_t dot = inputFile.find_last_of(".");
1072 string extension;
1073 if (dot != npos) {
1074 extension = inputFile.substr(dot+1);
1075 }
1076 if (extension == "xml" || extension == "cti") {
1077 throw CanteraError("ThermoPhase::initThermoFile",
1078 "The CTI and XML formats are no longer supported.");
1079 }
1080
1081 AnyMap root = AnyMap::fromYamlFile(inputFile);
1082 auto& phase = root["phases"].getMapWhere("name", id);
1083 setupPhase(*this, phase, root);
1084}
1085
1087{
1088 // Check to see that all of the species thermo objects have been initialized
1089 if (!m_spthermo.ready(m_kk)) {
1090 throw CanteraError("ThermoPhase::initThermo()",
1091 "Missing species thermo data");
1092 }
1093}
1094
1095void ThermoPhase::setState_TPQ(double T, double P, double Q)
1096{
1097 if (T > critTemperature()) {
1098 if (P > critPressure() || Q == 1) {
1099 setState_TP(T, P);
1100 return;
1101 } else {
1102 throw CanteraError("ThermoPhase::setState_TPQ",
1103 "Temperature ({}), pressure ({}) and vapor fraction ({}) "
1104 "are inconsistent, above the critical temperature.",
1105 T, P, Q);
1106 }
1107 }
1108
1109 double Psat = satPressure(T);
1110 if (std::abs(Psat / P - 1) < 1e-6) {
1111 setState_Tsat(T, Q);
1112 } else if ((Q == 0 && P >= Psat) || (Q == 1 && P <= Psat)) {
1113 setState_TP(T, P);
1114 } else {
1115 throw CanteraError("ThermoPhase::setState_TPQ",
1116 "Temperature ({}), pressure ({}) and vapor fraction ({}) "
1117 "are inconsistent.\nPsat at this T: {}\n"
1118 "Consider specifying the state using two fully independent "
1119 "properties (for example, temperature and density)",
1120 T, P, Q, Psat);
1121 }
1122}
1123
1124bool ThermoPhase::addSpecies(shared_ptr<Species> spec)
1125{
1126 if (!spec->thermo) {
1127 throw CanteraError("ThermoPhase::addSpecies",
1128 "Species {} has no thermo data", spec->name);
1129 }
1130 bool added = Phase::addSpecies(spec);
1131 if (added) {
1132 spec->thermo->validate(spec->name);
1133 m_spthermo.install_STIT(m_kk-1, spec->thermo);
1134 }
1135 return added;
1136}
1137
1138void ThermoPhase::modifySpecies(size_t k, shared_ptr<Species> spec)
1139{
1140 if (!spec->thermo) {
1141 throw CanteraError("ThermoPhase::modifySpecies",
1142 "Species {} has no thermo data", spec->name);
1143 }
1144 Phase::modifySpecies(k, spec);
1145 if (speciesName(k) != spec->name) {
1146 throw CanteraError("ThermoPhase::modifySpecies",
1147 "New species '{}' does not match existing species '{}' at index {}",
1148 spec->name, speciesName(k), k);
1149 }
1150 spec->thermo->validate(spec->name);
1151 m_spthermo.modifySpecies(k, spec->thermo);
1152}
1153
1154void ThermoPhase::setParameters(const AnyMap& phaseNode, const AnyMap& rootNode)
1155{
1156 m_input = phaseNode;
1157}
1158
1159AnyMap ThermoPhase::parameters(bool withInput) const
1160{
1161 AnyMap out;
1162 getParameters(out);
1163 if (withInput) {
1164 out.update(m_input);
1165 }
1166 return out;
1167}
1168
1170{
1171 phaseNode["name"] = name();
1172 phaseNode["thermo"] = ThermoFactory::factory()->canonicalize(type());
1173 vector<string> elementNames;
1174 for (size_t i = 0; i < nElements(); i++) {
1175 elementNames.push_back(elementName(i));
1176 }
1177 phaseNode["elements"] = elementNames;
1178 phaseNode["species"] = speciesNames();
1179
1180 AnyMap state;
1181 auto stateVars = nativeState();
1182 if (stateVars.count("T")) {
1183 state["T"].setQuantity(temperature(), "K");
1184 }
1185
1186 if (stateVars.count("D")) {
1187 state["density"].setQuantity(density(), "kg/m^3");
1188 } else if (stateVars.count("P")) {
1189 state["P"].setQuantity(pressure(), "Pa");
1190 }
1191
1192 if (stateVars.count("Y")) {
1193 map<string, double> Y;
1194 for (size_t k = 0; k < m_kk; k++) {
1195 double Yk = massFraction(k);
1196 if (Yk > 0) {
1197 Y[speciesName(k)] = Yk;
1198 }
1199 }
1200 state["Y"] = Y;
1201 state["Y"].setFlowStyle();
1202 } else if (stateVars.count("X")) {
1203 map<string, double> X;
1204 for (size_t k = 0; k < m_kk; k++) {
1205 double Xk = moleFraction(k);
1206 if (Xk > 0) {
1207 X[speciesName(k)] = Xk;
1208 }
1209 }
1210 state["X"] = X;
1211 state["X"].setFlowStyle();
1212 }
1213
1214 phaseNode["state"] = std::move(state);
1215
1216 static bool reg = AnyMap::addOrderingRules("Phase", {{"tail", "state"}});
1217 if (reg) {
1218 phaseNode["__type__"] = "Phase";
1219 }
1220}
1221
1223{
1224 return m_input;
1225}
1226
1228{
1229 return m_input;
1230}
1231
1234 m_tlast += 0.1234;
1235}
1236
1237void ThermoPhase::equilibrate(const string& XY, const string& solver,
1238 double rtol, int max_steps, int max_iter,
1239 int estimate_equil, int log_level)
1240{
1241 if (solver == "auto" || solver == "element_potential") {
1242 vector<double> initial_state(stateSize());
1243 saveState(initial_state);
1244 debuglog("Trying ChemEquil solver\n", log_level);
1245 try {
1246 PhaseEquilGuard guard(*this);
1247 ChemEquil E;
1248 E.options.maxIterations = max_steps;
1249 E.options.relTolerance = rtol;
1250 int ret = E.equilibrate(*this, XY.c_str(), log_level-1);
1251 if (ret < 0) {
1252 throw CanteraError("ThermoPhase::equilibrate",
1253 "ChemEquil solver failed. Return code: {}", ret);
1254 }
1255 debuglog("ChemEquil solver succeeded\n", log_level);
1256 return;
1257 } catch (std::exception& err) {
1258 debuglog("ChemEquil solver failed.\n", log_level);
1259 debuglog(err.what(), log_level);
1260 restoreState(initial_state);
1261 if (solver == "auto") {
1262 } else {
1263 throw;
1264 }
1265 }
1266 }
1267
1268 if (solver == "auto" || solver == "vcs" || solver == "gibbs") {
1269 MultiPhase M;
1270 // Create a non-owning shared_ptr, since this ThermoPhase object is guaranteed
1271 // to outlive the MultiPhase object.
1272 M.addPhase(shared_ptr<ThermoPhase>(this, [](ThermoPhase*) {}), 1.0);
1273 M.init();
1274 M.equilibrate(XY, solver, rtol, max_steps, max_iter,
1275 estimate_equil, log_level);
1276 return;
1277 }
1278
1279 if (solver != "auto") {
1280 throw CanteraError("ThermoPhase::equilibrate",
1281 "Invalid solver specified: '{}'", solver);
1282 }
1283}
1284
1285void ThermoPhase::getdlnActCoeffdlnN(const size_t ld, span<double> dlnActCoeffdlnN)
1286{
1287 checkArraySize("ThermoPhase::getdlnActCoeffdlnN", dlnActCoeffdlnN.size(), ld*m_kk);
1288 for (size_t m = 0; m < m_kk; m++) {
1289 for (size_t k = 0; k < m_kk; k++) {
1290 dlnActCoeffdlnN[ld * k + m] = 0.0;
1291 }
1292 }
1293 return;
1294}
1295
1296void ThermoPhase::getdlnActCoeffdlnN_numderiv(const size_t ld,
1297 span<double> dlnActCoeffdlnN)
1298{
1299 checkArraySize("ThermoPhase::getdlnActCoeffdlnN_numderiv",
1300 dlnActCoeffdlnN.size(), ld*m_kk);
1301 double deltaMoles_j = 0.0;
1302 double pres = pressure();
1303
1304 // Evaluate the current base activity coefficients if necessary
1305 vector<double> ActCoeff_Base(m_kk);
1306 getActivityCoefficients(ActCoeff_Base);
1307 vector<double> Xmol_Base(m_kk);
1308 getMoleFractions(Xmol_Base);
1309
1310 // Make copies of ActCoeff and Xmol_ for use in taking differences
1311 vector<double> ActCoeff(m_kk);
1312 vector<double> Xmol(m_kk);
1313 double v_totalMoles = 1.0;
1314 double TMoles_base = v_totalMoles;
1315
1316 // Loop over the columns species to be deltad
1317 for (size_t j = 0; j < m_kk; j++) {
1318 // Calculate a value for the delta moles of species j
1319 // -> Note Xmol_[] and Tmoles are always positive or zero quantities.
1320 // -> experience has shown that you always need to make the deltas
1321 // greater than needed to change the other mole fractions in order
1322 // to capture some effects.
1323 double moles_j_base = v_totalMoles * Xmol_Base[j];
1324 deltaMoles_j = 1.0E-7 * moles_j_base + v_totalMoles * 1.0E-13 + 1.0E-150;
1325
1326 // Now, update the total moles in the phase and all of the mole
1327 // fractions based on this.
1328 v_totalMoles = TMoles_base + deltaMoles_j;
1329 for (size_t k = 0; k < m_kk; k++) {
1330 Xmol[k] = Xmol_Base[k] * TMoles_base / v_totalMoles;
1331 }
1332 Xmol[j] = (moles_j_base + deltaMoles_j) / v_totalMoles;
1333
1334 // Go get new values for the activity coefficients.
1335 setMoleFractions(Xmol);
1336 setPressure(pres);
1337 getActivityCoefficients(ActCoeff);
1338
1339 // Calculate the column of the matrix
1340 span<double> lnActCoeffCol = dlnActCoeffdlnN.subspan(ld * j, m_kk);
1341 for (size_t k = 0; k < m_kk; k++) {
1342 lnActCoeffCol[k] = (2*moles_j_base + deltaMoles_j) *(ActCoeff[k] - ActCoeff_Base[k]) /
1343 ((ActCoeff[k] + ActCoeff_Base[k]) * deltaMoles_j);
1344 }
1345 // Revert to the base case Xmol_, v_totalMoles
1346 v_totalMoles = TMoles_base;
1347 Xmol = Xmol_Base;
1348 }
1349 setMoleFractions(Xmol_Base);
1350 setPressure(pres);
1351}
1352
1353string ThermoPhase::report(bool show_thermo, double threshold) const
1354{
1355 if (type() == "none") {
1356 throw NotImplementedError("ThermoPhase::report",
1357 "Not implemented for thermo model 'none'");
1358 }
1359
1360 fmt::memory_buffer b;
1361 // This is the width of the first column of names in the report.
1362 int name_width = 18;
1363
1364 string blank_leader = fmt::format("{:{}}", "", name_width);
1365
1366 string string_property = fmt::format("{{:>{}}} {{}}\n", name_width);
1367
1368 string one_property = fmt::format("{{:>{}}} {{:<.5g}} {{}}\n", name_width);
1369
1370 constexpr auto two_prop_header = "{} {:^15} {:^15}\n";
1371 string kg_kmol_header = fmt::format(
1372 two_prop_header, blank_leader, "1 kg", "1 kmol"
1373 );
1374 string Y_X_header = fmt::format(
1375 two_prop_header, blank_leader, "mass frac. Y", "mole frac. X"
1376 );
1377 string two_prop_sep = fmt::format(
1378 "{} {:-^15} {:-^15}\n", blank_leader, "", ""
1379 );
1380 string two_property = fmt::format(
1381 "{{:>{}}} {{:15.5g}} {{:15.5g}} {{}}\n", name_width
1382 );
1383
1384 string three_prop_header = fmt::format(
1385 "{} {:^15} {:^15} {:^15}\n", blank_leader, "mass frac. Y",
1386 "mole frac. X", "chem. pot. / RT"
1387 );
1388 string three_prop_sep = fmt::format(
1389 "{} {:-^15} {:-^15} {:-^15}\n", blank_leader, "", "", ""
1390 );
1391 string three_property = fmt::format(
1392 "{{:>{}}} {{:15.5g}} {{:15.5g}} {{:15.5g}}\n", name_width
1393 );
1394
1395 try {
1396 if (name() != "") {
1397 fmt_append(b, "\n {}:\n", name());
1398 }
1399 fmt_append(b, "\n");
1400 fmt_append(b, one_property, "temperature", temperature(), "K");
1401 fmt_append(b, one_property, "pressure", pressure(), "Pa");
1402 fmt_append(b, one_property, "density", density(), "kg/m^3");
1403 fmt_append(b, one_property,
1404 "mean mol. weight", meanMolecularWeight(), "kg/kmol");
1405
1406 double phi = electricPotential();
1407 if (phi != 0.0) {
1408 fmt_append(b, one_property, "potential", phi, "V");
1409 }
1410
1411 fmt_append(b, string_property, "phase of matter", phaseOfMatter());
1412
1413 if (show_thermo) {
1414 fmt_append(b, "\n");
1415 fmt_append(b, kg_kmol_header);
1416 fmt_append(b, two_prop_sep);
1417 fmt_append(b, two_property,
1418 "enthalpy", enthalpy_mass(), enthalpy_mole(), "J");
1419 fmt_append(b, two_property,
1420 "internal energy", intEnergy_mass(), intEnergy_mole(), "J");
1421 fmt_append(b, two_property,
1422 "entropy", entropy_mass(), entropy_mole(), "J/K");
1423 fmt_append(b, two_property,
1424 "Gibbs function", gibbs_mass(), gibbs_mole(), "J");
1425 fmt_append(b, two_property,
1426 "heat capacity c_p", cp_mass(), cp_mole(), "J/K");
1427 try {
1428 fmt_append(b, two_property,
1429 "heat capacity c_v", cv_mass(), cv_mole(), "J/K");
1430 } catch (NotImplementedError&) {
1431 fmt_append(b, string_property,
1432 "heat capacity c_v", "<not implemented>");
1433 }
1434 }
1435
1436 vector<double> x(m_kk);
1437 vector<double> y(m_kk);
1438 vector<double> mu(m_kk);
1442 int nMinor = 0;
1443 double xMinor = 0.0;
1444 double yMinor = 0.0;
1445 fmt_append(b, "\n");
1446 if (show_thermo) {
1447 fmt_append(b, three_prop_header);
1448 fmt_append(b, three_prop_sep);
1449 for (size_t k = 0; k < m_kk; k++) {
1450 if (abs(x[k]) >= threshold) {
1451 if (abs(x[k]) > SmallNumber) {
1452 fmt_append(b, three_property,
1453 speciesName(k), y[k], x[k], mu[k]/RT());
1454 } else {
1455 fmt_append(b, two_property, speciesName(k), y[k], x[k], "");
1456 }
1457 } else {
1458 nMinor++;
1459 xMinor += x[k];
1460 yMinor += y[k];
1461 }
1462 }
1463 } else {
1464 fmt_append(b, Y_X_header);
1465 fmt_append(b, two_prop_sep);
1466 for (size_t k = 0; k < m_kk; k++) {
1467 if (abs(x[k]) >= threshold) {
1468 fmt_append(b, two_property, speciesName(k), y[k], x[k], "");
1469 } else {
1470 nMinor++;
1471 xMinor += x[k];
1472 yMinor += y[k];
1473 }
1474 }
1475 }
1476 if (nMinor) {
1477 string minor = fmt::format("[{:+5d} minor]", nMinor);
1478 fmt_append(b, two_property, minor, yMinor, xMinor, "");
1479 }
1480 } catch (CanteraError& err) {
1481 return to_string(b) + err.what();
1482 }
1483 return to_string(b);
1484}
1485
1486}
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.
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)
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 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 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.