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 {
41 template <typename T, typename Functor>
42 std::pair<T, T>
43 NewtonSolve(const T & x,
44  const T & y,
45  const Real z_initial_guess,
46  const Real tolerance,
47  const Functor & func,
48  const std::string & caller_name,
49  const unsigned int max_its = 100)
50 {
51  // R represents residual
52 
53  std::function<bool(const T &, const T &)> abs_tol_check =
54  [tolerance](const T & R, const T & /*y*/)
55  { return std::abs(MetaPhysicL::raw_value(R)) < tolerance; };
56  std::function<bool(const T &, const T &)> rel_tol_check = [tolerance](const T & R, const T & y)
57  { return std::abs(MetaPhysicL::raw_value(R / y)) < tolerance; };
58  auto convergence_check = MooseUtils::absoluteFuzzyEqual(MetaPhysicL::raw_value(y), 0, tolerance)
59  ? abs_tol_check
60  : rel_tol_check;
61 
62  T z = z_initial_guess, R, new_y, dy_dx, dy_dz;
63  unsigned int iteration = 0;
64 
65  using std::isnan;
66 
67  do
68  {
69  func(x, z, new_y, dy_dx, dy_dz);
70  R = new_y - y;
71 
72  // We always want to perform at least one update in order to get derivatives on z correct (z
73  // corresponding to the initial guess will have no derivative information), so we don't
74  // immediately return if we are converged
75  const bool converged = convergence_check(R, y);
76 
77 #ifndef NDEBUG
78  static constexpr Real perturbation_factor = 1 + 1e-8;
79  T perturbed_y, dummy, dummy2;
80  func(x, perturbation_factor * z, perturbed_y, dummy, dummy2);
81  // Check the accuracy of the Jacobian
82  auto J_differenced = (perturbed_y - new_y) / (1e-8 * z);
83  if (!MooseUtils::relativeFuzzyEqual(J_differenced, dy_dz, 1e-2))
84  mooseDoOnce(mooseWarning(caller_name + ": Bad Jacobian in NewtonSolve"));
85 #endif
86 
87  z += -(R / dy_dz);
88 
89  // Check for NaNs
90  if (isnan(z))
91  mooseException(caller_name + ": NaN detected in Newton solve");
92 
93  if (converged)
94  break;
95  } while (++iteration < max_its);
96 
97  // Check for divergence or slow convergence of Newton's method
98  if (iteration >= max_its)
99  mooseException(caller_name +
100  ": Newton solve convergence failed: maximum number of iterations, ",
101  max_its,
102  ", exceeded");
103 
104  return {z, dy_dz};
105 }
106 
125 template <typename T, typename Functor1, typename Functor2>
126 void
127 NewtonSolve2D(const T & f,
128  const T & g,
129  const Real x0,
130  const Real y0,
131  T & x_final,
132  T & y_final,
133  const Real f_tol,
134  const Real g_tol,
135  const Functor1 & func1,
136  const Functor2 & func2,
137  const unsigned int max_its = 100)
138 {
139 
140  constexpr unsigned int system_size = 2;
141  DenseVector<T> targets = {{f, g}};
142  DenseVector<Real> tolerances = {{f_tol, g_tol}};
143  // R represents a residual equal to y - y_in
144  auto convergence_check = [&targets, &tolerances](const auto & minus_R)
145  {
146  using std::abs;
147 
148  for (const auto i : index_range(minus_R))
149  {
150  const auto error = abs(MooseUtils::absoluteFuzzyEqual(targets(i), 0, tolerances(i))
151  ? minus_R(i)
152  : minus_R(i) / targets(i));
153  if (error >= tolerances(i))
154  return false;
155  }
156  return true;
157  };
158 
159  DenseVector<T> u = {{x0, y0}};
160  DenseVector<T> minus_R(system_size), func_evals(system_size), u_update;
161  DenseMatrix<T> J(system_size, system_size);
162  unsigned int iteration = 0;
163 #ifndef NDEBUG
164  DenseVector<Real> svs(system_size), evs_real(system_size), evs_imag(system_size);
165  DenseMatrix<Real> raw_J(system_size, system_size), raw_J2(system_size, system_size);
166 #endif
167 
168  typedef std::function<void(const T &, const T &, T &, T &, T &)> FuncType;
169  std::array<FuncType, 2> func = {{func1, func2}};
170 
171  auto assign_solution = [&u, &x_final, &y_final]()
172  {
173  x_final = u(0);
174  y_final = u(1);
175  };
176 
177  using std::isnan, std::max, std::abs;
178 
179  do
180  {
181  for (const auto i : make_range(system_size))
182  func[i](u(0), u(1), func_evals(i), J(i, 0), J(i, 1));
183 
184  for (const auto i : make_range(system_size))
185  minus_R(i) = targets(i) - func_evals(i);
186 
187  // We always want to perform at least one update in order to get derivatives on z correct (z
188  // corresponding to the initial guess will have no derivative information), so we don't
189  // immediately return if we are converged
190  const bool converged = convergence_check(minus_R);
191 
192  // Check for NaNs before proceeding to system solve. We may simultaneously not have NaNs in z
193  // but have NaNs in the function evaluation
194  for (const auto i : make_range(system_size))
195  if (isnan(minus_R(i)))
196  {
197  assign_solution();
198  mooseException("NaN detected in Newton solve");
199  }
200 
201  // Do some Jacobi (rowmax) preconditioning
202  for (const auto i : make_range(system_size))
203  {
204  const auto rowmax = max(abs(J(i, 0)), abs(J(i, 1)));
205  for (const auto j : make_range(system_size))
206  J(i, j) /= rowmax;
207  minus_R(i) /= rowmax;
208  }
209 
210 #ifndef NDEBUG
211  //
212  // Check nature of linearized system
213  //
214  for (const auto i : make_range(system_size))
215  for (const auto j : make_range(system_size))
216  {
217  raw_J(i, j) = MetaPhysicL::raw_value(J(i, j));
218  raw_J2(i, j) = MetaPhysicL::raw_value(J(i, j));
219  }
220  raw_J.svd(svs);
221  raw_J2.evd(evs_real, evs_imag);
222 #endif
223 
224  J.lu_solve(minus_R, u_update);
225  // reset the decomposition
226  J.zero();
227  u += u_update;
228 
229  // Check for NaNs
230  for (const auto i : make_range(system_size))
231  if (isnan(u(i)))
232  {
233  assign_solution();
234  mooseException("NaN detected in Newton solve");
235  }
236 
237  if (converged)
238  break;
239  } while (++iteration < max_its);
240 
241  assign_solution();
242 
243  // Check for divergence or slow convergence of Newton's method
244  if (iteration >= max_its)
245  mooseException(
246  "Newton solve convergence failed: maximum number of iterations, ", max_its, ", exceeded");
247 }
248 } // namespace FluidPropertiesUtils
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
void mooseWarning(Args &&... args)
auto raw_value(const Eigen::Map< T > &in)
const std::vector< double > y
auto max(const L &left, const R &right)
std::pair< T, T > NewtonSolve(const T &x, const T &y, const Real z_initial_guess, const Real tolerance, const Functor &func, const std::string &caller_name, const unsigned int max_its=100)
NewtonSolve does a 1D Newton Solve to solve the equation y = f(x, z) for variable z...
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.
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 &func1, const Functor2 &func2, const unsigned int max_its=100)
NewtonSolve2D does a 2D Newton Solve to solve for the x and y such that: f = func1(x, y) and g = func2(x, y).
FunctorEnvelope< T > Functor
const std::vector< double > x
Real f(Real x)
Test function for Brents method.
static const std::string R
Definition: NS.h:162
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)