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