LCOV - code coverage report
Current view: top level - include/utils - NewtonInversion.h (source / functions) Hit Total Coverage
Test: idaholab/moose fluid_properties: #31706 (f8ed4a) with base bb0a08 Lines: 55 59 93.2 %
Date: 2025-11-03 17:24:35 Functions: 42 42 100.0 %
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             :       [tolerance](const T & R, const T & /*y*/)
      55           9 :   { return std::abs(MetaPhysicL::raw_value(R)) < tolerance; };
      56         227 :   std::function<bool(const T &, const T &)> rel_tol_check = [tolerance](const T & R, const T & y)
      57         597 :   { return std::abs(MetaPhysicL::raw_value(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             :   using std::isnan;
      66             : 
      67             :   do
      68             :   {
      69         266 :     func(x, z, new_y, dy_dx, dy_dz);
      70         606 :     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         606 :     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         606 :     z += -(R / dy_dz);
      88             : 
      89             :     // Check for NaNs
      90         606 :     if (isnan(z))
      91           0 :       mooseException(caller_name + ": NaN detected in Newton solve");
      92             : 
      93         606 :     if (converged)
      94             :       break;
      95         409 :   } while (++iteration < max_its);
      96             : 
      97             :   // Check for divergence or slow convergence of Newton's method
      98         197 :   if (iteration >= max_its)
      99           0 :     mooseException(caller_name +
     100             :                        ": Newton solve convergence failed: maximum number of iterations, ",
     101             :                    max_its,
     102             :                    ", exceeded");
     103             : 
     104         314 :   return {z, dy_dz};
     105             : }
     106             : 
     107             : /**
     108             :  * NewtonSolve2D does a 2D Newton Solve to solve for the x and y such that:
     109             :  * f = func1(x, y) and g = func2(x, y). This is done for example in the constant of (v, e)
     110             :  * to (p, T) variable set conversion.
     111             :  * @param[in] f target value for func1
     112             :  * @param[in] g target value for func2
     113             :  * @param[in] x0 initial guess for first output variable
     114             :  * @param[in] y0 initial guess for second output variable
     115             :  * @param[out] x_final output for first variable
     116             :  * @param[out] y_final output for second variable
     117             :  * @param[in] f_tol criterion for relative or absolute (if f is sufficently close to zero)
     118             :  * convergence checking
     119             :  * @param[in] g_tol criterion for relative or absolute (if g is sufficently close to zero)
     120             :  * convergence checking
     121             :  * @param[in] func1 two-variable function returning both values and derivatives as references
     122             :  * @param[in] func2 two-variable function returning both values and derivatives as references
     123             :  * @param[in] max_its the maximum number of iterations for Newton's method
     124             :  */
     125             : template <typename T, typename Functor1, typename Functor2>
     126             : void
     127      762478 : 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      762478 :   DenseVector<T> targets = {{f, g}};
     142      937001 :   DenseVector<Real> tolerances = {{f_tol, g_tol}};
     143             :   // R represents a residual equal to y - y_in
     144    20643455 :   auto convergence_check = [&targets, &tolerances](const auto & minus_R)
     145             :   {
     146             :     using std::abs;
     147             : 
     148    21413505 :     for (const auto i : index_range(minus_R))
     149             :     {
     150    20825571 :       const auto error = abs(MooseUtils::absoluteFuzzyEqual(targets(i), 0, tolerances(i))
     151          22 :                                  ? minus_R(i)
     152    20825527 :                                  : minus_R(i) / targets(i));
     153    20825549 :       if (error >= tolerances(i))
     154             :         return false;
     155             :     }
     156             :     return true;
     157             :   };
     158             : 
     159      937001 :   DenseVector<T> u = {{x0, y0}};
     160      762478 :   DenseVector<T> minus_R(system_size), func_evals(system_size), u_update;
     161      762478 :   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          12 :   auto assign_solution = [&u, &x_final, &y_final]()
     172             :   {
     173      762478 :     x_final = u(0);
     174      762478 :     y_final = u(1);
     175             :   };
     176             : 
     177             :   using std::isnan, std::max, std::abs;
     178             : 
     179             :   do
     180             :   {
     181    59642931 :     for (const auto i : make_range(system_size))
     182    39761954 :       func[i](u(0), u(1), func_evals(i), J(i, 0), J(i, 1));
     183             : 
     184    59642931 :     for (const auto i : make_range(system_size))
     185    39761954 :       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    19880977 :     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    59642882 :     for (const auto i : make_range(system_size))
     195    39761930 :       if (isnan(minus_R(i)))
     196             :       {
     197           0 :         assign_solution();
     198          25 :         mooseException("NaN detected in Newton solve");
     199             :       }
     200             : 
     201             :     // Do some Jacobi (rowmax) preconditioning
     202    59642856 :     for (const auto i : make_range(system_size))
     203             :     {
     204    39761904 :       const auto rowmax = max(abs(J(i, 0)), abs(J(i, 1)));
     205   119285712 :       for (const auto j : make_range(system_size))
     206    79523808 :         J(i, j) /= rowmax;
     207    39761904 :       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    19880952 :     J.lu_solve(minus_R, u_update);
     225             :     // reset the decomposition
     226          15 :     J.zero();
     227    19880952 :     u += u_update;
     228             : 
     229             :     // Check for NaNs
     230    59642664 :     for (const auto i : make_range(system_size))
     231    39761808 :       if (isnan(u(i)))
     232             :       {
     233           0 :         assign_solution();
     234          96 :         mooseException("NaN detected in Newton solve");
     235             :       }
     236             : 
     237    19880856 :     if (converged)
     238             :       break;
     239    19292901 :   } while (++iteration < max_its);
     240             : 
     241           4 :   assign_solution();
     242             : 
     243             :   // Check for divergence or slow convergence of Newton's method
     244      762357 :   if (iteration >= max_its)
     245      174402 :     mooseException(
     246             :         "Newton solve convergence failed: maximum number of iterations, ", max_its, ", exceeded");
     247     1350437 : }
     248             : } // namespace FluidPropertiesUtils

Generated by: LCOV version 1.14