LCOV - code coverage report
Current view: top level - src/userobjects - NodalPatchRecoveryBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 80 83 96.4 %
Date: 2025-07-17 01:28:37 Functions: 11 11 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 "NodalPatchRecoveryBase.h"
      11             : #include "MathUtils.h"
      12             : 
      13             : #include <Eigen/Dense>
      14             : 
      15             : // TIMPI includes
      16             : #include "timpi/communicator.h"
      17             : #include "timpi/parallel_sync.h"
      18             : #include "libmesh/parallel_eigen.h"
      19             : 
      20             : InputParameters
      21       14290 : NodalPatchRecoveryBase::validParams()
      22             : {
      23       14290 :   InputParameters params = ElementUserObject::validParams();
      24             : 
      25       14290 :   MooseEnum orders("CONSTANT FIRST SECOND THIRD FOURTH");
      26       14290 :   params.addRequiredParam<MooseEnum>(
      27             :       "patch_polynomial_order",
      28             :       orders,
      29             :       "Polynomial order used in least squares fitting of material property "
      30             :       "over the local patch of elements connected to a given node");
      31             : 
      32       14290 :   params.addRelationshipManager("ElementSideNeighborLayers",
      33             :                                 Moose::RelationshipManagerType::ALGEBRAIC,
      34           0 :                                 [](const InputParameters &, InputParameters & rm_params)
      35          36 :                                 { rm_params.set<unsigned short>("layers") = 2; });
      36             : 
      37       14290 :   params.addParamNamesToGroup("patch_polynomial_order", "Advanced");
      38             : 
      39       28580 :   return params;
      40       14290 : }
      41             : 
      42          13 : NodalPatchRecoveryBase::NodalPatchRecoveryBase(const InputParameters & parameters)
      43             :   : ElementUserObject(parameters),
      44          13 :     _qp(0),
      45          13 :     _patch_polynomial_order(
      46          13 :         static_cast<unsigned int>(getParam<MooseEnum>("patch_polynomial_order"))),
      47          13 :     _multi_index(MathUtils::multiIndex(_mesh.dimension(), _patch_polynomial_order)),
      48          26 :     _q(_multi_index.size())
      49             : {
      50          13 : }
      51             : 
      52             : Real
      53        4840 : NodalPatchRecoveryBase::nodalPatchRecovery(const Point & x,
      54             :                                            const std::vector<dof_id_type> & elem_ids) const
      55             : {
      56             :   // Before we go, check if we have enough sample points for solving the least square fitting
      57        4840 :   if (_q_point.size() * elem_ids.size() < _q)
      58           0 :     mooseError("There are not enough sample points to recover the nodal value, try reducing the "
      59             :                "polynomial order or using a higher-order quadrature scheme.");
      60             : 
      61             :   // Assemble the least squares problem over the patch
      62        4840 :   RealEigenMatrix A = RealEigenMatrix::Zero(_q, _q);
      63        4840 :   RealEigenVector b = RealEigenVector::Zero(_q);
      64       21320 :   for (auto elem_id : elem_ids)
      65             :   {
      66       16480 :     A += libmesh_map_find(_Ae, elem_id);
      67       16480 :     b += libmesh_map_find(_be, elem_id);
      68             :   }
      69             : 
      70             :   // Solve the least squares fitting
      71        4840 :   RealEigenVector coef = A.completeOrthogonalDecomposition().solve(b);
      72             : 
      73             :   // Compute the fitted nodal value
      74        4840 :   RealEigenVector p = evaluateBasisFunctions(x);
      75        9680 :   return p.dot(coef);
      76        4840 : }
      77             : 
      78             : RealEigenVector
      79       20840 : NodalPatchRecoveryBase::evaluateBasisFunctions(const Point & q_point) const
      80             : {
      81       20840 :   RealEigenVector p(_q);
      82             :   Real polynomial;
      83       83360 :   for (unsigned int r = 0; r < _multi_index.size(); r++)
      84             :   {
      85       62520 :     polynomial = 1.0;
      86             :     mooseAssert(_multi_index[r].size() == _mesh.dimension(), "Wrong multi-index size.");
      87      187560 :     for (unsigned int c = 0; c < _multi_index[r].size(); c++)
      88      166720 :       for (unsigned int p = 0; p < _multi_index[r][c]; p++)
      89       41680 :         polynomial *= q_point(c);
      90       62520 :     p(r) = polynomial;
      91             :   }
      92       20840 :   return p;
      93           0 : }
      94             : 
      95             : void
      96          60 : NodalPatchRecoveryBase::initialize()
      97             : {
      98          60 :   _Ae.clear();
      99          60 :   _be.clear();
     100          60 : }
     101             : 
     102             : void
     103        4000 : NodalPatchRecoveryBase::execute()
     104             : {
     105        4000 :   RealEigenMatrix Ae = RealEigenMatrix::Zero(_q, _q);
     106        4000 :   RealEigenVector be = RealEigenVector::Zero(_q);
     107       20000 :   for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     108             :   {
     109       16000 :     RealEigenVector p = evaluateBasisFunctions(_q_point[_qp]);
     110       16000 :     Ae += p * p.transpose();
     111       16000 :     be += computeValue() * p;
     112       16000 :   }
     113             : 
     114        4000 :   dof_id_type elem_id = _current_elem->id();
     115        4000 :   _Ae[elem_id] = Ae;
     116        4000 :   _be[elem_id] = be;
     117        4000 : }
     118             : 
     119             : void
     120           5 : NodalPatchRecoveryBase::threadJoin(const UserObject & uo)
     121             : {
     122           5 :   const auto & npr = static_cast<const NodalPatchRecoveryBase &>(uo);
     123           5 :   _Ae.insert(npr._Ae.begin(), npr._Ae.end());
     124           5 :   _be.insert(npr._be.begin(), npr._be.end());
     125           5 : }
     126             : 
     127             : void
     128          55 : NodalPatchRecoveryBase::finalize()
     129             : {
     130             :   // When calling nodalPatchRecovery, we may need to know _Ae and _be on algebraically ghosted
     131             :   // elements. However, this userobject is only run on local elements, so we need to query those
     132             :   // information from other processors in this finalize() method.
     133             : 
     134             :   // Populate algebraically ghosted elements to query
     135          55 :   std::unordered_map<processor_id_type, std::vector<dof_id_type>> query_ids;
     136          55 :   const ConstElemRange evaluable_elem_range = _fe_problem.getEvaluableElementRange();
     137        4555 :   for (auto elem : evaluable_elem_range)
     138        4500 :     if (elem->processor_id() != processor_id())
     139         500 :       query_ids[elem->processor_id()].push_back(elem->id());
     140             : 
     141             :   typedef std::pair<RealEigenMatrix, RealEigenVector> AbPair;
     142             : 
     143             :   // Answer queries received from other processors
     144          30 :   auto gather_data = [this](const processor_id_type /*pid*/,
     145             :                             const std::vector<dof_id_type> & elem_ids,
     146        1000 :                             std::vector<AbPair> & ab_pairs)
     147             :   {
     148         530 :     for (const auto i : index_range(elem_ids))
     149             :     {
     150         500 :       const auto elem_id = elem_ids[i];
     151         500 :       ab_pairs.emplace_back(libmesh_map_find(_Ae, elem_id), libmesh_map_find(_be, elem_id));
     152             :     }
     153          30 :   };
     154             : 
     155             :   // Gather answers received from other processors
     156          30 :   auto act_on_data = [this](const processor_id_type /*pid*/,
     157             :                             const std::vector<dof_id_type> & elem_ids,
     158        1000 :                             const std::vector<AbPair> & ab_pairs)
     159             :   {
     160         530 :     for (const auto i : index_range(elem_ids))
     161             :     {
     162         500 :       const auto elem_id = elem_ids[i];
     163         500 :       const auto & [Ae, be] = ab_pairs[i];
     164         500 :       _Ae[elem_id] = Ae;
     165         500 :       _be[elem_id] = be;
     166             :     }
     167          30 :   };
     168             : 
     169          55 :   libMesh::Parallel::pull_parallel_vector_data<AbPair>(
     170          55 :       _communicator, query_ids, gather_data, act_on_data, 0);
     171          55 : }

Generated by: LCOV version 1.14