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