Cantera  3.0.0
Loading...
Searching...
No Matches
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
11using namespace std;
12
13namespace Cantera
14{
15
16// unnamed-namespace for local helpers
17namespace
18{
19
20class Indx
21{
22public:
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 */
35double 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 */
101double 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
127const double DampFactor = sqrt(2.0);
128const size_t NDAMP = 7;
129
130// ---------------- MultiNewton methods ----------------
131
132MultiNewton::MultiNewton(int sz)
133 : m_n(sz)
134{
135}
136
137void 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
145double 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
157void 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
190double 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
202int 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
283int 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:267
int solve(const double *const b, double *const x)
Solve the matrix problem Ax = b.
Base class for exceptions thrown by Cantera classes.
Base class for one-dimensional domains.
Definition Domain1D.h:41
size_t nComponents() const
Number of components at each grid point.
Definition Domain1D.h:160
size_t nPoints() const
Number of grid points in this domain.
Definition Domain1D.h:182
virtual string componentName(size_t n) const
Name of the nth component. May be overloaded.
Definition Domain1D.cpp:56
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:418
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:95
size_t size() const
Total solution vector length;.
Definition OneDim.h:106
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:292
double ssnorm(double *x, double *r)
Steady-state max norm (infinity norm) of the residual evaluated using solution x.
Definition OneDim.cpp:324
double rdt() const
Reciprocal of the time step.
Definition OneDim.h:160
size_t nDomains() const
Number of domains.
Definition OneDim.h:64
Domain1D & domain(size_t i) const
Return a reference to domain i.
Definition OneDim.h:69
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:195
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
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...