16#include "cantera/numerics/eigen_dense.h"
23 string flag = string(xy);
26 }
else if (flag ==
"TV") {
28 }
else if (flag ==
"HP") {
30 }
else if (flag ==
"UV") {
32 }
else if (flag ==
"SP") {
34 }
else if (flag ==
"SV") {
36 }
else if (flag ==
"UP") {
39 throw CanteraError(
"_equilflag",
"unknown property pair "+flag);
46const char* targetPropertyName(
int XY)
59 return "internal energy";
61 return "specified property";
65[[noreturn]]
void throwTemperatureBoundError(
const string& XYstr,
int XY,
66 double target,
double current,
67 double currentT,
double Tmin,
68 double Tmax,
int boundDirection)
70 string bound = boundDirection > 0 ?
"upper" :
"lower";
71 double Tbound = boundDirection > 0 ? Tmax : Tmin;
73 "Equilibration with the '{}' property pair failed because the solver "
74 "reached the {} temperature bound of {} K. The target {} is {}, but "
75 "the current value is {} at T = {} K. The enforced temperature bounds "
76 "are {} K to {} K. Disable temperature-limit enforcement to allow "
77 "extrapolation beyond this range.",
78 XYstr, bound, Tbound, targetPropertyName(XY), target, current,
79 currentT, Tmin, Tmax);
101 m_jwork1.resize(
m_mm+2);
102 m_jwork2.resize(
m_mm+2);
103 m_mu_RT.resize(
m_kk);
106 m_orderVectorElements.resize(
m_mm);
108 for (
size_t m = 0; m <
m_mm; m++) {
109 m_orderVectorElements[m] = m;
111 m_orderVectorSpecies.resize(
m_kk);
112 for (
size_t k = 0; k <
m_kk; k++) {
113 m_orderVectorSpecies[k] = k;
118 for (
size_t m = 0; m <
m_mm; m++) {
119 for (
size_t k = 0; k <
m_kk; k++) {
123 if (s.
nAtoms(k,m) < 0.0) {
127 if (mneg !=
npos && mneg != m) {
129 "negative atom numbers allowed for only one element");
137 "species {} has {} atoms of element {}, "
138 "but this element is not an electron.",
147 for (
size_t k = 0; k <
m_kk; k++) {
148 for (
size_t m = 0; m <
m_mm; m++) {
157 fill(m_mu_RT.begin(), m_mu_RT.end(), 0.0);
158 for (
size_t k = 0; k <
m_kk; k++) {
159 for (
size_t m = 0; m <
m_mm; m++) {
160 m_mu_RT[k] += lambda_RT[m]*
nAtoms(k,m);
181 for (
size_t m = 0; m <
m_mm; m++) {
183 for (
size_t k = 0; k <
m_kk; k++) {
187 "negative mole fraction for {}: {}",
196 for (
size_t m = 0; m <
m_mm; m++) {
209 e.setInitialMixMoles(loglevel-1);
214 m_component[m] = e.componentIndex(m);
222 writelog(
"setInitialMoles: Estimated Mole Fractions\n");
225 for (
size_t k = 0; k <
m_kk; k++) {
229 writelog(
" Element_Name ElementGoal ElementMF\n");
230 for (
size_t m = 0; m <
m_mm; m++) {
239 span<double> elMolesGoal,
int loglevel)
241 vector<double> b(
m_mm, -999.0);
242 vector<double> mu_RT(
m_kk, 0.0);
243 vector<double> xMF_est(
m_kk, 0.0);
246 for (
size_t n = 0; n < s.
nSpecies(); n++) {
247 xMF_est[n] = std::max(xMF_est[n], 1e-20);
255 bool usedZeroedSpecies =
false;
258 &mp, m_orderVectorSpecies,
259 m_orderVectorElements, formRxnMatrix);
262 size_t k = m_orderVectorSpecies[m];
264 xMF_est[k] = std::max(xMF_est[k], 1e-8);
270 m_orderVectorSpecies, m_orderVectorElements);
273 scale(mu_RT.begin(), mu_RT.end(), mu_RT.begin(),
278 size_t isp = m_component[m];
284 for (
size_t n = 0; n < s.
nSpecies(); n++) {
292 aa(m,n) =
nAtoms(m_component[m], m_orderVectorElements[n]);
294 b[m] = mu_RT[m_component[m]];
304 lambda_RT[m_orderVectorElements[m]] = b[m];
307 lambda_RT[m_orderVectorElements[m]] = 0.0;
311 writelog(
" id CompSpecies ChemPot EstChemPot Diff\n");
313 size_t isp = m_component[m];
315 for (
size_t n = 0; n <
m_mm; n++) {
316 tmp +=
nAtoms(isp, n) * lambda_RT[n];
318 writelogf(
"%3d %16s %10.5g %10.5g %10.5g\n",
319 m, s.
speciesName(isp), mu_RT[isp], tmp, tmp - mu_RT[isp]);
323 for (
size_t m = 0; m <
m_mm; m++) {
335 return equilibrate(s, XY, elMolesGoal, loglevel-1);
339 span<double> elMolesGoal,
int loglevel)
341 bool tempFixed =
true;
350 "Input ThermoPhase is incompatible with initialization");
392 "illegal property pair '{}'", XYstr);
396 if (tempFixed &&
options.enforceTemperatureLimits) {
399 throw CanteraError(
"ChemEquil::equilibrate",
"Specified temperature"
400 " ({} K) outside valid range of {} K to {} K\n",
407 double xval = m_p1(s);
408 double yval = m_p2(s);
411 size_t nvar = mm + 1;
413 vector<double> x(nvar, -102.0);
414 vector<double> res_trial(nvar, 0.0);
422 size_t m = m_orderVectorElements[im];
423 if (elMolesGoal[m] > tmp) {
425 tmp = elMolesGoal[m];
430 "Element Abundance Vector is zeroed");
436 vector<double> xmm(
m_kk, 0.0);
437 for (
size_t k = 0; k <
m_kk; k++) {
445 double tmaxPhase = s.
maxTemp();
446 double tminPhase = s.
minTemp();
447 double tminSolver =
options.enforceTemperatureLimits ? tminPhase :
449 double tmaxSolver =
options.enforceTemperatureLimits ? tmaxPhase :
450 std::max(tmaxPhase + 1000.0, 10.0 * tmaxPhase);
451 if (tmaxSolver <= tminSolver) {
452 tmaxSolver = tminSolver + 20.0;
454 int limitingTemperatureBound = 0;
458 double tmin = std::max(s.
temperature(), tminSolver);
459 if (tmin > tmaxSolver) {
460 tmin = tmaxSolver - 20;
462 double tmax = std::min(tmin + 10., tmaxSolver);
463 if (tmax < tminSolver) {
464 tmax = tminSolver + 20;
467 double slope, phigh, plow, pval, dt;
482 double t0 = 0.5*(tmin + tmax);
486 for (
int it = 0; it < 10; it++) {
505 slope = (phigh - plow)/(tmax - tmin);
506 dt = (xval - pval)/slope;
509 if (fabs(dt) < 50.0) {
512 dt =
clip(dt, -200.0, 200.0);
513 if ((t0 + dt) < tminSolver) {
514 dt = 0.5*((t0) + tminSolver) - t0;
516 if ((t0 + dt) > tmaxSolver) {
517 dt = 0.5*((t0) + tmaxSolver) - t0;
521 if (t0 <= tminSolver || t0 >= tmaxSolver) {
522 double current = m_p1(s);
525 throwTemperatureBoundError(XYstr, XY, xval, current, currentT,
526 tminPhase, tmaxPhase,
527 t0 >= tmaxSolver ? 1 : -1);
559 vector<double> above(nvar);
560 vector<double> below(nvar);
561 for (
size_t m = 0; m < mm; m++) {
572 if (
options.enforceTemperatureLimits) {
573 above[mm] = log(tmaxPhase);
576 above[mm] = log(tmaxSolver);
577 below[mm] = log(tminSolver);
580 vector<double> oldx(nvar, 0.0);
587 bool stalled =
false;
588 bool rankDeficient =
false;
591 double maxResid = 0.0;
596 const double maxInexactResid = 1e-5;
606 auto iterationError = [&](
int iter,
const CanteraError& err) {
609 fmt::format(
"The element potential iteration failed at iteration {}.\n",
611 +
"The equilibrium state of this mixture cannot be resolved using the "
612 "element potential formulation.\nConsider trying the 'gibbs' or 'vcs' "
613 "solver, which use a different formulation.\n\n"
614 "The underlying error was:\n" + err.getMessage());
617 for (
int iter = 0; iter <
options.maxIterations; iter++) {
622 throw iterationError(iter, err);
626 double deltax = (xx - xval)/xval;
627 double deltay = (yy - yval)/yval;
628 bool passThis =
true;
630 for (
size_t m = 0; m < nvar; m++) {
631 double tval =
options.relTolerance;
648 tval = elMolesGoal[m] *
options.relTolerance +
options.absElemTol;
651 if (fabs(res_trial[m]) > tval) {
654 maxResid = std::max(maxResid, fabs(res_trial[m]) / tval);
656 if (maxResid < 0.9 * bestResid) {
657 bestResid = maxResid;
659 }
else if (++noProgress > 20) {
667 bool inexact = stalled && rankDeficient && dxmax < 1e-12
668 && maxResid *
options.relTolerance < maxInexactResid;
669 if ((passThis || inexact)
670 && fabs(deltax) <
options.relTolerance
671 && fabs(deltay) <
options.relTolerance) {
673 if (!passThis &&
options.warnOnInexactConvergence) {
678 for (
size_t m = 0; m < mm; m++) {
689 "The equilibrium composition of this mixture is dominated by a "
690 "few species, leaving some element potentials determined only "
691 "by species present in trace amounts.\nThese potentials cannot "
692 "be resolved to the requested relative tolerance of {:g}.\n"
693 "Returning the most accurate solution available, in which the "
694 "mole fraction of element {} deviates from the specified value "
695 "by a relative amount of {:g}.\nErrors in the computed species "
696 "mole fractions are expected to be of a similar relative "
697 "magnitude, and may be much larger for species present in trace "
698 "amounts.\nConsider using the 'gibbs' or 'vcs' solvers, which do "
699 "not use the element potential formulation and can usually meet "
700 "the specified tolerance for such mixtures.",
705 adjustEloc(s, elMolesGoal);
711 "Temperature ({} K) outside valid range of {} K "
714 return passThis ? 0 : 1;
722 equilJacobian(s, x, elMolesGoal, jac, xval, yval);
724 throw iterationError(iter, err);
728 writelogf(
"Jacobian matrix %d:\n", iter);
729 for (
size_t m = 0; m <=
m_mm; m++) {
731 for (
size_t n = 0; n <=
m_mm; n++) {
739 }
else if (m == m_skip) {
744 writelog(
" = - ({:10.5g})\n", res_trial[m]);
749 scale(res_trial.begin(), res_trial.end(), res_trial.begin(), -1.0);
756 MappedMatrix J(
const_cast<double*
>(jac.
data().data()),
758 Eigen::JacobiSVD<Eigen::MatrixXd> svd(
759 J, Eigen::ComputeThinU | Eigen::ComputeThinV);
765 svd.setThreshold(1e-7);
766 rankDeficient = svd.rank() < nvar;
769 solve(jac, res_trial);
772 throw iterationError(iter, err);
781 if (
options.enforceTemperatureLimits && !tempFixed) {
782 double newTempVal = x[mm] + res_trial[mm];
783 if (newTempVal > above[mm]) {
784 limitingTemperatureBound = 1;
785 }
else if (newTempVal < below[mm]) {
786 limitingTemperatureBound = -1;
789 for (
size_t m = 0; m < nvar; m++) {
790 double newval = x[m] + res_trial[m];
791 if (newval > above[m]) {
793 std::min(fctr,0.8*(above[m] - x[m])/(newval - x[m])));
794 }
else if (newval < below[m]) {
795 if (m <
m_mm && (m != m_skip)) {
797 if (x[m] < below[m] + 50.) {
798 res_trial[m] = below[m] - x[m];
801 fctr = std::min(fctr, 0.8*(x[m] - below[m])/(x[m] - newval));
805 if (m == mm && fabs(res_trial[mm]) > 0.2) {
806 fctr = std::min(fctr, 0.2/fabs(res_trial[mm]));
809 if (fctr != 1.0 && loglevel > 0) {
811 "Soln Damping because of bounds: %g", fctr);
815 scale(res_trial.begin(), res_trial.end(), res_trial.begin(), fctr);
819 for (
size_t m = 0; m < nvar; m++) {
820 dxmax = std::max(dxmax, fabs(x[m] - oldx[m]));
827 if (
options.enforceTemperatureLimits && !tempFixed && limitingTemperatureBound == 0) {
828 if (x[mm] >= above[mm] - 1e-10) {
829 limitingTemperatureBound = 1;
830 }
else if (x[mm] <= below[mm] + 1e-10) {
831 limitingTemperatureBound = -1;
834 double current = m_p1(s);
837 if (limitingTemperatureBound != 0) {
838 throwTemperatureBoundError(XYstr, XY, xval, current, currentT,
839 tminPhase, tmaxPhase, limitingTemperatureBound);
842 "no convergence in {} iterations.",
options.maxIterations);
851 for (
size_t m = 0; m <
m_mm; m++) {
853 if (step[m] > 1.25) {
854 damp = std::min(damp, 1.25 /step[m]);
856 if (step[m] < -1.25) {
857 damp = std::min(damp, -1.25 / step[m]);
860 if (step[m] > 0.75) {
861 damp = std::min(damp, 0.75 /step[m]);
863 if (step[m] < -0.75) {
864 damp = std::min(damp, -0.75 / step[m]);
870 for (
size_t m = 0; m < x.size(); m++) {
871 x[m] = oldx[m] + damp * step[m];
874 writelogf(
"Solution Unknowns: damp = %g\n", damp);
876 for (
size_t m = 0; m <
m_mm; m++) {
877 writelogf(
" % -10.5g % -10.5g % -10.5g\n", x[m], oldx[m], step[m]);
883 span<const double> elmFracGoal, span<double> resid,
884 double xval,
double yval,
int loglevel)
890 for (
size_t n = 0; n <
m_mm; n++) {
891 size_t m = m_orderVectorElements[n];
894 resid[m] = x[m] + 1000.0;
900 if (elmFracGoal[m] < 1.0E-10 || elmFrac[m] < 1.0E-10 || m ==
m_eloc) {
901 resid[m] = elmFracGoal[m] - elmFrac[m];
903 resid[m] = log((1.0 + elmFracGoal[m]) / (1.0 + elmFrac[m]));
909 writelog(
"Residual: ElFracGoal ElFracCurrent Resid\n");
910 for (
size_t n = 0; n <
m_mm; n++) {
911 writelogf(
" % -14.7E % -14.7E % -10.5E\n",
912 elmFracGoal[n], elmFrac[n], resid[n]);
918 resid[
m_mm] = xx/xval - 1.0;
919 resid[m_skip] = yy/yval - 1.0;
923 writelogf(
" XX : % -14.7E % -14.7E % -10.5E\n", xval, xx, resid[
m_mm]);
924 writelogf(
" YY(%1d): % -14.7E % -14.7E % -10.5E\n", m_skip, yval, yy, resid[m_skip]);
928void ChemEquil::equilJacobian(
ThermoPhase& s, span<double> x, span<const double> elmols,
929 DenseMatrix& jac,
double xval,
double yval,
int loglevel)
931 vector<double>& r0 = m_jwork1;
932 vector<double>& r1 = m_jwork2;
933 size_t len = x.size();
936 double atol = 1.e-10;
940 for (
size_t n = 0; n <
len; n++) {
942 double dx = std::max(atol, fabs(xsave) * 1.0E-7);
951 for (
size_t m = 0; m < x.size(); m++) {
952 jac(m, n) = (r1[m] - r0[m])*rdx;
959 span<const double> Xmol_i_calc, span<double> eMolesCalc,
960 span<double> n_i_calc,
double pressureConst)
962 double n_t_calc = 0.0;
966 vector<double> actCoeff(
m_kk, 1.0);
971 for (
size_t k = 0; k <
m_kk; k++) {
972 double tmp = - (
m_muSS_RT[k] + log(actCoeff[k]));
973 for (
size_t m = 0; m <
m_mm; m++) {
974 tmp +=
nAtoms(k,m) * x[m];
976 tmp = std::min(tmp, 100.0);
980 n_i_calc[k] = n_t * exp(tmp);
982 n_t_calc += n_i_calc[k];
984 for (
size_t m = 0; m <
m_mm; m++) {
986 for (
size_t k = 0; k <
m_kk; k++) {
987 eMolesCalc[m] +=
nAtoms(k,m) * n_i_calc[k];
999 bool modifiedMatrix =
false;
1000 size_t neq =
m_mm+1;
1003 vector<double> b(neq, 0.0);
1004 vector<double> n_i(
m_kk,0.0);
1005 vector<double> n_i_calc(
m_kk,0.0);
1006 vector<double> actCoeff(
m_kk, 1.0);
1010 double pressureConst = s.
pressure();
1011 vector<double> Xmol_i_calc = n_i;
1013 vector<double> x_old(
m_mm+1, 0.0);
1014 vector<double> resid(
m_mm+1, 0.0);
1015 vector<int> lumpSum(
m_mm+1, 0);
1021 vector<double> eMolesCalc(
m_mm, 0.0);
1022 vector<double> eMolesFix(
m_mm, 0.0);
1023 double elMolesTotal = 0.0;
1024 for (
size_t m = 0; m <
m_mm; m++) {
1025 elMolesTotal += elMoles[m];
1026 for (
size_t k = 0; k <
m_kk; k++) {
1027 eMolesFix[m] +=
nAtoms(k,m) * n_i[k];
1031 for (
size_t m = 0; m <
m_mm; m++) {
1032 if (elMoles[m] > 1.0E-70) {
1033 x[m] =
clip(x[m], -100.0, 50.0);
1035 x[m] =
clip(x[m], -1000.0, 50.0);
1040 double nAtomsMax = 1.0;
1044 for (
size_t k = 0; k <
m_kk; k++) {
1045 double tmp = - (
m_muSS_RT[k] + log(actCoeff[k]));
1047 for (
size_t m = 0; m <
m_mm; m++) {
1048 double sum =
nAtoms(k,m);
1051 nAtomsMax = std::max(nAtomsMax, sum2);
1061 writelog(
"estimateEP_Brinkley::\n\n");
1064 writelog(
"Initial mole numbers and mu_SS:\n");
1065 writelog(
" Name MoleNum mu_SS actCoeff\n");
1066 for (
size_t k = 0; k <
m_kk; k++) {
1067 writelogf(
"%15s %13.5g %13.5g %13.5g\n",
1070 writelogf(
"Initial n_t = %10.5g\n", n_t);
1071 writelog(
"Comparison of Goal Element Abundance with Initial Guess:\n");
1072 writelog(
" eName eCurrent eGoal\n");
1073 for (
size_t m = 0; m <
m_mm; m++) {
1078 for (
size_t m = 0; m <
m_mm; m++) {
1085 for (
int iter = 0; iter < 20*
options.maxIterations; iter++) {
1087 for (
size_t m = 0; m <
m_mm; m++) {
1093 writelogf(
"START ITERATION %d:\n", iter);
1096 double n_t_calc =
calcEmoles(s, x, n_t, Xmol_i_calc, eMolesCalc, n_i_calc,
1099 for (
size_t k = 0; k <
m_kk; k++) {
1100 Xmol_i_calc[k] = n_i_calc[k]/n_t_calc;
1104 writelog(
" Species: Calculated_Moles Calculated_Mole_Fraction\n");
1105 for (
size_t k = 0; k <
m_kk; k++) {
1109 writelogf(
"%15s: %10.5g\n",
"Total Molar Sum", n_t_calc);
1110 writelogf(
"(iter %d) element moles bal: Goal Calculated\n", iter);
1111 for (
size_t m = 0; m <
m_mm; m++) {
1117 bool normalStep =
true;
1120 for (
size_t m = 0; m <
m_mm; m++) {
1121 if (elMoles[m] > 0.001 * elMolesTotal) {
1122 if (eMolesCalc[m] > 1000. * elMoles[m]) {
1126 if (1000 * eMolesCalc[m] < elMoles[m]) {
1133 writelogf(
" NOTE: iter(%d) Doing an abnormal step due to row %d\n", iter, iM);
1138 for (
size_t im = 0; im <
m_mm; im++) {
1139 size_t m = m_orderVectorElements[im];
1141 if (im <
m_nComponents && elMoles[m] > 0.001 * elMolesTotal) {
1142 if (eMolesCalc[m] > 1000. * elMoles[m]) {
1146 if (1000 * eMolesCalc[m] < elMoles[m]) {
1152 if (n_t < (elMolesTotal / nAtomsMax)) {
1153 if (resid[
m_mm] < 0.0) {
1156 }
else if (n_t > elMolesTotal) {
1157 resid[
m_mm] = std::min(resid[
m_mm], 0.0);
1182 for (
size_t m = 0; m <
m_mm; m++) {
1186 double nCutoff = 1.0E-9 * n_t_calc;
1188 writelog(
" Lump Sum Elements Calculation: \n");
1190 for (
size_t m = 0; m <
m_mm; m++) {
1192 size_t kMSp2 =
npos;
1193 for (
size_t k = 0; k <
m_kk; k++) {
1194 if (n_i_calc[k] > nCutoff && fabs(
nAtoms(k,m)) > 0.001) {
1197 double factor = fabs(
nAtoms(kMSp,m) /
nAtoms(kMSp2,m));
1198 for (
size_t n = 0; n <
m_mm; n++) {
1199 if (fabs(factor *
nAtoms(kMSp2,n) -
nAtoms(kMSp,n)) > 1.0E-8) {
1216 for (
size_t im = 0; im <
m_mm; im++) {
1217 size_t m = m_orderVectorElements[im];
1219 for (
size_t n = 0; n <
m_mm; n++) {
1221 for (
size_t k = 0; k <
m_kk; k++) {
1225 a1(m,
m_mm) = eMolesCalc[m];
1226 a1(
m_mm, m) = eMolesCalc[m];
1228 for (
size_t n = 0; n <=
m_mm; n++) {
1239 for (
size_t im = 0; im <
m_mm; im++) {
1240 size_t m = m_orderVectorElements[im];
1242 resid[m] = elMoles[m] - eMolesCalc[m];
1254 tmp = resid[m] / (elMoles[m] + elMolesTotal*1.0E-6 +
options.absElemTol);
1256 tmp = resid[m] / (elMoles[m] +
options.absElemTol);
1261 for (
size_t m = 0; m <
m_mm; m++) {
1262 if (a1(m,m) < 1.0E-50) {
1264 writelogf(
" NOTE: Diagonalizing the analytical Jac row %d\n", m);
1266 for (
size_t n = 0; n <
m_mm; n++) {
1270 if (resid[m] > 0.0) {
1272 }
else if (resid[m] < 0.0) {
1280 resid[
m_mm] = n_t - n_t_calc;
1284 for (
size_t m = 0; m <=
m_mm; m++) {
1286 for (
size_t n = 0; n <=
m_mm; n++) {
1293 sum += pow(resid[
m_mm] /(n_t + 1.0E-15), 2);
1295 writelogf(
"(it %d) Convergence = %g\n", iter, sum);
1302 if (sum < 0.05 *
options.relTolerance) {
1308 for (
size_t m = 0; m <=
m_mm; m++) {
1310 for (
size_t n = 0; n <=
m_mm; n++) {
1311 tmp += fabs(a1(m,n));
1313 if (m <
m_mm && tmp < 1.0E-30) {
1315 writelogf(
" NOTE: Diagonalizing row %d\n", m);
1317 for (
size_t n = 0; n <=
m_mm; n++) {
1325 for (
size_t n = 0; n <=
m_mm; n++) {
1333 for (
size_t m = 0; m <=
m_mm; m++) {
1335 for (
size_t n = 0; n <=
m_mm; n++) {
1361 modifiedMatrix =
false;
1362 for (
size_t m = 0; m <
m_mm; m++) {
1363 size_t sameAsRow =
npos;
1364 for (
size_t im = 0; im < m; im++) {
1365 bool theSame =
true;
1366 for (
size_t n = 0; n <
m_mm; n++) {
1367 if (fabs(a1(m,n) - a1(im,n)) > 1.0E-7) {
1376 if (sameAsRow !=
npos || lumpSum[m]) {
1379 writelogf(
"Lump summing row %d, due to rank deficiency analysis\n", m);
1380 }
else if (sameAsRow !=
npos) {
1381 writelogf(
"Identified that rows %d and %d are the same\n", m, sameAsRow);
1384 modifiedMatrix =
true;
1385 for (
size_t n = 0; n <
m_mm; n++) {
1387 a1(m,m) += fabs(a1(m,n));
1395 writelog(
"Row Summed, MODIFIED Matrix:\n");
1396 for (
size_t m = 0; m <=
m_mm; m++) {
1398 for (
size_t n = 0; n <=
m_mm; n++) {
1410 "The Jacobian used to estimate the initial element potentials is "
1411 "singular.\nThe equilibrium state of this mixture cannot be "
1412 "resolved using the element potential formulation.\nThe 'gibbs' "
1413 "and 'vcs' solvers do not use this formulation and can usually "
1414 "solve such problems.\n\nThe underlying error was:\n"
1421 for (
size_t m = 0; m <
m_mm; m++) {
1422 if (resid[m] > 1.0) {
1423 beta = std::min(beta, 1.0 / resid[m]);
1425 if (resid[m] < -1.0) {
1426 beta = std::min(beta, -1.0 / resid[m]);
1430 writelogf(
"(it %d) Beta = %g\n", iter, beta);
1434 for (
size_t m = 0; m <
m_mm; m++) {
1435 x[m] += beta * resid[m];
1437 n_t *= exp(beta * resid[
m_mm]);
1440 writelogf(
"(it %d) OLD_SOLUTION NEW SOLUTION (undamped updated)\n", iter);
1441 for (
size_t m = 0; m <
m_mm; m++) {
1442 writelogf(
" %5s %10.5g %10.5g %10.5g\n",
1453 writelogf(
" ChemEquil::estimateEP_Brinkley() SUCCESS: equilibrium found at T = %g, Pres = %g\n",
1456 writelogf(
" ChemEquil::estimateEP_Brinkley() FAILURE: equilibrium not found at T = %g, Pres = %g\n",
1464void ChemEquil::adjustEloc(
ThermoPhase& s, span<double> elMolesGoal)
1469 if (fabs(elMolesGoal[
m_eloc]) > 1.0E-20) {
1473 size_t maxPosEloc =
npos;
1474 size_t maxNegEloc =
npos;
1475 double maxPosVal = -1.0;
1476 double maxNegVal = -1.0;
1478 for (
size_t k = 0; k <
m_kk; k++) {
1490 double sumPos = 0.0;
1491 double sumNeg = 0.0;
1492 for (
size_t k = 0; k <
m_kk; k++) {
1502 if (sumPos >= sumNeg) {
1503 if (sumPos <= 0.0) {
1506 double factor = (elMolesGoal[
m_eloc] + sumNeg) / sumPos;
1507 if (
m_loglevel > 0 && factor < 0.9999999999) {
1508 writelogf(
"adjustEloc: adjusted %s and friends from %g to %g to ensure neutrality condition\n",
1512 for (
size_t k = 0; k <
m_kk; k++) {
1518 double factor = (-elMolesGoal[
m_eloc] + sumPos) / sumNeg;
1519 if (
m_loglevel > 0 && factor < 0.9999999999) {
1520 writelogf(
"adjustEloc: adjusted %s and friends from %g to %g to ensure neutrality condition\n",
1524 for (
size_t k = 0; k <
m_kk; k++) {
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
size_t nRows() const
Number of rows.
size_t nColumns() const
Number of columns.
vector< double > & data()
Return a reference to the data vector.
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
int setInitialMoles(ThermoPhase &s, span< double > elMoleGoal, int loglevel=0)
Estimate the initial mole numbers.
void equilResidual(ThermoPhase &s, span< const double > x, span< const double > elmtotal, span< double > resid, double xval, double yval, int loglevel=0)
Evaluates the residual vector F, of length m_mm.
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...
size_t m_kk
number of species in the phase
int m_loglevel
Verbosity of printed output.
size_t m_nComponents
This is equal to the rank of the stoichiometric coefficient matrix when it is computed.
ThermoPhase * m_phase
Pointer to the ThermoPhase object used to initialize this object.
double m_elementTotalSum
Current value of the sum of the element abundances given the current element potentials.
void update(const ThermoPhase &s)
Update internally stored state information.
int estimateElementPotentials(ThermoPhase &s, span< double > lambda, span< double > elMolesGoal, int loglevel=0)
Generate a starting estimate for the element potentials.
size_t m_eloc
Index of the element id corresponding to the electric charge of each species.
double calcEmoles(ThermoPhase &s, span< double > x, const double &n_t, span< const double > Xmol_i_calc, span< double > eMolesCalc, span< double > n_i_calc, double pressureConst)
Given a vector of dimensionless element abundances, this routine calculates the moles of the elements...
void initialize(ThermoPhase &s)
Prepare for equilibrium calculations.
EquilOpt options
Options controlling how the calculation is carried out.
vector< double > m_molefractions
Current value of the mole fractions in the single phase. length = m_kk.
vector< double > m_comp
Storage of the element compositions. natom(k,m) = m_comp[k*m_mm+ m];.
double nAtoms(size_t k, size_t m) const
number of atoms of element m in species k.
double m_elemFracCutoff
element fractional cutoff, below which the element will be zeroed.
vector< double > m_muSS_RT
Dimensionless values of the Gibbs free energy for the standard state of each species,...
void dampStep(span< double > oldx, span< double > step, span< double > x)
Find an acceptable step size and take it.
vector< double > m_elementmolefracs
Current value of the element mole fractions.
size_t m_mm
number of elements in the phase
void setToEquilState(ThermoPhase &s, span< const double > x, double t)
Set mixture to an equilibrium state consistent with specified element potentials and temperature.
int estimateEP_Brinkley(ThermoPhase &s, span< double > lambda, span< double > elMoles)
Do a calculation of the element potentials using the Brinkley method, p.
A class for full (non-sparse) matrices with Fortran-compatible data storage, which adds matrix operat...
Multiphase chemical equilibrium solver.
A class for multiphase mixtures.
void init()
Process phases and build atomic composition array.
size_t nSpecies() const
Number of species, summed over all phases.
void addPhase(shared_ptr< ThermoPhase > p, double moles)
Add a phase to the mixture.
size_t nElements() const
Number of elements.
void getMoleFractions(span< double > x) const
Get the species mole fraction vector.
size_t nSpecies() const
Returns the number of species in the phase.
double temperature() const
Temperature (K).
virtual void setPressure(double p)
Set the internally stored pressure (Pa) at constant temperature and composition.
double atomicWeight(size_t m) const
Atomic weight of element m.
string speciesName(size_t k) const
Name of the species with index k.
virtual size_t stateSize() const
Return size of vector defining internal state of the phase.
double moleFraction(size_t k) const
Return the mole fraction of a single species.
virtual double density() const
Density (kg/m^3).
double nAtoms(size_t k, size_t m) const
Number of atoms of element m in species k.
virtual void setTemperature(double temp)
Set the internally stored temperature of the phase (K).
size_t nElements() const
Number of elements.
virtual void setMoleFractions(span< const double > x)
Set the mole fractions to the specified values.
virtual void restoreState(span< const double > state)
Restore the state of the phase from a previously saved state vector.
virtual double pressure() const
Return the thermodynamic pressure (Pa).
string elementName(size_t m) const
Name of the element with index m.
virtual void saveState(span< double > state) const
Write to array 'state' the current internal state.
Base class for a phase with thermodynamic properties.
virtual void getGibbs_RT(span< double > grt) const
Get the nondimensional Gibbs functions for the species in their standard states at the current T and ...
virtual double minTemp(size_t k=npos) const
Minimum temperature for which the thermodynamic data for the species or phase are valid.
virtual double maxTemp(size_t k=npos) const
Maximum temperature for which the thermodynamic data for the species are valid.
double entropy_mass() const
Specific entropy. Units: J/kg/K.
double intEnergy_mass() const
Specific internal energy. Units: J/kg.
virtual void getChemPotentials(span< double > mu) const
Get the species chemical potentials. Units: J/kmol.
virtual void getActivityCoefficients(span< double > ac) const
Get the array of non-dimensional molar-based activity coefficients at the current solution temperatur...
double enthalpy_mass() const
Specific enthalpy. Units: J/kg.
This file contains definitions for utility functions and text for modules, inputfiles and logging,...
size_t BasisOptimize(bool &usedZeroedSpecies, bool doFormRxn, MultiPhase *mphase, span< size_t > orderVectorSpecies, span< size_t > orderVectorElements, span< double > formRxnMatrix)
Choose the optimum basis of species for the equilibrium calculations.
void ElemRearrange(size_t nComponents, span< const double > elementAbundances, MultiPhase *mphase, span< size_t > orderVectorSpecies, span< size_t > orderVectorElements)
Handles the potential rearrangement of the constraint equations represented by the Formula Matrix.
virtual void setToEquilState(span< const double > mu_RT)
This method is used by the ChemEquil equilibrium solver.
void writelogf(const char *fmt, const Args &... args)
Write a formatted message to the screen.
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
U len(const T &container)
Get the size of a container, cast to a signed integer type.
void scale(InputIter begin, InputIter end, OutputIter out, S scale_factor)
Multiply elements of an array by a scale factor.
T clip(const T &value, const T &lower, const T &upper)
Clip value such that lower <= value <= upper.
const double GasConstant
Universal Gas Constant [J/kmol/K].
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Namespace for the Cantera kernel.
const size_t npos
index returned by functions to indicate "no position"
void solve(DenseMatrix &A, span< double > b, size_t nrhs, size_t ldb)
Solve Ax = b. Array b is overwritten on exit with x.
MappedVector asVectorXd(vector< double > &v)
Convenience wrapper for accessing std::vector as an Eigen VectorXd.
const double SmallNumber
smallest number to compare to zero.
int _equilflag(const char *xy)
map property strings to integers
const double BigNumber
largest number to compare to inf.
Contains declarations for string manipulation functions within Cantera.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...