Cantera  2.3.0
RootFind.cpp
Go to the documentation of this file.
1 //! @file: RootFind.cpp root finder for 1D problems
2 
3 // This file is part of Cantera. See License.txt in the top-level directory or
4 // at http://www.cantera.org/license.txt for license and copyright information.
5 
9 
10 using namespace std;
11 namespace Cantera
12 {
13 
14 //! Print out a form for the current function evaluation
15 /*!
16  * @param fp Pointer to the FILE object
17  * @param xval Current value of x
18  * @param fval Current value of f
19  * @param its Current iteration value
20  */
21 static void print_funcEval(FILE* fp, doublereal xval, doublereal fval, int its)
22 {
23  fprintf(fp,"\n");
24  fprintf(fp,"...............................................................\n");
25  fprintf(fp,".................. RootFind Function Evaluation ...............\n");
26  fprintf(fp,".................. iteration = %5d ........................\n", its);
27  fprintf(fp,".................. value = %12.5g ......................\n", xval);
28  fprintf(fp,".................. funct = %12.5g ......................\n", fval);
29  fprintf(fp,"...............................................................\n");
30  fprintf(fp,"\n");
31 }
32 
33 RootFind::RootFind(ResidEval* resid) :
34  m_residFunc(resid),
35  m_funcTargetValue(0.0),
36  m_atolf(1.0E-11),
37  m_atolx(1.0E-11),
38  m_rtolf(1.0E-5),
39  m_rtolx(1.0E-5),
40  m_maxstep(1000),
41  printLvl(0),
42  writeLogAllowed_(false),
43  DeltaXnorm_(0.01),
44  specifiedDeltaXnorm_(0),
45  DeltaXMax_(1.0E6),
46  specifiedDeltaXMax_(0),
47  FuncIsGenerallyIncreasing_(false),
48  FuncIsGenerallyDecreasing_(false),
49  deltaXConverged_(0.0),
50  x_maxTried_(-1.0E300),
51  fx_maxTried_(0.0),
52  x_minTried_(1.0E300),
53  fx_minTried_(0.0)
54 {
55  warn_deprecated("Unused. To be removed after Cantera 2.3. See "
56  "boost::math::tools::toms748_solve for an alternative.");
57 }
58 
60  m_residFunc(r.m_residFunc),
61  m_funcTargetValue(0.0),
62  m_atolf(1.0E-11),
63  m_atolx(1.0E-11),
64  m_rtolf(1.0E-5),
65  m_rtolx(1.0E-5),
66  m_maxstep(1000),
67  printLvl(0),
68  writeLogAllowed_(false),
69  DeltaXnorm_(0.01),
70  specifiedDeltaXnorm_(0),
71  DeltaXMax_(1.0E6),
72  specifiedDeltaXMax_(0),
73  FuncIsGenerallyIncreasing_(false),
74  FuncIsGenerallyDecreasing_(false),
75  deltaXConverged_(0.0),
76  x_maxTried_(-1.0E300),
77  fx_maxTried_(0.0),
78  x_minTried_(1.0E300),
79  fx_minTried_(0.0)
80 {
81  warn_deprecated("RootFind copy constructor",
82  "To be removed after Cantera 2.3.");
83  *this = r;
84 }
85 
87 {
88  warn_deprecated("RootFind assignment operator",
89  "To be removed after Cantera 2.3.");
90  if (this == &right) {
91  return *this;
92  }
93  m_residFunc = right.m_residFunc;
95  m_atolf = right.m_atolf;
96  m_atolx = right.m_atolx;
97  m_rtolf = right.m_rtolf;
98  m_rtolx = right.m_rtolx;
99  m_maxstep = right.m_maxstep;
100  printLvl = right.printLvl;
102  DeltaXnorm_ = right.DeltaXnorm_;
104  DeltaXMax_ = right.DeltaXMax_;
109  x_maxTried_ = right.x_maxTried_;
110  fx_maxTried_ = right.fx_maxTried_;
111  x_minTried_ = right.x_minTried_;
112  fx_minTried_ = right.fx_minTried_;
113 
114  return *this;
115 }
116 
117 doublereal RootFind::delXNonzero(doublereal x1) const
118 {
119  doublereal deltaX = 1.0E-14 * fabs(x1);
120  doublereal delmin = DeltaXnorm_ * 1.0E-14;
121  if (delmin > deltaX) {
122  return delmin;
123  }
124  return deltaX;
125 }
126 
127 doublereal RootFind::delXMeaningful(doublereal x1) const
128 {
129  doublereal del = delXNonzero(x1);
130  if (deltaXConverged_ > del) {
131  return deltaXConverged_;
132  }
133  return del;
134 }
135 
136 double RootFind::deltaXControlled(doublereal x2, doublereal x1) const
137 {
138  doublereal sgnn = 1.0;
139  if (x1 > x2) {
140  sgnn = -1.0;
141  }
142  doublereal deltaX = x2 - x1;
143  doublereal x = fabs(x2) + fabs(x1);
144  doublereal deltaXm = delXNonzero(x);
145  if (fabs(deltaX) < deltaXm) {
146  deltaX = sgnn * deltaXm;
147  }
148  return deltaX;
149 }
150 
151 bool RootFind::theSame(doublereal x2, doublereal x1, doublereal factor) const
152 {
153  doublereal x = fabs(x2) + fabs(x1);
154  doublereal deltaX = delXMeaningful(x);
155  doublereal deltaXSmall = factor * deltaX;
156  deltaXSmall = std::max(deltaXSmall , x * 1.0E-15);
157  if (fabs(x2 - x1) < deltaXSmall) {
158  return true;
159  }
160  return false;
161 }
162 
163 int RootFind::solve(doublereal xmin, doublereal xmax, int itmax, doublereal& funcTargetValue, doublereal* xbest)
164 {
165  // We store the function target and then actually calculate a modified
166  // functional, func = eval(x1) - m_funcTargetValue = 0
167  m_funcTargetValue = funcTargetValue;
168 
169  static int callNum = 0;
170  const char* stre = "RootFind ERROR: ";
171  const char* strw = "RootFind WARNING: ";
172  int converged = 0;
173  int bottomBump = 0;
174  int topBump = 0;
175  FILE* fp = 0;
176  int doFinalFuncCall = 0;
177  doublereal x1, x2, xnew, f1, f2, fnew, slope = 0;
178  doublereal deltaX2 = 0.0, deltaXnew = 0.0;
179  int posStraddle = 0;
180  int retn = ROOTFIND_FAILEDCONVERGENCE;
181  int foundPosF = 0;
182  int foundNegF = 0;
183  int foundStraddle = 0;
184  doublereal xPosF = 0.0;
185  doublereal fPosF = 1.0E300;
186  doublereal xNegF = 0.0;
187  doublereal fNegF = -1.0E300;
188  doublereal fnorm; // A valid norm for the making the function value dimensionless
189  doublereal xDelMin;
190  doublereal sgn;
191  doublereal fnoise = 0.0;
192  rfHistory_.clear();
193  rfTable rfT;
194  rfT.clear();
195  rfT.reasoning = "First Point: ";
196 
197  callNum++;
198  if (printLvl >= 3 && writeLogAllowed_) {
199  fp = fopen(fmt::format("RootFind_%d.log", callNum).c_str(), "w");
200  fprintf(fp, " Iter TP_its xval Func_val | Reasoning\n");
201  fprintf(fp, "-----------------------------------------------------"
202  "-------------------------------\n");
203  } else if (printLvl >= 3) {
204  writelog("WARNING: RootFind: printlvl >= 3, but debug mode not turned on\n");
205  }
206  if (xmax <= xmin) {
207  writelogf("%sxmin and xmax are bad: %g %g\n", stre, xmin, xmax);
208  funcTargetValue = func(*xbest);
209  return ROOTFIND_BADINPUT;
210  }
211 
212  // If the maximum step size has not been specified, set it here to 1/5 of
213  // the domain range of x.
214  if (!specifiedDeltaXMax_) {
215  DeltaXMax_ = 0.2 *(xmax - xmin);
216  }
217 
218  if (!specifiedDeltaXnorm_) {
219  DeltaXnorm_ = 0.2 * DeltaXMax_;
220  } else {
221  if (DeltaXnorm_ > DeltaXMax_) {
222  if (specifiedDeltaXnorm_) {
224  } else {
225  DeltaXnorm_ = 0.5 * DeltaXMax_;
226  }
227  }
228  }
229 
230  // Calculate an initial value of deltaXConverged_
231  deltaXConverged_ = m_rtolx * (*xbest) + m_atolx;
233  writelogf("%s DeltaXnorm_, %g, is too small compared to tols, increasing to %g\n",
236  }
237 
238  // Find the first function value f1 = func(x1), by using the value entered
239  // into xbest. Process it
240  x1 = *xbest;
241  if (x1 < xmin || x1 > xmax) {
242  x1 = (xmin + xmax) / 2.0;
243  rfT.reasoning += " x1 set middle between xmin and xmax because entrance is outside bounds.";
244  } else {
245  rfT.reasoning += " x1 set to entrance x.";
246  }
247 
248  x_maxTried_ = x1;
249  x_minTried_ = x1;
250  int its = 1;
251  f1 = func(x1);
252 
253  if (printLvl >= 3 && writeLogAllowed_) {
254  print_funcEval(fp, x1, f1, its);
255  fprintf(fp, "%-5d %-5d %-15.5E %-15.5E\n", -2, 0, x1, f1);
256  }
257 
258  if (f1 == 0.0) {
259  *xbest = x1;
260  return 0;
261  } else if (f1 > fnoise) {
262  foundPosF = 1;
263  xPosF = x1;
264  fPosF = f1;
265  } else if (f1 < -fnoise) {
266  foundNegF = 1;
267  xNegF = x1;
268  fNegF = f1;
269  }
270  rfT.its = its;
271  rfT.TP_its = 0;
272  rfT.xval = x1;
273  rfT.fval = f1;
274  rfT.foundPos = foundPosF;
275  rfT.foundNeg = foundNegF;
276  rfT.deltaXConverged = m_rtolx * (fabs(x1) + 0.001);
277  rfT.deltaFConverged = fabs(f1) * m_rtolf;
278  rfT.delX = xmax - xmin;
279  rfHistory_.push_back(rfT);
280  rfT.clear();
281 
282  // Now, this is actually a tricky part of the algorithm - Find the x value
283  // for the second point. It's tricky because we don't have a valid idea of
284  // the scale of x yet
285  rfT.reasoning = "Second Point: ";
286  if (x1 == 0.0) {
287  x2 = x1 + 0.01 * DeltaXnorm_;
288  rfT.reasoning += "Set by DeltaXnorm_";
289  } else {
290  x2 = x1 * 1.0001;
291  rfT.reasoning += "Set slightly higher.";
292  }
293  if (x2 > xmax) {
294  x2 = x1 - 0.01 * DeltaXnorm_;
295  rfT.reasoning += " - But adjusted to be within bounds";
296  }
297 
298  // Find the second function value f2 = func(x2), Process it
299  deltaX2 = x2 - x1;
300  its++;
301  f2 = func(x2);
302  if (printLvl >= 3 && writeLogAllowed_) {
303  print_funcEval(fp, x2, f2, its);
304  fprintf(fp, "%-5d %-5d %-15.5E %-15.5E", -1, 0, x2, f2);
305  }
306 
307  // Calculate the norm of the function, this is the nominal value of f. We
308  // try to reduce the nominal value of f by rtolf, this is the main
309  // convergence requirement.
310  if (m_funcTargetValue != 0.0) {
311  fnorm = m_atolf + fabs(m_funcTargetValue);
312  } else {
313  fnorm = 0.5*(fabs(f1) + fabs(f2)) + fabs(m_funcTargetValue) + m_atolf;
314  }
315  fnoise = 1.0E-100;
316  if (f2 > fnoise) {
317  if (!foundPosF) {
318  foundPosF = 1;
319  xPosF = x2;
320  fPosF = f2;
321  }
322  } else if (f2 < - fnoise) {
323  if (!foundNegF) {
324  foundNegF = 1;
325  xNegF = x2;
326  fNegF = f2;
327  }
328  } else if (f2 == 0.0) {
329  *xbest = x2;
330  return ROOTFIND_SUCCESS;
331  }
332  rfT.its = its;
333  rfT.TP_its = 0;
334  rfT.xval = x2;
335  rfT.fval = f2;
336  rfT.foundPos = foundPosF;
337  rfT.foundNeg = foundNegF;
338 
339  // See if we have already achieved a straddle
340  foundStraddle = foundPosF && foundNegF;
341  if (foundStraddle) {
342  if (xPosF > xNegF) {
343  posStraddle = 1;
344  } else {
345  posStraddle = 0;
346  }
347  }
348 
349  bool useNextStrat = false;
350  bool slopePointingToHigher = true;
351 
352  // MAIN LOOP
353  while (!converged && its < itmax) {
354  // Find an estimate of the next point, xnew, to try based on a linear
355  // approximation from the last two points.
356  if (fabs(x2 - x1) < 1.0E-14) {
357  writelogf(" RootFind: we are here x2 = %g x1 = %g\n", x2, x1);
358  }
359  doublereal delXtmp = deltaXControlled(x2, x1);
360  slope = (f2 - f1) / delXtmp;
361  rfT.slope = slope;
362  rfHistory_.push_back(rfT);
363  rfT.clear();
364  rfT.reasoning = "";
365  if (fabs(slope) <= 1.0E-100) {
366  if (printLvl >= 2) {
367  writelogf("%s functions evals produced the same result, %g, at %g and %g\n",
368  strw, f2, x1, x2);
369  }
370  xnew = x2 + DeltaXnorm_;
371  slopePointingToHigher = true;
372  useNextStrat = true;
373  rfT.reasoning += "Slope is close to zero. ";
374  } else {
375  useNextStrat = false;
376  xnew = x2 - f2 / slope;
377  if (xnew > x2) {
378  slopePointingToHigher = true;
379  } else {
380  slopePointingToHigher = false;
381  }
382  rfT.reasoning += "Slope is good. ";
383  }
384  if (printLvl >= 3 && writeLogAllowed_) {
385  fprintf(fp, " | xlin = %-11.5E", xnew);
386  }
387  deltaXnew = xnew - x2;
388 
389  // If the suggested step size is too big, throw out step
390  if (!foundStraddle) {
391  if (fabs(xnew - x2) > DeltaXMax_) {
392  useNextStrat = true;
393  rfT.reasoning += "Too large change in xnew from slope. ";
394  }
395  if (fabs(deltaXnew) < fabs(deltaX2)) {
396  deltaXnew = 1.2 * deltaXnew;
397  xnew = x2 + deltaXnew;
398  }
399  }
400 
401  // If the slope can't be trusted using a different strategy for picking
402  // the next point
403  if (useNextStrat) {
404  rfT.reasoning += fmt::format("Using DeltaXnorm, {} and FuncIsGenerallyIncreasing hints. ", DeltaXnorm_);
405  if (f2 < 0.0) {
407  if (slopePointingToHigher) {
408  xnew = std::min(x2 + 3.0*DeltaXnorm_, xnew);
409  } else {
410  xnew = x2 + DeltaXnorm_;
411  }
412  } else if (FuncIsGenerallyDecreasing_) {
413  if (!slopePointingToHigher) {
414  xnew = std::max(x2 - 3.0*DeltaXnorm_, xnew);
415  } else {
416  xnew = x2 - DeltaXnorm_;
417  }
418  } else {
419  if (slopePointingToHigher) {
420  xnew = x2 + DeltaXnorm_;
421  } else {
422  xnew = x2 - DeltaXnorm_;
423  }
424  }
425  } else {
427  if (!slopePointingToHigher) {
428  xnew = std::max(x2 + 3.0*DeltaXnorm_, xnew);
429  } else {
430  xnew = x2 + DeltaXnorm_;
431  }
432  } else if (FuncIsGenerallyIncreasing_) {
433  if (! slopePointingToHigher) {
434  xnew = std::min(x2 - 3.0*DeltaXnorm_, xnew);
435  } else {
436  xnew = x2 - DeltaXnorm_;
437  }
438  } else {
439  if (slopePointingToHigher) {
440  xnew = x2 + DeltaXnorm_;
441  } else {
442  xnew = x2 - DeltaXnorm_;
443  }
444  }
445  }
446  }
447 
448  // Here, if we have a straddle, we purposefully overshoot the smaller
449  // side by 5%. Yes it does lead to more iterations. However, we're
450  // interested in bounding x, and not just doing Newton's method.
451  if (foundStraddle) {
452  double delta = fabs(x2 - x1);
453  if (fabs(xnew - x1) < .01 * delta) {
454  xnew = x1 + 0.01 * (x2 - x1);
455  } else if (fabs(xnew - x2) < .01 * delta) {
456  xnew = x1 + 0.01 * (x2 - x1);
457  } else if ((xnew > x1 && xnew < x2) || (xnew < x1 && xnew > x2)) {
458  if (fabs(xnew - x1) < fabs(x2 - xnew)) {
459  xnew = x1 + 20./19. * (xnew - x1);
460  } else {
461  xnew = x2 + 20./19. * (xnew - x2);
462  }
463  }
464  }
465 
466  // OK, we have an estimate xnew. Put heuristic bounds on the step jump
467  if ((xnew > x1 && xnew < x2) || (xnew < x1 && xnew > x2)) {
468  // If we are doing a jump in between the two previous points, make
469  // sure the new trial is no closer that 10% of the distances between
470  // x2-x1 to any of the original points. This is an important part of
471  // finding a good bound.
472  xDelMin = fabs(x2 - x1) / 10.;
473  if (fabs(xnew - x1) < xDelMin) {
474  xnew = x1 + sign(xnew-x1) * xDelMin;
475  if (printLvl >= 3 && writeLogAllowed_) {
476  fprintf(fp, " | x10%% = %-11.5E", xnew);
477  }
478  }
479  if (fabs(xnew - x2) < 0.1 * xDelMin) {
480  xnew = x2 + sign(xnew-x2) * 0.1 * xDelMin;
481  if (printLvl >= 3 && writeLogAllowed_) {
482  fprintf(fp, " | x10%% = %-11.5E", xnew);
483  }
484  }
485  } else {
486  // If we are venturing into new ground, only allow the step jump to
487  // increase by 50% at each iteration, unless the step jump is less
488  // than the user has said that it is ok to take
489  doublereal xDelMax = 1.5 * fabs(x2 - x1);
490  if (specifiedDeltaXnorm_ && 0.5 * DeltaXnorm_ > xDelMax) {
491  xDelMax = 0.5 *DeltaXnorm_;
492  }
493  if (fabs(xDelMax) < fabs(xnew - x2)) {
494  xnew = x2 + sign(xnew-x2) * xDelMax;
495  if (printLvl >= 3 && writeLogAllowed_) {
496  fprintf(fp, " | xlimitsize = %-11.5E", xnew);
497  }
498  }
499 
500  // If we are doing a jump outside the two previous points, make sure
501  // the new trial is no closer that 10% of the distances between
502  // x2-x1 to any of the original points. This is an important part of
503  // finding a good bound.
504  xDelMin = 0.1 * fabs(x2 - x1);
505  if (fabs(xnew - x2) < xDelMin) {
506  xnew = x2 + sign(xnew - x2) * xDelMin;
507  if (printLvl >= 3 && writeLogAllowed_) {
508  fprintf(fp, " | x10%% = %-11.5E", xnew);
509  }
510  }
511  if (fabs(xnew - x1) < xDelMin) {
512  xnew = x1 + sign(xnew - x1) * xDelMin;
513  if (printLvl >= 3 && writeLogAllowed_) {
514  fprintf(fp, " | x10%% = %-11.5E", xnew);
515  }
516  }
517  }
518 
519  // HKM -> Not sure this section is needed
520  if (foundStraddle) {
521  double xorig = xnew;
522  if (posStraddle) {
523  if (f2 > 0.0) {
524  if (xnew > x2) {
525  xnew = (xNegF + x2)/2;
526  }
527  if (xnew < xNegF) {
528  xnew = (xNegF + x2)/2;
529  }
530  } else {
531  if (xnew < x2) {
532  xnew = (xPosF + x2)/2;
533  }
534  if (xnew > xPosF) {
535  xnew = (xPosF + x2)/2;
536  }
537  }
538  } else {
539  if (f2 > 0.0) {
540  if (xnew < x2) {
541  xnew = (xNegF + x2)/2;
542  }
543  if (xnew > xNegF) {
544  xnew = (xNegF + x2)/2;
545  }
546  } else {
547  if (xnew > x2) {
548  xnew = (xPosF + x2)/2;
549  }
550  if (xnew < xPosF) {
551  xnew = (xPosF + x2)/2;
552  }
553  }
554  }
555  if (printLvl >= 3 && writeLogAllowed_ && xorig != xnew) {
556  fprintf(fp, " | xstraddle = %-11.5E", xnew);
557  }
558  }
559 
560  // Enforce a minimum stepsize if we haven't found a straddle.
561  deltaXnew = xnew - x2;
562  if (fabs(deltaXnew) < 1.2 * delXMeaningful(xnew) && !foundStraddle) {
563  sgn = 1.0;
564  if (x2 > xnew) {
565  sgn = -1.0;
566  }
567  deltaXnew = 1.2 * delXMeaningful(xnew) * sgn;
568  rfT.reasoning += fmt::format("Enforcing minimum stepsize from {} to {}",
569  xnew - x2, deltaXnew);
570  xnew = x2 + deltaXnew;
571  }
572 
573  // Guard against going above xmax or below xmin
574  if (xnew > xmax) {
575  topBump++;
576  if (topBump < 3) {
577  xnew = x2 + (xmax - x2) / 2.0;
578  rfT.reasoning += fmt::format("xval reduced to {} because predicted xnew was above max value of {}", xnew, xmax);
579  } else {
580  if (x2 == xmax || x1 == xmax) {
581  // we are here when we are bumping against the top limit.
582  // No further action is possible
584  *xbest = xnew;
585  rfT.slope = slope;
586  rfT.reasoning += fmt::format("Giving up because we're at xmax and xnew point higher: {}", xnew);
587  goto done;
588  } else {
589  rfT.reasoning += fmt::format("xval reduced from {} to the max value, {}", xnew, xmax);
590  xnew = xmax;
591  }
592  }
593  if (printLvl >= 3 && writeLogAllowed_) {
594  fprintf(fp, " | xlimitmax = %-11.5E", xnew);
595  }
596  }
597  if (xnew < xmin) {
598  bottomBump++;
599  if (bottomBump < 3) {
600  rfT.reasoning += fmt::format("xnew increased from {} to {} because above min value of {}",
601  xnew, x2 - (x2 - xmin) / 2.0, xmin);
602  xnew = x2 - (x2 - xmin) / 2.0;
603  } else {
604  if (x2 == xmin || x1 == xmin) {
605  // we are here when we are bumping against the bottom limit.
606  // No further action is possible
608  *xbest = xnew;
609  rfT.slope = slope;
610  rfT.reasoning = fmt::format("Giving up because we're already at xmin and xnew points lower: {}", xnew);
611  goto done;
612  } else {
613  rfT.reasoning += fmt::format("xval increased from {} to the min value, {}", xnew, xmin);
614  xnew = xmin;
615  }
616  }
617  if (printLvl >= 3 && writeLogAllowed_) {
618  fprintf(fp, " | xlimitmin = %-11.5E", xnew);
619  }
620  }
621 
622  its++;
623  fnew = func(xnew);
624 
625  if (printLvl >= 3 && writeLogAllowed_) {
626  fprintf(fp,"\n");
627  print_funcEval(fp, xnew, fnew, its);
628  fprintf(fp, "%-5d %-5d %-15.5E %-15.5E", its, 0, xnew, fnew);
629  }
630  rfT.xval = xnew;
631  rfT.fval = fnew;
632  rfT.its = its;
633  if (foundStraddle) {
634  if (posStraddle) {
635  if (fnew > 0.0) {
636  if (xnew < xPosF) {
637  xPosF = xnew;
638  fPosF = fnew;
639  }
640  } else {
641  if (xnew > xNegF) {
642  xNegF = xnew;
643  fNegF = fnew;
644  }
645  }
646  } else {
647  if (fnew > 0.0) {
648  if (xnew > xPosF) {
649  xPosF = xnew;
650  fPosF = fnew;
651  }
652  } else {
653  if (xnew < xNegF) {
654  xNegF = xnew;
655  fNegF = fnew;
656  }
657  }
658  }
659  }
660 
661  if (! foundStraddle) {
662  if (fnew > fnoise) {
663  if (!foundPosF) {
664  foundPosF = 1;
665  rfT.foundPos = 1;
666  xPosF = xnew;
667  fPosF = fnew;
668  foundStraddle = 1;
669  if (xPosF > xNegF) {
670  posStraddle = 1;
671  } else {
672  posStraddle = 0;
673  }
674  }
675  } else if (fnew < - fnoise) {
676  if (!foundNegF) {
677  foundNegF = 1;
678  rfT.foundNeg = 1;
679  xNegF = xnew;
680  fNegF = fnew;
681  foundStraddle = 1;
682  if (xPosF > xNegF) {
683  posStraddle = 1;
684  } else {
685  posStraddle = 0;
686  }
687  }
688  }
689  }
690 
691  x1 = x2;
692  f1 = f2;
693 
694  x2 = xnew;
695  f2 = fnew;
696 
697  // As we go on to new data points, we make sure that we have the best
698  // straddle of the solution with the choice of F1 and F2 when we do have
699  // a straddle to work with.
700  if (foundStraddle) {
701  bool foundBetterPos = false;
702  bool foundBetterNeg = false;
703  if (posStraddle) {
704  if (f2 > 0.0) {
705  if (xPosF < x2) {
706  foundBetterPos = false;
707  x2 = xPosF;
708  f2 = fPosF;
709  }
710  if (f1 > 0.0) {
711  if (foundBetterPos) {
712  x1 = xNegF;
713  f1 = fNegF;
714  } else {
715  if (x1 >= x2) {
716  x1 = xNegF;
717  f1 = fNegF;
718  }
719  }
720  }
721  } else {
722  if (xNegF > x2) {
723  foundBetterNeg = false;
724  x2 = xNegF;
725  f2 = fNegF;
726  }
727  if (f1 < 0.0) {
728  if (foundBetterNeg) {
729  x1 = xPosF;
730  f1 = fPosF;
731  } else {
732  if (x1 <= x2) {
733  x1 = xPosF;
734  f1 = fPosF;
735  }
736  }
737  }
738  }
739  } else {
740  if (f2 < 0.0) {
741  if (xNegF < x2) {
742  foundBetterNeg = false;
743  x2 = xNegF;
744  f2 = fNegF;
745  }
746  if (f1 < 0.0) {
747  if (foundBetterNeg) {
748  x1 = xPosF;
749  f1 = fPosF;
750  } else {
751  if (x1 >= x2) {
752  x1 = xPosF;
753  f1 = fPosF;
754  }
755  }
756  }
757  } else {
758  if (xPosF > x2) {
759  foundBetterPos = true;
760  x2 = xPosF;
761  f2 = fPosF;
762  }
763  if (f1 > 0.0) {
764  if (foundBetterNeg) {
765  x1 = xNegF;
766  f1 = fNegF;
767  } else {
768  if (x1 <= x2) {
769  x1 = xNegF;
770  f1 = fNegF;
771  }
772  }
773  }
774  }
775  }
776  AssertThrow((f1* f2 <= 0.0), "F1 and F2 aren't bounding");
777  }
778 
779  deltaX2 = deltaXnew;
780  deltaXnew = x2 - x1;
781  deltaXConverged_ = 0.5 * deltaXConverged_ + 0.5 * (m_rtolx * 0.5 * (fabs(x2) + fabs(x1)) + m_atolx);
782  rfT.deltaXConverged = deltaXConverged_;
783  rfT.deltaFConverged = fnorm * m_rtolf;
784  if (foundStraddle) {
785  rfT.delX = std::max(fabs(deltaX2), fabs(deltaXnew));
786  } else {
787  rfT.delX = std::max(fabs(deltaX2), fabs(deltaXnew));
788  if (x2 < x1) {
789  rfT.delX = std::max(rfT.delX, x2 - xmin);
790  } else {
791  rfT.delX = std::max(rfT.delX, xmax - x2);
792  }
793  }
794  // Section To Determine CONVERGENCE criteria
795  doFinalFuncCall = 0;
796  if ((fabs(fnew / fnorm) < m_rtolf) && foundStraddle) {
797  if (fabs(deltaX2) < deltaXConverged_ && fabs(deltaXnew) < deltaXConverged_) {
798  converged = 1;
799  rfT.reasoning += "NormalConvergence";
800  retn = ROOTFIND_SUCCESS;
801  } else if (fabs(slope) > 1.0E-100) {
802  double xdels = fabs(fnew / slope);
803  if (xdels < deltaXConverged_ * 0.3) {
804  converged = 1;
805  rfT.reasoning += "NormalConvergence-SlopelimitsDelX";
806  doFinalFuncCall = 1;
807  retn = ROOTFIND_SUCCESS;
808  }
809  }
810 
811  // Check for excess convergence in the x coordinate
812  if (!converged && foundStraddle) {
813  doublereal denom = fabs(x1 - x2);
814  if (denom < 1.0E-200) {
816  converged = true;
817  rfT.reasoning += "ConvergenceFZero but X1X2Identical";
818  }
819  if (theSame(x2, x1, 1.0E-2)) {
820  converged = true;
821  rfT.reasoning += " ConvergenceF and XSame";
822  retn = ROOTFIND_SUCCESS;
823  }
824  }
825  } else {
826  // We are here when F is not converged, but we may want to end anyway
827  if (!converged && foundStraddle) {
828  doublereal denom = fabs(x1 - x2);
829  if (denom < 1.0E-200) {
831  converged = true;
832  rfT.reasoning += "FNotConverged but X1X2Identical";
833  }
834 
835  // The premise here is that if x1 and x2 get close to one
836  // another, then the accuracy of the calculation gets destroyed.
837  if (theSame(x2, x1, 1.0E-5)) {
838  converged = true;
840  rfT.reasoning += "FNotConverged but XSame";
841  }
842  }
843  }
844  }
845 
846 done:
847  if (converged) {
848  rfT.slope = slope;
849  rfHistory_.push_back(rfT);
850  rfT.clear();
851  rfT.its = its;
852  AssertThrow((f1* f2 <= 0.0), "F1 and F2 aren't bounding");
853 
854  double x_fpos = x2;
855  double x_fneg = x1;
856  if (f2 < 0.0) {
857  x_fpos = x1;
858  x_fneg = x2;
859  }
860  rfT.delX = fabs(x_fpos - x_fneg);
861  if (doFinalFuncCall || (fabs(f1) < 2.0 * fabs(f2))) {
862  double delXtmp = deltaXControlled(x2, x1);
863  slope = (f2 - f1) / delXtmp;
864  xnew = x2 - f2 / slope;
865  its++;
866  fnew = func(xnew);
867  if (fnew > 0.0) {
868  if (fabs(xnew - x_fneg) < fabs(x_fpos - x_fneg)) {
869  x_fpos = xnew;
870  rfT.delX = fabs(xnew - x_fneg);
871  }
872  } else {
873  if (fabs(xnew - x_fpos) < fabs(x_fpos - x_fneg)) {
874  x_fneg = xnew;
875  rfT.delX = fabs(xnew - x_fpos);
876  }
877  }
878  rfT.its = its;
879  if (fabs(fnew) < fabs(f2) && (fabs(fnew) < fabs(f1))) {
880  *xbest = xnew;
881  if (doFinalFuncCall) {
882  rfT.reasoning += "CONVERGENCE: Another Evaluation Requested";
883  rfT.delX = fabs(xnew - x2);
884  } else {
885  rfT.reasoning += "CONVERGENCE: Another Evaluation done because f1 < f2";
886  rfT.delX = fabs(xnew - x1);
887  }
888  rfT.fval = fnew;
889  rfT.xval = xnew;
890  x2 = xnew;
891  f2 = fnew;
892  } else if (fabs(f1) < fabs(f2)) {
893  rfT.its = its;
894  rfT.xval = xnew;
895  rfT.fval = fnew;
896 
897  rfT.slope = slope;
898  rfT.reasoning += "CONVERGENCE: Another Evaluation not as good as Second Point ";
899  rfHistory_.push_back(rfT);
900  rfT.clear();
901  rfT.its = its;
902  std::swap(f1, f2);
903  std::swap(x1, x2);
904  *xbest = x2;
905  if (fabs(fnew) < fabs(f1) && f1 * fnew > 0.0) {
906  std::swap(f1, fnew);
907  std::swap(x1, xnew);
908  }
909 
910  rfT.its = its;
911  rfT.xval = *xbest;
912  rfT.fval = f2;
913  rfT.delX = fabs(x_fpos - x_fneg);
914  rfT.reasoning += "CONVERGENCE: NormalEnding -> Second point used";
915  } else {
916  rfT.its = its;
917  rfT.xval = xnew;
918  rfT.fval = fnew;
919 
920  rfT.slope = slope;
921  rfT.reasoning += "CONVERGENCE: Another Evaluation not as good as First Point ";
922  rfHistory_.push_back(rfT);
923  rfT.clear();
924  rfT.its = its;
925  *xbest = x2;
926  rfT.xval = *xbest;
927  rfT.fval = f2;
928  rfT.delX = fabs(x_fpos - x_fneg);
929  rfT.reasoning += "CONVERGENCE: NormalEnding -> Last point used";
930  }
931  } else {
932  *xbest = x2;
933  rfT.xval = *xbest;
934  rfT.fval = f2;
935  rfT.delX = fabs(x2 - x1);
936  rfT.reasoning += "CONVERGENCE: NormalEnding -> Last point used";
937  }
938  funcTargetValue = f2 + m_funcTargetValue;
939  rfT.slope = slope;
940 
941  if (printLvl >= 1) {
942  writelogf("RootFind success: convergence achieved\n");
943  }
944  if (printLvl >= 3 && writeLogAllowed_) {
945  fprintf(fp, " | RootFind success in %d its, fnorm = %g\n", its, fnorm);
946  }
947  rfHistory_.push_back(rfT);
948  } else {
949  rfT.reasoning = "FAILED CONVERGENCE ";
950  rfT.slope = slope;
951  rfT.its = its;
952  if (retn == ROOTFIND_SOLNHIGHERTHANXMAX) {
953  if (printLvl >= 1) {
954  writelogf("RootFind ERROR: Soln probably lies higher than xmax, %g: best guess = %g\n", xmax, *xbest);
955  }
956  rfT.reasoning += fmt::format("Soln probably lies higher than xmax, {}: best guess = {}", xmax, *xbest);
957  } else if (retn == ROOTFIND_SOLNLOWERTHANXMIN) {
958  if (printLvl >= 1) {
959  writelogf("RootFind ERROR: Soln probably lies lower than xmin, %g: best guess = %g\n", xmin, *xbest);
960  }
961  rfT.reasoning += fmt::format("Soln probably lies lower than xmin, {}: best guess = {}", xmin, *xbest);
962  } else {
964  if (printLvl >= 1) {
965  writelogf("RootFind ERROR: maximum iterations exceeded without convergence, cause unknown\n");
966  }
967  rfT.reasoning += "Maximum iterations exceeded without convergence, cause unknown";
968  }
969  if (printLvl >= 3 && writeLogAllowed_) {
970  fprintf(fp, "\nRootFind failure in %d its\n", its);
971  }
972 
973  *xbest = x2;
974  funcTargetValue = f2 + m_funcTargetValue;
975  rfT.xval = *xbest;
976  rfT.fval = f2;
977  rfHistory_.push_back(rfT);
978  }
979  if (printLvl >= 3 && writeLogAllowed_) {
980  fclose(fp);
981  }
982 
983  if (printLvl >= 2) {
984  printTable();
985  }
986 
987  return retn;
988 }
989 
990 doublereal RootFind::func(doublereal x)
991 {
992  doublereal r;
993  checkFinite(x);
994  m_residFunc->evalSS(0.0, &x, &r);
995  checkFinite(r);
996  doublereal ff = r - m_funcTargetValue;
997  if (x >= x_maxTried_) {
998  x_maxTried_ = x;
999  fx_maxTried_ = ff;
1000  }
1001  if (x <= x_minTried_) {
1002  x_minTried_ = x;
1003  fx_minTried_ = ff;
1004  }
1005  return ff;
1006 }
1007 
1008 void RootFind::setTol(doublereal rtolf, doublereal atolf, doublereal rtolx, doublereal atolx)
1009 {
1010  m_atolf = atolf;
1011  m_rtolf = rtolf;
1012  if (rtolx <= 0.0) {
1013  m_rtolx = atolf;
1014  } else {
1015  m_rtolx = rtolx;
1016  }
1017  if (atolx <= 0.0) {
1018  m_atolx = atolf;
1019  } else {
1020  m_atolx = atolx;
1021  }
1022 }
1023 
1024 void RootFind::setPrintLvl(int printlvl)
1025 {
1026  printLvl = printlvl;
1027 }
1028 
1030 {
1031  if (value) {
1033  }
1035 }
1036 
1038 {
1039  if (value) {
1041  }
1043 }
1044 
1045 void RootFind::setDeltaX(doublereal deltaXNorm)
1046 {
1047  DeltaXnorm_ = deltaXNorm;
1049 }
1050 
1051 void RootFind::setDeltaXMax(doublereal deltaX)
1052 {
1053  DeltaXMax_ = deltaX;
1054  specifiedDeltaXMax_ = 1;
1055 }
1056 
1058 {
1059  writelogf("\t----------------------------------------------------------------------------------------------------------------------------------------\n");
1060  writelogf("\t RootFinder Summary table: \n");
1061  writelogf("\t FTarget = %g\n", m_funcTargetValue);
1062  writelogf("\t Iter | xval delX deltaXConv | slope | foundP foundN| F - F_targ deltaFConv | Reasoning\n");
1063  writelogf("\t----------------------------------------------------------------------------------------------------------------------------------------\n");
1064  for (int i = 0; i < (int) rfHistory_.size(); i++) {
1065  struct rfTable rfT = rfHistory_[i];
1066  writelogf("\t %3d |%- 17.11E %- 13.7E %- 13.7E |%- 13.5E| %3d %3d | %- 12.5E %- 12.5E | %s \n",
1067  rfT.its, rfT.xval, rfT.delX, rfT.deltaXConverged, rfT.slope, rfT.foundPos, rfT.foundNeg, rfT.fval,
1068  rfT.deltaFConverged, rfT.reasoning);
1069  }
1070  writelogf("\t----------------------------------------------------------------------------------------------------------------------------------------\n");
1071 }
1072 
1073 }
void checkFinite(const double tmp)
Check to see that a number is finite (not NaN, +Inf or -Inf)
Definition: checkFinite.cpp:15
void setDeltaX(doublereal deltaXNorm)
Set the minimum value of deltaX.
Definition: RootFind.cpp:1045
Various templated functions that carry out common vector operations (see Templated Utility Functions)...
#define ROOTFIND_FAILEDCONVERGENCE
This means that the root solver failed to achieve convergence.
Definition: RootFind.h:35
doublereal DeltaXnorm_
Delta X norm.
Definition: RootFind.h:337
#define ROOTFIND_SUCCESS_XCONVERGENCEONLY
This return value means that the root finder resolved a solution in the x coordinate, however, convergence in F was not achieved.
Definition: RootFind.h:33
doublereal fx_minTried_
Internal variable tracking f(x) of smallest x tried.
Definition: RootFind.h:377
void writelog(const std::string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:179
bool FuncIsGenerallyDecreasing_
Boolean indicating whether the function is decreasing with x.
Definition: RootFind.h:358
doublereal x_maxTried_
Internal variable tracking largest x tried.
Definition: RootFind.h:368
void warn_deprecated(const std::string &method, const std::string &extra)
Print a warning indicating that method is deprecated.
Definition: global.cpp:54
STL namespace.
Header file for implicit nonlinear solver of a one dimensional function (see Numerical Utilities with...
std::vector< struct rfTable > rfHistory_
Vector of iteration histories.
Definition: RootFind.h:420
void setFuncIsGenerallyDecreasing(bool value)
Set the function behavior flag.
Definition: RootFind.cpp:1037
Structure containing the iteration history.
Definition: RootFind.h:380
int specifiedDeltaXnorm_
Boolean indicating whether DeltaXnorm_ has been specified by the user or not.
Definition: RootFind.h:341
RootFind & operator=(const RootFind &right)
Definition: RootFind.cpp:86
void printTable()
Print the iteration history table.
Definition: RootFind.cpp:1057
doublereal delXNonzero(doublereal x1) const
Calculate a deltaX from an input value of x.
Definition: RootFind.cpp:117
int printLvl
Print level.
Definition: RootFind.h:328
doublereal m_atolx
Absolute tolerance for the value of x.
Definition: RootFind.h:315
int sign(T x)
Sign of a number. Returns -1 if x < 0, 1 if x > 0 and 0 if x == 0.
Definition: global.h:301
doublereal m_rtolx
Relative tolerance for the value of x.
Definition: RootFind.h:321
bool writeLogAllowed_
Boolean to turn on the possibility of writing a log file.
Definition: RootFind.h:332
doublereal m_rtolf
Relative tolerance for the value of f and x.
Definition: RootFind.h:318
doublereal DeltaXMax_
Delta X Max.
Definition: RootFind.h:348
doublereal x_minTried_
Internal variable tracking smallest x tried.
Definition: RootFind.h:374
void setTol(doublereal rtolf, doublereal atolf, doublereal rtolx=0.0, doublereal atolx=0.0)
Set the tolerance parameters for the rootfinder.
Definition: RootFind.cpp:1008
Root finder for 1D problems.
Definition: RootFind.h:133
void setFuncIsGenerallyIncreasing(bool value)
Set the function behavior flag.
Definition: RootFind.cpp:1029
Virtual base class for DAE residual function evaluators.
Definition: ResidEval.h:33
static void print_funcEval(FILE *fp, doublereal xval, doublereal fval, int its)
Print out a form for the current function evaluation.
Definition: RootFind.cpp:21
doublereal m_funcTargetValue
Target value for the function.
Definition: RootFind.h:309
bool FuncIsGenerallyIncreasing_
Boolean indicating whether the function is an increasing with x.
Definition: RootFind.h:355
#define AssertThrow(expr, procedure)
Assertion must be true or an error is thrown.
Definition: ctexceptions.h:253
bool theSame(doublereal x2, doublereal x1, doublereal factor=1.0) const
Function to decide whether two real numbers are the same or not.
Definition: RootFind.cpp:151
doublereal fx_maxTried_
Internal variable tracking f(x) of largest x tried.
Definition: RootFind.h:371
doublereal delXMeaningful(doublereal x1) const
Calculate a deltaX from an input value of x.
Definition: RootFind.cpp:127
#define ROOTFIND_SOLNHIGHERTHANXMAX
This means that the rootfinder believes the solution is higher than xmax.
Definition: RootFind.h:41
doublereal deltaXControlled(doublereal x2, doublereal x1) const
Calculate a controlled, nonzero delta between two numbers.
Definition: RootFind.cpp:136
ResidEval * m_residFunc
Pointer to the residual function evaluator.
Definition: RootFind.h:305
int solve(doublereal xmin, doublereal xmax, int itmax, doublereal &funcTargetValue, doublereal *xbest)
Using a line search method, find the root of a 1D function.
Definition: RootFind.cpp:163
void writelogf(const char *fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:200
void setDeltaXMax(doublereal deltaX)
Set the maximum value of deltaX.
Definition: RootFind.cpp:1051
#define ROOTFIND_SOLNLOWERTHANXMIN
This means that the rootfinder believes the solution is lower than xmin.
Definition: RootFind.h:39
#define ROOTFIND_BADINPUT
This means that the input to the root solver was defective.
Definition: RootFind.h:37
doublereal m_maxstep
Maximum number of step sizes.
Definition: RootFind.h:324
Contains declarations for string manipulation functions within Cantera.
doublereal deltaXConverged_
Value of delta X that is needed for convergence.
Definition: RootFind.h:365
RootFind(ResidEval *resid)
Constructor for the object.
Definition: RootFind.cpp:33
void setPrintLvl(int printLvl)
Set the print level from the rootfinder.
Definition: RootFind.cpp:1024
Namespace for the Cantera kernel.
Definition: application.cpp:29
doublereal m_atolf
Absolute tolerance for the value of f.
Definition: RootFind.h:312
int specifiedDeltaXMax_
Boolean indicating whether DeltaXMax_ has been specified by the user or not.
Definition: RootFind.h:352
#define ROOTFIND_SUCCESS
This means that the root solver was a success.
Definition: RootFind.h:25
doublereal func(doublereal x)
Return the function value.
Definition: RootFind.cpp:990