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