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(double* a) const
96{
98 for (size_t k = 0; k < nSpecies(); k++) {
99 a[k] /= standardConcentration(k);
100 }
101}
102
104{
106 for (size_t k = 0; k < m_kk; k++) {
107 lnac[k] = std::log(lnac[k]);
108 }
109}
110
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, 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, 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.size(), state.data());
160 try {
162 setPressure(p);
163 } catch (std::exception&) {
164 restorePartialState(state.size(), state.data());
165 throw;
166 }
167}
168
169void ThermoPhase::setState_HP(double Htarget, double p, double rtol)
170{
171 vector<double> state(partialStateSize());
172 savePartialState(state.size(), state.data());
173 try {
174 setState_HPorUV(Htarget, p, rtol, false);
175 } catch (std::exception&) {
176 restorePartialState(state.size(), state.data());
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.size(), state.data());
186 try {
187 setState_HPorUV(u, v, rtol, true);
188 } catch (std::exception&) {
189 restorePartialState(state.size(), state.data());
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.size(), state.data());
506 try {
507 setState_SPorSV(Starget, p, rtol, false);
508 } catch (std::exception&) {
509 restorePartialState(state.size(), state.data());
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.size(), state.data());
519 try {
520 setState_SPorSV(Starget, v, rtol, true);
521 } catch (std::exception&) {
522 restorePartialState(state.size(), state.data());
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(const double* y) const
701{
702 // indices of fuel elements
703 size_t iC = elementIndex("C", false);
704 size_t iS = elementIndex("S", false);
705 size_t iH = elementIndex("H", false);
706
707 double o2req = 0.0;
708 double sum = 0.0;
709 for (size_t k = 0; k != m_kk; ++k) {
710 sum += y[k];
711 double x = y[k] / molecularWeights()[k];
712 if (iC != npos) {
713 o2req += x * nAtoms(k, iC);
714 }
715 if (iS != npos) {
716 o2req += x * nAtoms(k, iS);
717 }
718 if (iH != npos) {
719 o2req += x * 0.25 * nAtoms(k, iH);
720 }
721 }
722 if (sum == 0.0) {
723 throw CanteraError("ThermoPhase::o2Required",
724 "No composition specified");
725 }
726 return o2req/sum;
727}
728
729double ThermoPhase::o2Present(const double* y) const
730{
731 size_t iO = elementIndex("O", true);
732 double o2pres = 0.0;
733 double sum = 0.0;
734 for (size_t k = 0; k != m_kk; ++k) {
735 sum += y[k];
736 o2pres += y[k] / molecularWeights()[k] * nAtoms(k, iO);
737 }
738 if (sum == 0.0) {
739 throw CanteraError("ThermoPhase::o2Present",
740 "No composition specified");
741 }
742 return 0.5 * o2pres / sum;
743}
744
746 const Composition& oxComp,
747 ThermoBasis basis) const
748{
749 vector<double> fuel(getCompositionFromMap(fuelComp));
750 vector<double> ox(getCompositionFromMap(oxComp));
751 return stoichAirFuelRatio(fuel.data(), ox.data(), basis);
752}
753
754double ThermoPhase::stoichAirFuelRatio(const string& fuelComp, const string& oxComp,
755 ThermoBasis basis) const
756{
757 return stoichAirFuelRatio(
758 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
759 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
760 basis);
761}
762
763double ThermoPhase::stoichAirFuelRatio(const double* fuelComp,
764 const double* oxComp,
765 ThermoBasis basis) const
766{
767 vector<double> fuel, ox;
768 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
769 fuel.resize(m_kk);
770 ox.resize(m_kk);
771 moleFractionsToMassFractions(fuelComp, fuel.data());
772 moleFractionsToMassFractions(oxComp, ox.data());
773 fuelComp = fuel.data();
774 oxComp = ox.data();
775 }
776
777 double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp);
778 double o2_required_ox = o2Required(oxComp) - o2Present(oxComp);
779
780 if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) {
781 throw CanteraError("ThermoPhase::stoichAirFuelRatio",
782 "Fuel composition contains too much oxygen or "
783 "oxidizer contains not enough oxygen. "
784 "Fuel and oxidizer composition mixed up?");
785 }
786
787 if (o2_required_ox == 0.0) {
788 return std::numeric_limits<double>::infinity();
789 }
790
791 return o2_required_fuel / (-o2_required_ox);
792}
793
794void ThermoPhase::setEquivalenceRatio(double phi, const double* fuelComp,
795 const double* oxComp, ThermoBasis basis)
796{
797 if (phi < 0.0) {
798 throw CanteraError("ThermoPhase::setEquivalenceRatio",
799 "Equivalence ratio phi must be >= 0");
800 }
801
802 double p = pressure();
803
804 vector<double> fuel, ox;
805 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
806 fuel.resize(m_kk);
807 ox.resize(m_kk);
808 moleFractionsToMassFractions(fuelComp, fuel.data());
809 moleFractionsToMassFractions(oxComp, ox.data());
810 fuelComp = fuel.data();
811 oxComp = ox.data();
812 }
813
814 double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass);
815
816 double sum_f = std::accumulate(fuelComp, fuelComp+m_kk, 0.0);
817 double sum_o = std::accumulate(oxComp, oxComp+m_kk, 0.0);
818
819 vector<double> y(m_kk);
820 for (size_t k = 0; k != m_kk; ++k) {
821 y[k] = phi * fuelComp[k]/sum_f + AFR_st * oxComp[k]/sum_o;
822 }
823
824 setMassFractions(y.data());
825 setPressure(p);
826}
827
828void ThermoPhase::setEquivalenceRatio(double phi, const string& fuelComp,
829 const string& oxComp, ThermoBasis basis)
830{
832 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
833 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
834 basis);
835}
836
837void ThermoPhase::setEquivalenceRatio(double phi, const Composition& fuelComp,
838 const Composition& oxComp, ThermoBasis basis)
839{
840 vector<double> fuel = getCompositionFromMap(fuelComp);
841 vector<double> ox = getCompositionFromMap(oxComp);
842 setEquivalenceRatio(phi, fuel.data(), ox.data(), basis);
843}
844
846{
847 double o2_required = o2Required(massFractions());
848 double o2_present = o2Present(massFractions());
849
850 if (o2_present == 0.0) { // pure fuel
851 return std::numeric_limits<double>::infinity();
852 }
853
854 return o2_required / o2_present;
855}
856
858 const Composition& oxComp,
859 ThermoBasis basis) const
860{
861 vector<double> fuel(getCompositionFromMap(fuelComp));
862 vector<double> ox(getCompositionFromMap(oxComp));
863 return equivalenceRatio(fuel.data(), ox.data(), basis);
864}
865
866double ThermoPhase::equivalenceRatio(const string& fuelComp, const string& oxComp,
867 ThermoBasis basis) const
868{
869 return equivalenceRatio(
870 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
871 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
872 basis);
873}
874
875double ThermoPhase::equivalenceRatio(const double* fuelComp,
876 const double* oxComp,
877 ThermoBasis basis) const
878{
879 double Z = mixtureFraction(fuelComp, oxComp, basis);
880
881 if (Z == 0.0) {
882 return 0.0; // pure oxidizer
883 }
884
885 if (Z == 1.0) {
886 return std::numeric_limits<double>::infinity(); // pure fuel
887 }
888
889 vector<double> fuel, ox;
890 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
891 fuel.resize(m_kk);
892 ox.resize(m_kk);
893 moleFractionsToMassFractions(fuelComp, fuel.data());
894 moleFractionsToMassFractions(oxComp, ox.data());
895 fuelComp = fuel.data();
896 oxComp = ox.data();
897 }
898
899 double AFR_st = stoichAirFuelRatio(fuelComp, oxComp, ThermoBasis::mass);
900
901 return std::max(Z / (1.0 - Z) * AFR_st, 0.0);
902}
903
904void ThermoPhase::setMixtureFraction(double mixFrac, const Composition& fuelComp,
905 const Composition& oxComp, ThermoBasis basis)
906{
907 vector<double> fuel(getCompositionFromMap(fuelComp));
908 vector<double> ox(getCompositionFromMap(oxComp));
909 setMixtureFraction(mixFrac, fuel.data(), ox.data(), basis);
910}
911
912void ThermoPhase::setMixtureFraction(double mixFrac, const string& fuelComp,
913 const string& oxComp, ThermoBasis basis)
914{
915 setMixtureFraction(mixFrac,
916 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
917 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
918 basis);
919}
920
921void ThermoPhase::setMixtureFraction(double mixFrac, const double* fuelComp,
922 const double* oxComp, ThermoBasis basis)
923{
924 if (mixFrac < 0.0 || mixFrac > 1.0) {
925 throw CanteraError("ThermoPhase::setMixtureFraction",
926 "Mixture fraction must be between 0 and 1");
927 }
928
929 vector<double> fuel, ox;
930 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
931 fuel.resize(m_kk);
932 ox.resize(m_kk);
933 moleFractionsToMassFractions(fuelComp, fuel.data());
934 moleFractionsToMassFractions(oxComp, ox.data());
935 fuelComp = fuel.data();
936 oxComp = ox.data();
937 }
938
939 double sum_yf = std::accumulate(fuelComp, fuelComp+m_kk, 0.0);
940 double sum_yo = std::accumulate(oxComp, oxComp+m_kk, 0.0);
941
942 if (sum_yf == 0.0 || sum_yo == 0.0) {
943 throw CanteraError("ThermoPhase::setMixtureFraction",
944 "No fuel and/or oxidizer composition specified");
945 }
946
947 double p = pressure();
948
949 vector<double> y(m_kk);
950
951 for (size_t k = 0; k != m_kk; ++k) {
952 y[k] = mixFrac * fuelComp[k]/sum_yf + (1.0-mixFrac) * oxComp[k]/sum_yo;
953 }
954
955 setMassFractions_NoNorm(y.data());
956 setPressure(p);
957}
958
960 const Composition& oxComp,
961 ThermoBasis basis,
962 const string& element) const
963{
964 vector<double> fuel(getCompositionFromMap(fuelComp));
965 vector<double> ox(getCompositionFromMap(oxComp));
966 return mixtureFraction(fuel.data(), ox.data(), basis, element);
967}
968
969double ThermoPhase::mixtureFraction(const string& fuelComp, const string& oxComp,
970 ThermoBasis basis, const string& element) const
971{
972 return mixtureFraction(
973 parseCompString(fuelComp.find(":") != string::npos ? fuelComp : fuelComp+":1.0"),
974 parseCompString(oxComp.find(":") != string::npos ? oxComp : oxComp+":1.0"),
975 basis, element);
976}
977
978double ThermoPhase::mixtureFraction(const double* fuelComp, const double* oxComp,
979 ThermoBasis basis, const string& element) const
980{
981 vector<double> fuel, ox;
982 if (basis == ThermoBasis::molar) { // convert input compositions to mass fractions
983 fuel.resize(m_kk);
984 ox.resize(m_kk);
985 moleFractionsToMassFractions(fuelComp, fuel.data());
986 moleFractionsToMassFractions(oxComp, ox.data());
987 fuelComp = fuel.data();
988 oxComp = ox.data();
989 }
990
991 if (element == "Bilger") // compute the mixture fraction based on the Bilger mixture fraction
992 {
993 double o2_required_fuel = o2Required(fuelComp) - o2Present(fuelComp);
994 double o2_required_ox = o2Required(oxComp) - o2Present(oxComp);
995 double o2_required_mix = o2Required(massFractions()) - o2Present(massFractions());
996
997 if (o2_required_fuel < 0.0 || o2_required_ox > 0.0) {
998 throw CanteraError("ThermoPhase::mixtureFraction",
999 "Fuel composition contains too much oxygen or "
1000 "oxidizer contains not enough oxygen. "
1001 "Fuel and oxidizer composition mixed up?");
1002 }
1003
1004 double denominator = o2_required_fuel - o2_required_ox;
1005
1006 if (denominator == 0.0) {
1007 throw CanteraError("ThermoPhase::mixtureFraction",
1008 "Fuel and oxidizer have the same composition");
1009 }
1010
1011 double Z = (o2_required_mix - o2_required_ox) / denominator;
1012
1013 return std::min(std::max(Z, 0.0), 1.0);
1014 } else {
1015 // compute the mixture fraction from a single element
1016 double sum_yf = std::accumulate(fuelComp, fuelComp+m_kk, 0.0);
1017 double sum_yo = std::accumulate(oxComp, oxComp+m_kk, 0.0);
1018
1019 if (sum_yf == 0.0 || sum_yo == 0.0) {
1020 throw CanteraError("ThermoPhase::mixtureFraction",
1021 "No fuel and/or oxidizer composition specified");
1022 }
1023
1024 auto elementalFraction = [this](size_t m, const double* y) {
1025 double Z_m = 0.0;
1026 for (size_t k = 0; k != m_kk; ++k) {
1027 Z_m += y[k] / molecularWeight(k) * nAtoms(k, m);
1028 }
1029 return Z_m;
1030 };
1031
1032 size_t m = elementIndex(element, true);
1033 double Z_m_fuel = elementalFraction(m, fuelComp)/sum_yf;
1034 double Z_m_ox = elementalFraction(m, oxComp)/sum_yo;
1035 double Z_m_mix = elementalFraction(m, massFractions());
1036
1037 if (Z_m_fuel == Z_m_ox) {
1038 throw CanteraError("ThermoPhase::mixtureFraction",
1039 "Fuel and oxidizer have the same composition for element {}",
1040 element);
1041 }
1042 double Z = (Z_m_mix - Z_m_ox) / (Z_m_fuel - Z_m_ox);
1043 return std::min(std::max(Z, 0.0), 1.0);
1044 }
1045}
1046
1048{
1049 return m_spthermo;
1050}
1051
1053{
1054 return m_spthermo;
1055}
1056
1057
1058void ThermoPhase::initThermoFile(const string& inputFile, const string& id)
1059{
1060 if (inputFile.empty()) {
1061 // No input file specified - nothing to set up
1062 return;
1063 }
1064 size_t dot = inputFile.find_last_of(".");
1065 string extension;
1066 if (dot != npos) {
1067 extension = inputFile.substr(dot+1);
1068 }
1069 if (extension == "xml" || extension == "cti") {
1070 throw CanteraError("ThermoPhase::initThermoFile",
1071 "The CTI and XML formats are no longer supported.");
1072 }
1073
1074 AnyMap root = AnyMap::fromYamlFile(inputFile);
1075 auto& phase = root["phases"].getMapWhere("name", id);
1076 setupPhase(*this, phase, root);
1077}
1078
1080{
1081 // Check to see that all of the species thermo objects have been initialized
1082 if (!m_spthermo.ready(m_kk)) {
1083 throw CanteraError("ThermoPhase::initThermo()",
1084 "Missing species thermo data");
1085 }
1086}
1087
1088void ThermoPhase::setState_TPQ(double T, double P, double Q)
1089{
1090 if (T > critTemperature()) {
1091 if (P > critPressure() || Q == 1) {
1092 setState_TP(T, P);
1093 return;
1094 } else {
1095 throw CanteraError("ThermoPhase::setState_TPQ",
1096 "Temperature ({}), pressure ({}) and vapor fraction ({}) "
1097 "are inconsistent, above the critical temperature.",
1098 T, P, Q);
1099 }
1100 }
1101
1102 double Psat = satPressure(T);
1103 if (std::abs(Psat / P - 1) < 1e-6) {
1104 setState_Tsat(T, Q);
1105 } else if ((Q == 0 && P >= Psat) || (Q == 1 && P <= Psat)) {
1106 setState_TP(T, P);
1107 } else {
1108 throw CanteraError("ThermoPhase::setState_TPQ",
1109 "Temperature ({}), pressure ({}) and vapor fraction ({}) "
1110 "are inconsistent.\nPsat at this T: {}\n"
1111 "Consider specifying the state using two fully independent "
1112 "properties (for example, temperature and density)",
1113 T, P, Q, Psat);
1114 }
1115}
1116
1117bool ThermoPhase::addSpecies(shared_ptr<Species> spec)
1118{
1119 if (!spec->thermo) {
1120 throw CanteraError("ThermoPhase::addSpecies",
1121 "Species {} has no thermo data", spec->name);
1122 }
1123 bool added = Phase::addSpecies(spec);
1124 if (added) {
1125 spec->thermo->validate(spec->name);
1126 m_spthermo.install_STIT(m_kk-1, spec->thermo);
1127 }
1128 return added;
1129}
1130
1131void ThermoPhase::modifySpecies(size_t k, shared_ptr<Species> spec)
1132{
1133 if (!spec->thermo) {
1134 throw CanteraError("ThermoPhase::modifySpecies",
1135 "Species {} has no thermo data", spec->name);
1136 }
1137 Phase::modifySpecies(k, spec);
1138 if (speciesName(k) != spec->name) {
1139 throw CanteraError("ThermoPhase::modifySpecies",
1140 "New species '{}' does not match existing species '{}' at index {}",
1141 spec->name, speciesName(k), k);
1142 }
1143 spec->thermo->validate(spec->name);
1144 m_spthermo.modifySpecies(k, spec->thermo);
1145}
1146
1147void ThermoPhase::setParameters(const AnyMap& phaseNode, const AnyMap& rootNode)
1148{
1149 m_input = phaseNode;
1150}
1151
1152AnyMap ThermoPhase::parameters(bool withInput) const
1153{
1154 AnyMap out;
1155 getParameters(out);
1156 if (withInput) {
1157 out.update(m_input);
1158 }
1159 return out;
1160}
1161
1163{
1164 phaseNode["name"] = name();
1165 phaseNode["thermo"] = ThermoFactory::factory()->canonicalize(type());
1166 vector<string> elementNames;
1167 for (size_t i = 0; i < nElements(); i++) {
1168 elementNames.push_back(elementName(i));
1169 }
1170 phaseNode["elements"] = elementNames;
1171 phaseNode["species"] = speciesNames();
1172
1173 AnyMap state;
1174 auto stateVars = nativeState();
1175 if (stateVars.count("T")) {
1176 state["T"].setQuantity(temperature(), "K");
1177 }
1178
1179 if (stateVars.count("D")) {
1180 state["density"].setQuantity(density(), "kg/m^3");
1181 } else if (stateVars.count("P")) {
1182 state["P"].setQuantity(pressure(), "Pa");
1183 }
1184
1185 if (stateVars.count("Y")) {
1186 map<string, double> Y;
1187 for (size_t k = 0; k < m_kk; k++) {
1188 double Yk = massFraction(k);
1189 if (Yk > 0) {
1190 Y[speciesName(k)] = Yk;
1191 }
1192 }
1193 state["Y"] = Y;
1194 state["Y"].setFlowStyle();
1195 } else if (stateVars.count("X")) {
1196 map<string, double> X;
1197 for (size_t k = 0; k < m_kk; k++) {
1198 double Xk = moleFraction(k);
1199 if (Xk > 0) {
1200 X[speciesName(k)] = Xk;
1201 }
1202 }
1203 state["X"] = X;
1204 state["X"].setFlowStyle();
1205 }
1206
1207 phaseNode["state"] = std::move(state);
1208
1209 static bool reg = AnyMap::addOrderingRules("Phase", {{"tail", "state"}});
1210 if (reg) {
1211 phaseNode["__type__"] = "Phase";
1212 }
1213}
1214
1216{
1217 return m_input;
1218}
1219
1221{
1222 return m_input;
1223}
1224
1227 m_tlast += 0.1234;
1228}
1229
1230void ThermoPhase::equilibrate(const string& XY, const string& solver,
1231 double rtol, int max_steps, int max_iter,
1232 int estimate_equil, int log_level)
1233{
1234 if (solver == "auto" || solver == "element_potential") {
1235 vector<double> initial_state;
1236 saveState(initial_state);
1237 debuglog("Trying ChemEquil solver\n", log_level);
1238 try {
1239 PhaseEquilGuard guard(*this);
1240 ChemEquil E;
1241 E.options.maxIterations = max_steps;
1242 E.options.relTolerance = rtol;
1243 int ret = E.equilibrate(*this, XY.c_str(), log_level-1);
1244 if (ret < 0) {
1245 throw CanteraError("ThermoPhase::equilibrate",
1246 "ChemEquil solver failed. Return code: {}", ret);
1247 }
1248 debuglog("ChemEquil solver succeeded\n", log_level);
1249 return;
1250 } catch (std::exception& err) {
1251 debuglog("ChemEquil solver failed.\n", log_level);
1252 debuglog(err.what(), log_level);
1253 restoreState(initial_state);
1254 if (solver == "auto") {
1255 } else {
1256 throw;
1257 }
1258 }
1259 }
1260
1261 if (solver == "auto" || solver == "vcs" || solver == "gibbs") {
1262 MultiPhase M;
1263 M.addPhase(this, 1.0);
1264 M.init();
1265 M.equilibrate(XY, solver, rtol, max_steps, max_iter,
1266 estimate_equil, log_level);
1267 return;
1268 }
1269
1270 if (solver != "auto") {
1271 throw CanteraError("ThermoPhase::equilibrate",
1272 "Invalid solver specified: '{}'", solver);
1273 }
1274}
1275
1276void ThermoPhase::getdlnActCoeffdlnN(const size_t ld, double* const dlnActCoeffdlnN)
1277{
1278 for (size_t m = 0; m < m_kk; m++) {
1279 for (size_t k = 0; k < m_kk; k++) {
1280 dlnActCoeffdlnN[ld * k + m] = 0.0;
1281 }
1282 }
1283 return;
1284}
1285
1286void ThermoPhase::getdlnActCoeffdlnN_numderiv(const size_t ld,
1287 double* const dlnActCoeffdlnN)
1288{
1289 double deltaMoles_j = 0.0;
1290 double pres = pressure();
1291
1292 // Evaluate the current base activity coefficients if necessary
1293 vector<double> ActCoeff_Base(m_kk);
1294 getActivityCoefficients(ActCoeff_Base.data());
1295 vector<double> Xmol_Base(m_kk);
1296 getMoleFractions(Xmol_Base.data());
1297
1298 // Make copies of ActCoeff and Xmol_ for use in taking differences
1299 vector<double> ActCoeff(m_kk);
1300 vector<double> Xmol(m_kk);
1301 double v_totalMoles = 1.0;
1302 double TMoles_base = v_totalMoles;
1303
1304 // Loop over the columns species to be deltad
1305 for (size_t j = 0; j < m_kk; j++) {
1306 // Calculate a value for the delta moles of species j
1307 // -> Note Xmol_[] and Tmoles are always positive or zero quantities.
1308 // -> experience has shown that you always need to make the deltas
1309 // greater than needed to change the other mole fractions in order
1310 // to capture some effects.
1311 double moles_j_base = v_totalMoles * Xmol_Base[j];
1312 deltaMoles_j = 1.0E-7 * moles_j_base + v_totalMoles * 1.0E-13 + 1.0E-150;
1313
1314 // Now, update the total moles in the phase and all of the mole
1315 // fractions based on this.
1316 v_totalMoles = TMoles_base + deltaMoles_j;
1317 for (size_t k = 0; k < m_kk; k++) {
1318 Xmol[k] = Xmol_Base[k] * TMoles_base / v_totalMoles;
1319 }
1320 Xmol[j] = (moles_j_base + deltaMoles_j) / v_totalMoles;
1321
1322 // Go get new values for the activity coefficients.
1323 setMoleFractions(Xmol.data());
1324 setPressure(pres);
1325 getActivityCoefficients(ActCoeff.data());
1326
1327 // Calculate the column of the matrix
1328 double* const lnActCoeffCol = dlnActCoeffdlnN + ld * j;
1329 for (size_t k = 0; k < m_kk; k++) {
1330 lnActCoeffCol[k] = (2*moles_j_base + deltaMoles_j) *(ActCoeff[k] - ActCoeff_Base[k]) /
1331 ((ActCoeff[k] + ActCoeff_Base[k]) * deltaMoles_j);
1332 }
1333 // Revert to the base case Xmol_, v_totalMoles
1334 v_totalMoles = TMoles_base;
1335 Xmol = Xmol_Base;
1336 }
1337 setMoleFractions(Xmol_Base.data());
1338 setPressure(pres);
1339}
1340
1341string ThermoPhase::report(bool show_thermo, double threshold) const
1342{
1343 if (type() == "none") {
1344 throw NotImplementedError("ThermoPhase::report",
1345 "Not implemented for thermo model 'none'");
1346 }
1347
1348 fmt::memory_buffer b;
1349 // This is the width of the first column of names in the report.
1350 int name_width = 18;
1351
1352 string blank_leader = fmt::format("{:{}}", "", name_width);
1353
1354 string string_property = fmt::format("{{:>{}}} {{}}\n", name_width);
1355
1356 string one_property = fmt::format("{{:>{}}} {{:<.5g}} {{}}\n", name_width);
1357
1358 constexpr auto two_prop_header = "{} {:^15} {:^15}\n";
1359 string kg_kmol_header = fmt::format(
1360 two_prop_header, blank_leader, "1 kg", "1 kmol"
1361 );
1362 string Y_X_header = fmt::format(
1363 two_prop_header, blank_leader, "mass frac. Y", "mole frac. X"
1364 );
1365 string two_prop_sep = fmt::format(
1366 "{} {:-^15} {:-^15}\n", blank_leader, "", ""
1367 );
1368 string two_property = fmt::format(
1369 "{{:>{}}} {{:15.5g}} {{:15.5g}} {{}}\n", name_width
1370 );
1371
1372 string three_prop_header = fmt::format(
1373 "{} {:^15} {:^15} {:^15}\n", blank_leader, "mass frac. Y",
1374 "mole frac. X", "chem. pot. / RT"
1375 );
1376 string three_prop_sep = fmt::format(
1377 "{} {:-^15} {:-^15} {:-^15}\n", blank_leader, "", "", ""
1378 );
1379 string three_property = fmt::format(
1380 "{{:>{}}} {{:15.5g}} {{:15.5g}} {{:15.5g}}\n", name_width
1381 );
1382
1383 try {
1384 if (name() != "") {
1385 fmt_append(b, "\n {}:\n", name());
1386 }
1387 fmt_append(b, "\n");
1388 fmt_append(b, one_property, "temperature", temperature(), "K");
1389 fmt_append(b, one_property, "pressure", pressure(), "Pa");
1390 fmt_append(b, one_property, "density", density(), "kg/m^3");
1391 fmt_append(b, one_property,
1392 "mean mol. weight", meanMolecularWeight(), "kg/kmol");
1393
1394 double phi = electricPotential();
1395 if (phi != 0.0) {
1396 fmt_append(b, one_property, "potential", phi, "V");
1397 }
1398
1399 fmt_append(b, string_property, "phase of matter", phaseOfMatter());
1400
1401 if (show_thermo) {
1402 fmt_append(b, "\n");
1403 fmt_append(b, kg_kmol_header);
1404 fmt_append(b, two_prop_sep);
1405 fmt_append(b, two_property,
1406 "enthalpy", enthalpy_mass(), enthalpy_mole(), "J");
1407 fmt_append(b, two_property,
1408 "internal energy", intEnergy_mass(), intEnergy_mole(), "J");
1409 fmt_append(b, two_property,
1410 "entropy", entropy_mass(), entropy_mole(), "J/K");
1411 fmt_append(b, two_property,
1412 "Gibbs function", gibbs_mass(), gibbs_mole(), "J");
1413 fmt_append(b, two_property,
1414 "heat capacity c_p", cp_mass(), cp_mole(), "J/K");
1415 try {
1416 fmt_append(b, two_property,
1417 "heat capacity c_v", cv_mass(), cv_mole(), "J/K");
1418 } catch (NotImplementedError&) {
1419 fmt_append(b, string_property,
1420 "heat capacity c_v", "<not implemented>");
1421 }
1422 }
1423
1424 vector<double> x(m_kk);
1425 vector<double> y(m_kk);
1426 vector<double> mu(m_kk);
1427 getMoleFractions(&x[0]);
1428 getMassFractions(&y[0]);
1429 getChemPotentials(&mu[0]);
1430 int nMinor = 0;
1431 double xMinor = 0.0;
1432 double yMinor = 0.0;
1433 fmt_append(b, "\n");
1434 if (show_thermo) {
1435 fmt_append(b, three_prop_header);
1436 fmt_append(b, three_prop_sep);
1437 for (size_t k = 0; k < m_kk; k++) {
1438 if (abs(x[k]) >= threshold) {
1439 if (abs(x[k]) > SmallNumber) {
1440 fmt_append(b, three_property,
1441 speciesName(k), y[k], x[k], mu[k]/RT());
1442 } else {
1443 fmt_append(b, two_property, speciesName(k), y[k], x[k], "");
1444 }
1445 } else {
1446 nMinor++;
1447 xMinor += x[k];
1448 yMinor += y[k];
1449 }
1450 }
1451 } else {
1452 fmt_append(b, Y_X_header);
1453 fmt_append(b, two_prop_sep);
1454 for (size_t k = 0; k < m_kk; k++) {
1455 if (abs(x[k]) >= threshold) {
1456 fmt_append(b, two_property, speciesName(k), y[k], x[k], "");
1457 } else {
1458 nMinor++;
1459 xMinor += x[k];
1460 yMinor += y[k];
1461 }
1462 }
1463 }
1464 if (nMinor) {
1465 string minor = fmt::format("[{:+5d} minor]", nMinor);
1466 fmt_append(b, two_property, minor, yMinor, xMinor, "");
1467 }
1468 } catch (CanteraError& err) {
1469 return to_string(b) + err.what();
1470 }
1471 return to_string(b);
1472}
1473
1474}
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.
double massFraction(size_t k) const
Return the mass fraction of a single species.
Definition Phase.cpp:480
virtual bool addSpecies(shared_ptr< Species > spec)
Add a Species to this Phase.
Definition Phase.cpp:724
virtual void setMoleFractions(const double *const x)
Set the mole fractions to the specified values.
Definition Phase.cpp:307
void assertCompressible(const string &setter) const
Ensure that phase is compressible.
Definition Phase.h:850
void restoreState(const vector< double > &state)
Restore a state saved on a previous call to saveState.
Definition Phase.cpp:283
virtual void restorePartialState(size_t lenstate, const double *state)
Set the internal thermodynamic state of the phase, excluding composition.
Definition Phase.cpp:240
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:245
virtual void setMassFractions_NoNorm(const double *const y)
Set the mass fractions to the specified values without normalizing.
Definition Phase.cpp:373
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:890
virtual void modifySpecies(size_t k, shared_ptr< Species > spec)
Modify the thermodynamic data associated with a species.
Definition Phase.cpp:837
size_t nDim() const
Returns the number of spatial dimensions (1, 2, or 3)
Definition Phase.h:582
void setState_TD(double t, double rho)
Set the internally stored temperature (K) and density (kg/m^3)
Definition Phase.cpp:395
double temperature() const
Temperature (K).
Definition Phase.h:598
virtual void setPressure(double p)
Set the internally stored pressure (Pa) at constant temperature and composition.
Definition Phase.h:652
double meanMolecularWeight() const
The mean molecular weight. Units: (kg/kmol)
Definition Phase.h:691
void moleFractionsToMassFractions(const double *X, double *Y) const
Converts a mixture composition from mass fractions to mole fractions.
Definition Phase.cpp:970
void saveState(vector< double > &state) const
Save the current internal state of the phase.
Definition Phase.cpp:263
void setMassFractionsByName(const Composition &yMap)
Set the species mass fractions by name.
Definition Phase.cpp:384
string speciesName(size_t k) const
Name of the species with index k.
Definition Phase.cpp:143
virtual void setDensity(const double density_)
Set the internally stored density (kg/m^3) of the phase.
Definition Phase.cpp:609
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:945
void getMoleFractions(double *const x) const
Get the species mole fraction vector.
Definition Phase.cpp:459
void setMoleFractionsByName(const Composition &xMap)
Set the species mole fractions by name.
Definition Phase.cpp:348
const double * massFractions() const
Return a const pointer to the mass fraction array.
Definition Phase.h:478
const vector< double > & molecularWeights() const
Return a const reference to the internal vector of molecular weights.
Definition Phase.cpp:420
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition Phase.cpp:464
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:623
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:659
size_t nElements() const
Number of elements.
Definition Phase.cpp:30
virtual void savePartialState(size_t lenstate, double *state) const
Save the current thermodynamic state of the phase, excluding composition.
Definition Phase.cpp:225
virtual void setMassFractions(const double *const y)
Set the mass fractions to the specified values and normalize them.
Definition Phase.cpp:359
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
double molecularWeight(size_t k) const
Molecular weight of species k.
Definition Phase.cpp:408
shared_ptr< Species > species(const string &name) const
Return the Species object for the named species.
Definition Phase.cpp:897
virtual void invalidateCache()
Invalidate any cached values which are normally updated only when a change in state is detected.
Definition Phase.cpp:926
void getMassFractions(double *const y) const
Get the species mass fractions.
Definition Phase.cpp:496
virtual size_t partialStateSize() const
Get the size of the partial state vector of the phase.
Definition Phase.h:316
virtual double pressure() const
Return the thermodynamic pressure (Pa).
Definition Phase.h:616
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:574
string name() const
Return the name of the phase.
Definition Phase.cpp:20
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.
double o2Present(const double *y) const
Helper function for computing the amount of oxygen available in the current mixture.
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.
virtual void setState_TPX(double t, double p, const double *x)
Set the temperature (K), pressure (Pa), and mole fractions.
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.
virtual double critPressure() const
Critical pressure (Pa).
virtual void setState_TPY(double t, double p, const double *y)
Set the internally stored temperature (K), pressure (Pa), and mass fractions of the phase.
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).
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.
virtual void getActivityConcentrations(double *c) const
This method returns an array of generalized concentrations.
double stoichAirFuelRatio(const double *fuelComp, const double *oxComp, ThermoBasis basis=ThermoBasis::molar) const
Compute the stoichiometric air to fuel ratio (kg oxidizer / kg fuel) given fuel and oxidizer composit...
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 double maxTemp(size_t k=npos) const
Maximum temperature for which the thermodynamic data for the species are valid.
double mixtureFraction(const double *fuelComp, 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...
double o2Required(const double *y) const
Helper function for computing the amount of oxygen required for complete oxidation.
void getElectrochemPotentials(double *mu) const
Get the species electrochemical potentials.
virtual void getdlnActCoeffdlnN(const size_t ld, double *const dlnActCoeffdlnN)
Get the array of derivatives of the log activity coefficients with respect to the log of the species ...
virtual void getActivityCoefficients(double *ac) const
Get the array of non-dimensional molar-based activity coefficients at the current solution temperatur...
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 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.
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.
virtual void getActivities(double *a) const
Get the array of non-dimensional activities at the current solution temperature, pressure,...
void setMixtureFraction(double mixFrac, const double *fuelComp, const double *oxComp, ThermoBasis basis=ThermoBasis::molar)
Set the mixture composition according to the mixture fraction = kg fuel / (kg oxidizer + kg fuel)
virtual void resetHf298(const size_t k=npos)
Restore the original heat of formation of one or more species.
virtual void getChemPotentials(double *mu) const
Get the species chemical potentials. Units: J/kmol.
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)
virtual void getLnActivityCoefficients(double *lnac) const
Get the array of non-dimensional molar-based ln activity coefficients at the current solution tempera...
double intEnergy_mass() const
Specific internal energy. Units: J/kg.
virtual Units standardConcentrationUnits() const
Returns the units of the "standard concentration" for this phase.
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.
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 setEquivalenceRatio(double phi, const double *fuelComp, const double *oxComp, ThermoBasis basis=ThermoBasis::molar)
Set the mixture composition according to the equivalence ratio.
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.
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:82
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:133
const double OneAtm
One atmosphere [Pa].
Definition ct_defs.h:98
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:182
const int cAC_CONVENTION_MOLAR
Standard state uses the molar convention.
const double SmallNumber
smallest number to compare to zero.
Definition ct_defs.h:160
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:179
Contains declarations for string manipulation functions within Cantera.