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