LCOV - code coverage report
Current view: top level - include/utils - GreenGaussGradient.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 225 243 92.6 %
Date: 2025-07-17 01:28:37 Functions: 15 22 68.2 %
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             : #pragma once
      11             : 
      12             : #include "MooseFunctor.h"
      13             : #include "MathFVUtils.h"
      14             : #include "FVUtils.h"
      15             : #include "MooseMeshUtils.h"
      16             : #include "VectorComponentFunctor.h"
      17             : #include "ArrayComponentFunctor.h"
      18             : #include "libmesh/elem.h"
      19             : 
      20             : namespace Moose
      21             : {
      22             : namespace FV
      23             : {
      24             : /**
      25             :  * Compute a cell gradient using the method of Green-Gauss
      26             :  * @param elem_arg An element argument specifying the current element and whether to perform skew
      27             :  * corrections
      28             :  * @param state_arg A state argument that indicates what temporal / solution iteration data to use
      29             :  * when evaluating the provided functor
      30             :  * @param functor The functor that will provide information such as cell and face value evaluations
      31             :  * necessary to construct the cell gradient
      32             :  * @param two_term_boundary_expansion Whether to perform a two-term expansion to compute
      33             :  * extrapolated boundary face values. If this is true, then an implicit system has to be solved. If
      34             :  * false, then the cell center value will be used as the extrapolated boundary face value
      35             :  * @param mesh The mesh on which we are computing the gradient
      36             :  * @return The computed cell gradient
      37             :  */
      38             : template <typename T,
      39             :           typename Enable = typename std::enable_if<libMesh::ScalarTraits<T>::value>::type>
      40             : libMesh::VectorValue<T>
      41    21011348 : greenGaussGradient(const ElemArg & elem_arg,
      42             :                    const StateArg & state_arg,
      43             :                    const FunctorBase<T> & functor,
      44             :                    const bool two_term_boundary_expansion,
      45             :                    const MooseMesh & mesh,
      46             :                    const bool force_green_gauss = false)
      47             : {
      48             :   mooseAssert(elem_arg.elem, "This should be non-null");
      49    21011348 :   const auto coord_type = mesh.getCoordSystem(elem_arg.elem->subdomain_id());
      50    21011348 :   const auto rz_radial_coord = mesh.getAxisymmetricRadialCoord();
      51             : 
      52    21011348 :   const T elem_value = functor(elem_arg, state_arg);
      53             : 
      54             :   // We'll count the number of extrapolated boundary faces (ebfs)
      55    21011348 :   unsigned int num_ebfs = 0;
      56             : 
      57             :   // Gradient to be returned
      58    21011348 :   VectorValue<T> grad;
      59             : 
      60    21011348 :   if (!elem_arg.correct_skewness || force_green_gauss) // Do Green-Gauss
      61             :   {
      62             :     try
      63             :     {
      64    19480413 :       bool volume_set = false;
      65    19480413 :       Real volume = 0;
      66             : 
      67             :       // If we are performing a two term Taylor expansion for extrapolated boundary faces (faces on
      68             :       // boundaries that do not have associated Dirichlet conditions), then the element gradient
      69             :       // depends on the boundary face value and the boundary face value depends on the element
      70             :       // gradient, so we have a system of equations to solve. Here is the system:
      71             :       //
      72             :       // \nabla \phi_C - \frac{1}{V} \sum_{ebf} \phi_{ebf} \vec{S_f} =
      73             :       //   \frac{1}{V} \sum_{of} \phi_{of} \vec{S_f}                       eqn. 1
      74             :       //
      75             :       // \phi_{ebf} - \vec{d_{Cf}} \cdot \nabla \phi_C = \phi_C            eqn. 2
      76             :       //
      77             :       // where $C$ refers to the cell centroid, $ebf$ refers to an extrapolated boundary face, $of$
      78             :       // refers to "other faces", e.g. non-ebf faces, and $f$ is a general face. $d_{Cf}$ is the
      79             :       // vector drawn from the element centroid to the face centroid, and $\vec{S_f}$ is the surface
      80             :       // vector, e.g. the face area times the outward facing normal
      81             : 
      82             :       // ebf eqns: element gradient coefficients, e.g. eqn. 2, LHS term 2 coefficient
      83    19480413 :       std::vector<libMesh::VectorValue<Real>> ebf_grad_coeffs;
      84             :       // ebf eqns: rhs b values. These will actually correspond to the elem_value so we can use a
      85             :       // pointer and avoid copying. This is the RHS of eqn. 2
      86    19480413 :       std::vector<const T *> ebf_b;
      87             : 
      88             :       // elem grad eqns: ebf coefficients, e.g. eqn. 1, LHS term 2 coefficients
      89    19480413 :       std::vector<libMesh::VectorValue<Real>> grad_ebf_coeffs;
      90             :       // elem grad eqns: rhs b value, e.g. eqn. 1 RHS
      91    19480413 :       libMesh::VectorValue<T> grad_b = 0;
      92             : 
      93    78996187 :       auto action_functor = [&volume_set,
      94             :                              &volume,
      95             :                              &elem_value,
      96             :                              &elem_arg,
      97             :                              &num_ebfs,
      98             :                              &ebf_grad_coeffs,
      99             :                              &ebf_b,
     100             :                              &grad_ebf_coeffs,
     101             :                              &grad_b,
     102             :                              &state_arg,
     103             :                              &functor,
     104             :                              two_term_boundary_expansion,
     105             :                              coord_type,
     106             :                              rz_radial_coord](const Elem & libmesh_dbg_var(functor_elem),
     107             :                                               const Elem *,
     108             :                                               const FaceInfo * const fi,
     109             :                                               const Point & surface_vector,
     110             :                                               Real coord,
     111             :                                               const bool elem_has_info)
     112             :       {
     113             :         mooseAssert(fi, "We need a FaceInfo for this action_functor");
     114             :         mooseAssert(
     115             :             elem_arg.elem == &functor_elem,
     116             :             "Just a sanity check that the element being passed in is the one we passed out.");
     117             : 
     118    73246005 :         if (functor.isExtrapolatedBoundaryFace(*fi, elem_arg.elem, state_arg))
     119             :         {
     120     2192245 :           if (two_term_boundary_expansion)
     121             :           {
     122      474781 :             num_ebfs += 1;
     123             : 
     124             :             // eqn. 2
     125      474781 :             ebf_grad_coeffs.push_back(-1. * (elem_has_info
     126      474781 :                                                  ? (fi->faceCentroid() - fi->elemCentroid())
     127       19511 :                                                  : (fi->faceCentroid() - fi->neighborCentroid())));
     128      474781 :             ebf_b.push_back(&elem_value);
     129             : 
     130             :             // eqn. 1
     131      474781 :             grad_ebf_coeffs.push_back(-surface_vector);
     132             :           }
     133             :           else
     134             :             // We are doing a one-term expansion for the extrapolated boundary faces, in which case
     135             :             // we have no eqn. 2 and we have no second term in the LHS of eqn. 1. Instead we apply
     136             :             // the element centroid value as the face value (one-term expansion) in the RHS of eqn.
     137             :             // 1
     138     1717464 :             grad_b += surface_vector * elem_value;
     139             :         }
     140             :         else
     141    71053760 :           grad_b +=
     142         880 :               surface_vector * functor(Moose::FaceArg{fi,
     143             :                                                       Moose::FV::LimiterType::CentralDifference,
     144             :                                                       true,
     145    71053760 :                                                       elem_arg.correct_skewness,
     146    71053760 :                                                       elem_arg.elem,
     147             :                                                       nullptr},
     148             :                                        state_arg);
     149             : 
     150    73246005 :         if (!volume_set)
     151             :         {
     152             :           // We use the FaceInfo volumes because those values have been pre-computed and cached.
     153             :           // An explicit call to elem->volume() here would incur unnecessary expense
     154    19480413 :           if (elem_has_info)
     155             :           {
     156     1380515 :             MooseMeshUtils::coordTransformFactor(
     157             :                 fi->elemCentroid(), coord, coord_type, rz_radial_coord);
     158     1380515 :             volume = fi->elemVolume() * coord;
     159             :           }
     160             :           else
     161             :           {
     162    18099898 :             MooseMeshUtils::coordTransformFactor(
     163             :                 fi->neighborCentroid(), coord, coord_type, rz_radial_coord);
     164    18099898 :             volume = fi->neighborVolume() * coord;
     165             :           }
     166             : 
     167    19480413 :           volume_set = true;
     168             :         }
     169             :       };
     170             : 
     171    19480413 :       Moose::FV::loopOverElemFaceInfo(
     172    19480413 :           *elem_arg.elem, mesh, action_functor, coord_type, rz_radial_coord);
     173             : 
     174             :       mooseAssert(volume_set && volume > 0, "We should have set the volume");
     175    19480413 :       grad_b /= volume;
     176             : 
     177    19480413 :       if (coord_type == Moose::CoordinateSystemType::COORD_RZ)
     178             :       {
     179             :         mooseAssert(rz_radial_coord != libMesh::invalid_uint, "rz_radial_coord must be set");
     180       32365 :         grad_b(rz_radial_coord) -= elem_value / elem_arg.elem->vertex_average()(rz_radial_coord);
     181             :       }
     182             : 
     183             :       mooseAssert(
     184             :           coord_type != Moose::CoordinateSystemType::COORD_RSPHERICAL,
     185             :           "We have not yet implemented the correct translation from gradient to divergence for "
     186             :           "spherical coordinates yet.");
     187             : 
     188             :       // test for simple case
     189    19480413 :       if (num_ebfs == 0)
     190    19021454 :         grad = grad_b;
     191             :       else
     192             :       {
     193             :         // We have to solve a system
     194      458959 :         const unsigned int sys_dim = Moose::dim + num_ebfs;
     195      458959 :         DenseVector<T> x(sys_dim), b(sys_dim);
     196      458959 :         DenseMatrix<T> A(sys_dim, sys_dim);
     197             : 
     198             :         // Let's make i refer to Moose::dim indices, and j refer to num_ebfs indices
     199             : 
     200             :         // eqn. 1
     201     1835836 :         for (const auto i : make_range(Moose::dim))
     202             :         {
     203             :           // LHS term 1 coeffs
     204     1376877 :           A(i, i) = 1;
     205             : 
     206             :           // LHS term 2 coeffs
     207     2801220 :           for (const auto j : make_range(num_ebfs))
     208     1424343 :             A(i, Moose::dim + j) = grad_ebf_coeffs[j](i) / volume;
     209             : 
     210             :           // RHS
     211     1376877 :           b(i) = grad_b(i);
     212             :         }
     213             : 
     214             :         // eqn. 2
     215      933740 :         for (const auto j : make_range(num_ebfs))
     216             :         {
     217             :           // LHS term 1 coeffs
     218      474781 :           A(Moose::dim + j, Moose::dim + j) = 1;
     219             : 
     220             :           // LHS term 2 coeffs
     221     1899124 :           for (const auto i : make_range(unsigned(Moose::dim)))
     222     1424343 :             A(Moose::dim + j, i) = ebf_grad_coeffs[j](i);
     223             : 
     224             :           // RHS
     225      474781 :           b(Moose::dim + j) = *ebf_b[j];
     226             :         }
     227             : 
     228      458959 :         A.lu_solve(b, x);
     229             :         // libMesh is generous about what it considers nonsingular. Let's check a little more
     230             :         // strictly
     231      456805 :         if (MooseUtils::absoluteFuzzyEqual(MetaPhysicL::raw_value(A(sys_dim - 1, sys_dim - 1)), 0))
     232         364 :           throw libMesh::LogicError("Matrix A is singular!");
     233     1825764 :         for (const auto i : make_range(Moose::dim))
     234     1369323 :           grad(i) = x(i);
     235      463995 :       }
     236    19487967 :     }
     237        2518 :     catch (libMesh::LogicError & e)
     238             :     {
     239             :       // Retry without two-term
     240        2518 :       if (!two_term_boundary_expansion)
     241           0 :         mooseError(
     242             :             "I believe we should only get singular systems when two-term boundary expansion is "
     243             :             "being used. The error thrown during the computation of the gradient: ",
     244           0 :             e.what());
     245             : 
     246        2518 :       grad = greenGaussGradient(elem_arg, state_arg, functor, false, mesh, true);
     247             :     }
     248    19480413 :   }
     249             :   else // Do Least-Squares
     250             :   {
     251             :     try
     252             :     {
     253             : 
     254             :       // The least squares method aims to find the gradient at the element centroid by minimizing
     255             :       // the difference between the estimated and actual value differences across neighboring cells.
     256             :       // The least squares formulation is:
     257             :       //
     258             :       // Minimize J = \sum_{n} [ w_n ((\nabla \phi_C \cdot \delta \vec{x}_n) - \delta \phi_n) ]^2
     259             :       //
     260             :       // where:
     261             :       // - \(\nabla \phi_C\) is the gradient at the element centroid C (unknown).
     262             :       // - \(\delta \vec{x}_n = \vec{x}_n - \vec{x}_C\) is the vector from the element centroid to
     263             :       // neighbor \(n\).
     264             :       // - \(\delta \phi_n = \phi_n - \phi_C\) is the difference in the scalar value between
     265             :       // neighbor \(n\) and element \(C\).
     266             :       // - w_n = 1/||\vec{x}_n|| is a vector of weights that is used to handle large aspect ratio
     267             :       // cells
     268             :       //
     269             :       // To handle extrapolated boundary faces (faces on boundaries without Dirichlet conditions),
     270             :       // we include additional unknowns and equations in the least squares system.
     271             :       // For ebfs, we may not know the value \(\phi_n\), so we include it as an unknown.
     272             :       // This results in an augmented system that simultaneously solves for the gradient and the ebf
     273             :       // values.
     274             : 
     275             :       // Get estimated number of faces in a cell
     276     1530935 :       const auto ptr_range = elem_arg.elem->neighbor_ptr_range();
     277     1530935 :       const size_t num_faces = std::distance(ptr_range.begin(), ptr_range.end());
     278             : 
     279             :       // Lists to store position differences and value differences for least squares computation
     280     1530935 :       std::vector<Point> delta_x_list; // List of position differences between neighbor centroids
     281             :                                        // and element centroid
     282     1530935 :       delta_x_list.reserve(num_faces);
     283             : 
     284             :       std::vector<T>
     285     1530935 :           delta_phi_list; // List of value differences between neighbor values and element value
     286     1530935 :       delta_phi_list.reserve(num_faces);
     287             : 
     288             :       // Variables to handle extrapolated boundary faces (ebfs) in the least squares method
     289     1530935 :       std::vector<Point> delta_x_ebf_list; // Position differences for ebfs
     290     1530935 :       delta_phi_list.reserve(num_faces);
     291             : 
     292             :       std::vector<VectorValue<Real>>
     293     1530935 :           ebf_grad_coeffs; // Coefficients for the gradient in ebf equations
     294     1530935 :       delta_phi_list.reserve(num_faces);
     295             : 
     296     1530935 :       std::vector<const T *> ebf_b; // RHS values for ebf equations (pointers to avoid copying)
     297     1530935 :       delta_phi_list.reserve(num_faces);
     298             : 
     299     1530935 :       unsigned int num_ebfs = 0; // Number of extrapolated boundary faces
     300             : 
     301             :       // Action functor to collect data from each face of the element
     302     5030215 :       auto action_functor = [&elem_value,
     303             :                              &elem_arg,
     304             :                              &num_ebfs,
     305             :                              &ebf_grad_coeffs,
     306             :                              &ebf_b,
     307             :                              &delta_x_list,
     308             :                              &delta_phi_list,
     309             :                              &delta_x_ebf_list,
     310             :                              &state_arg,
     311             :                              &functor,
     312             :                              two_term_boundary_expansion](const Elem & libmesh_dbg_var(loc_elem),
     313             :                                                           const Elem * loc_neigh,
     314             :                                                           const FaceInfo * const fi,
     315             :                                                           const Point & /*surface_vector*/,
     316             :                                                           Real /*coord*/,
     317             :                                                           const bool elem_has_info)
     318             :       {
     319             :         mooseAssert(fi, "We need a FaceInfo for this action_functor");
     320             :         mooseAssert(
     321             :             elem_arg.elem == &loc_elem,
     322             :             "Just a sanity check that the element being passed in is the one we passed out.");
     323             : 
     324     4592805 :         if (functor.isExtrapolatedBoundaryFace(*fi, elem_arg.elem, state_arg))
     325             :         {
     326             :           // Extrapolated Boundary Face (ebf)
     327       17360 :           const Point delta_x = elem_has_info ? (fi->faceCentroid() - fi->elemCentroid())
     328           0 :                                               : (fi->faceCentroid() - fi->neighborCentroid());
     329       17360 :           delta_x_list.push_back(delta_x);
     330             : 
     331       17360 :           T delta_phi;
     332             : 
     333       17360 :           if (two_term_boundary_expansion)
     334             :           {
     335             :             // Two-term expansion: include ebf values as unknowns in the augmented system
     336       17360 :             num_ebfs += 1;
     337             : 
     338             :             // Coefficient for the gradient in the ebf equation
     339       17360 :             ebf_grad_coeffs.push_back(-delta_x);
     340             :             // RHS value for the ebf equation
     341       17360 :             ebf_b.push_back(&elem_value);
     342             : 
     343       17360 :             delta_phi = -elem_value;
     344       17360 :             delta_x_ebf_list.push_back(delta_x);
     345             :           }
     346             :           else
     347             :           {
     348             :             // One-term expansion: assume zero difference across the boundary (\delta \phi = 0)
     349           0 :             delta_phi = T(0);
     350             :           }
     351       17360 :           delta_phi_list.push_back(delta_phi);
     352       17360 :         }
     353             :         else
     354             :         {
     355             :           // Internal Face or Boundary Face with Dirichlet condition
     356     4575445 :           Point delta_x;
     357     4575445 :           T neighbor_value;
     358     4575445 :           if (functor.isInternalFace(*fi))
     359             :           {
     360             :             // Internal face with a neighboring element
     361     4494070 :             delta_x = loc_neigh->vertex_average() - elem_arg.elem->vertex_average();
     362             : 
     363     4494070 :             const ElemArg neighbor_arg = {loc_neigh, elem_arg.correct_skewness};
     364     4494070 :             neighbor_value = functor(neighbor_arg, state_arg);
     365             :           }
     366             :           else
     367             :           {
     368             :             // Boundary face with Dirichlet condition
     369       81375 :             delta_x = elem_has_info ? (fi->faceCentroid() - fi->elemCentroid())
     370           0 :                                     : (fi->faceCentroid() - fi->neighborCentroid());
     371       81375 :             neighbor_value = functor(Moose::FaceArg{fi,
     372             :                                                     Moose::FV::LimiterType::CentralDifference,
     373             :                                                     true,
     374       81375 :                                                     elem_arg.correct_skewness,
     375       81375 :                                                     elem_arg.elem,
     376             :                                                     nullptr},
     377             :                                      state_arg);
     378             :           }
     379             : 
     380     4575445 :           delta_x_list.push_back(delta_x);
     381     4575445 :           delta_phi_list.push_back(neighbor_value - elem_value);
     382     4575445 :         }
     383             :       };
     384             : 
     385             :       // Loop over element faces and apply the action_functor
     386     1530935 :       Moose::FV::loopOverElemFaceInfo(
     387     1530935 :           *elem_arg.elem, mesh, action_functor, coord_type, rz_radial_coord);
     388             : 
     389             :       // Compute Least Squares gradient
     390     1530935 :       const unsigned int n = delta_x_list.size();
     391             :       mooseAssert(n >= dim, "Not enough neighbors to perform least squares");
     392             : 
     393     1530935 :       DenseMatrix<T> ATA(dim, dim);
     394     1530935 :       DenseVector<T> ATb(dim);
     395             : 
     396             :       // Assemble the normal equations for the least squares method
     397             :       // ATA = \sum_n (\delta \vec{x}_n^T * \delta \vec{x}_n)
     398             :       // ATb = \sum_n (\delta \vec{x}_n^T * \delta \phi_n)
     399     1530935 :       ATA.zero();
     400     1530935 :       ATb.zero();
     401             : 
     402     6123740 :       for (unsigned int i = 0; i < n; ++i)
     403             :       {
     404     4592805 :         const Point & delta_x = delta_x_list[i];
     405     4592805 :         const T & delta_phi = delta_phi_list[i];
     406             : 
     407    18371220 :         for (unsigned int d1 = 0; d1 < dim; ++d1)
     408             :         {
     409    13778415 :           const Real dx_d1 = delta_x(d1);
     410    13778415 :           const Real dx_norm = delta_x.norm();
     411    13778415 :           const Real dx_d1_norm = dx_d1 / dx_norm;
     412             : 
     413    55113660 :           for (unsigned int d2 = 0; d2 < dim; ++d2)
     414             :           {
     415    41335245 :             const Real dx_d2_norm = delta_x(d2) / dx_norm;
     416    41335245 :             ATA(d1, d2) += dx_d1_norm * dx_d2_norm;
     417             :           }
     418             : 
     419    13778415 :           ATb(d1) += dx_d1_norm * delta_phi / dx_norm;
     420             :         }
     421             :       }
     422             : 
     423             :       // Add regularization to ATA to prevent singularity
     424     1530935 :       T epsilon = 1e-12; // Small regularization parameter
     425     6123740 :       for (unsigned int d = 0; d < dim; ++d)
     426             :       {
     427     4592805 :         ATA(d, d) += epsilon;
     428             :       }
     429             : 
     430     1530935 :       if (num_ebfs == 0)
     431             :       {
     432             :         // Solve and store the least squares gradient
     433     1513575 :         DenseVector<T> grad_ls(dim);
     434     1513575 :         ATA.lu_solve(ATb, grad_ls);
     435             : 
     436             :         // Store the least squares gradient
     437     6054300 :         for (unsigned int d = 0; d < dim; ++d)
     438             :         {
     439     4540725 :           grad(d) = grad_ls(d);
     440             :         }
     441     1513575 :       }
     442             :       else
     443             :       {
     444             :         // We have to solve a system of equations
     445       17360 :         const unsigned int sys_dim = Moose::dim + num_ebfs;
     446       17360 :         DenseVector<T> x(sys_dim), b(sys_dim);
     447       17360 :         DenseMatrix<T> A(sys_dim, sys_dim);
     448             : 
     449             :         // Let's make i refer to Moose::dim indices, and j refer to num_ebfs indices
     450             : 
     451             :         // eqn. 1: Element gradient equations
     452       69440 :         for (unsigned int d1 = 0; d1 < dim; ++d1)
     453             :         {
     454             :           // LHS term 1 coefficients (gradient components)
     455      208320 :           for (unsigned int d2 = 0; d2 < dim; ++d2)
     456      156240 :             A(d1, d2) = ATA(d1, d2);
     457             : 
     458             :           // RHS
     459       52080 :           b(d1) = ATb(d1);
     460             :         }
     461             : 
     462             :         // LHS term 2 coefficients (ebf contributions)
     463       34720 :         for (const auto i : make_range(num_ebfs))
     464             :         {
     465       17360 :           const Point & delta_x = delta_x_ebf_list[i];
     466       69440 :           for (unsigned int d1 = 0; d1 < dim; ++d1)
     467             :           {
     468       52080 :             const Real dx_d1 = delta_x(d1);
     469       52080 :             A(d1, Moose::dim + i) -= dx_d1 / delta_x.norm() / delta_x.norm();
     470             :           }
     471             :         }
     472             : 
     473             :         // eqn. 2: Extrapolated boundary face equations
     474       34720 :         for (const auto j : make_range(num_ebfs))
     475             :         {
     476             :           // LHS term 1 coefficients (ebf values)
     477       17360 :           A(Moose::dim + j, Moose::dim + j) = 1;
     478             : 
     479             :           // LHS term 2 coefficients (gradient components)
     480       69440 :           for (const auto i : make_range(unsigned(Moose::dim)))
     481       52080 :             A(Moose::dim + j, i) = ebf_grad_coeffs[j](i);
     482             : 
     483             :           // RHS
     484       17360 :           b(Moose::dim + j) = *ebf_b[j];
     485             :         }
     486             : 
     487             :         // Solve the system
     488       17360 :         A.lu_solve(b, x);
     489             : 
     490             :         // Check for singularity
     491       17360 :         if (MooseUtils::absoluteFuzzyEqual(MetaPhysicL::raw_value(A(sys_dim - 1, sys_dim - 1)), 0))
     492           0 :           throw libMesh::LogicError("Matrix A is singular!");
     493             : 
     494             :         // Extract the gradient components
     495       69440 :         for (const auto i : make_range(Moose::dim))
     496       52080 :           grad(i) = x(i);
     497       17360 :       }
     498             : 
     499             :       mooseAssert(
     500             :           coord_type != Moose::CoordinateSystemType::COORD_RSPHERICAL,
     501             :           "We have not yet implemented the correct translation from gradient to divergence for "
     502             :           "spherical coordinates yet.");
     503     1530935 :     }
     504           0 :     catch (libMesh::LogicError & e)
     505             :     {
     506             :       // Log warning and default to simple green Gauss
     507           0 :       mooseWarning(
     508             :           "Singular matrix encountered in least squares gradient computation. Falling back "
     509             :           "to Green-Gauss gradient.");
     510             : 
     511           0 :       grad = greenGaussGradient(
     512             :           elem_arg, state_arg, functor, false, mesh, /* force_green_gauss */ true);
     513             :     }
     514             :   }
     515             : 
     516    42022696 :   return grad;
     517             : 
     518             :   // Notes to future developer:
     519             :   // Note 1:
     520             :   // For the least squares gradient, the LS matrix could be precomputed and stored for every cell
     521             :   // I tried doing this on October 2024, but the element lookup for these matrices is too slow
     522             :   // and seems better to compute weights on the fly.
     523             :   // Consider building a map from elem_id to these matrices and speed up lookup with octree
     524             :   // Note 2:
     525             :   // In the future one would like to have a hybrid gradient scheme, where:
     526             :   // \nabla \phi = \beta (\nabla \phi)_{LS} + (1 - \beta) (\nabla \phi)_{GG}
     527             :   // Then optize \beta based on mesh heuristics
     528    21011128 : }
     529             : 
     530             : /**
     531             :  * Compute a face gradient from Green-Gauss cell gradients, with orthogonality correction
     532             :  * On the boundaries, the boundary element value is used
     533             :  * @param face_arg A face argument specifying the current faceand whether to perform skew
     534             :  * corrections
     535             :  * @param state_arg A state argument that indicates what temporal / solution iteration data to use
     536             :  * when evaluating the provided functor
     537             :  * @param functor The functor that will provide information such as cell and face value evaluations
     538             :  * necessary to construct the cell gradient
     539             :  * @param two_term_boundary_expansion Whether to perform a two-term expansion to compute
     540             :  * extrapolated boundary face values. If this is true, then an implicit system has to be solved. If
     541             :  * false, then the cell center value will be used as the extrapolated boundary face value
     542             :  * @param mesh The mesh on which we are computing the gradient
     543             :  * @return The computed cell gradient
     544             :  */
     545             : template <typename T,
     546             :           typename Enable = typename std::enable_if<libMesh::ScalarTraits<T>::value>::type>
     547             : libMesh::VectorValue<T>
     548          75 : greenGaussGradient(const FaceArg & face_arg,
     549             :                    const StateArg & state_arg,
     550             :                    const FunctorBase<T> & functor,
     551             :                    const bool two_term_boundary_expansion,
     552             :                    const MooseMesh & mesh)
     553             : {
     554             :   mooseAssert(face_arg.fi, "We should have a face info to compute a face gradient");
     555          75 :   const auto & fi = *(face_arg.fi);
     556          75 :   const auto & elem_arg = face_arg.makeElem();
     557          75 :   const auto & neighbor_arg = face_arg.makeNeighbor();
     558          75 :   const bool defined_on_elem = functor.hasBlocks(fi.elemSubdomainID());
     559          75 :   const bool defined_on_neighbor = fi.neighborPtr() && functor.hasBlocks(fi.neighborSubdomainID());
     560             : 
     561          75 :   if (defined_on_elem && defined_on_neighbor)
     562             :   {
     563          27 :     const auto & value_elem = functor(elem_arg, state_arg);
     564          27 :     const auto & value_neighbor = functor(neighbor_arg, state_arg);
     565             : 
     566             :     // This is the component of the gradient which is parallel to the line connecting
     567             :     // the cell centers. Therefore, we can use our second order, central difference
     568             :     // scheme to approximate it.
     569          27 :     libMesh::VectorValue<T> face_gradient = (value_neighbor - value_elem) / fi.dCNMag() * fi.eCN();
     570             : 
     571             :     // We only need nonorthogonal correctors in 2+ dimensions
     572          27 :     if (mesh.dimension() > 1)
     573             :     {
     574             :       // We are using an orthogonal approach for the non-orthogonal correction, for more information
     575             :       // see Hrvoje Jasak's PhD Thesis (Imperial College, 1996)
     576          27 :       libMesh::VectorValue<T> interpolated_gradient;
     577             : 
     578             :       // Compute the gradients in the two cells on both sides of the face
     579          27 :       const auto & grad_elem =
     580          27 :           greenGaussGradient(elem_arg, state_arg, functor, two_term_boundary_expansion, mesh);
     581          27 :       const auto & grad_neighbor =
     582          27 :           greenGaussGradient(neighbor_arg, state_arg, functor, two_term_boundary_expansion, mesh);
     583             : 
     584          27 :       Moose::FV::interpolate(Moose::FV::InterpMethod::Average,
     585             :                              interpolated_gradient,
     586             :                              grad_elem,
     587             :                              grad_neighbor,
     588             :                              fi,
     589             :                              true);
     590             : 
     591          27 :       face_gradient += interpolated_gradient - (interpolated_gradient * fi.eCN()) * fi.eCN();
     592           0 :     }
     593             : 
     594          27 :     return face_gradient;
     595           0 :   }
     596          48 :   else if (defined_on_elem)
     597          48 :     return functor.gradient(elem_arg, state_arg);
     598           0 :   else if (defined_on_neighbor)
     599           0 :     return functor.gradient(neighbor_arg, state_arg);
     600             :   else
     601           0 :     mooseError("The functor must be defined on one of the sides");
     602             : }
     603             : 
     604             : template <typename T>
     605             : TensorValue<T>
     606           2 : greenGaussGradient(const ElemArg & elem_arg,
     607             :                    const StateArg & state_arg,
     608             :                    const Moose::FunctorBase<libMesh::VectorValue<T>> & functor,
     609             :                    const bool two_term_boundary_expansion,
     610             :                    const MooseMesh & mesh)
     611             : {
     612           2 :   TensorValue<T> ret;
     613           8 :   for (const auto i : make_range(Moose::dim))
     614             :   {
     615           6 :     VectorComponentFunctor<T> scalar_functor(functor, i);
     616           0 :     const auto row_gradient =
     617           6 :         greenGaussGradient(elem_arg, state_arg, scalar_functor, two_term_boundary_expansion, mesh);
     618          24 :     for (const auto j : make_range(unsigned(Moose::dim)))
     619          18 :       ret(i, j) = row_gradient(j);
     620             :   }
     621             : 
     622           2 :   return ret;
     623           0 : }
     624             : 
     625             : template <typename T>
     626             : TensorValue<T>
     627           1 : greenGaussGradient(const FaceArg & face_arg,
     628             :                    const StateArg & state_arg,
     629             :                    const Moose::FunctorBase<libMesh::VectorValue<T>> & functor,
     630             :                    const bool two_term_boundary_expansion,
     631             :                    const MooseMesh & mesh)
     632             : {
     633           1 :   TensorValue<T> ret;
     634           4 :   for (const auto i : make_range(unsigned(Moose::dim)))
     635             :   {
     636           3 :     VectorComponentFunctor<T> scalar_functor(functor, i);
     637           0 :     const auto row_gradient =
     638           3 :         greenGaussGradient(face_arg, state_arg, scalar_functor, two_term_boundary_expansion, mesh);
     639          12 :     for (const auto j : make_range(unsigned(Moose::dim)))
     640           9 :       ret(i, j) = row_gradient(j);
     641             :   }
     642             : 
     643           1 :   return ret;
     644           0 : }
     645             : 
     646             : template <typename T>
     647             : typename Moose::FunctorBase<std::vector<T>>::GradientType
     648          80 : greenGaussGradient(const ElemArg & elem_arg,
     649             :                    const StateArg & state_arg,
     650             :                    const Moose::FunctorBase<std::vector<T>> & functor,
     651             :                    const bool two_term_boundary_expansion,
     652             :                    const MooseMesh & mesh)
     653             : {
     654             :   // Determine the size of the container
     655          80 :   const auto vals = functor(elem_arg, state_arg);
     656             :   typedef typename Moose::FunctorBase<std::vector<T>>::GradientType GradientType;
     657          80 :   GradientType ret(vals.size());
     658         160 :   for (const auto i : index_range(ret))
     659             :   {
     660             :     // Note that this can be very inefficient. Within the scalar greenGaussGradient routine we're
     661             :     // going to do value type evaluations of the array functor from scalar_functor and we will be
     662             :     // discarding all the value type evaluations other than the one corresponding to i
     663          80 :     ArrayComponentFunctor<T, FunctorBase<std::vector<T>>> scalar_functor(functor, i);
     664          80 :     ret[i] =
     665          80 :         greenGaussGradient(elem_arg, state_arg, scalar_functor, two_term_boundary_expansion, mesh);
     666             :   }
     667             : 
     668         160 :   return ret;
     669          80 : }
     670             : 
     671             : template <typename T>
     672             : typename Moose::FunctorBase<std::vector<T>>::GradientType
     673          36 : greenGaussGradient(const FaceArg & face_arg,
     674             :                    const StateArg & state_arg,
     675             :                    const Moose::FunctorBase<std::vector<T>> & functor,
     676             :                    const bool two_term_boundary_expansion,
     677             :                    const MooseMesh & mesh)
     678             : {
     679             :   // Determine the size of the container
     680          36 :   const auto vals = functor(face_arg, state_arg);
     681             :   typedef typename Moose::FunctorBase<std::vector<T>>::GradientType GradientType;
     682          36 :   GradientType ret(vals.size());
     683          72 :   for (const auto i : index_range(ret))
     684             :   {
     685             :     // Note that this can be very inefficient. Within the scalar greenGaussGradient routine we're
     686             :     // going to do value type evaluations of the array functor from scalar_functor and we will be
     687             :     // discarding all the value type evaluations other than the one corresponding to i
     688          36 :     ArrayComponentFunctor<T, FunctorBase<std::vector<T>>> scalar_functor(functor, i);
     689          36 :     ret[i] =
     690          36 :         greenGaussGradient(face_arg, state_arg, scalar_functor, two_term_boundary_expansion, mesh);
     691             :   }
     692             : 
     693          72 :   return ret;
     694          36 : }
     695             : 
     696             : template <typename T, std::size_t N>
     697             : typename Moose::FunctorBase<std::array<T, N>>::GradientType
     698          76 : greenGaussGradient(const ElemArg & elem_arg,
     699             :                    const StateArg & state_arg,
     700             :                    const Moose::FunctorBase<std::array<T, N>> & functor,
     701             :                    const bool two_term_boundary_expansion,
     702             :                    const MooseMesh & mesh)
     703             : {
     704             :   typedef typename Moose::FunctorBase<std::array<T, N>>::GradientType GradientType;
     705          76 :   GradientType ret;
     706         152 :   for (const auto i : make_range(N))
     707             :   {
     708             :     // Note that this can be very inefficient. Within the scalar greenGaussGradient routine we're
     709             :     // going to do value type evaluations of the array functor from scalar_functor and we will be
     710             :     // discarding all the value type evaluations other than the one corresponding to i
     711          76 :     ArrayComponentFunctor<T, FunctorBase<std::array<T, N>>> scalar_functor(functor, i);
     712          76 :     ret[i] =
     713          76 :         greenGaussGradient(elem_arg, state_arg, scalar_functor, two_term_boundary_expansion, mesh);
     714             :   }
     715             : 
     716          76 :   return ret;
     717             : }
     718             : 
     719             : template <typename T, std::size_t N>
     720             : typename Moose::FunctorBase<std::array<T, N>>::GradientType
     721          36 : greenGaussGradient(const FaceArg & face_arg,
     722             :                    const StateArg & state_arg,
     723             :                    const Moose::FunctorBase<std::array<T, N>> & functor,
     724             :                    const bool two_term_boundary_expansion,
     725             :                    const MooseMesh & mesh)
     726             : {
     727             :   typedef typename Moose::FunctorBase<std::array<T, N>>::GradientType GradientType;
     728          36 :   GradientType ret;
     729          72 :   for (const auto i : make_range(N))
     730             :   {
     731             :     // Note that this can be very inefficient. Within the scalar greenGaussGradient routine we're
     732             :     // going to do value type evaluations of the array functor from scalar_functor and we will be
     733             :     // discarding all the value type evaluations other than the one corresponding to i
     734          36 :     ArrayComponentFunctor<T, FunctorBase<std::array<T, N>>> scalar_functor(functor, i);
     735          36 :     ret[i] =
     736          36 :         greenGaussGradient(face_arg, state_arg, scalar_functor, two_term_boundary_expansion, mesh);
     737             :   }
     738             : 
     739          36 :   return ret;
     740             : }
     741             : }
     742             : }

Generated by: LCOV version 1.14