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