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

Generated by: LCOV version 1.14