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
6 #include "cantera/oneD/MultiNewton.h"
7 #include "cantera/base/utilities.h"
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:
Indx(size_t nv,size_t np)23 Indx(size_t nv, size_t np) : m_nv(nv), m_np(np) {}
24 size_t m_nv, m_np;
operator ()(size_t m,size_t j)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 */
bound_step(const doublereal * x,const doublereal * step,Domain1D & r,int loglevel)35 doublereal bound_step(const doublereal* x, const doublereal* step,
36 Domain1D& r, int loglevel)
37 {
38 size_t np = r.nPoints();
39 size_t nv = r.nComponents();
40 Indx index(nv, np);
41 doublereal fbound = 1.0;
42 bool wroteTitle = false;
43 for (size_t m = 0; m < nv; m++) {
44 double above = r.upperBound(m);
45 double below = r.lowerBound(m);
46
47 for (size_t j = 0; j < np; j++) {
48 double val = x[index(m,j)];
49 if (loglevel > 0 && (val > above + 1.0e-12 || val < below - 1.0e-12)) {
50 writelog("\nERROR: solution out of bounds.\n");
51 writelog("domain {:d}: {:>20s}({:d}) = {:10.3e} ({:10.3e}, {:10.3e})\n",
52 r.domainIndex(), r.componentName(m), j, val, below, above);
53 }
54
55 double newval = val + step[index(m,j)];
56
57 if (newval > above) {
58 fbound = std::max(0.0, std::min(fbound,
59 (above - val)/(newval - val)));
60 } else if (newval < below) {
61 fbound = std::min(fbound, (val - below)/(val - newval));
62 }
63
64 if (loglevel > 1 && (newval > above || newval < below)) {
65 if (!wroteTitle) {
66 writelog("\nNewton step takes solution out of bounds.\n\n");
67 writelog(" {:>12s} {:>12s} {:>4s} {:>10s} {:>10s} {:>10s} {:>10s}\n",
68 "domain","component","pt","value","step","min","max");
69 wroteTitle = true;
70 }
71 writelog(" {:4d} {:>12s} {:4d} {:10.3e} {:10.3e} {:10.3e} {:10.3e}\n",
72 r.domainIndex(), r.componentName(m), j,
73 val, step[index(m,j)], below, above);
74 }
75 }
76 }
77 return fbound;
78 }
79
80 /**
81 * This function computes the square of a weighted norm of a step vector for one
82 * domain.
83 *
84 * @param x Solution vector for this domain.
85 * @param step Newton step vector for this domain.
86 * @param r Object representing the domain. Used to get tolerances,
87 * number of components, and number of points.
88 *
89 * The return value is
90 * \f[
91 * \sum_{n,j} \left(\frac{s_{n,j}}{w_n}\right)^2
92 * \f]
93 * where the error weight for solution component \f$n\f$ is given by
94 * \f[
95 * w_n = \epsilon_{r,n} \frac{\sum_j |x_{n,j}|}{J} + \epsilon_{a,n}.
96 * \f]
97 * Here \f$\epsilon_{r,n} \f$ is the relative error tolerance for component n,
98 * and multiplies the average magnitude of solution component n in the domain.
99 * The second term, \f$\epsilon_{a,n}\f$, is the absolute error tolerance for
100 * component n.
101 */
norm_square(const doublereal * x,const doublereal * step,Domain1D & r)102 doublereal norm_square(const doublereal* x,
103 const doublereal* step, Domain1D& r)
104 {
105 double sum = 0.0;
106 doublereal f2max = 0.0;
107 size_t nv = r.nComponents();
108 size_t np = r.nPoints();
109
110 for (size_t n = 0; n < nv; n++) {
111 double esum = 0.0;
112 for (size_t j = 0; j < np; j++) {
113 esum += fabs(x[nv*j + n]);
114 }
115 double ewt = r.rtol(n)*esum/np + r.atol(n);
116 for (size_t j = 0; j < np; j++) {
117 double f = step[nv*j + n]/ewt;
118 sum += f*f;
119 f2max = std::max(f*f, f2max);
120 }
121 }
122 return sum;
123 }
124
125 } // end unnamed-namespace
126
127
128 // constants
129 const doublereal DampFactor = sqrt(2.0);
130 const size_t NDAMP = 7;
131
132 // ---------------- MultiNewton methods ----------------
133
MultiNewton(int sz)134 MultiNewton::MultiNewton(int sz)
135 : m_maxAge(5)
136 {
137 m_n = sz;
138 m_elapsed = 0.0;
139 }
140
resize(size_t sz)141 void MultiNewton::resize(size_t sz)
142 {
143 m_n = sz;
144 m_x.resize(m_n);
145 m_stp.resize(m_n);
146 m_stp1.resize(m_n);
147 }
148
norm2(const doublereal * x,const doublereal * step,OneDim & r) const149 doublereal MultiNewton::norm2(const doublereal* x,
150 const doublereal* step, OneDim& r) const
151 {
152 double sum = 0.0;
153 size_t nd = r.nDomains();
154 for (size_t n = 0; n < nd; n++) {
155 double f = norm_square(x + r.start(n), step + r.start(n), r.domain(n));
156 sum += f;
157 }
158 sum /= r.size();
159 return sqrt(sum);
160 }
161
step(doublereal * x,doublereal * step,OneDim & r,MultiJac & jac,int loglevel)162 void MultiNewton::step(doublereal* x, doublereal* step,
163 OneDim& r, MultiJac& jac, int loglevel)
164 {
165 r.eval(npos, x, step);
166 for (size_t n = 0; n < r.size(); n++) {
167 step[n] = -step[n];
168 }
169
170 try {
171 jac.solve(step, step);
172 } catch (CanteraError&) {
173 int iok = jac.info() - 1;
174 if (iok >= 0) {
175 size_t nd = r.nDomains();
176 size_t n;
177 for (n = nd-1; n != npos; n--) {
178 if (iok >= static_cast<int>(r.start(n))) {
179 break;
180 }
181 }
182 Domain1D& dom = r.domain(n);
183 size_t offset = iok - r.start(n);
184 size_t pt = offset/dom.nComponents();
185 size_t comp = offset - pt*dom.nComponents();
186 throw CanteraError("MultiNewton::step",
187 "Jacobian is singular for domain {}, component {} at point {}\n"
188 "(Matrix row {})",
189 dom.id(), dom.componentName(comp), pt, iok);
190 } else {
191 throw;
192 }
193 }
194 }
195
boundStep(const doublereal * x0,const doublereal * step0,const OneDim & r,int loglevel)196 doublereal MultiNewton::boundStep(const doublereal* x0,
197 const doublereal* step0, const OneDim& r, int loglevel)
198 {
199 doublereal fbound = 1.0;
200 for (size_t i = 0; i < r.nDomains(); i++) {
201 fbound = std::min(fbound,
202 bound_step(x0 + r.start(i), step0 + r.start(i),
203 r.domain(i), loglevel));
204 }
205 return fbound;
206 }
207
dampStep(const doublereal * x0,const doublereal * step0,doublereal * x1,doublereal * step1,doublereal & s1,OneDim & r,MultiJac & jac,int loglevel,bool writetitle)208 int MultiNewton::dampStep(const doublereal* x0, const doublereal* step0,
209 doublereal* x1, doublereal* step1, doublereal& s1,
210 OneDim& r, MultiJac& jac, int loglevel, bool writetitle)
211 {
212 // write header
213 if (loglevel > 0 && writetitle) {
214 writelog("\n\nDamped Newton iteration:\n");
215 writeline('-', 65, false);
216
217 writelog("\n{} {:>9s} {:>9s} {:>9s} {:>9s} {:>9s} {:>5s} {:>5s}\n",
218 "m","F_damp","F_bound","log10(ss)",
219 "log10(s0)","log10(s1)","N_jac","Age");
220 writeline('-', 65);
221 }
222
223 // compute the weighted norm of the undamped step size step0
224 doublereal s0 = norm2(x0, step0, r);
225
226 // compute the multiplier to keep all components in bounds
227 doublereal fbound = boundStep(x0, step0, r, loglevel-1);
228
229 // if fbound is very small, then x0 is already close to the boundary and
230 // step0 points out of the allowed domain. In this case, the Newton
231 // algorithm fails, so return an error condition.
232 if (fbound < 1.e-10) {
233 debuglog("\nAt limits.\n", loglevel);
234 return -3;
235 }
236
237 // ---------- Attempt damped step ----------
238
239 // damping coefficient starts at 1.0
240 doublereal damp = 1.0;
241 size_t m;
242 for (m = 0; m < NDAMP; m++) {
243 double ff = fbound*damp;
244
245 // step the solution by the damped step size
246 for (size_t j = 0; j < m_n; j++) {
247 x1[j] = ff*step0[j] + x0[j];
248 }
249
250 // compute the next undamped step that would result if x1 is accepted
251 step(x1, step1, r, jac, loglevel-1);
252
253 // compute the weighted norm of step1
254 s1 = norm2(x1, step1, r);
255
256 // write log information
257 if (loglevel > 0) {
258 doublereal ss = r.ssnorm(x1,step1);
259 writelog("\n{:d} {:9.5f} {:9.5f} {:9.5f} {:9.5f} {:9.5f} {:4d} {:d}/{:d}",
260 m, damp, fbound, log10(ss+SmallNumber),
261 log10(s0+SmallNumber), log10(s1+SmallNumber),
262 jac.nEvals(), jac.age(), m_maxAge);
263 }
264
265 // if the norm of s1 is less than the norm of s0, then accept this
266 // damping coefficient. Also accept it if this step would result in a
267 // converged solution. Otherwise, decrease the damping coefficient and
268 // try again.
269 if (s1 < 1.0 || s1 < s0) {
270 break;
271 }
272 damp /= DampFactor;
273 }
274
275 // If a damping coefficient was found, return 1 if the solution after
276 // stepping by the damped step would represent a converged solution, and
277 // return 0 otherwise. If no damping coefficient could be found, return -2.
278 if (m < NDAMP) {
279 if (s1 > 1.0) {
280 return 0;
281 } else {
282 return 1;
283 }
284 } else {
285 return -2;
286 }
287 }
288
solve(doublereal * x0,doublereal * x1,OneDim & r,MultiJac & jac,int loglevel)289 int MultiNewton::solve(doublereal* x0, doublereal* x1,
290 OneDim& r, MultiJac& jac, int loglevel)
291 {
292 clock_t t0 = clock();
293 int m = 0;
294 bool forceNewJac = false;
295 doublereal s1=1.e30;
296
297 copy(x0, x0 + m_n, &m_x[0]);
298
299 bool frst = true;
300 doublereal rdt = r.rdt();
301 int j0 = jac.nEvals();
302 int nJacReeval = 0;
303
304 while (true) {
305 // Check whether the Jacobian should be re-evaluated.
306 if (jac.age() > m_maxAge) {
307 if (loglevel > 0) {
308 writelog("\nMaximum Jacobian age reached ({})\n", m_maxAge);
309 }
310 forceNewJac = true;
311 }
312
313 if (forceNewJac) {
314 r.eval(npos, &m_x[0], &m_stp[0], 0.0, 0);
315 jac.eval(&m_x[0], &m_stp[0], 0.0);
316 jac.updateTransient(rdt, r.transientMask().data());
317 forceNewJac = false;
318 }
319
320 // compute the undamped Newton step
321 step(&m_x[0], &m_stp[0], r, jac, loglevel-1);
322
323 // increment the Jacobian age
324 jac.incrementAge();
325
326 // damp the Newton step
327 m = dampStep(&m_x[0], &m_stp[0], x1, &m_stp1[0], s1, r, jac, loglevel-1, frst);
328 if (loglevel == 1 && m >= 0) {
329 if (frst) {
330 writelog("\n\n {:>10s} {:>10s} {:>5s}",
331 "log10(ss)","log10(s1)","N_jac");
332 writelog("\n ------------------------------------");
333 }
334 doublereal ss = r.ssnorm(&m_x[0], &m_stp[0]);
335 writelog("\n {:10.4f} {:10.4f} {:d}",
336 log10(ss),log10(s1),jac.nEvals());
337 }
338 frst = false;
339
340 // Successful step, but not converged yet. Take the damped step, and try
341 // again.
342 if (m == 0) {
343 copy(x1, x1 + m_n, m_x.begin());
344 } else if (m == 1) {
345 // convergence
346 if (rdt == 0) {
347 jac.setAge(0); // for efficient sensitivity analysis
348 }
349 break;
350 } else if (m < 0) {
351 // If dampStep fails, first try a new Jacobian if an old one was
352 // being used. If it was a new Jacobian, then return -1 to signify
353 // failure.
354 if (jac.age() > 1) {
355 forceNewJac = true;
356 if (nJacReeval > 3) {
357 break;
358 }
359 nJacReeval++;
360 debuglog("\nRe-evaluating Jacobian, since no damping "
361 "coefficient\ncould be found with this Jacobian.\n",
362 loglevel);
363 } else {
364 break;
365 }
366 }
367 }
368
369 if (m < 0) {
370 copy(m_x.begin(), m_x.end(), x1);
371 }
372 if (m > 0 && jac.nEvals() == j0) {
373 m = 100;
374 }
375 m_elapsed += (clock() - t0)/(1.0*CLOCKS_PER_SEC);
376 return m;
377 }
378
379 } // end namespace Cantera
380