https://mooseframework.inl.gov
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 {
42 template <typename T, typename Functor>
43 std::pair<T, T>
44 NewtonSolve(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 
142 template <typename T, typename Functor1, typename Functor2>
143 void
144 NewtonSolve2D(const T & f,
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
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
const double T
void mooseWarning(Args &&... args)
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, y) and g = g_from_x_y(x, y).
auto raw_value(const Eigen::Map< T > &in)
const std::vector< double > y
const double R
auto max(const L &left, const R &right)
bool converged(const std::vector< std::pair< unsigned int, Real >> &residuals, const std::vector< Real > &abs_tolerances)
Based on the residuals, determine if the iterative process converged or not.
FunctorEnvelope< T > Functor
const std::vector< double > x
Real f(Real x)
Test function for Brents method.
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...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void evd(DenseVector< Real > &lambda_real, DenseVector< Real > &lambda_imag)
IntRange< T > make_range(T beg, T end)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
auto index_range(const T &sizable)