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