Cantera  4.0.0a1
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());
211
212 double rdt = r.rdt();
213 int nJacReeval = 0;
214 auto jac = r.linearSolver();
215 while (true) {
216 // Check whether the Jacobian should be re-evaluated.
217 if (jac->age() > m_maxAge) {
218 if (loglevel > 1) {
219 writelog("\n Maximum Jacobian age reached ({}), updating it.", m_maxAge);
220 }
221 forceNewJac = true;
222 }
223
224 if (forceNewJac) {
225 r.evalJacobian(m_x);
226 try {
227 jac->updateTransient(rdt, r.transientMask());
228 } catch (CanteraError& err) {
229 // Allow solver to continue after failure to factorize the steady-state
230 // Jacobian by returning to time stepping mode
231 if (rdt == 0.0) {
232 if (loglevel > 1) {
233 writelog("\n Steady Jacobian factorization failed:"
234 "\n {}:\n {}",
235 err.getMethod(), err.getMessage());
236 }
237 return -4;
238 } else {
239 throw;
240 }
241 }
242 forceNewJac = false;
243 }
244
245 // compute the undamped Newton step
246 step(m_x, m_stp, r, loglevel-1);
247
248 // increment the Jacobian age
249 jac->incrementAge();
250
251 // damp the Newton step
252 status = dampStep(m_x, m_stp, x1, m_stp1, s1, r, loglevel-1, write_header);
253 write_header = false;
254
255 // Successful step, but not converged yet. Take the damped step, and try
256 // again.
257 if (status == 0) {
258 copy(x1.begin(), x1.end(), m_x.begin());
259 } else if (status == 1) { // convergence
260 if (rdt == 0) {
261 jac->setAge(0); // for efficient sensitivity analysis
262 }
263 break;
264 } else if (status < 0) {
265 // If dampStep fails, first try a new Jacobian if an old one was
266 // being used. If it was a new Jacobian, then return -1 to signify
267 // failure.
268 if (jac->age() > 1) {
269 forceNewJac = true;
270 if (nJacReeval > 3) {
271 break;
272 }
273 nJacReeval++;
274 if (loglevel > 1) {
275 writelog("\n Re-evaluating Jacobian (damping coefficient not found"
276 " with this Jacobian)");
277 }
278 } else {
279 break;
280 }
281 }
282 }
283 // Close off the damped iteration table that is written by the dampedStep() method
284 if (loglevel > 1) {
285 writelog("\n {:->70}", "");
286 }
287
288 if (status < 0) { // Reset x1 to x0 if the solution failed
289 copy(m_x.begin(), m_x.end(), x1.begin());
290 }
291 m_elapsed += (clock() - t0)/(1.0*CLOCKS_PER_SEC);
292 return status;
293}
294
295} // 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.
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...