Cantera  4.0.0a1
Loading...
Searching...
No Matches
ChemEquil.cpp
Go to the documentation of this file.
1/**
2 * @file ChemEquil.cpp
3 * Chemical equilibrium. Implementation file for class
4 * ChemEquil.
5 */
6
7// This file is part of Cantera. See License.txt in the top-level directory or
8// at https://cantera.org/license.txt for license and copyright information.
9
15#include "cantera/base/global.h"
16
17namespace Cantera
18{
19
20int _equilflag(const char* xy)
21{
22 string flag = string(xy);
23 if (flag == "TP") {
24 return TP;
25 } else if (flag == "TV") {
26 return TV;
27 } else if (flag == "HP") {
28 return HP;
29 } else if (flag == "UV") {
30 return UV;
31 } else if (flag == "SP") {
32 return SP;
33 } else if (flag == "SV") {
34 return SV;
35 } else if (flag == "UP") {
36 return UP;
37 } else {
38 throw CanteraError("_equilflag","unknown property pair "+flag);
39 }
40 return -1;
41}
42
43ChemEquil::ChemEquil(ThermoPhase& s)
44{
45 initialize(s);
46}
47
49{
50 // store a pointer to s and some of its properties locally.
51 m_phase = &s;
52 m_p0 = s.refPressure();
53 m_kk = s.nSpecies();
54 m_mm = s.nElements();
56
57 // allocate space in internal work arrays within the ChemEquil object
58 m_molefractions.resize(m_kk);
60 m_comp.resize(m_mm * m_kk);
61 m_jwork1.resize(m_mm+2);
62 m_jwork2.resize(m_mm+2);
63 m_startSoln.resize(m_mm+1);
64 m_grt.resize(m_kk);
65 m_mu_RT.resize(m_kk);
66 m_muSS_RT.resize(m_kk);
67 m_component.resize(m_mm,npos);
68 m_orderVectorElements.resize(m_mm);
69
70 for (size_t m = 0; m < m_mm; m++) {
71 m_orderVectorElements[m] = m;
72 }
73 m_orderVectorSpecies.resize(m_kk);
74 for (size_t k = 0; k < m_kk; k++) {
75 m_orderVectorSpecies[k] = k;
76 }
77
78 // set up elemental composition matrix
79 size_t mneg = npos;
80 for (size_t m = 0; m < m_mm; m++) {
81 for (size_t k = 0; k < m_kk; k++) {
82 // handle the case of negative atom numbers (used to
83 // represent positive ions, where the 'element' is an
84 // electron
85 if (s.nAtoms(k,m) < 0.0) {
86 // if negative atom numbers have already been specified
87 // for some element other than this one, throw
88 // an exception
89 if (mneg != npos && mneg != m) {
90 throw CanteraError("ChemEquil::initialize",
91 "negative atom numbers allowed for only one element");
92 }
93 mneg = m;
94
95 // the element should be an electron... if it isn't
96 // print a warning.
97 if (s.atomicWeight(m) > 1.0e-3) {
98 warn_user("ChemEquil::initialize",
99 "species {} has {} atoms of element {}, "
100 "but this element is not an electron.",
101 s.speciesName(k), s.nAtoms(k,m), s.elementName(m));
102 }
103 }
104 }
105 }
106 m_eloc = mneg;
107
108 // set up the elemental composition matrix
109 for (size_t k = 0; k < m_kk; k++) {
110 for (size_t m = 0; m < m_mm; m++) {
111 m_comp[k*m_mm + m] = s.nAtoms(k,m);
112 }
113 }
114}
115
116void ChemEquil::setToEquilState(ThermoPhase& s, span<const double> lambda_RT, double t)
117{
118 // Construct the chemical potentials by summing element potentials
119 fill(m_mu_RT.begin(), m_mu_RT.end(), 0.0);
120 for (size_t k = 0; k < m_kk; k++) {
121 for (size_t m = 0; m < m_mm; m++) {
122 m_mu_RT[k] += lambda_RT[m]*nAtoms(k,m);
123 }
124 }
125
126 // Set the temperature
127 s.setTemperature(t);
128
129 // Call the phase-specific method to set the phase to the
130 // equilibrium state with the specified species chemical
131 // potentials.
132 s.setToEquilState(m_mu_RT);
133 update(s);
134}
135
137{
138 // get the mole fractions, temperature, and density
140 m_temp = s.temperature();
141 m_dens = s.density();
142
143 // compute the elemental mole fractions
144 double sum = 0.0;
145 for (size_t m = 0; m < m_mm; m++) {
146 m_elementmolefracs[m] = 0.0;
147 for (size_t k = 0; k < m_kk; k++) {
149 if (m_molefractions[k] < 0.0) {
150 throw CanteraError("ChemEquil::update",
151 "negative mole fraction for {}: {}",
153 }
154 }
155 sum += m_elementmolefracs[m];
156 }
157 // Store the sum for later use
158 m_elementTotalSum = sum;
159 // normalize the element mole fractions
160 for (size_t m = 0; m < m_mm; m++) {
161 m_elementmolefracs[m] /= sum;
162 }
163}
164
165int ChemEquil::setInitialMoles(ThermoPhase& s, span<double> elMoleGoal, int loglevel)
166{
167 MultiPhase mp;
168 // Create a non-owning shared_ptr, since ThermoPhase `s` is guaranteed to outlive
169 // the MultiPhase object.
170 mp.addPhase(shared_ptr<ThermoPhase>(&s, [](ThermoPhase*) {}), 1.0);
171 mp.init();
172 MultiPhaseEquil e(&mp, true, loglevel-1);
173 e.setInitialMixMoles(loglevel-1);
174
175 // store component indices
176 m_nComponents = std::min(m_nComponents, m_kk);
177 for (size_t m = 0; m < m_nComponents; m++) {
178 m_component[m] = e.componentIndex(m);
179 }
180
181 // Update the current values of the temp, density, and mole fraction,
182 // and element abundance vectors kept within the ChemEquil object.
183 update(s);
184
185 if (m_loglevel > 0) {
186 writelog("setInitialMoles: Estimated Mole Fractions\n");
187 writelogf(" Temperature = %g\n", s.temperature());
188 writelogf(" Pressure = %g\n", s.pressure());
189 for (size_t k = 0; k < m_kk; k++) {
190 writelogf(" %-12s % -10.5g\n",
191 s.speciesName(k), s.moleFraction(k));
192 }
193 writelog(" Element_Name ElementGoal ElementMF\n");
194 for (size_t m = 0; m < m_mm; m++) {
195 writelogf(" %-12s % -10.5g% -10.5g\n",
196 s.elementName(m), elMoleGoal[m], m_elementmolefracs[m]);
197 }
198 }
199 return 0;
200}
201
203 span<double> elMolesGoal, int loglevel)
204{
205 vector<double> b(m_mm, -999.0);
206 vector<double> mu_RT(m_kk, 0.0);
207 vector<double> xMF_est(m_kk, 0.0);
208
209 s.getMoleFractions(xMF_est);
210 for (size_t n = 0; n < s.nSpecies(); n++) {
211 xMF_est[n] = std::max(xMF_est[n], 1e-20);
212 }
213 s.setMoleFractions(xMF_est);
214 s.getMoleFractions(xMF_est);
215
216 MultiPhase mp;
217 mp.addPhase(shared_ptr<ThermoPhase>(&s, [](ThermoPhase*) {}), 1.0);
218 mp.init();
219 bool usedZeroedSpecies = false;
220 vector<double> formRxnMatrix(mp.nSpecies() * mp.nElements());
221 m_nComponents = BasisOptimize(usedZeroedSpecies, false,
222 &mp, m_orderVectorSpecies,
223 m_orderVectorElements, formRxnMatrix);
224
225 for (size_t m = 0; m < m_nComponents; m++) {
226 size_t k = m_orderVectorSpecies[m];
227 m_component[m] = k;
228 xMF_est[k] = std::max(xMF_est[k], 1e-8);
229 }
230 s.setMoleFractions(xMF_est);
231 s.getMoleFractions(xMF_est);
232
233 ElemRearrange(m_nComponents, elMolesGoal, &mp,
234 m_orderVectorSpecies, m_orderVectorElements);
235
236 s.getChemPotentials(mu_RT);
237 scale(mu_RT.begin(), mu_RT.end(), mu_RT.begin(),
238 1.0/(GasConstant* s.temperature()));
239
240 if (loglevel > 0) {
241 for (size_t m = 0; m < m_nComponents; m++) {
242 size_t isp = m_component[m];
243 writelogf("isp = %d, %s\n", isp, s.speciesName(isp));
244 }
245 writelogf("Pressure = %g\n", s.pressure());
246 writelogf("Temperature = %g\n", s.temperature());
247 writelog(" id Name MF mu/RT \n");
248 for (size_t n = 0; n < s.nSpecies(); n++) {
249 writelogf("%10d %15s %10.5g %10.5g\n",
250 n, s.speciesName(n), xMF_est[n], mu_RT[n]);
251 }
252 }
254 for (size_t m = 0; m < m_nComponents; m++) {
255 for (size_t n = 0; n < m_nComponents; n++) {
256 aa(m,n) = nAtoms(m_component[m], m_orderVectorElements[n]);
257 }
258 b[m] = mu_RT[m_component[m]];
259 }
260
261 int info = 0;
262 try {
263 solve(aa, b);
264 } catch (CanteraError&) {
265 info = -2;
266 }
267 for (size_t m = 0; m < m_nComponents; m++) {
268 lambda_RT[m_orderVectorElements[m]] = b[m];
269 }
270 for (size_t m = m_nComponents; m < m_mm; m++) {
271 lambda_RT[m_orderVectorElements[m]] = 0.0;
272 }
273
274 if (loglevel > 0) {
275 writelog(" id CompSpecies ChemPot EstChemPot Diff\n");
276 for (size_t m = 0; m < m_nComponents; m++) {
277 size_t isp = m_component[m];
278 double tmp = 0.0;
279 for (size_t n = 0; n < m_mm; n++) {
280 tmp += nAtoms(isp, n) * lambda_RT[n];
281 }
282 writelogf("%3d %16s %10.5g %10.5g %10.5g\n",
283 m, s.speciesName(isp), mu_RT[isp], tmp, tmp - mu_RT[isp]);
284 }
285
286 writelog(" id ElName Lambda_RT\n");
287 for (size_t m = 0; m < m_mm; m++) {
288 writelogf(" %3d %6s %10.5g\n", m, s.elementName(m), lambda_RT[m]);
289 }
290 }
291 return info;
292}
293
294int ChemEquil::equilibrate(ThermoPhase& s, const char* XY, int loglevel)
295{
296 initialize(s);
297 update(s);
298 vector<double> elMolesGoal = m_elementmolefracs;
299 return equilibrate(s, XY, elMolesGoal, loglevel-1);
300}
301
302int ChemEquil::equilibrate(ThermoPhase& s, const char* XYstr,
303 span<double> elMolesGoal, int loglevel)
304{
305 int fail = 0;
306 bool tempFixed = true;
307 int XY = _equilflag(XYstr);
308 vector<double> state(s.stateSize());
309 s.saveState(state);
310 m_loglevel = loglevel;
311
312 // Check Compatibility
313 if (m_mm != s.nElements() || m_kk != s.nSpecies()) {
314 throw CanteraError("ChemEquil::equilibrate",
315 "Input ThermoPhase is incompatible with initialization");
316 }
317
318 initialize(s);
319 update(s);
320 switch (XY) {
321 case TP:
322 case PT:
323 m_p1 = [](ThermoPhase& s) { return s.temperature(); };
324 m_p2 = [](ThermoPhase& s) { return s.pressure(); };
325 break;
326 case HP:
327 case PH:
328 tempFixed = false;
329 m_p1 = [](ThermoPhase& s) { return s.enthalpy_mass(); };
330 m_p2 = [](ThermoPhase& s) { return s.pressure(); };
331 break;
332 case SP:
333 case PS:
334 tempFixed = false;
335 m_p1 = [](ThermoPhase& s) { return s.entropy_mass(); };
336 m_p2 = [](ThermoPhase& s) { return s.pressure(); };
337 break;
338 case SV:
339 case VS:
340 tempFixed = false;
341 m_p1 = [](ThermoPhase& s) { return s.entropy_mass(); };
342 m_p2 = [](ThermoPhase& s) { return s.density(); };
343 break;
344 case TV:
345 case VT:
346 m_p1 = [](ThermoPhase& s) { return s.temperature(); };
347 m_p2 = [](ThermoPhase& s) { return s.density(); };
348 break;
349 case UV:
350 case VU:
351 tempFixed = false;
352 m_p1 = [](ThermoPhase& s) { return s.intEnergy_mass(); };
353 m_p2 = [](ThermoPhase& s) { return s.density(); };
354 break;
355 default:
356 throw CanteraError("ChemEquil::equilibrate",
357 "illegal property pair '{}'", XYstr);
358 }
359 // If the temperature is one of the specified variables, and
360 // it is outside the valid range, throw an exception.
361 if (tempFixed) {
362 double tfixed = s.temperature();
363 if (tfixed > s.maxTemp() + 1.0 || tfixed < s.minTemp() - 1.0) {
364 throw CanteraError("ChemEquil::equilibrate", "Specified temperature"
365 " ({} K) outside valid range of {} K to {} K\n",
366 s.temperature(), s.minTemp(), s.maxTemp());
367 }
368 }
369
370 // Before we do anything to change the ThermoPhase object, we calculate and
371 // store the two specified thermodynamic properties that we are after.
372 double xval = m_p1(s);
373 double yval = m_p2(s);
374
375 size_t mm = m_mm;
376 size_t nvar = mm + 1;
377 DenseMatrix jac(nvar, nvar); // Jacobian
378 vector<double> x(nvar, -102.0); // solution vector
379 vector<double> res_trial(nvar, 0.0); // residual
380
381 // Replace one of the element abundance fraction equations with the
382 // specified property calculation.
383 //
384 // We choose the equation of the element with the highest element abundance.
385 double tmp = -1.0;
386 for (size_t im = 0; im < m_nComponents; im++) {
387 size_t m = m_orderVectorElements[im];
388 if (elMolesGoal[m] > tmp) {
389 m_skip = m;
390 tmp = elMolesGoal[m];
391 }
392 }
393 if (tmp <= 0.0) {
394 throw CanteraError("ChemEquil::equilibrate",
395 "Element Abundance Vector is zeroed");
396 }
397
398 // start with a composition with everything non-zero. Note that since we
399 // have already save the target element moles, changing the composition at
400 // this point only affects the starting point, not the final solution.
401 vector<double> xmm(m_kk, 0.0);
402 for (size_t k = 0; k < m_kk; k++) {
403 xmm[k] = s.moleFraction(k) + 1.0E-32;
404 }
405 s.setMoleFractions(xmm);
406
407 // Update the internally stored values of m_temp, m_dens, and the element
408 // mole fractions.
409 update(s);
410
411 double tmaxPhase = s.maxTemp();
412 double tminPhase = s.minTemp();
413 // loop to estimate T
414 if (!tempFixed) {
415 double tmin = std::max(s.temperature(), tminPhase);
416 if (tmin > tmaxPhase) {
417 tmin = tmaxPhase - 20;
418 }
419 double tmax = std::min(tmin + 10., tmaxPhase);
420 if (tmax < tminPhase) {
421 tmax = tminPhase + 20;
422 }
423
424 double slope, phigh, plow, pval, dt;
425
426 // first get the property values at the upper and lower temperature
427 // limits. Since p1 (h, s, or u) is monotonic in T, these values
428 // determine the upper and lower bounds (phigh, plow) for p1.
429
430 s.setTemperature(tmax);
431 setInitialMoles(s, elMolesGoal, loglevel - 1);
432 phigh = m_p1(s);
433
434 s.setTemperature(tmin);
435 setInitialMoles(s, elMolesGoal, loglevel - 1);
436 plow = m_p1(s);
437
438 // start with T at the midpoint of the range
439 double t0 = 0.5*(tmin + tmax);
440 s.setTemperature(t0);
441
442 // loop up to 5 times
443 for (int it = 0; it < 10; it++) {
444 // set the composition and get p1
445 setInitialMoles(s, elMolesGoal, loglevel - 1);
446 pval = m_p1(s);
447
448 // If this value of p1 is greater than the specified property value,
449 // then the current temperature is too high. Use it as the new upper
450 // bound. Otherwise, it is too low, so use it as the new lower
451 // bound.
452 if (pval > xval) {
453 tmax = t0;
454 phigh = pval;
455 } else {
456 tmin = t0;
457 plow = pval;
458 }
459
460 // Determine the new T estimate by linearly interpolating
461 // between the upper and lower bounds
462 slope = (phigh - plow)/(tmax - tmin);
463 dt = (xval - pval)/slope;
464
465 // If within 50 K, terminate the search
466 if (fabs(dt) < 50.0) {
467 break;
468 }
469 dt = clip(dt, -200.0, 200.0);
470 if ((t0 + dt) < tminPhase) {
471 dt = 0.5*((t0) + tminPhase) - t0;
472 }
473 if ((t0 + dt) > tmaxPhase) {
474 dt = 0.5*((t0) + tmaxPhase) - t0;
475 }
476 // update the T estimate
477 t0 += dt;
478 if (t0 <= tminPhase || t0 >= tmaxPhase || t0 < 100.0) {
479 throw CanteraError("ChemEquil::equilibrate", "T out of bounds");
480 }
481 s.setTemperature(t0);
482 }
483 }
484
485 setInitialMoles(s, elMolesGoal,loglevel);
486
487 // Calculate initial estimates of the element potentials. This algorithm
488 // uses the MultiPhaseEquil object's initialization capabilities to
489 // calculate an initial estimate of the mole fractions for a set of linearly
490 // independent component species. Then, the element potentials are solved
491 // for based on the chemical potentials of the component species.
492 estimateElementPotentials(s, x, elMolesGoal);
493
494 // Do a better estimate of the element potentials. We have found that the
495 // current estimate may not be good enough to avoid drastic numerical issues
496 // associated with the use of a numerically generated Jacobian.
497 //
498 // The Brinkley algorithm assumes a constant T, P system and uses a
499 // linearized analytical Jacobian that turns out to be very stable.
500 int info = estimateEP_Brinkley(s, x, elMolesGoal);
501 if (info == 0) {
502 setToEquilState(s, x, s.temperature());
503 }
504
505 // Install the log(temp) into the last solution unknown slot.
506 x[m_mm] = log(s.temperature());
507
508 // Setting the max and min values for x[]. Also, if element abundance vector
509 // is zero, setting x[] to -1000. This effectively zeroes out all species
510 // containing that element.
511 vector<double> above(nvar);
512 vector<double> below(nvar);
513 for (size_t m = 0; m < mm; m++) {
514 above[m] = 200.0;
515 below[m] = -2000.0;
516 if (elMolesGoal[m] < m_elemFracCutoff && m != m_eloc) {
517 x[m] = -1000.0;
518 }
519 }
520
521 // Set the temperature bounds to be 25 degrees different than the max and
522 // min temperatures.
523 above[mm] = log(s.maxTemp() + 25.0);
524 below[mm] = log(s.minTemp() - 25.0);
525
526 vector<double> grad(nvar, 0.0); // gradient of f = F*F/2
527 vector<double> oldx(nvar, 0.0); // old solution
528 vector<double> oldresid(nvar, 0.0);
529
530 for (int iter = 0; iter < options.maxIterations; iter++) {
531 // check for convergence.
532 equilResidual(s, x, elMolesGoal, res_trial, xval, yval);
533 double f = 0.5*dot(res_trial.begin(), res_trial.end(), res_trial.begin());
534 double xx = m_p1(s);
535 double yy = m_p2(s);
536 double deltax = (xx - xval)/xval;
537 double deltay = (yy - yval)/yval;
538 bool passThis = true;
539 for (size_t m = 0; m < nvar; m++) {
540 double tval = options.relTolerance;
541 if (m < mm) {
542 // Special case convergence requirements for electron element.
543 // This is a special case because the element coefficients may
544 // be both positive and negative. And, typically they sum to
545 // 0.0. Therefore, there is no natural absolute value for this
546 // quantity. We supply the absolute value tolerance here. Note,
547 // this is made easier since the element abundances are
548 // normalized to one within this routine.
549 //
550 // Note, the 1.0E-13 value was recently relaxed from 1.0E-15,
551 // because convergence failures were found to occur for the
552 // lower value at small pressure (0.01 pascal).
553 if (m == m_eloc) {
554 tval = elMolesGoal[m] * options.relTolerance + options.absElemTol
555 + 1.0E-13;
556 } else {
557 tval = elMolesGoal[m] * options.relTolerance + options.absElemTol;
558 }
559 }
560 if (fabs(res_trial[m]) > tval) {
561 passThis = false;
562 }
563 }
564 if (iter > 0 && passThis && fabs(deltax) < options.relTolerance
565 && fabs(deltay) < options.relTolerance) {
566 options.iterations = iter;
567
568 if (m_eloc != npos) {
569 adjustEloc(s, elMolesGoal);
570 }
571
572 if (s.temperature() > s.maxTemp() + 1.0 ||
573 s.temperature() < s.minTemp() - 1.0) {
574 warn_user("ChemEquil::equilibrate",
575 "Temperature ({} K) outside valid range of {} K "
576 "to {} K", s.temperature(), s.minTemp(), s.maxTemp());
577 }
578 return 0;
579 }
580 // compute the residual and the Jacobian using the current
581 // solution vector
582 equilResidual(s, x, elMolesGoal, res_trial, xval, yval);
583 f = 0.5*dot(res_trial.begin(), res_trial.end(), res_trial.begin());
584
585 // Compute the Jacobian matrix
586 equilJacobian(s, x, elMolesGoal, jac, xval, yval);
587
588 if (m_loglevel > 0) {
589 writelogf("Jacobian matrix %d:\n", iter);
590 for (size_t m = 0; m <= m_mm; m++) {
591 writelog(" [ ");
592 for (size_t n = 0; n <= m_mm; n++) {
593 writelog("{:10.5g} ", jac(m,n));
594 }
595 writelog(" ]");
596 if (m < m_mm) {
597 writelog("x_{:10s}", s.elementName(m));
598 } else if (m_eloc == m) {
599 writelog("x_ELOC");
600 } else if (m == m_skip) {
601 writelog("x_YY");
602 } else {
603 writelog("x_XX");
604 }
605 writelog(" = - ({:10.5g})\n", res_trial[m]);
606 }
607 }
608
609 oldx = x;
610 double oldf = f;
611 scale(res_trial.begin(), res_trial.end(), res_trial.begin(), -1.0);
612
613 // Solve the system
614 try {
615 solve(jac, res_trial);
616 } catch (CanteraError& err) {
617 s.restoreState(state);
618 throw CanteraError("ChemEquil::equilibrate",
619 "Jacobian is singular. \nTry adding more species, "
620 "changing the elemental composition slightly, \nor removing "
621 "unused elements.\n\n" + err.getMessage());
622 }
623
624 // find the factor by which the Newton step can be multiplied
625 // to keep the solution within bounds.
626 double fctr = 1.0;
627 for (size_t m = 0; m < nvar; m++) {
628 double newval = x[m] + res_trial[m];
629 if (newval > above[m]) {
630 fctr = std::max(0.0,
631 std::min(fctr,0.8*(above[m] - x[m])/(newval - x[m])));
632 } else if (newval < below[m]) {
633 if (m < m_mm && (m != m_skip)) {
634 res_trial[m] = -50;
635 if (x[m] < below[m] + 50.) {
636 res_trial[m] = below[m] - x[m];
637 }
638 } else {
639 fctr = std::min(fctr, 0.8*(x[m] - below[m])/(x[m] - newval));
640 }
641 }
642 // Delta Damping
643 if (m == mm && fabs(res_trial[mm]) > 0.2) {
644 fctr = std::min(fctr, 0.2/fabs(res_trial[mm]));
645 }
646 }
647 if (fctr != 1.0 && loglevel > 0) {
648 warn_user("ChemEquil::equilibrate",
649 "Soln Damping because of bounds: %g", fctr);
650 }
651
652 // multiply the step by the scaling factor
653 scale(res_trial.begin(), res_trial.end(), res_trial.begin(), fctr);
654
655 if (!dampStep(s, oldx, oldf, grad, res_trial,
656 x, f, elMolesGoal , xval, yval)) {
657 fail++;
658 if (fail > 3) {
659 s.restoreState(state);
660 throw CanteraError("ChemEquil::equilibrate",
661 "Cannot find an acceptable Newton damping coefficient.");
662 }
663 } else {
664 fail = 0;
665 }
666 }
667
668 // no convergence
669 s.restoreState(state);
670 throw CanteraError("ChemEquil::equilibrate",
671 "no convergence in {} iterations.", options.maxIterations);
672}
673
674
675int ChemEquil::dampStep(ThermoPhase& mix, span<double> oldx, double oldf,
676 span<double> grad, span<double> step, span<double> x,
677 double& f, span<double> elmols, double xval, double yval)
678{
679 // Carry out a delta damping approach on the dimensionless element
680 // potentials.
681 double damp = 1.0;
682 for (size_t m = 0; m < m_mm; m++) {
683 if (m == m_eloc) {
684 if (step[m] > 1.25) {
685 damp = std::min(damp, 1.25 /step[m]);
686 }
687 if (step[m] < -1.25) {
688 damp = std::min(damp, -1.25 / step[m]);
689 }
690 } else {
691 if (step[m] > 0.75) {
692 damp = std::min(damp, 0.75 /step[m]);
693 }
694 if (step[m] < -0.75) {
695 damp = std::min(damp, -0.75 / step[m]);
696 }
697 }
698 }
699
700 // Update the solution unknown
701 for (size_t m = 0; m < x.size(); m++) {
702 x[m] = oldx[m] + damp * step[m];
703 }
704 if (m_loglevel > 0) {
705 writelogf("Solution Unknowns: damp = %g\n", damp);
706 writelog(" X_new X_old Step\n");
707 for (size_t m = 0; m < m_mm; m++) {
708 writelogf(" % -10.5g % -10.5g % -10.5g\n", x[m], oldx[m], step[m]);
709 }
710 }
711 return 1;
712}
713
714void ChemEquil::equilResidual(ThermoPhase& s, span<const double> x,
715 span<const double> elmFracGoal, span<double> resid,
716 double xval, double yval, int loglevel)
717{
718 setToEquilState(s, x, exp(x[m_mm]));
719
720 // residuals are the total element moles
721 vector<double>& elmFrac = m_elementmolefracs;
722 for (size_t n = 0; n < m_mm; n++) {
723 size_t m = m_orderVectorElements[n];
724 // drive element potential for absent elements to -1000
725 if (elmFracGoal[m] < m_elemFracCutoff && m != m_eloc) {
726 resid[m] = x[m] + 1000.0;
727 } else if (n >= m_nComponents) {
728 resid[m] = x[m];
729 } else {
730 // Change the calculation for small element number, using
731 // L'Hopital's rule. The log formulation is unstable.
732 if (elmFracGoal[m] < 1.0E-10 || elmFrac[m] < 1.0E-10 || m == m_eloc) {
733 resid[m] = elmFracGoal[m] - elmFrac[m];
734 } else {
735 resid[m] = log((1.0 + elmFracGoal[m]) / (1.0 + elmFrac[m]));
736 }
737 }
738 }
739
740 if (loglevel > 0 && !m_doResPerturb) {
741 writelog("Residual: ElFracGoal ElFracCurrent Resid\n");
742 for (size_t n = 0; n < m_mm; n++) {
743 writelogf(" % -14.7E % -14.7E % -10.5E\n",
744 elmFracGoal[n], elmFrac[n], resid[n]);
745 }
746 }
747
748 double xx = m_p1(s);
749 double yy = m_p2(s);
750 resid[m_mm] = xx/xval - 1.0;
751 resid[m_skip] = yy/yval - 1.0;
752
753 if (loglevel > 0 && !m_doResPerturb) {
754 writelog(" Goal Xvalue Resid\n");
755 writelogf(" XX : % -14.7E % -14.7E % -10.5E\n", xval, xx, resid[m_mm]);
756 writelogf(" YY(%1d): % -14.7E % -14.7E % -10.5E\n", m_skip, yval, yy, resid[m_skip]);
757 }
758}
759
760void ChemEquil::equilJacobian(ThermoPhase& s, span<double> x, span<const double> elmols,
761 DenseMatrix& jac, double xval, double yval, int loglevel)
762{
763 vector<double>& r0 = m_jwork1;
764 vector<double>& r1 = m_jwork2;
765 size_t len = x.size();
766 r0.resize(len);
767 r1.resize(len);
768 double atol = 1.e-10;
769
770 equilResidual(s, x, elmols, r0, xval, yval, loglevel-1);
771
772 m_doResPerturb = false;
773 for (size_t n = 0; n < len; n++) {
774 double xsave = x[n];
775 double dx = std::max(atol, fabs(xsave) * 1.0E-7);
776 x[n] = xsave + dx;
777 dx = x[n] - xsave;
778 double rdx = 1.0/dx;
779
780 // calculate perturbed residual
781 equilResidual(s, x, elmols, r1, xval, yval, loglevel-1);
782
783 // compute nth column of Jacobian
784 for (size_t m = 0; m < x.size(); m++) {
785 jac(m, n) = (r1[m] - r0[m])*rdx;
786 }
787 x[n] = xsave;
788 }
789 m_doResPerturb = false;
790}
791
792double ChemEquil::calcEmoles(ThermoPhase& s, span<double> x, const double& n_t,
793 span<const double> Xmol_i_calc, span<double> eMolesCalc,
794 span<double> n_i_calc, double pressureConst)
795{
796 double n_t_calc = 0.0;
797
798 // Calculate the activity coefficients of the solution, at the previous
799 // solution state.
800 vector<double> actCoeff(m_kk, 1.0);
801 s.setMoleFractions(Xmol_i_calc);
802 s.setPressure(pressureConst);
803 s.getActivityCoefficients(actCoeff);
804
805 for (size_t k = 0; k < m_kk; k++) {
806 double tmp = - (m_muSS_RT[k] + log(actCoeff[k]));
807 for (size_t m = 0; m < m_mm; m++) {
808 tmp += nAtoms(k,m) * x[m];
809 }
810 tmp = std::min(tmp, 100.0);
811 if (tmp < -300.) {
812 n_i_calc[k] = 0.0;
813 } else {
814 n_i_calc[k] = n_t * exp(tmp);
815 }
816 n_t_calc += n_i_calc[k];
817 }
818 for (size_t m = 0; m < m_mm; m++) {
819 eMolesCalc[m] = 0.0;
820 for (size_t k = 0; k < m_kk; k++) {
821 eMolesCalc[m] += nAtoms(k,m) * n_i_calc[k];
822 }
823 }
824 return n_t_calc;
825}
826
827int ChemEquil::estimateEP_Brinkley(ThermoPhase& s, span<double> x, span<double> elMoles)
828{
829 // Before we do anything, we will save the state of the solution. Then, if
830 // things go drastically wrong, we will restore the saved state.
831 vector<double> state(s.stateSize());
832 s.saveState(state);
833 bool modifiedMatrix = false;
834 size_t neq = m_mm+1;
835 int retn = 1;
836 DenseMatrix a1(neq, neq, 0.0);
837 vector<double> b(neq, 0.0);
838 vector<double> n_i(m_kk,0.0);
839 vector<double> n_i_calc(m_kk,0.0);
840 vector<double> actCoeff(m_kk, 1.0);
841 double beta = 1.0;
842
843 s.getMoleFractions(n_i);
844 double pressureConst = s.pressure();
845 vector<double> Xmol_i_calc = n_i;
846
847 vector<double> x_old(m_mm+1, 0.0);
848 vector<double> resid(m_mm+1, 0.0);
849 vector<int> lumpSum(m_mm+1, 0);
850
851 // Get the nondimensional Gibbs functions for the species at their standard
852 // states of solution at the current T and P of the solution.
854
855 vector<double> eMolesCalc(m_mm, 0.0);
856 vector<double> eMolesFix(m_mm, 0.0);
857 double elMolesTotal = 0.0;
858 for (size_t m = 0; m < m_mm; m++) {
859 elMolesTotal += elMoles[m];
860 for (size_t k = 0; k < m_kk; k++) {
861 eMolesFix[m] += nAtoms(k,m) * n_i[k];
862 }
863 }
864
865 for (size_t m = 0; m < m_mm; m++) {
866 if (elMoles[m] > 1.0E-70) {
867 x[m] = clip(x[m], -100.0, 50.0);
868 } else {
869 x[m] = clip(x[m], -1000.0, 50.0);
870 }
871 }
872
873 double n_t = 0.0;
874 double nAtomsMax = 1.0;
875 s.setMoleFractions(Xmol_i_calc);
876 s.setPressure(pressureConst);
877 s.getActivityCoefficients(actCoeff);
878 for (size_t k = 0; k < m_kk; k++) {
879 double tmp = - (m_muSS_RT[k] + log(actCoeff[k]));
880 double sum2 = 0.0;
881 for (size_t m = 0; m < m_mm; m++) {
882 double sum = nAtoms(k,m);
883 tmp += sum * x[m];
884 sum2 += sum;
885 nAtomsMax = std::max(nAtomsMax, sum2);
886 }
887 if (tmp > 100.) {
888 n_t += 2.8E43;
889 } else {
890 n_t += exp(tmp);
891 }
892 }
893
894 if (m_loglevel > 0) {
895 writelog("estimateEP_Brinkley::\n\n");
896 writelogf("temp = %g\n", s.temperature());
897 writelogf("pres = %g\n", s.pressure());
898 writelog("Initial mole numbers and mu_SS:\n");
899 writelog(" Name MoleNum mu_SS actCoeff\n");
900 for (size_t k = 0; k < m_kk; k++) {
901 writelogf("%15s %13.5g %13.5g %13.5g\n",
902 s.speciesName(k), n_i[k], m_muSS_RT[k], actCoeff[k]);
903 }
904 writelogf("Initial n_t = %10.5g\n", n_t);
905 writelog("Comparison of Goal Element Abundance with Initial Guess:\n");
906 writelog(" eName eCurrent eGoal\n");
907 for (size_t m = 0; m < m_mm; m++) {
908 writelogf("%5s %13.5g %13.5g\n",
909 s.elementName(m), eMolesFix[m], elMoles[m]);
910 }
911 }
912 for (size_t m = 0; m < m_mm; m++) {
913 if (m != m_eloc && elMoles[m] <= options.absElemTol) {
914 x[m] = -200.;
915 }
916 }
917
918 // Main Loop.
919 for (int iter = 0; iter < 20* options.maxIterations; iter++) {
920 // Save the old solution
921 for (size_t m = 0; m < m_mm; m++) {
922 x_old[m] = x[m];
923 }
924 x_old[m_mm] = n_t;
925 // Calculate the mole numbers of species
926 if (m_loglevel > 0) {
927 writelogf("START ITERATION %d:\n", iter);
928 }
929 // Calculate the mole numbers of species and elements.
930 double n_t_calc = calcEmoles(s, x, n_t, Xmol_i_calc, eMolesCalc, n_i_calc,
931 pressureConst);
932
933 for (size_t k = 0; k < m_kk; k++) {
934 Xmol_i_calc[k] = n_i_calc[k]/n_t_calc;
935 }
936
937 if (m_loglevel > 0) {
938 writelog(" Species: Calculated_Moles Calculated_Mole_Fraction\n");
939 for (size_t k = 0; k < m_kk; k++) {
940 writelogf("%15s: %10.5g %10.5g\n",
941 s.speciesName(k), n_i_calc[k], Xmol_i_calc[k]);
942 }
943 writelogf("%15s: %10.5g\n", "Total Molar Sum", n_t_calc);
944 writelogf("(iter %d) element moles bal: Goal Calculated\n", iter);
945 for (size_t m = 0; m < m_mm; m++) {
946 writelogf(" %8s: %10.5g %10.5g \n",
947 s.elementName(m), elMoles[m], eMolesCalc[m]);
948 }
949 }
950
951 bool normalStep = true;
952 // Decide if we are to do a normal step or a modified step
953 size_t iM = npos;
954 for (size_t m = 0; m < m_mm; m++) {
955 if (elMoles[m] > 0.001 * elMolesTotal) {
956 if (eMolesCalc[m] > 1000. * elMoles[m]) {
957 normalStep = false;
958 iM = m;
959 }
960 if (1000 * eMolesCalc[m] < elMoles[m]) {
961 normalStep = false;
962 iM = m;
963 }
964 }
965 }
966 if (m_loglevel > 0 && !normalStep) {
967 writelogf(" NOTE: iter(%d) Doing an abnormal step due to row %d\n", iter, iM);
968 }
969 if (!normalStep) {
970 beta = 1.0;
971 resid[m_mm] = 0.0;
972 for (size_t im = 0; im < m_mm; im++) {
973 size_t m = m_orderVectorElements[im];
974 resid[m] = 0.0;
975 if (im < m_nComponents && elMoles[m] > 0.001 * elMolesTotal) {
976 if (eMolesCalc[m] > 1000. * elMoles[m]) {
977 resid[m] = -0.5;
978 resid[m_mm] -= 0.5;
979 }
980 if (1000 * eMolesCalc[m] < elMoles[m]) {
981 resid[m] = 0.5;
982 resid[m_mm] += 0.5;
983 }
984 }
985 }
986 if (n_t < (elMolesTotal / nAtomsMax)) {
987 if (resid[m_mm] < 0.0) {
988 resid[m_mm] = 0.1;
989 }
990 } else if (n_t > elMolesTotal) {
991 resid[m_mm] = std::min(resid[m_mm], 0.0);
992 }
993 } else {
994 // Determine whether the matrix should be dumbed down because the
995 // coefficient matrix of species (with significant concentrations)
996 // is rank deficient.
997 //
998 // The basic idea is that at any time during the calculation only a
999 // small subset of species with sufficient concentration matters. If
1000 // the rank of the element coefficient matrix for that subset of
1001 // species is less than the number of elements, then the matrix
1002 // created by the Brinkley method below may become singular.
1003 //
1004 // The logic below looks for obvious cases where the current element
1005 // coefficient matrix is rank deficient.
1006 //
1007 // The way around rank-deficiency is to lump-sum the corresponding
1008 // row of the matrix. Note, lump-summing seems to work very well in
1009 // terms of its stability properties, that is, it heads in the right
1010 // direction, albeit with lousy convergence rates.
1011 //
1012 // NOTE: This probably should be extended to a full blown Gauss-
1013 // Jordan factorization scheme in the future. For Example the scheme
1014 // below would fail for the set: HCl NH4Cl, NH3. Hopefully, it's
1015 // caught by the equal rows logic below.
1016 for (size_t m = 0; m < m_mm; m++) {
1017 lumpSum[m] = 1;
1018 }
1019
1020 double nCutoff = 1.0E-9 * n_t_calc;
1021 if (m_loglevel > 0) {
1022 writelog(" Lump Sum Elements Calculation: \n");
1023 }
1024 for (size_t m = 0; m < m_mm; m++) {
1025 size_t kMSp = npos;
1026 size_t kMSp2 = npos;
1027 for (size_t k = 0; k < m_kk; k++) {
1028 if (n_i_calc[k] > nCutoff && fabs(nAtoms(k,m)) > 0.001) {
1029 if (kMSp != npos) {
1030 kMSp2 = k;
1031 double factor = fabs(nAtoms(kMSp,m) / nAtoms(kMSp2,m));
1032 for (size_t n = 0; n < m_mm; n++) {
1033 if (fabs(factor * nAtoms(kMSp2,n) - nAtoms(kMSp,n)) > 1.0E-8) {
1034 lumpSum[m] = 0;
1035 break;
1036 }
1037 }
1038 } else {
1039 kMSp = k;
1040 }
1041 }
1042 }
1043 if (m_loglevel > 0) {
1044 writelogf(" %5s %3d : %5d %5d\n",
1045 s.elementName(m), lumpSum[m], kMSp, kMSp2);
1046 }
1047 }
1048
1049 // Formulate the matrix.
1050 for (size_t im = 0; im < m_mm; im++) {
1051 size_t m = m_orderVectorElements[im];
1052 if (im < m_nComponents) {
1053 for (size_t n = 0; n < m_mm; n++) {
1054 a1(m,n) = 0.0;
1055 for (size_t k = 0; k < m_kk; k++) {
1056 a1(m,n) += nAtoms(k,m) * nAtoms(k,n) * n_i_calc[k];
1057 }
1058 }
1059 a1(m,m_mm) = eMolesCalc[m];
1060 a1(m_mm, m) = eMolesCalc[m];
1061 } else {
1062 for (size_t n = 0; n <= m_mm; n++) {
1063 a1(m,n) = 0.0;
1064 }
1065 a1(m,m) = 1.0;
1066 }
1067 }
1068 a1(m_mm, m_mm) = 0.0;
1069
1070 // Formulate the residual, resid, and the estimate for the
1071 // convergence criteria, sum
1072 double sum = 0.0;
1073 for (size_t im = 0; im < m_mm; im++) {
1074 size_t m = m_orderVectorElements[im];
1075 if (im < m_nComponents) {
1076 resid[m] = elMoles[m] - eMolesCalc[m];
1077 } else {
1078 resid[m] = 0.0;
1079 }
1080
1081 // For equations with positive and negative coefficients,
1082 // (electronic charge), we must mitigate the convergence
1083 // criteria by a condition limited by finite precision of
1084 // inverting a matrix. Other equations with just positive
1085 // coefficients aren't limited by this.
1086 double tmp;
1087 if (m == m_eloc) {
1088 tmp = resid[m] / (elMoles[m] + elMolesTotal*1.0E-6 + options.absElemTol);
1089 } else {
1090 tmp = resid[m] / (elMoles[m] + options.absElemTol);
1091 }
1092 sum += tmp * tmp;
1093 }
1094
1095 for (size_t m = 0; m < m_mm; m++) {
1096 if (a1(m,m) < 1.0E-50) {
1097 if (m_loglevel > 0) {
1098 writelogf(" NOTE: Diagonalizing the analytical Jac row %d\n", m);
1099 }
1100 for (size_t n = 0; n < m_mm; n++) {
1101 a1(m,n) = 0.0;
1102 }
1103 a1(m,m) = 1.0;
1104 if (resid[m] > 0.0) {
1105 resid[m] = 1.0;
1106 } else if (resid[m] < 0.0) {
1107 resid[m] = -1.0;
1108 } else {
1109 resid[m] = 0.0;
1110 }
1111 }
1112 }
1113
1114 resid[m_mm] = n_t - n_t_calc;
1115
1116 if (m_loglevel > 0) {
1117 writelog("Matrix:\n");
1118 for (size_t m = 0; m <= m_mm; m++) {
1119 writelog(" [");
1120 for (size_t n = 0; n <= m_mm; n++) {
1121 writelogf(" %10.5g", a1(m,n));
1122 }
1123 writelogf("] = %10.5g\n", resid[m]);
1124 }
1125 }
1126
1127 sum += pow(resid[m_mm] /(n_t + 1.0E-15), 2);
1128 if (m_loglevel > 0) {
1129 writelogf("(it %d) Convergence = %g\n", iter, sum);
1130 }
1131
1132 // Insist on 20x accuracy compared to the top routine. There are
1133 // instances, for ill-conditioned or singular matrices where this is
1134 // needed to move the system to a point where the matrices aren't
1135 // singular.
1136 if (sum < 0.05 * options.relTolerance) {
1137 retn = 0;
1138 break;
1139 }
1140
1141 // Row Sum scaling
1142 for (size_t m = 0; m <= m_mm; m++) {
1143 double tmp = 0.0;
1144 for (size_t n = 0; n <= m_mm; n++) {
1145 tmp += fabs(a1(m,n));
1146 }
1147 if (m < m_mm && tmp < 1.0E-30) {
1148 if (m_loglevel > 0) {
1149 writelogf(" NOTE: Diagonalizing row %d\n", m);
1150 }
1151 for (size_t n = 0; n <= m_mm; n++) {
1152 if (n != m) {
1153 a1(m,n) = 0.0;
1154 a1(n,m) = 0.0;
1155 }
1156 }
1157 }
1158 tmp = 1.0/tmp;
1159 for (size_t n = 0; n <= m_mm; n++) {
1160 a1(m,n) *= tmp;
1161 }
1162 resid[m] *= tmp;
1163 }
1164
1165 if (m_loglevel > 0) {
1166 writelog("Row Summed Matrix:\n");
1167 for (size_t m = 0; m <= m_mm; m++) {
1168 writelog(" [");
1169 for (size_t n = 0; n <= m_mm; n++) {
1170 writelogf(" %10.5g", a1(m,n));
1171 }
1172 writelogf("] = %10.5g\n", resid[m]);
1173 }
1174 }
1175
1176 // Next Step: We have row-summed the equations. However, there are
1177 // some degenerate cases where two rows will be multiplies of each
1178 // other in terms of 0 < m, 0 < m part of the matrix. This occurs on
1179 // a case by case basis, and depends upon the current state of the
1180 // element potential values, which affect the concentrations of
1181 // species.
1182 //
1183 // So, the way we have found to eliminate this problem is to lump-
1184 // sum one of the rows of the matrix, except for the last column,
1185 // and stick it all on the diagonal. Then, we at least have a non-
1186 // singular matrix, and the modified equation moves the
1187 // corresponding unknown in the correct direction.
1188 //
1189 // The previous row-sum operation has made the identification of
1190 // identical rows much simpler.
1191 //
1192 // Note at least 6E-4 is necessary for the comparison. I'm guessing
1193 // 1.0E-3. If two rows are anywhere close to being equivalent, the
1194 // algorithm can get stuck in an oscillatory mode.
1195 modifiedMatrix = false;
1196 for (size_t m = 0; m < m_mm; m++) {
1197 size_t sameAsRow = npos;
1198 for (size_t im = 0; im < m; im++) {
1199 bool theSame = true;
1200 for (size_t n = 0; n < m_mm; n++) {
1201 if (fabs(a1(m,n) - a1(im,n)) > 1.0E-7) {
1202 theSame = false;
1203 break;
1204 }
1205 }
1206 if (theSame) {
1207 sameAsRow = im;
1208 }
1209 }
1210 if (sameAsRow != npos || lumpSum[m]) {
1211 if (m_loglevel > 0) {
1212 if (lumpSum[m]) {
1213 writelogf("Lump summing row %d, due to rank deficiency analysis\n", m);
1214 } else if (sameAsRow != npos) {
1215 writelogf("Identified that rows %d and %d are the same\n", m, sameAsRow);
1216 }
1217 }
1218 modifiedMatrix = true;
1219 for (size_t n = 0; n < m_mm; n++) {
1220 if (n != m) {
1221 a1(m,m) += fabs(a1(m,n));
1222 a1(m,n) = 0.0;
1223 }
1224 }
1225 }
1226 }
1227
1228 if (m_loglevel > 0 && modifiedMatrix) {
1229 writelog("Row Summed, MODIFIED Matrix:\n");
1230 for (size_t m = 0; m <= m_mm; m++) {
1231 writelog(" [");
1232 for (size_t n = 0; n <= m_mm; n++) {
1233 writelogf(" %10.5g", a1(m,n));
1234 }
1235 writelogf("] = %10.5g\n", resid[m]);
1236 }
1237 }
1238
1239 try {
1240 solve(a1, resid);
1241 } catch (CanteraError& err) {
1242 s.restoreState(state);
1243 throw CanteraError("ChemEquil::estimateEP_Brinkley",
1244 "Jacobian is singular. \nTry adding more species, "
1245 "changing the elemental composition slightly, \nor removing "
1246 "unused elements.\n\n" + err.getMessage());
1247 }
1248
1249 // Figure out the damping coefficient: Use a delta damping
1250 // coefficient formulation: magnitude of change is capped to exp(1).
1251 beta = 1.0;
1252 for (size_t m = 0; m < m_mm; m++) {
1253 if (resid[m] > 1.0) {
1254 beta = std::min(beta, 1.0 / resid[m]);
1255 }
1256 if (resid[m] < -1.0) {
1257 beta = std::min(beta, -1.0 / resid[m]);
1258 }
1259 }
1260 if (m_loglevel > 0 && beta != 1.0) {
1261 writelogf("(it %d) Beta = %g\n", iter, beta);
1262 }
1263 }
1264 // Update the solution vector
1265 for (size_t m = 0; m < m_mm; m++) {
1266 x[m] += beta * resid[m];
1267 }
1268 n_t *= exp(beta * resid[m_mm]);
1269
1270 if (m_loglevel > 0) {
1271 writelogf("(it %d) OLD_SOLUTION NEW SOLUTION (undamped updated)\n", iter);
1272 for (size_t m = 0; m < m_mm; m++) {
1273 writelogf(" %5s %10.5g %10.5g %10.5g\n",
1274 s.elementName(m), x_old[m], x[m], resid[m]);
1275 }
1276 writelogf(" n_t %10.5g %10.5g %10.5g \n", x_old[m_mm], n_t, exp(resid[m_mm]));
1277 }
1278 }
1279 if (m_loglevel > 0) {
1280 double temp = s.temperature();
1281 double pres = s.pressure();
1282
1283 if (retn == 0) {
1284 writelogf(" ChemEquil::estimateEP_Brinkley() SUCCESS: equilibrium found at T = %g, Pres = %g\n",
1285 temp, pres);
1286 } else {
1287 writelogf(" ChemEquil::estimateEP_Brinkley() FAILURE: equilibrium not found at T = %g, Pres = %g\n",
1288 temp, pres);
1289 }
1290 }
1291 return retn;
1292}
1293
1294
1295void ChemEquil::adjustEloc(ThermoPhase& s, span<double> elMolesGoal)
1296{
1297 if (m_eloc == npos) {
1298 return;
1299 }
1300 if (fabs(elMolesGoal[m_eloc]) > 1.0E-20) {
1301 return;
1302 }
1304 size_t maxPosEloc = npos;
1305 size_t maxNegEloc = npos;
1306 double maxPosVal = -1.0;
1307 double maxNegVal = -1.0;
1308 if (m_loglevel > 0) {
1309 for (size_t k = 0; k < m_kk; k++) {
1310 if (nAtoms(k,m_eloc) > 0.0 && m_molefractions[k] > maxPosVal && m_molefractions[k] > 0.0) {
1311 maxPosVal = m_molefractions[k];
1312 maxPosEloc = k;
1313 }
1314 if (nAtoms(k,m_eloc) < 0.0 && m_molefractions[k] > maxNegVal && m_molefractions[k] > 0.0) {
1315 maxNegVal = m_molefractions[k];
1316 maxNegEloc = k;
1317 }
1318 }
1319 }
1320
1321 double sumPos = 0.0;
1322 double sumNeg = 0.0;
1323 for (size_t k = 0; k < m_kk; k++) {
1324 if (nAtoms(k,m_eloc) > 0.0) {
1325 sumPos += nAtoms(k,m_eloc) * m_molefractions[k];
1326 }
1327 if (nAtoms(k,m_eloc) < 0.0) {
1328 sumNeg += nAtoms(k,m_eloc) * m_molefractions[k];
1329 }
1330 }
1331 sumNeg = - sumNeg;
1332
1333 if (sumPos >= sumNeg) {
1334 if (sumPos <= 0.0) {
1335 return;
1336 }
1337 double factor = (elMolesGoal[m_eloc] + sumNeg) / sumPos;
1338 if (m_loglevel > 0 && factor < 0.9999999999) {
1339 writelogf("adjustEloc: adjusted %s and friends from %g to %g to ensure neutrality condition\n",
1340 s.speciesName(maxPosEloc),
1341 m_molefractions[maxPosEloc], m_molefractions[maxPosEloc]*factor);
1342 }
1343 for (size_t k = 0; k < m_kk; k++) {
1344 if (nAtoms(k,m_eloc) > 0.0) {
1345 m_molefractions[k] *= factor;
1346 }
1347 }
1348 } else {
1349 double factor = (-elMolesGoal[m_eloc] + sumPos) / sumNeg;
1350 if (m_loglevel > 0 && factor < 0.9999999999) {
1351 writelogf("adjustEloc: adjusted %s and friends from %g to %g to ensure neutrality condition\n",
1352 s.speciesName(maxNegEloc),
1353 m_molefractions[maxNegEloc], m_molefractions[maxNegEloc]*factor);
1354 }
1355 for (size_t k = 0; k < m_kk; k++) {
1356 if (nAtoms(k,m_eloc) < 0.0) {
1357 m_molefractions[k] *= factor;
1358 }
1359 }
1360 }
1361
1364}
1365
1366} // namespace
Chemical equilibrium.
Header file for class ThermoPhase, the base class for phases with thermodynamic properties,...
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
int dampStep(ThermoPhase &s, span< double > oldx, double oldf, span< double > grad, span< double > step, span< double > x, double &f, span< double > elmols, double xval, double yval)
Find an acceptable step size and take it.
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
Definition ChemEquil.h:250
int m_loglevel
Verbosity of printed output.
Definition ChemEquil.h:302
size_t m_nComponents
This is equal to the rank of the stoichiometric coefficient matrix when it is computed.
Definition ChemEquil.h:255
ThermoPhase * m_phase
Pointer to the ThermoPhase object used to initialize this object.
Definition ChemEquil.h:136
double m_elementTotalSum
Current value of the sum of the element abundances given the current element potentials.
Definition ChemEquil.h:264
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.
Definition ChemEquil.h:280
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.
Definition ChemEquil.cpp:48
EquilOpt options
Options controlling how the calculation is carried out.
Definition ChemEquil.h:127
vector< double > m_molefractions
Current value of the mole fractions in the single phase. length = m_kk.
Definition ChemEquil.h:260
vector< double > m_comp
Storage of the element compositions. natom(k,m) = m_comp[k*m_mm+ m];.
Definition ChemEquil.h:274
double nAtoms(size_t k, size_t m) const
number of atoms of element m in species k.
Definition ChemEquil.h:139
double m_elemFracCutoff
element fractional cutoff, below which the element will be zeroed.
Definition ChemEquil.h:294
vector< double > m_muSS_RT
Dimensionless values of the Gibbs free energy for the standard state of each species,...
Definition ChemEquil.h:290
vector< double > m_elementmolefracs
Current value of the element mole fractions.
Definition ChemEquil.h:268
size_t m_mm
number of elements in the phase
Definition ChemEquil.h:249
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...
Definition DenseMatrix.h:42
double absElemTol
Abs Tol in element number.
Definition ChemEquil.h:30
int iterations
Iteration counter.
Definition ChemEquil.h:32
double relTolerance
Relative tolerance.
Definition ChemEquil.h:29
int maxIterations
Maximum number of iterations.
Definition ChemEquil.h:31
Multiphase chemical equilibrium solver.
A class for multiphase mixtures.
Definition MultiPhase.h:62
void init()
Process phases and build atomic composition array.
size_t nSpecies() const
Number of species, summed over all phases.
Definition MultiPhase.h:116
void addPhase(shared_ptr< ThermoPhase > p, double moles)
Add a phase to the mixture.
size_t nElements() const
Number of elements.
Definition MultiPhase.h:86
void getMoleFractions(span< double > x) const
Get the species mole fraction vector.
Definition Phase.cpp:441
size_t nSpecies() const
Returns the number of species in the phase.
Definition Phase.h:246
double temperature() const
Temperature (K).
Definition Phase.h:585
virtual void setPressure(double p)
Set the internally stored pressure (Pa) at constant temperature and composition.
Definition Phase.h:639
double atomicWeight(size_t m) const
Atomic weight of element m.
Definition Phase.cpp:69
string speciesName(size_t k) const
Name of the species with index k.
Definition Phase.cpp:143
virtual size_t stateSize() const
Return size of vector defining internal state of the phase.
Definition Phase.cpp:249
double moleFraction(size_t k) const
Return the mole fraction of a single species.
Definition Phase.cpp:447
virtual double density() const
Density (kg/m^3).
Definition Phase.h:610
double nAtoms(size_t k, size_t m) const
Number of atoms of element m in species k.
Definition Phase.cpp:101
virtual void setTemperature(double temp)
Set the internally stored temperature of the phase (K).
Definition Phase.h:646
size_t nElements() const
Number of elements.
Definition Phase.cpp:30
virtual void setMoleFractions(span< const double > x)
Set the mole fractions to the specified values.
Definition Phase.cpp:283
virtual void restoreState(span< const double > state)
Restore the state of the phase from a previously saved state vector.
Definition Phase.cpp:269
virtual double pressure() const
Return the thermodynamic pressure (Pa).
Definition Phase.h:603
string elementName(size_t m) const
Name of the element with index m.
Definition Phase.cpp:43
virtual void saveState(span< double > state) const
Write to array 'state' the current internal state.
Definition Phase.cpp:257
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 double refPressure() const
Returns the reference pressure in Pa.
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.
Definition global.h:191
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:171
U len(const T &container)
Get the size of a container, cast to a signed integer type.
Definition utilities.h:231
double dot(InputIter x_begin, InputIter x_end, InputIter2 y_begin)
Function that calculates a templated inner product.
Definition utilities.h:96
void scale(InputIter begin, InputIter end, OutputIter out, S scale_factor)
Multiply elements of an array by a scale factor.
Definition utilities.h:118
T clip(const T &value, const T &lower, const T &upper)
Clip value such that lower <= value <= upper.
Definition global.h:326
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition ct_defs.h:123
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Definition global.h:263
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
const size_t npos
index returned by functions to indicate "no position"
Definition ct_defs.h:183
void solve(DenseMatrix &A, span< double > b, size_t nrhs, size_t ldb)
Solve Ax = b. Array b is overwritten on exit with x.
int _equilflag(const char *xy)
map property strings to integers
Definition ChemEquil.cpp:20
Contains declarations for string manipulation functions within Cantera.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...