Cantera  3.1.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 string separator = fmt::format("\n {:=>69}", ""); // equals sign separator
43
44 for (size_t m = 0; m < nv; m++) {
45 double above = r.upperBound(m);
46 double below = r.lowerBound(m);
47
48 for (size_t j = 0; j < np; j++) {
49 double val = x[index(m,j)];
50 if (loglevel > 0 && (val > above + 1.0e-12 || val < below - 1.0e-12)) {
51 writelog("\nERROR: solution out of bounds.\n");
52 writelog("domain {:d}: {:>20s}({:d}) = {:10.3e} ({:10.3e}, {:10.3e})\n",
53 r.domainIndex(), r.componentName(m), j, val, below, above);
54 }
55
56 double newval = val + step[index(m,j)];
57
58 if (newval > above) {
59 fbound = std::max(0.0, std::min(fbound,
60 (above - val)/(newval - val)));
61 } else if (newval < below) {
62 fbound = std::min(fbound, (val - below)/(val - newval));
63 }
64
65 if (loglevel > 1 && (newval > above || newval < below)) {
66 if (!wroteTitle){
67 string header = fmt::format(" {:=>10}","") +
68 "Undamped Newton step takes solution out of bounds" +
69 fmt::format("{:=>10}", "");
70 writelog("\n{}", header);
71 writelog(separator);
72 // Split header across 2 lines to shorten the line length
73 writelog("\n {:<7s} {:23s} {:<9s} {:<9s} {:<9s}",
74 "Domain/", "", "Value", "Min", "Max");
75 writelog("\n {:8s} {:<9s} {:<9s} {:6s} {:5s} {:5s}",
76 "Grid Loc", "Component", "Value", "Change", "Bound", "Bound");
77 writelog(separator);
78 wroteTitle = true;
79 }
80 // This create a dynamic spacing to allow for a nicely formatted output in the
81 // Domain/Grid Loc column
82 int domainLength = to_string(r.domainIndex()).length(); // For adjusting spacing
83 int gridLength = to_string(j).length(); // For adjusting spacing
84 int padding = 9; // Total spacing for first column
85 string formatString = fmt::format("{{:<{}d}} / {{:<{}d}}{:>{}}",
86 domainLength, gridLength, "",
87 padding-3-domainLength-gridLength);
88 writelog(fmt::format("\n {}", formatString), r.domainIndex(), j);
89 writelog(" {:<12s} {:>10.3e} {:>10.3e} {:>10.3e} {:>10.3e}",
90 r.componentName(m), val, step[index(m,j)], below, above);
91 }
92 }
93 }
94
95 if (loglevel > 1 && wroteTitle) { // If a title was written, close up the table
96 writelog(separator);
97 }
98 return fbound;
99}
100
101/**
102 * This function computes the square of a weighted norm of a step vector for one
103 * domain.
104 *
105 * @param x Solution vector for this domain.
106 * @param step Newton step vector for this domain.
107 * @param r Object representing the domain. Used to get tolerances,
108 * number of components, and number of points.
109 *
110 * The return value is
111 * @f[
112 * \sum_{n,j} \left(\frac{s_{n,j}}{w_n}\right)^2
113 * @f]
114 * where the error weight for solution component @f$ n @f$ is given by
115 * @f[
116 * w_n = \epsilon_{r,n} \frac{\sum_j |x_{n,j}|}{J} + \epsilon_{a,n}.
117 * @f]
118 * Here @f$ \epsilon_{r,n} @f$ is the relative error tolerance for component n,
119 * and multiplies the average magnitude of solution component n in the domain.
120 * The second term, @f$ \epsilon_{a,n} @f$, is the absolute error tolerance for
121 * component n.
122 *
123 * The purpose is to measure the "size" of the step vector @f$ s @f$ in a way that
124 * takes into account the relative importance or scale of different solution
125 * components.
126 *
127 * Normalization: Each component of the step vector is normalized by a weight that
128 * depends on both the current magnitude of the solution vector and specified
129 * tolerances. This makes the norm dimensionless and scaled appropriately, avoiding
130 * issues where some components dominate due to differences in their scales.
131 */
132double norm_square(const double* x, const double* step, Domain1D& r)
133{
134 double sum = 0.0;
135 double f2max = 0.0;
136 size_t nv = r.nComponents();
137 size_t np = r.nPoints();
138
139 for (size_t n = 0; n < nv; n++) {
140 double esum = 0.0;
141 for (size_t j = 0; j < np; j++) {
142 esum += fabs(x[nv*j + n]);
143 }
144 double ewt = r.rtol(n)*esum/np + r.atol(n);
145 for (size_t j = 0; j < np; j++) {
146 double f = step[nv*j + n]/ewt;
147 sum += f*f;
148 f2max = std::max(f*f, f2max);
149 }
150 }
151 return sum;
152}
153
154} // end unnamed-namespace
155
156
157// ---------------- MultiNewton methods ----------------
158
160 : m_n(sz)
161{
162}
163
164void MultiNewton::resize(size_t sz)
165{
166 m_n = sz;
167 m_x.resize(m_n);
168 m_stp.resize(m_n);
169 m_stp1.resize(m_n);
170}
171
172double MultiNewton::norm2(const double* x, const double* step, OneDim& r) const
173{
174 double sum = 0.0;
175 size_t nd = r.nDomains();
176 for (size_t n = 0; n < nd; n++) {
177 double f = norm_square(x + r.start(n), step + r.start(n), r.domain(n));
178 sum += f;
179 }
180 sum /= r.size();
181 return sqrt(sum);
182}
183
184void MultiNewton::step(double* x, double* step, OneDim& r, MultiJac& jac, int loglevel)
185{
186 r.eval(npos, x, step);
187 for (size_t n = 0; n < r.size(); n++) {
188 step[n] = -step[n];
189 }
190
191 try {
192 jac.solve(step, step);
193 } catch (CanteraError&) {
194 if (jac.info() > 0) {
195 // Positive value for "info" indicates the row where factorization failed
196 size_t row = static_cast<size_t>(jac.info() - 1);
197 // Find the domain, grid point, and solution component corresponding
198 // to this row
199 for (size_t n = 0; n < r.nDomains(); n++) {
200 Domain1D& dom = r.domain(n);
201 size_t nComp = dom.nComponents();
202 if (row >= dom.loc() && row < dom.loc() + nComp * dom.nPoints()) {
203 size_t offset = row - dom.loc();
204 size_t pt = offset / nComp;
205 size_t comp = offset - pt * nComp;
206 throw CanteraError("MultiNewton::step",
207 "Jacobian is singular for domain {}, component {} at point {}\n"
208 "(Matrix row {})",
209 dom.id(), dom.componentName(comp), pt, row);
210 }
211 }
212 }
213 throw;
214 }
215}
216
217double MultiNewton::boundStep(const double* x0, const double* step0, const OneDim& r,
218 int loglevel)
219{
220 double fbound = 1.0;
221 for (size_t i = 0; i < r.nDomains(); i++) {
222 fbound = std::min(fbound,
223 bound_step(x0 + r.start(i), step0 + r.start(i),
224 r.domain(i), loglevel));
225 }
226 return fbound;
227}
228
229int MultiNewton::dampStep(const double* x0, const double* step0,
230 double* x1, double* step1, double& s1,
231 OneDim& r, MultiJac& jac, int loglevel, bool writetitle)
232{
233 // write header
234 if (loglevel > 0 && writetitle) {
235 writelog("\n\n {:-^70}", " Damped Newton iteration ");
236 writelog("\n {:<4s} {:<10s} {:<10s} {:<7s} {:<7s} {:<7s} {:<5s} {:<3s}\n",
237 "Iter", "F_damp", "F_bound", "log(ss)",
238 "log(s0)", "log(s1)", "N_jac", "Age");
239 writelog(" {:->70}", "");
240 }
241
242 // compute the weighted norm of the undamped step size step0
243 double s0 = norm2(x0, step0, r);
244
245 // compute the multiplier to keep all components in bounds
246 double fbound = boundStep(x0, step0, r, loglevel-1);
247
248 // if fbound is very small, then x0 is already close to the boundary and
249 // step0 points out of the allowed domain. In this case, the Newton
250 // algorithm fails, so return an error condition.
251 if (fbound < 1.e-10) {
252 debuglog("\n No damped step can be taken without violating solution component bounds.", loglevel);
253 return -3;
254 }
255
256 // ---------- Attempt damped step ----------
257
258 // damping coefficient starts at 1.0, but must be scaled by the
259 // fbound factor to ensure that the solution remains within bounds.
260 double alpha = fbound*1.0;
261 size_t m;
262 for (m = 0; m < m_maxDampIter; m++) {
263 // step the solution by the damped step size
264 // x_{k+1} = x_k + alpha_k*J(x_k)^-1 F(x_k)
265 for (size_t j = 0; j < m_n; j++) {
266 x1[j] = x0[j] + alpha*step0[j];
267 }
268
269 // compute the next undamped step that would result if x1 is accepted
270 // J(x_k)^-1 F(x_k+1)
271 step(x1, step1, r, jac, loglevel-1);
272
273 // compute the weighted norm of step1
274 s1 = norm2(x1, step1, r);
275
276 if (loglevel > 0) {
277 double ss = r.ssnorm(x1,step1);
278 writelog("\n {:<4d} {:<9.3e} {:<9.3e} {:>6.3f} {:>6.3f} {:>6.3f} {:<5d} {:d}/{:d}",
279 m, alpha, fbound, log10(ss+SmallNumber),
280 log10(s0+SmallNumber), log10(s1+SmallNumber),
281 jac.nEvals(), jac.age(), m_maxAge);
282 }
283
284 // If the norm of s1 is less than the norm of s0, then accept this
285 // damping coefficient. Also accept it if this step would result in a
286 // converged solution. Otherwise, decrease the damping coefficient and
287 // try again.
288 if (s1 < 1.0 || s1 < s0) {
289 break;
290 }
291 alpha /= m_dampFactor;
292 }
293
294 // If a damping coefficient was found, return 1 if the solution after
295 // stepping by the damped step would represent a converged solution, and
296 // return 0 otherwise. If no damping coefficient could be found, return -2.
297 if (m < m_maxDampIter) {
298 if (s1 > 1.0) {
299 debuglog("\n Damping coefficient found (solution has not converged yet)", loglevel);
300 return 0;
301 } else {
302 debuglog("\n Damping coefficient found (solution has converged)", loglevel);
303 return 1;
304 }
305 } else {
306 debuglog("\n No damping coefficient found (max damping iterations reached)", loglevel);
307 return -2;
308 }
309}
310
311int MultiNewton::solve(double* x0, double* x1, OneDim& r, MultiJac& jac, int loglevel)
312{
313 clock_t t0 = clock();
314 int status = 0;
315 bool forceNewJac = false;
316 bool write_header = true;
317 double s1=1.e30;
318
319 copy(x0, x0 + m_n, &m_x[0]);
320
321 double rdt = r.rdt();
322 int nJacReeval = 0;
323 while (true) {
324 // Check whether the Jacobian should be re-evaluated.
325 if (jac.age() > m_maxAge) {
326 if (loglevel > 1) {
327 writelog("\n Maximum Jacobian age reached ({}), updating it.", m_maxAge);
328 }
329 forceNewJac = true;
330 }
331
332 if (forceNewJac) {
333 r.eval(npos, &m_x[0], &m_stp[0], 0.0, 0);
334 jac.eval(&m_x[0], &m_stp[0], 0.0);
335 jac.updateTransient(rdt, r.transientMask().data());
336 forceNewJac = false;
337 }
338
339 // compute the undamped Newton step
340 step(&m_x[0], &m_stp[0], r, jac, loglevel-1);
341
342 // increment the Jacobian age
343 jac.incrementAge();
344
345 // damp the Newton step
346 status = dampStep(&m_x[0], &m_stp[0], x1, &m_stp1[0], s1, r, jac, loglevel-1, write_header);
347 write_header = false;
348
349 // Successful step, but not converged yet. Take the damped step, and try
350 // again.
351 if (status == 0) {
352 copy(x1, x1 + m_n, m_x.begin());
353 } else if (status == 1) { // convergence
354 if (rdt == 0) {
355 jac.setAge(0); // for efficient sensitivity analysis
356 }
357 break;
358 } else if (status < 0) {
359 // If dampStep fails, first try a new Jacobian if an old one was
360 // being used. If it was a new Jacobian, then return -1 to signify
361 // failure.
362 if (jac.age() > 1) {
363 forceNewJac = true;
364 if (nJacReeval > 3) {
365 break;
366 }
367 nJacReeval++;
368 if (loglevel > 1) {
369 writelog("\n Re-evaluating Jacobian (damping coefficient not found"
370 " with this Jacobian)");
371 }
372 } else {
373 break;
374 }
375 }
376 }
377 // Close off the damped iteration table that is written by the dampedStep() method
378 if (loglevel > 1) {
379 writelog("\n {:->70}", "");
380 }
381
382 if (status < 0) { // Reset x1 to x0 if the solution failed
383 copy(m_x.begin(), m_x.end(), x1);
384 }
385 m_elapsed += (clock() - t0)/(1.0*CLOCKS_PER_SEC);
386 return status;
387}
388
389} // 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.
Base class for exceptions thrown by Cantera classes.
Base class for one-dimensional domains.
Definition Domain1D.h:29
size_t nComponents() const
Number of components at each grid point.
Definition Domain1D.h:151
string id() const
Returns the identifying tag for this domain.
Definition Domain1D.h:479
size_t nPoints() const
Number of grid points in this domain.
Definition Domain1D.h:173
virtual string componentName(size_t n) const
Name of component n. May be overloaded.
Definition Domain1D.cpp:67
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:53
void setAge(int age)
Set the Jacobian age.
Definition MultiJac.h:71
void eval(double *x0, double *resid0, double rdt)
Evaluates the Jacobian at x0 using finite differences.
Definition MultiJac.cpp:35
void updateTransient(double rdt, integer *mask)
Update the transient terms in the Jacobian by using the transient mask.
Definition MultiJac.cpp:21
void incrementAge()
Increment the Jacobian age.
Definition MultiJac.h:63
int age() const
Number of times 'incrementAge' has been called since the last evaluation.
Definition MultiJac.h:58
double m_dampFactor
Factor by which the damping coefficient is reduced in each iteration.
void resize(size_t points)
Change the problem size.
vector< double > m_x
Work array holding the system state after the last successful step. Size m_n.
void step(double *x, double *step, OneDim &r, MultiJac &jac, int loglevel)
Compute the undamped Newton step.
double norm2(const double *x, const double *step, OneDim &r) const
Compute the weighted 2-norm of step.
double m_elapsed
Elapsed CPU time spent computing the Jacobian.
vector< double > m_stp1
Work array holding the damped Newton step. Size m_n.
vector< double > m_stp
Work array holding the undamped Newton step or the system residual. Size m_n.
size_t m_maxDampIter
Maximum number of damping iterations.
int dampStep(const double *x0, const double *step0, double *x1, double *step1, double &s1, OneDim &r, MultiJac &jac, int loglevel, bool writetitle)
Performs a damped Newton step to solve the system of nonlinear equations.
int m_maxAge
Maximum allowable Jacobian age before it is recomputed.
MultiNewton(int sz)
Constructor.
int solve(double *x0, double *x1, OneDim &r, MultiJac &jac, int loglevel)
Find the solution to F(x) = 0 by damped Newton iteration.
double boundStep(const double *x0, const double *step0, const OneDim &r, int loglevel)
Return the factor by which the undamped Newton step 'step0' must be multiplied in order to keep all s...
size_t m_n
number of variables
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:96
size_t size() const
Total solution vector length;.
Definition OneDim.h:107
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:241
double ssnorm(double *x, double *r)
Steady-state max norm (infinity norm) of the residual evaluated using solution x.
Definition OneDim.cpp:273
vector< int > & transientMask()
Access the vector indicating which equations contain a transient term.
Definition OneDim.h:210
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:154
void writelog(const string &fmt, const Args &... args)
Write a formatted message to the screen.
Definition global.h:171
Namespace for the Cantera kernel.
Definition AnyMap.cpp:595
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 Flow1D.h:24
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...