LCOV - code coverage report
Current view: top level - src/userobjects - NodalPatchRecoveryBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 146 153 95.4 %
Date: 2026-05-29 20:35:17 Functions: 21 21 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             : #include <iomanip>
      20             : 
      21             : // Remove duplicates and sort entries from a vector of element IDs.
      22             : // This is necessary because user input may contain repeated element IDs,
      23             : // which is problematic when using these IDs as keys.
      24             : // In Patch Recovery, we also want to avoid duplicate elements contributing
      25             : // multiple times to the Ae and be.
      26             : static std::vector<dof_id_type>
      27        6435 : removeDuplicateEntries(const std::vector<dof_id_type> & ids)
      28             : {
      29        6435 :   std::vector<dof_id_type> key = ids;
      30        6435 :   std::sort(key.begin(), key.end());
      31        6435 :   key.erase(std::unique(key.begin(), key.end()), key.end());
      32        6435 :   return key;
      33           0 : }
      34             : 
      35             : InputParameters
      36        6841 : NodalPatchRecoveryBase::validParams()
      37             : {
      38        6841 :   InputParameters params = ElementUserObject::validParams();
      39             : 
      40       27364 :   MooseEnum orders("CONSTANT FIRST SECOND THIRD FOURTH");
      41       20523 :   params.addRequiredParam<MooseEnum>(
      42             :       "patch_polynomial_order",
      43             :       orders,
      44             :       "Polynomial order used in least squares fitting of material property "
      45             :       "over the local patch of elements connected to a given node");
      46             : 
      47       20523 :   params.addRelationshipManager("ElementSideNeighborLayers",
      48             :                                 Moose::RelationshipManagerType::ALGEBRAIC,
      49        7858 :                                 [](const InputParameters &, InputParameters & rm_params)
      50        2034 :                                 { rm_params.set<unsigned short>("layers") = 2; });
      51             : 
      52       20523 :   params.addParamNamesToGroup("patch_polynomial_order", "Advanced");
      53             : 
      54       13682 :   return params;
      55        6841 : }
      56             : 
      57         374 : NodalPatchRecoveryBase::NodalPatchRecoveryBase(const InputParameters & parameters)
      58             :   : ElementUserObject(parameters),
      59         374 :     _qp(0),
      60         374 :     _patch_polynomial_order(
      61         374 :         static_cast<unsigned int>(getParam<MooseEnum>("patch_polynomial_order"))),
      62         374 :     _multi_index(MathUtils::multiIndex(_mesh.dimension(), _patch_polynomial_order)),
      63         374 :     _q(_multi_index.size()),
      64         374 :     _distributed_mesh(_mesh.isDistributedMesh()),
      65        1496 :     _proc_ids(n_processors())
      66             : {
      67         374 :   std::iota(_proc_ids.begin(), _proc_ids.end(), 0);
      68         374 : }
      69             : 
      70             : Real
      71        4961 : NodalPatchRecoveryBase::nodalPatchRecovery(const Point & x,
      72             :                                            const std::vector<dof_id_type> & elem_ids) const
      73             : {
      74        4961 :   const RealEigenVector coef = getCoefficients(elem_ids); // const version
      75             :   // Compute the fitted nodal value
      76        4961 :   RealEigenVector p = evaluateBasisFunctions(x);
      77        9922 :   return p.dot(coef);
      78        4961 : }
      79             : 
      80             : const RealEigenVector
      81        5698 : NodalPatchRecoveryBase::getCoefficients(const std::vector<dof_id_type> & elem_ids) const
      82             : {
      83        5698 :   auto elem_ids_reduced = removeDuplicateEntries(elem_ids);
      84             : 
      85        5698 :   RealEigenVector coef = RealEigenVector::Zero(_q);
      86             :   // Before we go, check if we have enough sample points for solving the least square fitting
      87        5698 :   if (_q_point.size() * elem_ids_reduced.size() < _q)
      88           0 :     mooseError("There are not enough sample points to recover the nodal value, try reducing the "
      89             :                "polynomial order or using a higher-order quadrature scheme.");
      90             : 
      91             :   // Assemble the least squares problem over the patch
      92        5698 :   RealEigenMatrix A = RealEigenMatrix::Zero(_q, _q);
      93        5698 :   RealEigenVector b = RealEigenVector::Zero(_q);
      94       28578 :   for (auto elem_id : elem_ids_reduced)
      95             :   {
      96       22880 :     const auto elem = _mesh.elemPtr(elem_id);
      97       22880 :     if (elem /*prevent segmentation fault in distributed mesh*/)
      98       22559 :       if (!hasBlocks(elem->subdomain_id()))
      99           0 :         mooseError("Element with id = ",
     100             :                    elem_id,
     101             :                    " is not in the block. "
     102             :                    "Please use nodalPatchRecovery with elements in the block only.");
     103             : 
     104       22880 :     if (_Ae.find(elem_id) == _Ae.end())
     105           0 :       mooseError("Missing entry for elem_id = ", elem_id, " in _Ae.");
     106       22880 :     if (_be.find(elem_id) == _be.end())
     107           0 :       mooseError("Missing entry for elem_id = ", elem_id, " in _be.");
     108             : 
     109       22880 :     A += libmesh_map_find(_Ae, elem_id);
     110       22880 :     b += libmesh_map_find(_be, elem_id);
     111             :   }
     112             : 
     113             :   // Solve the least squares fitting
     114        5698 :   coef = A.completeOrthogonalDecomposition().solve(b);
     115             : 
     116       11396 :   return coef;
     117        5698 : }
     118             : 
     119             : const RealEigenVector
     120         737 : NodalPatchRecoveryBase::getCachedCoefficients(const std::vector<dof_id_type> & elem_ids)
     121             : {
     122             :   // Check cache
     123         737 :   auto key = removeDuplicateEntries(elem_ids);
     124             : 
     125         737 :   if (key == _cached_elem_ids)
     126           0 :     return _cached_coef;
     127             :   else
     128             :   {
     129         737 :     const auto coef = getCoefficients(key); // const version
     130             : 
     131         737 :     _cached_elem_ids = key; // Update the cached element IDs
     132         737 :     _cached_coef = coef;    // Update the cached coefficients
     133             : 
     134         737 :     return coef;
     135         737 :   }
     136         737 : }
     137             : 
     138             : RealEigenVector
     139      121799 : NodalPatchRecoveryBase::evaluateBasisFunctions(const Point & q_point) const
     140             : {
     141      121799 :   RealEigenVector p(_q);
     142             :   Real polynomial;
     143      716572 :   for (unsigned int r = 0; r < _multi_index.size(); r++)
     144             :   {
     145      594773 :     polynomial = 1.0;
     146             :     mooseAssert(_multi_index[r].size() == _mesh.dimension(), "Wrong multi-index size.");
     147     2111999 :     for (unsigned int c = 0; c < _multi_index[r].size(); c++)
     148     2186808 :       for (unsigned int p = 0; p < _multi_index[r][c]; p++)
     149      669582 :         polynomial *= q_point(c);
     150      594773 :     p(r) = polynomial;
     151             :   }
     152      121799 :   return p;
     153           0 : }
     154             : 
     155             : void
     156        1262 : NodalPatchRecoveryBase::initialize()
     157             : {
     158             :   // Clear cached data to ensure coefficients are correctly recomputed in the next patch recovery
     159             :   // iteration. _Ae and _be must also be reset, as the associated patch elements may differ in the
     160             :   // upcoming iteration.
     161             : 
     162        1262 :   _cached_elem_ids.clear();
     163        1262 :   _cached_coef = RealEigenVector::Zero(_q);
     164        1262 :   _Ae.clear();
     165        1262 :   _be.clear();
     166        1262 : }
     167             : 
     168             : void
     169       24276 : NodalPatchRecoveryBase::execute()
     170             : {
     171       24276 :   RealEigenMatrix Ae = RealEigenMatrix::Zero(_q, _q);
     172       24276 :   RealEigenVector be = RealEigenVector::Zero(_q);
     173      141114 :   for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     174             :   {
     175      116838 :     RealEigenVector p = evaluateBasisFunctions(_q_point[_qp]);
     176      116838 :     Ae += p * p.transpose();
     177      116838 :     be += computeValue() * p;
     178      116838 :   }
     179             : 
     180       24276 :   dof_id_type elem_id = _current_elem->id();
     181             : 
     182       24276 :   _Ae[elem_id] = Ae;
     183       24276 :   _be[elem_id] = be;
     184       24276 : }
     185             : 
     186             : void
     187         103 : NodalPatchRecoveryBase::threadJoin(const UserObject & uo)
     188             : {
     189         103 :   const auto & npr = static_cast<const NodalPatchRecoveryBase &>(uo);
     190         103 :   _Ae.insert(npr._Ae.begin(), npr._Ae.end());
     191         103 :   _be.insert(npr._be.begin(), npr._be.end());
     192         103 : }
     193             : 
     194             : void
     195        1159 : NodalPatchRecoveryBase::finalize()
     196             : {
     197             :   // When calling nodalPatchRecovery, we may need to know _Ae and _be on algebraically ghosted
     198             :   // elements. However, this userobject is only run on local elements, so we need to query those
     199             :   // information from other processors in this finalize() method.
     200        1159 :   sync();
     201        1159 : }
     202             : 
     203             : std::unordered_map<processor_id_type, std::vector<dof_id_type>>
     204         737 : NodalPatchRecoveryBase::gatherRequestList(const std::vector<dof_id_type> & specific_elems)
     205             : {
     206         737 :   std::unordered_map<processor_id_type, std::vector<dof_id_type>> query_ids;
     207             : 
     208             :   typedef std::pair<processor_id_type, dof_id_type> PidElemPair;
     209         737 :   std::unordered_map<processor_id_type, std::vector<PidElemPair>> push_data;
     210             : 
     211        6725 :   for (const auto & entry : specific_elems)
     212             :   {
     213        5988 :     const auto * elem = _mesh.elemPtr(entry);
     214        5988 :     if (_distributed_mesh)
     215             :     {
     216        1048 :       if (!elem)
     217         321 :         continue; // Prevent segmentation fault in distributed mesh
     218             : 
     219         727 :       if (hasBlocks(elem->subdomain_id()))
     220        2181 :         for (processor_id_type pid = 0; pid < n_processors(); ++pid)
     221        1454 :           if (pid != processor_id())
     222         727 :             push_data[pid].push_back(std::make_pair(elem->processor_id(), elem->id()));
     223             :     }
     224             :     else
     225             :       // Add to query_ids if this element's data is on a different processor
     226        4940 :       addToQuery(elem, query_ids);
     227             :   }
     228             : 
     229         737 :   if (_distributed_mesh)
     230             :   {
     231             :     auto push_receiver =
     232          96 :         [&](const processor_id_type, const std::vector<PidElemPair> & received_data)
     233             :     {
     234         823 :       for (const auto & [pid, id] : received_data)
     235         727 :         query_ids[pid].push_back(id);
     236          96 :     };
     237             : 
     238         130 :     Parallel::push_parallel_vector_data(_mesh.comm(), push_data, push_receiver);
     239             :   }
     240             : 
     241        1474 :   return query_ids;
     242         737 : }
     243             : 
     244             : std::unordered_map<processor_id_type, std::vector<dof_id_type>>
     245        1159 : NodalPatchRecoveryBase::gatherRequestList()
     246             : {
     247        1159 :   std::unordered_map<processor_id_type, std::vector<dof_id_type>> query_ids;
     248             : 
     249             :   typedef std::pair<processor_id_type, dof_id_type> PidElemPair;
     250        1159 :   std::unordered_map<processor_id_type, std::vector<PidElemPair>> push_data;
     251             : 
     252       67133 :   for (const auto & elem : _fe_problem.getEvaluableElementRange())
     253       65974 :     addToQuery(elem, query_ids);
     254             : 
     255        2318 :   return query_ids;
     256        1159 : }
     257             : 
     258             : void
     259        1159 : NodalPatchRecoveryBase::sync()
     260             : {
     261        1159 :   const auto query_ids = gatherRequestList();
     262        1159 :   syncHelper(query_ids);
     263        1159 : }
     264             : 
     265             : void
     266         737 : NodalPatchRecoveryBase::sync(const std::vector<dof_id_type> & specific_elems)
     267             : {
     268         737 :   const auto query_ids = gatherRequestList(specific_elems);
     269         737 :   syncHelper(query_ids);
     270         737 : }
     271             : 
     272             : void
     273        1896 : NodalPatchRecoveryBase::syncHelper(
     274             :     const std::unordered_map<processor_id_type, std::vector<dof_id_type>> & query_ids)
     275             : {
     276             :   typedef std::pair<RealEigenMatrix, RealEigenVector> AbPair;
     277             : 
     278             :   // Answer queries received from other processors
     279         849 :   auto gather_data = [this](const processor_id_type /*pid*/,
     280             :                             const std::vector<dof_id_type> & elem_ids,
     281             :                             std::vector<AbPair> & ab_pairs)
     282             :   {
     283        6178 :     for (const auto & elem_id : elem_ids)
     284        5329 :       ab_pairs.emplace_back(libmesh_map_find(_Ae, elem_id), libmesh_map_find(_be, elem_id));
     285         849 :   };
     286             : 
     287             :   // Gather answers received from other processors
     288         849 :   auto act_on_data = [this](const processor_id_type /*pid*/,
     289             :                             const std::vector<dof_id_type> & elem_ids,
     290             :                             const std::vector<AbPair> & ab_pairs)
     291             :   {
     292        6178 :     for (const auto i : index_range(elem_ids))
     293             :     {
     294        5329 :       const auto elem_id = elem_ids[i];
     295        5329 :       const auto & [Ae, be] = ab_pairs[i];
     296        5329 :       _Ae[elem_id] = Ae;
     297        5329 :       _be[elem_id] = be;
     298             :     }
     299         849 :   };
     300             : 
     301             :   // the send and receive are called inside the pull_parallel_vector_data function
     302        1896 :   libMesh::Parallel::pull_parallel_vector_data<AbPair>(
     303        1896 :       _communicator, query_ids, gather_data, act_on_data, 0);
     304        1896 : }
     305             : 
     306             : void
     307       70914 : NodalPatchRecoveryBase::addToQuery(
     308             :     const libMesh::Elem * elem,
     309             :     std::unordered_map<processor_id_type, std::vector<dof_id_type>> & query_ids) const
     310             : {
     311       70914 :   if (hasBlocks(elem->subdomain_id()) && elem->processor_id() != processor_id())
     312        4602 :     query_ids[elem->processor_id()].push_back(elem->id());
     313       70914 : }

Generated by: LCOV version 1.14