Cantera  3.1.0a1
MultiNewton.cpp
Go to the documentation of this file.
1 //! @file MultiNewton.cpp Damped Newton solver for 1D multi-domain problems
2 
3 // This file is part of Cantera. See License.txt in the top-level directory or
4 // at https://cantera.org/license.txt for license and copyright information.
5 
8 
9 #include <ctime>
10 
11 using namespace std;
12 
13 namespace Cantera
14 {
15 
16 // unnamed-namespace for local helpers
17 namespace
18 {
19 
20 class Indx
21 {
22 public:
23  Indx(size_t nv, size_t np) : m_nv(nv), m_np(np) {}
24  size_t m_nv, m_np;
25  size_t operator()(size_t m, size_t j) {
26  return j*m_nv + m;
27  }
28 };
29 
30 /**
31  * Return a damping coefficient that keeps the solution after taking one
32  * Newton step between specified lower and upper bounds. This function only
33  * considers one domain.
34  */
35 double bound_step(const double* x, const double* step, Domain1D& r, int loglevel)
36 {
37  size_t np = r.nPoints();
38  size_t nv = r.nComponents();
39  Indx index(nv, np);
40  double fbound = 1.0;
41  bool wroteTitle = false;
42  for (size_t m = 0; m < nv; m++) {
43  double above = r.upperBound(m);
44  double below = r.lowerBound(m);
45 
46  for (size_t j = 0; j < np; j++) {
47  double val = x[index(m,j)];
48  if (loglevel > 0 && (val > above + 1.0e-12 || val < below - 1.0e-12)) {
49  writelog("\nERROR: solution out of bounds.\n");
50  writelog("domain {:d}: {:>20s}({:d}) = {:10.3e} ({:10.3e}, {:10.3e})\n",
51  r.domainIndex(), r.componentName(m), j, val, below, above);
52  }
53 
54  double newval = val + step[index(m,j)];
55 
56  if (newval > above) {
57  fbound = std::max(0.0, std::min(fbound,
58  (above - val)/(newval - val)));
59  } else if (newval < below) {
60  fbound = std::min(fbound, (val - below)/(val - newval));
61  }
62 
63  if (loglevel > 1 && (newval > above || newval < below)) {
64  if (!wroteTitle) {
65  writelog("\nNewton step takes solution out of bounds.\n\n");
66  writelog(" {:>12s} {:>12s} {:>4s} {:>10s} {:>10s} {:>10s} {:>10s}\n",
67  "domain","component","pt","value","step","min","max");
68  wroteTitle = true;
69  }
70  writelog(" {:4d} {:>12s} {:4d} {:10.3e} {:10.3e} {:10.3e} {:10.3e}\n",
71  r.domainIndex(), r.componentName(m), j,
72  val, step[index(m,j)], below, above);
73  }
74  }
75  }
76  return fbound;
77 }
78 
79 /**
80  * This function computes the square of a weighted norm of a step vector for one
81  * domain.
82  *
83  * @param x Solution vector for this domain.
84  * @param step Newton step vector for this domain.
85  * @param r Object representing the domain. Used to get tolerances,
86  * number of components, and number of points.
87  *
88  * The return value is
89  * @f[
90  * \sum_{n,j} \left(\frac{s_{n,j}}{w_n}\right)^2
91  * @f]
92  * where the error weight for solution component @f$ n @f$ is given by
93  * @f[
94  * w_n = \epsilon_{r,n} \frac{\sum_j |x_{n,j}|}{J} + \epsilon_{a,n}.
95  * @f]
96  * Here @f$ \epsilon_{r,n} @f$ is the relative error tolerance for component n,
97  * and multiplies the average magnitude of solution component n in the domain.
98  * The second term, @f$ \epsilon_{a,n} @f$, is the absolute error tolerance for
99  * component n.
100  */
101 double norm_square(const double* x, const double* step, Domain1D& r)
102 {
103  double sum = 0.0;
104  double f2max = 0.0;
105  size_t nv = r.nComponents();
106  size_t np = r.nPoints();
107 
108  for (size_t n = 0; n < nv; n++) {
109  double esum = 0.0;
110  for (size_t j = 0; j < np; j++) {
111  esum += fabs(x[nv*j + n]);
112  }
113  double ewt = r.rtol(n)*esum/np + r.atol(n);
114  for (size_t j = 0; j < np; j++) {
115  double f = step[nv*j + n]/ewt;
116  sum += f*f;
117  f2max = std::max(f*f, f2max);
118  }
119  }
120  return sum;
121 }
122 
123 } // end unnamed-namespace
124 
125 
126 // constants
127 const double DampFactor = sqrt(2.0);
128 const size_t NDAMP = 7;
129 
130 // ---------------- MultiNewton methods ----------------
131 
132 MultiNewton::MultiNewton(int sz)
133  : m_n(sz)
134 {
135 }
136 
137 void MultiNewton::resize(size_t sz)
138 {
139  m_n = sz;
140  m_x.resize(m_n);
141  m_stp.resize(m_n);
142  m_stp1.resize(m_n);
143 }
144 
145 double MultiNewton::norm2(const double* x, const double* step, OneDim& r) const
146 {
147  double sum = 0.0;
148  size_t nd = r.nDomains();
149  for (size_t n = 0; n < nd; n++) {
150  double f = norm_square(x + r.start(n), step + r.start(n), r.domain(n));
151  sum += f;
152  }
153  sum /= r.size();
154  return sqrt(sum);
155 }
156 
157 void MultiNewton::step(double* x, double* step, OneDim& r, MultiJac& jac, int loglevel)
158 {
159  r.eval(npos, x, step);
160  for (size_t n = 0; n < r.size(); n++) {
161  step[n] = -step[n];
162  }
163 
164  try {
165  jac.solve(step, step);
166  } catch (CanteraError&) {
167  if (jac.info() > 0) {
168  // Positive value for "info" indicates the row where factorization failed
169  size_t row = static_cast<size_t>(jac.info() - 1);
170  // Find the domain, grid point, and solution component corresponding
171  // to this row
172  for (size_t n = 0; n < r.nDomains(); n++) {
173  Domain1D& dom = r.domain(n);
174  size_t nComp = dom.nComponents();
175  if (row >= dom.loc() && row < dom.loc() + nComp * dom.nPoints()) {
176  size_t offset = row - dom.loc();
177  size_t pt = offset / nComp;
178  size_t comp = offset - pt * nComp;
179  throw CanteraError("MultiNewton::step",
180  "Jacobian is singular for domain {}, component {} at point {}\n"
181  "(Matrix row {})",
182  dom.id(), dom.componentName(comp), pt, row);
183  }
184  }
185  }
186  throw;
187  }
188 }
189 
190 double MultiNewton::boundStep(const double* x0, const double* step0, const OneDim& r,
191  int loglevel)
192 {
193  double fbound = 1.0;
194  for (size_t i = 0; i < r.nDomains(); i++) {
195  fbound = std::min(fbound,
196  bound_step(x0 + r.start(i), step0 + r.start(i),
197  r.domain(i), loglevel));
198  }
199  return fbound;
200 }
201 
202 int MultiNewton::dampStep(const double* x0, const double* step0,
203  double* x1, double* step1, double& s1,
204  OneDim& r, MultiJac& jac, int loglevel, bool writetitle)
205 {
206  // write header
207  if (loglevel > 0 && writetitle) {
208  writelog("\n\nDamped Newton iteration:\n");
209  writeline('-', 65, false);
210 
211  writelog("\n{} {:>9s} {:>9s} {:>9s} {:>9s} {:>9s} {:>5s} {:>5s}\n",
212  "m","F_damp","F_bound","log10(ss)",
213  "log10(s0)","log10(s1)","N_jac","Age");
214  writeline('-', 65);
215  }
216 
217  // compute the weighted norm of the undamped step size step0
218  double s0 = norm2(x0, step0, r);
219 
220  // compute the multiplier to keep all components in bounds
221  double fbound = boundStep(x0, step0, r, loglevel-1);
222 
223  // if fbound is very small, then x0 is already close to the boundary and
224  // step0 points out of the allowed domain. In this case, the Newton
225  // algorithm fails, so return an error condition.
226  if (fbound < 1.e-10) {
227  debuglog("\nAt limits.\n", loglevel);
228  return -3;
229  }
230 
231  // ---------- Attempt damped step ----------
232 
233  // damping coefficient starts at 1.0
234  double damp = 1.0;
235  size_t m;
236  for (m = 0; m < NDAMP; m++) {
237  double ff = fbound*damp;
238 
239  // step the solution by the damped step size
240  for (size_t j = 0; j < m_n; j++) {
241  x1[j] = ff*step0[j] + x0[j];
242  }
243 
244  // compute the next undamped step that would result if x1 is accepted
245  step(x1, step1, r, jac, loglevel-1);
246 
247  // compute the weighted norm of step1
248  s1 = norm2(x1, step1, r);
249 
250  // write log information
251  if (loglevel > 0) {
252  double ss = r.ssnorm(x1,step1);
253  writelog("\n{:d} {:9.5f} {:9.5f} {:9.5f} {:9.5f} {:9.5f} {:4d} {:d}/{:d}",
254  m, damp, fbound, log10(ss+SmallNumber),
255  log10(s0+SmallNumber), log10(s1+SmallNumber),
256  jac.nEvals(), jac.age(), m_maxAge);
257  }
258 
259  // if the norm of s1 is less than the norm of s0, then accept this
260  // damping coefficient. Also accept it if this step would result in a
261  // converged solution. Otherwise, decrease the damping coefficient and
262  // try again.
263  if (s1 < 1.0 || s1 < s0) {
264  break;
265  }
266  damp /= DampFactor;
267  }
268 
269  // If a damping coefficient was found, return 1 if the solution after
270  // stepping by the damped step would represent a converged solution, and
271  // return 0 otherwise. If no damping coefficient could be found, return -2.
272  if (m < NDAMP) {
273  if (s1 > 1.0) {
274  return 0;
275  } else {
276  return 1;
277  }
278  } else {
279  return -2;
280  }
281 }
282 
283 int MultiNewton::solve(double* x0, double* x1, OneDim& r, MultiJac& jac, int loglevel)
284 {
285  clock_t t0 = clock();
286  int m = 0;
287  bool forceNewJac = false;
288  double s1=1.e30;
289 
290  copy(x0, x0 + m_n, &m_x[0]);
291 
292  bool frst = true;
293  double rdt = r.rdt();
294  int j0 = jac.nEvals();
295  int nJacReeval = 0;
296 
297  while (true) {
298  // Check whether the Jacobian should be re-evaluated.
299  if (jac.age() > m_maxAge) {
300  if (loglevel > 0) {
301  writelog("\nMaximum Jacobian age reached ({})\n", m_maxAge);
302  }
303  forceNewJac = true;
304  }
305 
306  if (forceNewJac) {
307  r.eval(npos, &m_x[0], &m_stp[0], 0.0, 0);
308  jac.eval(&m_x[0], &m_stp[0], 0.0);
309  jac.updateTransient(rdt, r.transientMask().data());
310  forceNewJac = false;
311  }
312 
313  // compute the undamped Newton step
314  step(&m_x[0], &m_stp[0], r, jac, loglevel-1);
315 
316  // increment the Jacobian age
317  jac.incrementAge();
318 
319  // damp the Newton step
320  m = dampStep(&m_x[0], &m_stp[0], x1, &m_stp1[0], s1, r, jac, loglevel-1, frst);
321  if (loglevel == 1 && m >= 0) {
322  if (frst) {
323  writelog("\n\n {:>10s} {:>10s} {:>5s}",
324  "log10(ss)","log10(s1)","N_jac");
325  writelog("\n ------------------------------------");
326  }
327  double ss = r.ssnorm(&m_x[0], &m_stp[0]);
328  writelog("\n {:10.4f} {:10.4f} {:d}",
329  log10(ss),log10(s1),jac.nEvals());
330  }
331  frst = false;
332 
333  // Successful step, but not converged yet. Take the damped step, and try
334  // again.
335  if (m == 0) {
336  copy(x1, x1 + m_n, m_x.begin());
337  } else if (m == 1) {
338  // convergence
339  if (rdt == 0) {
340  jac.setAge(0); // for efficient sensitivity analysis
341  }
342  break;
343  } else if (m < 0) {
344  // If dampStep fails, first try a new Jacobian if an old one was
345  // being used. If it was a new Jacobian, then return -1 to signify
346  // failure.
347  if (jac.age() > 1) {
348  forceNewJac = true;
349  if (nJacReeval > 3) {
350  break;
351  }
352  nJacReeval++;
353  debuglog("\nRe-evaluating Jacobian, since no damping "
354  "coefficient\ncould be found with this Jacobian.\n",
355  loglevel);
356  } else {
357  break;
358  }
359  }
360  }
361 
362  if (m < 0) {
363  copy(m_x.begin(), m_x.end(), x1);
364  }
365  if (m > 0 && jac.nEvals() == j0) {
366  m = 100;
367  }
368  m_elapsed += (clock() - t0)/(1.0*CLOCKS_PER_SEC);
369  return m;
370 }
371 
372 } // end namespace Cantera
int info() const
LAPACK "info" flag after last factor/solve operation.
Definition: BandMatrix.h:239
int solve(const double *const b, double *const x)
Solve the matrix problem Ax = b.
Definition: BandMatrix.cpp:253
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:66
Base class for one-dimensional domains.
Definition: Domain1D.h:28
size_t nComponents() const
Number of components at each grid point.
Definition: Domain1D.h:145
size_t nPoints() const
Number of grid points in this domain.
Definition: Domain1D.h:167
virtual string componentName(size_t n) const
Name of the nth component. May be overloaded.
Definition: Domain1D.cpp:49
virtual size_t loc(size_t j=0) const
Location of the start of the local solution vector in the global solution vector,.
Definition: Domain1D.h:384
Class MultiJac evaluates the Jacobian of a system of equations defined by a residual function supplie...
Definition: MultiJac.h:24
int nEvals() const
Number of Jacobian evaluations.
Definition: MultiJac.h:42
void setAge(int age)
Set the Jacobian age.
Definition: MultiJac.h:59
void eval(double *x0, double *resid0, double rdt)
Evaluate the Jacobian at x0.
Definition: MultiJac.cpp:36
void incrementAge()
Increment the Jacobian age.
Definition: MultiJac.h:52
int age() const
Number of times 'incrementAge' has been called since the last evaluation.
Definition: MultiJac.h:47
Container class for multiple-domain 1D problems.
Definition: OneDim.h:27
size_t start(size_t i) const
The index of the start of domain i in the solution vector.
Definition: OneDim.h:88
size_t size() const
Total solution vector length;.
Definition: OneDim.h:99
void eval(size_t j, double *x, double *r, double rdt=-1.0, int count=1)
Evaluate the multi-domain residual function.
Definition: OneDim.cpp:246
double ssnorm(double *x, double *r)
Steady-state max norm (infinity norm) of the residual evaluated using solution x.
Definition: OneDim.cpp:278
double rdt() const
Reciprocal of the time step.
Definition: OneDim.h:153
size_t nDomains() const
Number of domains.
Definition: OneDim.h:57
Domain1D & domain(size_t i) const
Return a reference to domain i.
Definition: OneDim.h:62
void debuglog(const string &msg, int loglevel)
Write a message to the log only if loglevel > 0.
Definition: global.h:158
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition: global.h:175
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
const double SmallNumber
smallest number to compare to zero.
Definition: ct_defs.h:158
offset
Offsets of solution components in the 1D solution array.
Definition: StFlow.h:24
int solve(DenseMatrix &A, double *b, size_t nrhs, size_t ldb)
Solve Ax = b. Array b is overwritten on exit with x.
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...