https://mooseframework.inl.gov
Loading...
Searching...
No Matches
NewtonInversion.h
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
10// * This file is part of the MOOSE framework
11// * https://mooseframework.inl.gov
12// *
13// * All rights reserved, see COPYRIGHT for full restrictions
14// * https://github.com/idaholab/moose/blob/master/COPYRIGHT
15// *
16// * Licensed under LGPL 2.1, please see LICENSE for details
17// * https://www.gnu.org/licenses/lgpl-2.1.html
18
19#pragma once
20
21#include "Moose.h"
22#include "MooseUtils.h"
23#include "libmesh/dense_matrix.h"
24
26{
42template <typename T, typename Functor>
43std::pair<T, T>
44NewtonSolve(const T & x,
45 const T & y,
46 const Real z_initial_guess,
47 const Real tolerance,
48 const Functor & y_from_x_z,
49 const std::string & caller_name,
50 const unsigned int max_its = 100,
51 const bool verbose = false)
52{
53 // R represents residual
54
55 std::function<bool(const T &, const T &)> abs_tol_check =
56 [tolerance](const T & R, const T & /*y*/)
57 { return std::abs(MetaPhysicL::raw_value(R)) < tolerance; };
58 std::function<bool(const T &, const T &)> rel_tol_check = [tolerance](const T & R, const T & y)
59 { return std::abs(MetaPhysicL::raw_value(R / y)) < tolerance; };
60 auto convergence_check = MooseUtils::absoluteFuzzyEqual(MetaPhysicL::raw_value(y), 0, tolerance)
61 ? abs_tol_check
62 : rel_tol_check;
63
64 T z = z_initial_guess, R, new_y, dy_dx, dy_dz;
65 unsigned int iteration = 0;
66
67 using std::isnan;
68 if (verbose)
69 Moose::out << "Target value for 1D Newton inversion:\n" << y << std::endl;
70
71 do
72 {
73 y_from_x_z(x, z, new_y, dy_dx, dy_dz);
74 R = new_y - y;
75
76 // We always want to perform at least one update in order to get derivatives on z correct (z
77 // corresponding to the initial guess will have no derivative information), so we don't
78 // immediately return if we are converged
79 const bool converged = convergence_check(R, y);
80
81#ifndef NDEBUG
82 static constexpr Real perturbation_factor = 1 + 1e-8;
83 T perturbed_y, dummy, dummy2;
84 y_from_x_z(x, perturbation_factor * z, perturbed_y, dummy, dummy2);
85 // Check the accuracy of the Jacobian
86 auto J_differenced = (perturbed_y - new_y) / (1e-8 * z);
87 if (!MooseUtils::relativeFuzzyEqual(J_differenced, dy_dz, 1e-2))
88 mooseDoOnce(mooseWarning(caller_name + ": Bad Jacobian in NewtonSolve"));
89#endif
90
91 z += -(R / dy_dz);
92
93 if (verbose)
94 {
95 Moose::out << "Iteration " << iteration << std::endl;
96 Moose::out << "Current solution vector: " << z << std::endl;
97 Moose::out << "Current (minus) residual: " << -R << std::endl;
98 Moose::out << "Current Jacobian: " << dy_dz << std::endl;
99 }
100
101 // Check for NaNs
102 if (isnan(z))
103 mooseException(caller_name + ": NaN detected in Newton solve");
104
105 if (converged)
106 break;
107 } while (++iteration < max_its);
108
109 // Check for divergence or slow convergence of Newton's method
110 if (iteration >= max_its)
111 mooseException(caller_name +
112 ": Newton solve convergence failed: maximum number of iterations, ",
113 max_its,
114 ", exceeded");
115
116 // z was updated, we need to recompute the derivative
117 y_from_x_z(x, z, new_y, dy_dx, dy_dz);
118
119 return {z, dy_dz};
120}
121
142template <typename T, typename Functor1, typename Functor2>
143void
145 const T & g,
146 const Real x0,
147 const Real y0,
148 T & x_final,
149 T & y_final,
150 const Real f_tol,
151 const Real g_tol,
152 const Functor1 & f_from_x_y,
153 const Functor2 & g_from_x_y,
154 const std::string & caller_name = "",
155 const unsigned int max_its = 100,
156 bool debug = false)
157{
158
159 constexpr unsigned int system_size = 2;
160 DenseVector<T> targets = {{f, g}};
161 DenseVector<Real> tolerances = {{f_tol, g_tol}};
162 // R represents a residual equal to y - y_in
163 auto convergence_check = [&targets, &tolerances](const auto & minus_R)
164 {
165 using std::abs;
166
167 for (const auto i : index_range(minus_R))
168 {
169 const auto error = abs(MooseUtils::absoluteFuzzyEqual(targets(i), 0, tolerances(i))
170 ? minus_R(i)
171 : minus_R(i) / targets(i));
172 if (error >= tolerances(i))
173 return false;
174 }
175 return true;
176 };
177
178 DenseVector<T> u = {{x0, y0}};
179 DenseVector<T> minus_R(system_size), func_evals(system_size), u_update(system_size);
180 DenseMatrix<T> J(system_size, system_size);
181 unsigned int iteration = 0;
182#ifndef NDEBUG
183 DenseVector<Real> svs(system_size), evs_real(system_size), evs_imag(system_size);
184 DenseMatrix<Real> raw_J(system_size, system_size), raw_J2(system_size, system_size);
185#endif
186
187 typedef std::function<void(const T &, const T &, T &, T &, T &)> FuncType;
188 std::array<FuncType, 2> func = {{f_from_x_y, g_from_x_y}};
189
190 auto assign_solution = [&u, &x_final, &y_final]()
191 {
192 x_final = u(0);
193 y_final = u(1);
194 };
195 auto status_string = [&u, &func_evals, &targets, &minus_R](unsigned int comp) -> std::stringstream
196 {
197 std::stringstream ss;
198 ss << "Current solution for component " << comp << ": " << u(comp)
199 << " (current ordinate: " << func_evals(comp) << " -> target: " << targets(comp)
200 << ", scaled residual: " << minus_R(comp) << ")";
201 return ss;
202 };
203 if (debug)
204 Moose::out << "Target values for 2D Newton inversion:\n" << targets << std::endl;
205
206 using std::isnan, std::max, std::abs;
207
208 do
209 {
210 for (const auto i : make_range(system_size))
211 func[i](u(0), u(1), func_evals(i), J(i, 0), J(i, 1));
212
213 for (const auto i : make_range(system_size))
214 minus_R(i) = targets(i) - func_evals(i);
215
216 // We always want to perform at least one update in order to get derivatives on z correct (z
217 // corresponding to the initial guess will have no derivative information), so we don't
218 // immediately return if we are converged
219 const bool converged = convergence_check(minus_R);
220
221 // Check for NaNs before proceeding to system solve. We may simultaneously not have NaNs in z
222 // but have NaNs in the function evaluation
223 for (const auto i : make_range(system_size))
224 if (isnan(minus_R(i)))
225 {
226 assign_solution();
227 mooseException(caller_name + ": NaN detected in Newton solve");
228 }
229
230 if (debug)
231 {
232 Moose::out << "Iteration " << iteration << std::endl;
233 Moose::out << "Current solution vector:\n" << u << std::endl;
234 Moose::out << "Current (minus) residual:\n" << minus_R << std::endl;
235 Moose::out << "Current Jacobian:\n" << J << std::endl;
236 }
237
238 // Do some Jacobi (rowmax) preconditioning and check for an empty row
239 int degenerate_row = -1;
240 for (const auto i : make_range(system_size))
241 {
242 const auto rowmax = max(abs(J(i, 0)), abs(J(i, 1)));
243 if (rowmax > 0)
244 {
245 for (const auto j : make_range(system_size))
246 J(i, j) /= rowmax;
247 minus_R(i) /= rowmax;
248 }
249 else
250 {
251 if (degenerate_row != -1)
252 mooseException(caller_name + ": Jacobian is all zeros in NewtonSolve2D");
253 degenerate_row = i;
254 }
255 }
256
257#ifndef NDEBUG
258 //
259 // Check nature of linearized system
260 //
261 for (const auto i : make_range(system_size))
262 for (const auto j : make_range(system_size))
263 {
264 raw_J(i, j) = MetaPhysicL::raw_value(J(i, j));
265 raw_J2(i, j) = MetaPhysicL::raw_value(J(i, j));
266 }
267 raw_J.svd(svs);
268 raw_J2.evd(evs_real, evs_imag);
269 if (debug)
270 Moose::out << "Jacobian singular values:\n" << svs << std::endl;
271#endif
272
273 if (degenerate_row == -1)
274 J.lu_solve(minus_R, u_update);
275 else
276 {
277 // use a 1D newton when the Jacobian has an empty row
278 const auto other_row = system_size - 1 - degenerate_row;
279 u_update(other_row) = minus_R(other_row) / J(other_row, other_row);
280 u_update(degenerate_row) = 0;
281 }
282 // reset the decomposition
283 J.zero();
284 u += u_update;
285
286 // Check for NaNs
287 for (const auto i : make_range(system_size))
288 if (isnan(u(i)))
289 {
290 assign_solution();
291 mooseException(caller_name + ": NaN detected in NewtonSolve2D\n" + status_string(0).str() +
292 "\n" + status_string(1).str());
293 }
294
295 if (converged)
296 break;
297 } while (++iteration < max_its);
298
299 assign_solution();
300
301 // Check for divergence or slow convergence of Newton's method
302 if (iteration >= max_its)
303 mooseException(caller_name +
304 ": Newton solve convergence failed: maximum number of iterations, ",
305 max_its,
306 ", exceeded.\n" + status_string(0).str() + "\n" + status_string(1).str());
307}
308} // namespace FluidPropertiesUtils
Real f(Real x)
Test function for Brents method.
const std::vector< double > y
const std::vector< double > x
const double R
const double T
void mooseWarning(Args &&... args)
std::pair< T, T > NewtonSolve(const T &x, const T &y, const Real z_initial_guess, const Real tolerance, const Functor &y_from_x_z, const std::string &caller_name, const unsigned int max_its=100, const bool verbose=false)
NewtonSolve does a 1D Newton Solve to solve the equation y = f(x, z) for variable z.
void NewtonSolve2D(const T &f, const T &g, const Real x0, const Real y0, T &x_final, T &y_final, const Real f_tol, const Real g_tol, const Functor1 &f_from_x_y, const Functor2 &g_from_x_y, const std::string &caller_name="", const unsigned int max_its=100, bool debug=false)
NewtonSolve2D does a 2D Newton Solve to solve for the x and y such that: f = f_from_x_y(x,...
auto raw_value(const Eigen::Map< T > &in)