LCOV - code coverage report
Current view: top level - include/utils - NewtonInversion.h (source / functions) Hit Total Coverage
Test: idaholab/moose fluid_properties: #31405 (292dce) with base fef103 Lines: 55 60 91.7 %
Date: 2025-09-04 07:53:14 Functions: 43 47 91.5 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      25             : namespace FluidPropertiesUtils
      26             : {
      27             : /**
      28             :  * NewtonSolve does a 1D Newton Solve to solve the equation y = f(x, z) for variable z.
      29             :  * @param[in] x constant first argument of the f(x, z) term
      30             :  * @param[in] y constant which should be equal to f(x, z) with a converged z
      31             :  * @param[in] z_initial_guess initial guess for return variables
      32             :  * @param[in] tolerance criterion for relative or absolute (if y is sufficiently close to zero)
      33             :  * convergence checking
      34             :  * @param[in] function two-variable function returning both values and derivatives as references
      35             :  * @param[in] caller_name name of the fluid properties appended to name of the routine calling the
      36             :  * method
      37             :  * @param[in] max_its the maximum number of iterations for Newton's method
      38             :  * @return a pair in which the first member is the value z such that f(x, z) = y and the second
      39             :  * member is dy/dz
      40             :  */
      41             : template <typename T, typename Functor>
      42             : std::pair<T, T>
      43         197 : 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           0 :       [tolerance](const T & R, const T & /*y*/)
      55           9 :   { return MetaPhysicL::raw_value(std::abs(R)) < tolerance; };
      56         287 :   std::function<bool(const T &, const T &)> rel_tol_check = [tolerance](const T & R, const T & y)
      57         288 :   { return MetaPhysicL::raw_value(std::abs(R / y)) < tolerance; };
      58         393 :   auto convergence_check = MooseUtils::absoluteFuzzyEqual(MetaPhysicL::raw_value(y), 0, tolerance)
      59             :                                ? abs_tol_check
      60             :                                : rel_tol_check;
      61             : 
      62          75 :   T z = z_initial_guess, R, new_y, dy_dx, dy_dz;
      63             :   unsigned int iteration = 0;
      64             : 
      65             :   do
      66             :   {
      67         266 :     func(x, z, new_y, dy_dx, dy_dz);
      68         606 :     R = new_y - y;
      69             : 
      70             :     // We always want to perform at least one update in order to get derivatives on z correct (z
      71             :     // corresponding to the initial guess will have no derivative information), so we don't
      72             :     // immediately return if we are converged
      73         606 :     const bool converged = convergence_check(R, y);
      74             : 
      75             : #ifndef NDEBUG
      76             :     static constexpr Real perturbation_factor = 1 + 1e-8;
      77             :     T perturbed_y, dummy, dummy2;
      78             :     func(x, perturbation_factor * z, perturbed_y, dummy, dummy2);
      79             :     // Check the accuracy of the Jacobian
      80             :     auto J_differenced = (perturbed_y - new_y) / (1e-8 * z);
      81             :     if (!MooseUtils::relativeFuzzyEqual(J_differenced, dy_dz, 1e-2))
      82             :       mooseDoOnce(mooseWarning(caller_name + ": Bad Jacobian in NewtonSolve"));
      83             : #endif
      84             : 
      85         606 :     z += -(R / dy_dz);
      86             : 
      87             :     // Check for NaNs
      88         606 :     if (std::isnan(z))
      89           0 :       mooseException(caller_name + ": NaN detected in Newton solve");
      90             : 
      91         606 :     if (converged)
      92             :       break;
      93         409 :   } while (++iteration < max_its);
      94             : 
      95             :   // Check for divergence or slow convergence of Newton's method
      96         197 :   if (iteration >= max_its)
      97           0 :     mooseException(caller_name +
      98             :                        ": Newton solve convergence failed: maximum number of iterations, ",
      99             :                    max_its,
     100             :                    ", exceeded");
     101             : 
     102         314 :   return {z, dy_dz};
     103             : }
     104             : 
     105             : /**
     106             :  * NewtonSolve2D does a 2D Newton Solve to solve for the x and y such that:
     107             :  * f = func1(x, y) and g = func2(x, y). This is done for example in the constant of (v, e)
     108             :  * to (p, T) variable set conversion.
     109             :  * @param[in] f target value for func1
     110             :  * @param[in] g target value for func2
     111             :  * @param[in] x0 initial guess for first output variable
     112             :  * @param[in] y0 initial guess for second output variable
     113             :  * @param[out] x_final output for first variable
     114             :  * @param[out] y_final output for second variable
     115             :  * @param[in] f_tol criterion for relative or absolute (if f is sufficently close to zero)
     116             :  * convergence checking
     117             :  * @param[in] g_tol criterion for relative or absolute (if g is sufficently close to zero)
     118             :  * convergence checking
     119             :  * @param[in] func1 two-variable function returning both values and derivatives as references
     120             :  * @param[in] func2 two-variable function returning both values and derivatives as references
     121             :  * @param[in] max_its the maximum number of iterations for Newton's method
     122             :  */
     123             : template <typename T, typename Functor1, typename Functor2>
     124             : void
     125      762478 : NewtonSolve2D(const T & f,
     126             :               const T & g,
     127             :               const Real x0,
     128             :               const Real y0,
     129             :               T & x_final,
     130             :               T & y_final,
     131             :               const Real f_tol,
     132             :               const Real g_tol,
     133             :               const Functor1 & func1,
     134             :               const Functor2 & func2,
     135             :               const unsigned int max_its = 100)
     136             : {
     137             : 
     138             :   constexpr unsigned int system_size = 2;
     139      762478 :   DenseVector<T> targets = {{f, g}};
     140      937001 :   DenseVector<Real> tolerances = {{f_tol, g_tol}};
     141             :   // R represents a residual equal to y - y_in
     142    20643455 :   auto convergence_check = [&targets, &tolerances](const auto & minus_R)
     143             :   {
     144    21413505 :     for (const auto i : index_range(minus_R))
     145             :     {
     146    20825571 :       const auto error = std::abs(MooseUtils::absoluteFuzzyEqual(targets(i), 0, tolerances(i))
     147          22 :                                       ? minus_R(i)
     148    20825527 :                                       : minus_R(i) / targets(i));
     149    20825549 :       if (error >= tolerances(i))
     150             :         return false;
     151             :     }
     152             :     return true;
     153             :   };
     154             : 
     155      937001 :   DenseVector<T> u = {{x0, y0}};
     156      762478 :   DenseVector<T> minus_R(system_size), func_evals(system_size), u_update;
     157      762478 :   DenseMatrix<T> J(system_size, system_size);
     158             :   unsigned int iteration = 0;
     159             : #ifndef NDEBUG
     160             :   DenseVector<Real> svs(system_size), evs_real(system_size), evs_imag(system_size);
     161             :   DenseMatrix<Real> raw_J(system_size, system_size), raw_J2(system_size, system_size);
     162             : #endif
     163             : 
     164             :   typedef std::function<void(const T &, const T &, T &, T &, T &)> FuncType;
     165             :   std::array<FuncType, 2> func = {{func1, func2}};
     166             : 
     167          12 :   auto assign_solution = [&u, &x_final, &y_final]()
     168             :   {
     169      762478 :     x_final = u(0);
     170      762478 :     y_final = u(1);
     171             :   };
     172             : 
     173             :   do
     174             :   {
     175    59642931 :     for (const auto i : make_range(system_size))
     176    39761954 :       func[i](u(0), u(1), func_evals(i), J(i, 0), J(i, 1));
     177             : 
     178    59642931 :     for (const auto i : make_range(system_size))
     179    39761954 :       minus_R(i) = targets(i) - func_evals(i);
     180             : 
     181             :     // We always want to perform at least one update in order to get derivatives on z correct (z
     182             :     // corresponding to the initial guess will have no derivative information), so we don't
     183             :     // immediately return if we are converged
     184    19880977 :     const bool converged = convergence_check(minus_R);
     185             : 
     186             :     // Check for NaNs before proceeding to system solve. We may simultaneously not have NaNs in z
     187             :     // but have NaNs in the function evaluation
     188    59642882 :     for (const auto i : make_range(system_size))
     189    39761930 :       if (std::isnan(minus_R(i)))
     190             :       {
     191           0 :         assign_solution();
     192          25 :         mooseException("NaN detected in Newton solve");
     193             :       }
     194             : 
     195             :     // Do some Jacobi (rowmax) preconditioning
     196    59642856 :     for (const auto i : make_range(system_size))
     197             :     {
     198    39761904 :       const auto rowmax = std::max(std::abs(J(i, 0)), std::abs(J(i, 1)));
     199   119285712 :       for (const auto j : make_range(system_size))
     200    79523808 :         J(i, j) /= rowmax;
     201    39761904 :       minus_R(i) /= rowmax;
     202             :     }
     203             : 
     204             : #ifndef NDEBUG
     205             :     //
     206             :     // Check nature of linearized system
     207             :     //
     208             :     for (const auto i : make_range(system_size))
     209             :       for (const auto j : make_range(system_size))
     210             :       {
     211             :         raw_J(i, j) = MetaPhysicL::raw_value(J(i, j));
     212             :         raw_J2(i, j) = MetaPhysicL::raw_value(J(i, j));
     213             :       }
     214             :     raw_J.svd(svs);
     215             :     raw_J2.evd(evs_real, evs_imag);
     216             : #endif
     217             : 
     218    19880952 :     J.lu_solve(minus_R, u_update);
     219             :     // reset the decomposition
     220          15 :     J.zero();
     221    19880952 :     u += u_update;
     222             : 
     223             :     // Check for NaNs
     224    59642664 :     for (const auto i : make_range(system_size))
     225    39761808 :       if (std::isnan(u(i)))
     226             :       {
     227           0 :         assign_solution();
     228          96 :         mooseException("NaN detected in Newton solve");
     229             :       }
     230             : 
     231    19880856 :     if (converged)
     232             :       break;
     233    19292901 :   } while (++iteration < max_its);
     234             : 
     235           4 :   assign_solution();
     236             : 
     237             :   // Check for divergence or slow convergence of Newton's method
     238      762357 :   if (iteration >= max_its)
     239      174402 :     mooseException(
     240             :         "Newton solve convergence failed: maximum number of iterations, ", max_its, ", exceeded");
     241     1350437 : }
     242             : } // namespace FluidPropertiesUtils

Generated by: LCOV version 1.14