Cantera  4.0.0a2
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
17 : m_n(sz)
18{
19}
20
21void MultiNewton::resize(size_t sz)
22{
23 m_n = sz;
24 m_x.resize(m_n);
25 m_stp.resize(m_n);
26 m_stp1.resize(m_n);
27}
28
29void MultiNewton::step(span<const double> x, span<double> step,
30 SteadyStateSystem& r, int loglevel)
31{
32 r.eval(x, step);
33 for (size_t n = 0; n < r.size(); n++) {
34 step[n] = -step[n];
35 }
36
37 auto jac = r.linearSolver();
38 try {
39 jac->solve(step, step);
40 } catch (CanteraError&) {
41 if (jac->info() > 0) {
42 // Positive value for "info" indicates the row where factorization failed
43 size_t row = static_cast<size_t>(jac->info() - 1);
44 throw CanteraError("MultiNewton::step",
45 "Jacobian is singular for matrix row {}:\n{}",
46 row, r.componentName(row));
47 }
48 throw;
49 }
50}
51
52double MultiNewton::boundStep(span<const double> x0, span<const double> step0,
53 const SteadyStateSystem& r, int loglevel)
54{
55 const static string separator = fmt::format("\n {:=>71}", ""); // equals sign separator
56 double fbound = 1.0;
57 bool wroteTitle = false;
58
59 for (size_t i = 0; i < size(); i++) {
60 double above = r.upperBound(i);
61 double below = r.lowerBound(i);
62
63 double val = x0[i];
64 if (loglevel > 0 && (val > above + 1.0e-12 || val < below - 1.0e-12)) {
65 writelog("\nERROR: solution component {} out of bounds.\n", i);
66 writelog("{}: value = {:10.3e} (lower = {:10.3e}, upper = {:10.3e})\n",
67 r.componentName(i), val, below, above);
68 }
69
70 double newval = val + step0[i];
71
72 if (newval > above) {
73 fbound = std::max(0.0, std::min(fbound,
74 (above - val)/(newval - val)));
75 } else if (newval < below) {
76 fbound = std::min(fbound, (val - below)/(val - newval));
77 }
78
79 if (loglevel > 1 && (newval > above || newval < below)) {
80 if (!wroteTitle){
81 string header = fmt::format(" {:=>10}", "") +
82 " Undamped Newton step takes solution out of bounds " +
83 fmt::format("{:=>10}", "");
84 writelog("\n{}", header);
85 writelog(separator);
86 const auto& [custom1, custom2] = r.componentTableHeader();
87 // Split header across 2 lines to shorten the line length
88 writelog("\n {:<24s} {:<10s} {:<10s} {:<9s} {:<9s}",
89 custom1, "", "Value", "Min", "Max");
90 writelog("\n {:<24s} {:<10s} {:<10s} {:<9s} {:<9s}",
91 custom2, "Value", "Change", "Bound", "Bound");
92 writelog(separator);
93 wroteTitle = true;
94 }
95 string comp_info = r.componentTableLabel(i);
96 writelog("\n {:<24s} {:>10.3e} {:>10.3e} {:>9.2e} {:>9.2e}",
97 comp_info, val, step0[i], below, above);
98 }
99 }
100 if (loglevel > 1 && wroteTitle) { // If a title was written, close up the table
101 writelog(separator);
102 }
103 return fbound;
104}
105
106int MultiNewton::dampStep(span<const double> x0, span<const double> step0,
107 span<double> x1, span<double> step1, double& s1,
108 SteadyStateSystem& r, int loglevel, bool writetitle)
109{
110 // write header
111 if (loglevel > 0 && writetitle) {
112 writelog("\n\n {:-^70}", " Damped Newton iteration ");
113 writelog("\n {:<4s} {:<10s} {:<10s} {:<7s} {:<7s} {:<7s} {:<5s} {:<3s}\n",
114 "Iter", "F_damp", "F_bound", "log(ss)",
115 "log(s0)", "log(s1)", "N_jac", "Age");
116 writelog(" {:->70}", "");
117 }
118
119 // compute the weighted norm of the undamped step size step0
120 double s0 = r.weightedNorm(step0);
121
122 // compute the multiplier to keep all components in bounds
123 double fbound = boundStep(x0, step0, r, loglevel-1);
124
125 // if fbound is very small, then x0 is already close to the boundary and
126 // step0 points out of the allowed domain. In this case, the Newton
127 // algorithm fails, so return an error condition.
128 if (fbound < 1.e-10) {
129 debuglog("\n No damped step can be taken without violating solution component bounds.", loglevel);
130 return -3;
131 }
132
133 // ---------- Attempt damped step ----------
134
135 // damping coefficient starts at 1.0, but must be scaled by the
136 // fbound factor to ensure that the solution remains within bounds.
137 double alpha = fbound*1.0;
138 size_t m;
139 auto jac = r.linearSolver();
140 for (m = 0; m < m_maxDampIter; m++) {
141 // step the solution by the damped step size
142 // x_{k+1} = x_k + alpha_k*J(x_k)^-1 F(x_k)
143 for (size_t j = 0; j < m_n; j++) {
144 x1[j] = x0[j] + alpha*step0[j];
145 }
146
147 // compute the next undamped step that would result if x1 is accepted
148 // J(x_k)^-1 F(x_k+1)
149 try {
150 // Allow solver to continue after step failure (which can involve problems
151 // such as errors setting the thermodynamic state) by returning to time
152 // stepping mode.
153 step(x1, step1, r, loglevel-1);
154 } catch (CanteraError& err) {
155 if (loglevel > 1) {
156 writelog("\n Reducing step size due to evaluation failure on trial step:"
157 "\n {}:\n {}", err.getMethod(), err.getMessage());
158 }
159 alpha /= m_dampFactor;
160 continue;
161 }
162
163 // compute the weighted norm of step1
164 s1 = r.weightedNorm(step1);
165
166 if (loglevel > 0) {
167 double ss = r.ssnorm(x1, step1);
168 writelog("\n {:<4d} {:<9.3e} {:<9.3e} {:>6.3f} {:>6.3f} {:>6.3f} {:<5d} {:d}/{:d}",
169 m, alpha, fbound, log10(ss+SmallNumber),
170 log10(s0+SmallNumber), log10(s1+SmallNumber),
171 jac->nEvals(), jac->age(), m_maxAge);
172 }
173
174 // If the norm of s1 is less than the norm of s0, then accept this
175 // damping coefficient. Also accept it if this step would result in a
176 // converged solution. Otherwise, decrease the damping coefficient and
177 // try again.
178 if (s1 < 1.0 || s1 < s0) {
179 break;
180 }
181 alpha /= m_dampFactor;
182 }
183
184 // If a damping coefficient was found, return 1 if the solution after
185 // stepping by the damped step would represent a converged solution, and
186 // return 0 otherwise. If no damping coefficient could be found, return -2.
187 if (m < m_maxDampIter) {
188 if (s1 > 1.0) {
189 debuglog("\n Damping coefficient found (solution has not converged yet)", loglevel);
190 return 0;
191 } else {
192 debuglog("\n Damping coefficient found (solution has converged)", loglevel);
193 return 1;
194 }
195 } else {
196 debuglog("\n No damping coefficient found (max damping iterations reached)", loglevel);
197 return -2;
198 }
199}
200
201int MultiNewton::solve(span<const double> x0, span<double> x1,
202 SteadyStateSystem& r, int loglevel)
203{
204 clock_t t0 = clock();
205 int status = 0;
206 bool forceNewJac = false;
207 bool write_header = true;
208 double s1=1.e30;
209
210 copy(x0.begin(), x0.end(), m_x.begin());
212
213 double rdt = r.rdt();
214 int nJacReeval = 0;
215 auto jac = r.linearSolver();
216 while (true) {
218 // Check whether the Jacobian should be re-evaluated.
219 if (jac->age() > m_maxAge) {
220 if (loglevel > 1) {
221 writelog("\n Maximum Jacobian age reached ({}), updating it.", m_maxAge);
222 }
223 forceNewJac = true;
224 }
225
226 if (forceNewJac) {
227 r.evalJacobian(m_x);
228 try {
229 jac->updateTransient(rdt, r.transientMask());
230 } catch (CanteraError& err) {
231 // Allow solver to continue after failure to factorize the steady-state
232 // Jacobian by returning to time stepping mode
233 if (rdt == 0.0) {
234 if (loglevel > 1) {
235 writelog("\n Steady Jacobian factorization failed:"
236 "\n {}:\n {}",
237 err.getMethod(), err.getMessage());
238 }
239 return -4;
240 } else {
241 throw;
242 }
243 }
244 forceNewJac = false;
245 }
246
247 // compute the undamped Newton step
248 step(m_x, m_stp, r, loglevel-1);
249
250 // increment the Jacobian age
251 jac->incrementAge();
252
253 // damp the Newton step
254 status = dampStep(m_x, m_stp, x1, m_stp1, s1, r, loglevel-1, write_header);
255 write_header = false;
256
257 // Successful step, but not converged yet. Take the damped step, and try
258 // again.
259 if (status == 0) {
260 copy(x1.begin(), x1.end(), m_x.begin());
261 } else if (status == 1) { // convergence
262 if (rdt == 0) {
263 jac->setAge(0); // for efficient sensitivity analysis
264 }
265 break;
266 } else if (status < 0) {
267 // If dampStep fails, first try a new Jacobian if an old one was
268 // being used. If it was a new Jacobian, then return -1 to signify
269 // failure.
270 if (jac->age() > 1) {
271 forceNewJac = true;
272 if (nJacReeval > 3) {
273 break;
274 }
275 nJacReeval++;
276 if (loglevel > 1) {
277 writelog("\n Re-evaluating Jacobian (damping coefficient not found"
278 " with this Jacobian)");
279 }
280 } else {
281 break;
282 }
283 }
284 }
285 // Close off the damped iteration table that is written by the dampedStep() method
286 if (loglevel > 1) {
287 writelog("\n {:->70}", "");
288 }
289
290 if (status < 0) { // Reset x1 to x0 if the solution failed
291 copy(m_x.begin(), m_x.end(), x1.begin());
292 }
293 m_elapsed += (clock() - t0)/(1.0*CLOCKS_PER_SEC);
294 return status;
295}
296
297} // end namespace Cantera
Base class for exceptions thrown by Cantera classes.
virtual string getMessage() const
Method overridden by derived classes to format the error message.
virtual string getMethod() const
Get the name of the method that threw the exception.
double m_dampFactor
Factor by which the damping coefficient is reduced in each iteration.
size_t size()
Get the number of variables in the system.
Definition MultiNewton.h:34
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.
int dampStep(span< const double > x0, span< const double > step0, span< double > x1, span< double > step1, double &s1, SteadyStateSystem &r, int loglevel, bool writetitle)
Performs a damped Newton step to solve the system of nonlinear equations.
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 m_maxAge
Maximum allowable Jacobian age before it is recomputed.
MultiNewton(int sz)
Constructor.
double boundStep(span< const double > x0, span< const double > step0, const SteadyStateSystem &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
void step(span< const double > x, span< double > step, SteadyStateSystem &r, int loglevel)
Compute the undamped Newton step.
int solve(span< const double > x0, span< double > x1, SteadyStateSystem &r, int loglevel)
Find the solution to F(x) = 0 by damped Newton iteration.
int m_lastIterations
Number of Newton iterations taken in the last solve() call.
Base class for representing a system of differential-algebraic equations and solving for its steady-s...
shared_ptr< SystemJacobian > linearSolver() const
Get the the linear solver being used to hold the Jacobian matrix and solve linear systems as part of ...
size_t size() const
Total solution vector length;.
vector< int > & transientMask()
Access the vector indicating which equations contain a transient term.
virtual double upperBound(size_t i) const =0
Get the upper bound for global component i in the state vector.
double rdt() const
Reciprocal of the time step.
virtual string componentName(size_t i) const
Get the name of the i-th component of the state vector.
virtual string componentTableLabel(size_t i) const
Get elements of the component name, aligned with the column headings given by componentTableHeader().
virtual double weightedNorm(span< const double > step) const =0
Compute the weighted norm of step.
virtual void eval(span< const double > x, span< double > r, double rdt=-1.0, int count=1)=0
Evaluate the residual function.
virtual double lowerBound(size_t i) const =0
Get the lower bound for global component i in the state vector.
double ssnorm(span< const double > x, span< double > r)
Steady-state max norm (infinity norm) of the residual evaluated using solution x.
virtual void evalJacobian(span< const double > x0)=0
Evaluates the Jacobian at x0 using finite differences.
virtual pair< string, string > componentTableHeader() const
Get header lines describing the column names included in a component label.
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 double SmallNumber
smallest number to compare to zero.
Definition ct_defs.h:161
Various templated functions that carry out common vector and polynomial operations (see Templated Arr...