LCOV - code coverage report
Current view: top level - src/variables - INSFVVelocityVariable.C (source / functions) Hit Total Coverage
Test: idaholab/moose navier_stokes: 9fc4b0 Lines: 116 116 100.0 %
Date: 2025-08-14 10:14:56 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "INSFVVelocityVariable.h"
      11             : #include "FaceInfo.h"
      12             : #include "ADReal.h"
      13             : #include "MathFVUtils.h"
      14             : #include "FVUtils.h"
      15             : #include "MooseError.h"
      16             : #include "SubProblem.h"
      17             : #include "INSFVNoSlipWallBC.h"
      18             : #include "INSFVAttributes.h"
      19             : #include "SystemBase.h"
      20             : #include "FVDirichletBCBase.h"
      21             : #include "Assembly.h"
      22             : 
      23             : #include "libmesh/elem.h"
      24             : #include "libmesh/vector_value.h"
      25             : #include "libmesh/tensor_value.h"
      26             : #include "libmesh/dense_vector.h"
      27             : #include "libmesh/dense_matrix.h"
      28             : #include "libmesh/libmesh_common.h"
      29             : 
      30             : #include <vector>
      31             : #include <utility>
      32             : 
      33             : namespace Moose
      34             : {
      35             : namespace FV
      36             : {
      37             : template <typename ActionFunctor>
      38             : void
      39    55873265 : loopOverElemFaceInfo(const Elem & elem,
      40             :                      const MooseMesh & mesh,
      41             :                      const SubProblem & subproblem,
      42             :                      ActionFunctor & act)
      43             : {
      44    55873265 :   const auto coord_type = subproblem.getCoordSystem(elem.subdomain_id());
      45    60199371 :   loopOverElemFaceInfo(elem,
      46             :                        mesh,
      47             :                        act,
      48             :                        coord_type,
      49     4326106 :                        coord_type == Moose::COORD_RZ ? subproblem.getAxisymmetricRadialCoord()
      50             :                                                      : libMesh::invalid_uint);
      51    55873265 : }
      52             : }
      53             : }
      54             : 
      55             : registerMooseObject("NavierStokesApp", INSFVVelocityVariable);
      56             : 
      57             : InputParameters
      58       32780 : INSFVVelocityVariable::validParams()
      59             : {
      60       32780 :   return INSFVVariable::validParams();
      61             : }
      62             : 
      63       17393 : INSFVVelocityVariable::INSFVVelocityVariable(const InputParameters & params) : INSFVVariable(params)
      64             : {
      65       17393 : }
      66             : 
      67             : ADReal
      68     6183569 : INSFVVelocityVariable::getExtrapolatedBoundaryFaceValue(const FaceInfo & fi,
      69             :                                                         const bool two_term_expansion,
      70             :                                                         const bool correct_skewness,
      71             :                                                         const Elem * elem_to_extrapolate_from,
      72             :                                                         const StateArg & time) const
      73             : {
      74             :   ADReal boundary_value;
      75             :   bool elem_to_extrapolate_from_is_fi_elem;
      76             :   std::tie(elem_to_extrapolate_from, elem_to_extrapolate_from_is_fi_elem) =
      77    18550707 :       [this, &fi, elem_to_extrapolate_from]() -> std::pair<const Elem *, bool>
      78             :   {
      79     6183569 :     if (elem_to_extrapolate_from)
      80     5637471 :       return {elem_to_extrapolate_from, elem_to_extrapolate_from == &fi.elem()};
      81             :     else
      82             :     {
      83             :       const auto [elem_guaranteed_to_have_dofs,
      84             :                   other_elem,
      85             :                   elem_guaranteed_to_have_dofs_is_fi_elem] =
      86      546098 :           Moose::FV::determineElemOneAndTwo(fi, *this);
      87             :       libmesh_ignore(other_elem);
      88      546098 :       return {elem_guaranteed_to_have_dofs, elem_guaranteed_to_have_dofs_is_fi_elem};
      89             :     }
      90     6183569 :   }();
      91             : 
      92     6183569 :   if (two_term_expansion && isFullyDevelopedFlowFace(fi))
      93             :   {
      94             :     const Point vector_to_face = elem_to_extrapolate_from_is_fi_elem
      95     4892238 :                                      ? (fi.faceCentroid() - fi.elemCentroid())
      96             :                                      : (fi.faceCentroid() - fi.neighborCentroid());
      97     4892238 :     boundary_value = uncorrectedAdGradSln(fi, time, correct_skewness) * vector_to_face +
      98     9784476 :                      getElemValue(elem_to_extrapolate_from, time);
      99             :   }
     100             :   else
     101     2582662 :     boundary_value = INSFVVariable::getExtrapolatedBoundaryFaceValue(
     102     1291331 :         fi, two_term_expansion, correct_skewness, elem_to_extrapolate_from, time);
     103             : 
     104     6183569 :   return boundary_value;
     105             : }
     106             : 
     107             : VectorValue<ADReal>
     108   141941522 : INSFVVelocityVariable::uncorrectedAdGradSln(const FaceInfo & fi,
     109             :                                             const StateArg & time,
     110             :                                             const bool correct_skewness) const
     111             : {
     112             :   VectorValue<ADReal> unc_grad;
     113   141941522 :   if (_two_term_boundary_expansion && isFullyDevelopedFlowFace(fi))
     114             :   {
     115     4958664 :     const auto & cell_gradient = adGradSln(&fi.elem(), time, correct_skewness);
     116             :     const auto & normal = fi.normal();
     117    14875992 :     unc_grad = cell_gradient - (cell_gradient * normal) * normal;
     118             :   }
     119             :   else
     120   273965716 :     unc_grad = INSFVVariable::uncorrectedAdGradSln(fi, time, correct_skewness);
     121             : 
     122   141941522 :   return unc_grad;
     123             : }
     124             : 
     125             : const VectorValue<ADReal> &
     126   285783177 : INSFVVelocityVariable::adGradSln(const Elem * const elem,
     127             :                                  const StateArg & time,
     128             :                                  bool correct_skewness) const
     129             : {
     130   285783177 :   VectorValue<ADReal> * value_pointer = &_temp_cell_gradient;
     131             : 
     132             :   // We ensure that no caching takes place when we compute skewness-corrected
     133             :   // quantities.
     134   285783177 :   if (_cache_cell_gradients && !correct_skewness && time.state == 0)
     135             :   {
     136             :     auto it = _elem_to_grad.find(elem);
     137             : 
     138   285597147 :     if (it != _elem_to_grad.end())
     139   229909912 :       return it->second;
     140             :   }
     141             : 
     142    55873265 :   ADReal elem_value = getElemValue(elem, time);
     143             : 
     144             :   // We'll save off the extrapolated boundary faces (ebf) for later assignment to the cache (these
     145             :   // are the keys). The boolean in the pair will denote whether the ebf face is a fully developed
     146             :   // flow (e.g. fdf) face
     147             :   std::vector<std::pair<const FaceInfo *, bool>> ebf_faces;
     148             : 
     149             :   try
     150             :   {
     151             :     VectorValue<ADReal> & grad = *value_pointer;
     152             : 
     153    55873265 :     bool volume_set = false;
     154    55873265 :     Real volume = 0;
     155             : 
     156             :     // If we are performing a two term Taylor expansion for extrapolated boundary faces (faces on
     157             :     // boundaries that do not have associated Dirichlet conditions), then the element gradient
     158             :     // depends on the boundary face value and the boundary face value depends on the element
     159             :     // gradient, so we have a system of equations to solve. Here is the system:
     160             :     //
     161             :     // \nabla \phi_C - \frac{1}{V} \sum_{ebf} \phi_{ebf} \vec{S_f} =
     162             :     //   \frac{1}{V} \sum_{of} \phi_{of} \vec{S_f}                                            eqn.
     163             :     //   1
     164             :     //
     165             :     // \phi_{ebf} - \vec{d_{Cf}} \cdot \nabla \phi_C = \phi_C                                 eqn.
     166             :     // 2
     167             :     //
     168             :     // where $C$ refers to the cell centroid, $ebf$ refers to an extrapolated boundary face, $of$
     169             :     // refers to "other faces", e.g. non-ebf faces, and $f$ is a general face. $d_{Cf}$ is the
     170             :     // vector drawn from the element centroid to the face centroid, and $\vec{S_f}$ is the surface
     171             :     // vector, e.g. the face area times the outward facing normal
     172             :     //
     173             :     // NOTE: On fully developed flow boundaries, we modify our equation set slightly. In equation
     174             :     // 2,
     175             :     // $\nabla \phi_C$ is replaced with $\nabla \phi_{ebf,fdf}$ where $fdf$ denotes fully
     176             :     // developed flow. Moreover, we introduce a third equation:
     177             :     //
     178             :     // \nabla \phi_{ebf,fdf} - \nabla \phi_C + (\nabla \phi_C \cdot \hat{n}) \hat{n} = 0      eqn.
     179             :     // 3
     180             :     //
     181             :     // These modifications correspond to Moukalled's equations 15.140 and 15.141, but with
     182             :     // $\hat{e_b}$ replaced with $\hat{n}$ because we believe the equation as written doesn't
     183             :     // reflect the intent of the text, which is to guarantee a zero normal gradient in the
     184             :     // direction of the surface normal
     185             : 
     186             :     // ebf eqns: element gradient coefficients, e.g. eqn. 2, LHS term 2 coefficient. *Note* that
     187             :     // each element of the std::vector could correspond to a cell centroid gradient or to a face
     188             :     // gradient computed on a fully developed flow face
     189             :     std::vector<VectorValue<Real>> ebf_grad_coeffs;
     190             :     // ebf eqns: rhs b values. These will actually correspond to the elem_value so we can use a
     191             :     // pointer and avoid copying. This is the RHS of eqn. 2
     192             :     std::vector<const ADReal *> ebf_b;
     193             : 
     194             :     // elem grad eqns: ebf coefficients, e.g. eqn. 1, LHS term 2 coefficients
     195             :     std::vector<VectorValue<Real>> grad_ebf_coeffs;
     196             :     // elem grad eqns: rhs b value, e.g. eqn. 1 RHS
     197    55873265 :     VectorValue<ADReal> grad_b = 0;
     198             : 
     199             :     // eqn. 3 coefficients for cell centroid gradient, e.g. the coefficients that fall out of term
     200             :     // 2 on the LHS of eqn. 3
     201             :     std::vector<TensorValue<Real>> fdf_grad_centroid_coeffs;
     202             : 
     203             :     const unsigned int lm_dim = LIBMESH_DIM;
     204             : 
     205   224940674 :     auto action_functor = [&volume_set,
     206             :                            &volume,
     207             :                            &elem_value,
     208             : #ifndef NDEBUG
     209             :                            &elem,
     210             : #endif
     211             :                            &ebf_faces,
     212             :                            &ebf_grad_coeffs,
     213             :                            &ebf_b,
     214             :                            &grad_ebf_coeffs,
     215             :                            &grad_b,
     216             :                            &fdf_grad_centroid_coeffs,
     217             :                            correct_skewness,
     218             :                            &time,
     219             :                            this](const Elem & functor_elem,
     220             :                                  const Elem * const neighbor,
     221             :                                  const FaceInfo * const fi,
     222             :                                  const Point & surface_vector,
     223             :                                  Real coord,
     224             :                                  const bool elem_has_info)
     225             :     {
     226             :       mooseAssert(fi, "We need a FaceInfo for this action_functor");
     227             :       mooseAssert(elem == &functor_elem,
     228             :                   "Just a sanity check that the element being passed in is the one we passed out.");
     229             : 
     230   224940674 :       if (isExtrapolatedBoundaryFace(*fi, &functor_elem, time))
     231             :       {
     232     3504906 :         if (_two_term_boundary_expansion)
     233             :         {
     234     2319249 :           const bool fdf_face = isFullyDevelopedFlowFace(*fi);
     235     2319249 :           ebf_faces.push_back(std::make_pair(fi, fdf_face));
     236             : 
     237             :           // eqn. 2
     238     4638498 :           ebf_grad_coeffs.push_back(-1. * (elem_has_info
     239     2319249 :                                                ? (fi->faceCentroid() - fi->elemCentroid())
     240             :                                                : (fi->faceCentroid() - fi->neighborCentroid())));
     241     2319249 :           ebf_b.push_back(&elem_value);
     242             : 
     243             :           // eqn. 1
     244     2319249 :           grad_ebf_coeffs.push_back(-surface_vector);
     245             : 
     246             :           // eqn. 3
     247     2319249 :           if (fdf_face)
     248             :           {
     249             :             // Will be nice in C++17 we'll get a returned reference from this method
     250      675001 :             fdf_grad_centroid_coeffs.emplace_back();
     251      675001 :             auto & current_coeffs = fdf_grad_centroid_coeffs.back();
     252      675001 :             const auto normal = fi->normal();
     253     2700004 :             for (const auto i : make_range(lm_dim))
     254     8100012 :               for (const auto j : make_range(lm_dim))
     255             :               {
     256             :                 auto & current_coeff = current_coeffs(i, j);
     257     6075009 :                 current_coeff = normal(i) * normal(j);
     258     6075009 :                 if (i == j)
     259     2025003 :                   current_coeff -= 1.;
     260             :               }
     261             :           }
     262             :         }
     263             :         else
     264             :           // We are doing a one-term expansion for the extrapolated boundary faces, in which case
     265             :           // we have no eqn. 2 and we have no second term in the LHS of eqn. 1. Instead we apply
     266             :           // the element centroid value as the face value (one-term expansion) in the RHS of eqn.
     267             :           // 1
     268     1185657 :           grad_b += surface_vector * elem_value;
     269             :       }
     270   221435768 :       else if (isInternalFace(*fi))
     271             :         grad_b +=
     272   426914834 :             surface_vector * (*this)(Moose::FaceArg({fi,
     273             :                                                      Moose::FV::LimiterType::CentralDifference,
     274             :                                                      true,
     275             :                                                      correct_skewness,
     276             :                                                      nullptr,
     277             :                                                      nullptr}),
     278   213457417 :                                      time);
     279             :       else
     280             :       {
     281             :         mooseAssert(isDirichletBoundaryFace(*fi, &functor_elem, time),
     282             :                     "We've run out of face types");
     283     7978351 :         grad_b += surface_vector * getDirichletBoundaryFaceValue(*fi, &functor_elem, time);
     284             :       }
     285             : 
     286   224940674 :       if (!volume_set)
     287             :       {
     288             :         // We use the FaceInfo volumes because those values have been pre-computed and cached.
     289             :         // An explicit call to elem->volume() here would incur unnecessary expense
     290    55873265 :         if (elem_has_info)
     291             :         {
     292     3882673 :           coordTransformFactor(
     293     3882673 :               this->_subproblem, functor_elem.subdomain_id(), fi->elemCentroid(), coord);
     294     3882673 :           volume = fi->elemVolume() * coord;
     295             :         }
     296             :         else
     297             :         {
     298    51990592 :           coordTransformFactor(
     299    51990592 :               this->_subproblem, neighbor->subdomain_id(), fi->neighborCentroid(), coord);
     300    51990592 :           volume = fi->neighborVolume() * coord;
     301             :         }
     302             : 
     303    55873265 :         volume_set = true;
     304             :       }
     305   280813939 :     };
     306             : 
     307    55873265 :     Moose::FV::loopOverElemFaceInfo(*elem, this->_mesh, this->_subproblem, action_functor);
     308             : 
     309             :     mooseAssert(volume_set && volume > 0, "We should have set the volume");
     310    55873265 :     grad_b /= volume;
     311             : 
     312    55873265 :     const auto coord_system = this->_subproblem.getCoordSystem(elem->subdomain_id());
     313    55873265 :     if (coord_system == Moose::CoordinateSystemType::COORD_RZ)
     314             :     {
     315     4326106 :       const auto r_coord = this->_subproblem.getAxisymmetricRadialCoord();
     316     8652212 :       grad_b(r_coord) -= elem_value / elem->vertex_average()(r_coord);
     317             :     }
     318             : 
     319             :     mooseAssert(
     320             :         coord_system != Moose::CoordinateSystemType::COORD_RSPHERICAL,
     321             :         "We have not yet implemented the correct translation from gradient to divergence for "
     322             :         "spherical coordinates yet.");
     323             : 
     324             :     mooseAssert(
     325             :         ebf_faces.size() < UINT_MAX,
     326             :         "You've created a mystical element that has more faces than can be held by unsigned "
     327             :         "int. I applaud you.");
     328    55873265 :     const auto num_ebfs = static_cast<unsigned int>(ebf_faces.size());
     329             : 
     330             :     // test for simple case
     331    55873265 :     if (num_ebfs == 0)
     332             :       grad = grad_b;
     333             :     else
     334             :     {
     335             :       // We have to solve a system
     336             :       const unsigned int sys_dim =
     337     2209910 :           lm_dim + num_ebfs + lm_dim * static_cast<unsigned int>(fdf_grad_centroid_coeffs.size());
     338     2209910 :       DenseVector<ADReal> x(sys_dim), b(sys_dim);
     339     2209910 :       DenseMatrix<ADReal> A(sys_dim, sys_dim);
     340             : 
     341             :       // eqn. 1
     342     8839640 :       for (const auto lm_dim_index : make_range(lm_dim))
     343             :       {
     344             :         // LHS term 1 coeffs
     345     6629730 :         A(lm_dim_index, lm_dim_index) = 1;
     346             : 
     347             :         // LHS term 2 coeffs
     348    13587477 :         for (const auto ebf_index : make_range(num_ebfs))
     349     6957747 :           A(lm_dim_index, lm_dim + ebf_index) = grad_ebf_coeffs[ebf_index](lm_dim_index) / volume;
     350             : 
     351             :         // RHS
     352     6629730 :         b(lm_dim_index) = grad_b(lm_dim_index);
     353             :       }
     354             : 
     355             :       unsigned int num_fdf_faces = 0;
     356             : 
     357             :       // eqn. 2
     358     4529159 :       for (const auto ebf_index : make_range(num_ebfs))
     359             :       {
     360             :         // LHS term 1 coeffs
     361     2319249 :         A(lm_dim + ebf_index, lm_dim + ebf_index) = 1;
     362             : 
     363     2319249 :         const bool fdf_face = ebf_faces[ebf_index].second;
     364             :         const unsigned int starting_j_index =
     365     2319249 :             fdf_face ? lm_dim + num_ebfs + num_fdf_faces * lm_dim : 0;
     366             : 
     367     2319249 :         num_fdf_faces += fdf_face;
     368             : 
     369             :         // LHS term 2 coeffs
     370     9276996 :         for (const auto lm_dim_index : make_range(lm_dim))
     371     6957747 :           A(lm_dim + ebf_index, starting_j_index + lm_dim_index) =
     372     6957747 :               ebf_grad_coeffs[ebf_index](lm_dim_index);
     373             : 
     374             :         // RHS
     375     2319249 :         b(lm_dim + ebf_index) = *ebf_b[ebf_index];
     376             :       }
     377             : 
     378             :       mooseAssert(num_fdf_faces == fdf_grad_centroid_coeffs.size(),
     379             :                   "Bad math in INSFVVelocityVariable::adGradlnSln(const Elem *). Please contact a "
     380             :                   "MOOSE developer");
     381             : 
     382             :       // eqn. 3
     383     2884911 :       for (const auto fdf_face_index : make_range(num_fdf_faces))
     384             :       {
     385      675001 :         const auto starting_i_index = lm_dim + num_ebfs + fdf_face_index * lm_dim;
     386             : 
     387     2700004 :         for (const auto lm_dim_i_index : make_range(lm_dim))
     388             :         {
     389     2025003 :           auto i_index = starting_i_index + lm_dim_i_index;
     390     2025003 :           A(i_index, i_index) = 1;
     391             : 
     392     8100012 :           for (const auto lm_dim_j_index : make_range(lm_dim))
     393             :             // j_index = lm_dim_j_index
     394             :             A(i_index, lm_dim_j_index) =
     395     6075009 :                 fdf_grad_centroid_coeffs[fdf_face_index](lm_dim_i_index, lm_dim_j_index);
     396             :         }
     397             :       }
     398             : 
     399     2209910 :       A.lu_solve(b, x);
     400     8827960 :       for (const auto lm_dim_index : make_range(lm_dim))
     401     6620970 :         grad(lm_dim_index) = x(lm_dim_index);
     402     2209910 :     }
     403             : 
     404    55870345 :     if (_cache_cell_gradients && !correct_skewness)
     405             :     {
     406             :       auto pr = _elem_to_grad.emplace(elem, std::move(grad));
     407             :       mooseAssert(pr.second, "Insertion should have just happened.");
     408    55684315 :       return pr.first->second;
     409             :     }
     410             :     else
     411             :       return grad;
     412    55882025 :   }
     413        2920 :   catch (libMesh::LogicError &)
     414             :   {
     415             :     // Retry without two-term
     416             :     mooseAssert(_two_term_boundary_expansion,
     417             :                 "I believe we should only get singular systems when two-term boundary expansion is "
     418             :                 "being used");
     419        2920 :     const_cast<INSFVVelocityVariable *>(this)->_two_term_boundary_expansion = false;
     420        2920 :     const auto & grad = adGradSln(elem, time, correct_skewness);
     421             : 
     422             :     // Two term boundary expansion should only fail at domain corners. We want to keep trying it
     423             :     // at other boundary locations
     424        2920 :     const_cast<INSFVVelocityVariable *>(this)->_two_term_boundary_expansion = true;
     425             : 
     426             :     return grad;
     427        2920 :   }
     428    55873265 : }

Generated by: LCOV version 1.14