LCOV - code coverage report
Current view: top level - src/userobjects - NodalPatchRecoveryBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: fef103 Lines: 146 153 95.4 %
Date: 2025-09-03 20:01:23 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        7096 : removeDuplicateEntries(const std::vector<dof_id_type> & ids)
      28             : {
      29        7096 :   std::vector<dof_id_type> key = ids;
      30        7096 :   std::sort(key.begin(), key.end());
      31        7096 :   key.erase(std::unique(key.begin(), key.end()), key.end());
      32        7096 :   return key;
      33           0 : }
      34             : 
      35             : InputParameters
      36       29267 : NodalPatchRecoveryBase::validParams()
      37             : {
      38       29267 :   InputParameters params = ElementUserObject::validParams();
      39             : 
      40      117068 :   MooseEnum orders("CONSTANT FIRST SECOND THIRD FOURTH");
      41       87801 :   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       87801 :   params.addRelationshipManager("ElementSideNeighborLayers",
      48             :                                 Moose::RelationshipManagerType::ALGEBRAIC,
      49       30314 :                                 [](const InputParameters &, InputParameters & rm_params)
      50        2094 :                                 { rm_params.set<unsigned short>("layers") = 2; });
      51             : 
      52       87801 :   params.addParamNamesToGroup("patch_polynomial_order", "Advanced");
      53             : 
      54       58534 :   return params;
      55       29267 : }
      56             : 
      57         380 : NodalPatchRecoveryBase::NodalPatchRecoveryBase(const InputParameters & parameters)
      58             :   : ElementUserObject(parameters),
      59         380 :     _qp(0),
      60         380 :     _patch_polynomial_order(
      61         380 :         static_cast<unsigned int>(getParam<MooseEnum>("patch_polynomial_order"))),
      62         380 :     _multi_index(MathUtils::multiIndex(_mesh.dimension(), _patch_polynomial_order)),
      63         380 :     _q(_multi_index.size()),
      64         380 :     _distributed_mesh(_mesh.isDistributedMesh()),
      65        1520 :     _proc_ids(n_processors())
      66             : {
      67         380 :   std::iota(_proc_ids.begin(), _proc_ids.end(), 0);
      68         380 : }
      69             : 
      70             : Real
      71        5566 : NodalPatchRecoveryBase::nodalPatchRecovery(const Point & x,
      72             :                                            const std::vector<dof_id_type> & elem_ids) const
      73             : {
      74        5566 :   const RealEigenVector coef = getCoefficients(elem_ids); // const version
      75             :   // Compute the fitted nodal value
      76        5566 :   RealEigenVector p = evaluateBasisFunctions(x);
      77       11132 :   return p.dot(coef);
      78        5566 : }
      79             : 
      80             : const RealEigenVector
      81        6331 : NodalPatchRecoveryBase::getCoefficients(const std::vector<dof_id_type> & elem_ids) const
      82             : {
      83        6331 :   auto elem_ids_reduced = removeDuplicateEntries(elem_ids);
      84             : 
      85        6331 :   RealEigenVector coef = RealEigenVector::Zero(_q);
      86             :   // Before we go, check if we have enough sample points for solving the least square fitting
      87        6331 :   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        6331 :   RealEigenMatrix A = RealEigenMatrix::Zero(_q, _q);
      93        6331 :   RealEigenVector b = RealEigenVector::Zero(_q);
      94       31647 :   for (auto elem_id : elem_ids_reduced)
      95             :   {
      96       25316 :     const auto elem = _mesh.elemPtr(elem_id);
      97       25316 :     if (elem /*prevent segmentation fault in distributed mesh*/)
      98       25003 :       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       25316 :     if (_Ae.find(elem_id) == _Ae.end())
     105           0 :       mooseError("Missing entry for elem_id = ", elem_id, " in _Ae.");
     106       25316 :     if (_be.find(elem_id) == _be.end())
     107           0 :       mooseError("Missing entry for elem_id = ", elem_id, " in _be.");
     108             : 
     109       25316 :     A += libmesh_map_find(_Ae, elem_id);
     110       25316 :     b += libmesh_map_find(_be, elem_id);
     111             :   }
     112             : 
     113             :   // Solve the least squares fitting
     114        6331 :   coef = A.completeOrthogonalDecomposition().solve(b);
     115             : 
     116       12662 :   return coef;
     117        6331 : }
     118             : 
     119             : const RealEigenVector
     120         765 : NodalPatchRecoveryBase::getCachedCoefficients(const std::vector<dof_id_type> & elem_ids)
     121             : {
     122             :   // Check cache
     123         765 :   auto key = removeDuplicateEntries(elem_ids);
     124             : 
     125         765 :   if (key == _cached_elem_ids)
     126           0 :     return _cached_coef;
     127             :   else
     128             :   {
     129         765 :     const auto coef = getCoefficients(key); // const version
     130             : 
     131         765 :     _cached_elem_ids = key; // Update the cached element IDs
     132         765 :     _cached_coef = coef;    // Update the cached coefficients
     133             : 
     134         765 :     return coef;
     135         765 :   }
     136         765 : }
     137             : 
     138             : RealEigenVector
     139      133421 : NodalPatchRecoveryBase::evaluateBasisFunctions(const Point & q_point) const
     140             : {
     141      133421 :   RealEigenVector p(_q);
     142             :   Real polynomial;
     143      789940 :   for (unsigned int r = 0; r < _multi_index.size(); r++)
     144             :   {
     145      656519 :     polynomial = 1.0;
     146             :     mooseAssert(_multi_index[r].size() == _mesh.dimension(), "Wrong multi-index size.");
     147     2335637 :     for (unsigned int c = 0; c < _multi_index[r].size(); c++)
     148     2421864 :       for (unsigned int p = 0; p < _multi_index[r][c]; p++)
     149      742746 :         polynomial *= q_point(c);
     150      656519 :     p(r) = polynomial;
     151             :   }
     152      133421 :   return p;
     153           0 : }
     154             : 
     155             : void
     156        1312 : 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        1312 :   _cached_elem_ids.clear();
     163        1312 :   _cached_coef = RealEigenVector::Zero(_q);
     164        1312 :   _Ae.clear();
     165        1312 :   _be.clear();
     166        1312 : }
     167             : 
     168             : void
     169       26449 : NodalPatchRecoveryBase::execute()
     170             : {
     171       26449 :   RealEigenMatrix Ae = RealEigenMatrix::Zero(_q, _q);
     172       26449 :   RealEigenVector be = RealEigenVector::Zero(_q);
     173      154304 :   for (_qp = 0; _qp < _qrule->n_points(); _qp++)
     174             :   {
     175      127855 :     RealEigenVector p = evaluateBasisFunctions(_q_point[_qp]);
     176      127855 :     Ae += p * p.transpose();
     177      127855 :     be += computeValue() * p;
     178      127855 :   }
     179             : 
     180       26449 :   dof_id_type elem_id = _current_elem->id();
     181             : 
     182       26449 :   _Ae[elem_id] = Ae;
     183       26449 :   _be[elem_id] = be;
     184       26449 : }
     185             : 
     186             : void
     187          99 : NodalPatchRecoveryBase::threadJoin(const UserObject & uo)
     188             : {
     189          99 :   const auto & npr = static_cast<const NodalPatchRecoveryBase &>(uo);
     190          99 :   _Ae.insert(npr._Ae.begin(), npr._Ae.end());
     191          99 :   _be.insert(npr._be.begin(), npr._be.end());
     192          99 : }
     193             : 
     194             : void
     195        1213 : 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        1213 :   sync();
     201        1213 : }
     202             : 
     203             : std::unordered_map<processor_id_type, std::vector<dof_id_type>>
     204         765 : NodalPatchRecoveryBase::gatherRequestList(const std::vector<dof_id_type> & specific_elems)
     205             : {
     206         765 :   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         765 :   std::unordered_map<processor_id_type, std::vector<PidElemPair>> push_data;
     210             : 
     211        7129 :   for (const auto & entry : specific_elems)
     212             :   {
     213        6364 :     const auto * elem = _mesh.elemPtr(entry);
     214        6364 :     if (_distributed_mesh)
     215             :     {
     216        1024 :       if (!elem)
     217         313 :         continue; // Prevent segmentation fault in distributed mesh
     218             : 
     219         711 :       if (hasBlocks(elem->subdomain_id()))
     220        2133 :         for (processor_id_type pid = 0; pid < n_processors(); ++pid)
     221        1422 :           if (pid != processor_id())
     222         711 :             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        5340 :       addToQuery(elem, query_ids);
     227             :   }
     228             : 
     229         765 :   if (_distributed_mesh)
     230             :   {
     231             :     auto push_receiver =
     232          92 :         [&](const processor_id_type, const std::vector<PidElemPair> & received_data)
     233             :     {
     234         803 :       for (const auto & [pid, id] : received_data)
     235         711 :         query_ids[pid].push_back(id);
     236          92 :     };
     237             : 
     238         124 :     Parallel::push_parallel_vector_data(_mesh.comm(), push_data, push_receiver);
     239             :   }
     240             : 
     241        1530 :   return query_ids;
     242         765 : }
     243             : 
     244             : std::unordered_map<processor_id_type, std::vector<dof_id_type>>
     245        1213 : NodalPatchRecoveryBase::gatherRequestList()
     246             : {
     247        1213 :   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        1213 :   std::unordered_map<processor_id_type, std::vector<PidElemPair>> push_data;
     251             : 
     252       71816 :   for (const auto & elem : _fe_problem.getEvaluableElementRange())
     253       70603 :     addToQuery(elem, query_ids);
     254             : 
     255        2426 :   return query_ids;
     256        1213 : }
     257             : 
     258             : void
     259        1213 : NodalPatchRecoveryBase::sync()
     260             : {
     261        1213 :   const auto query_ids = gatherRequestList();
     262        1213 :   syncHelper(query_ids);
     263        1213 : }
     264             : 
     265             : void
     266         765 : NodalPatchRecoveryBase::sync(const std::vector<dof_id_type> & specific_elems)
     267             : {
     268         765 :   const auto query_ids = gatherRequestList(specific_elems);
     269         765 :   syncHelper(query_ids);
     270         765 : }
     271             : 
     272             : void
     273        1978 : 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         815 :   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        5990 :     for (const auto & elem_id : elem_ids)
     284        5175 :       ab_pairs.emplace_back(libmesh_map_find(_Ae, elem_id), libmesh_map_find(_be, elem_id));
     285         815 :   };
     286             : 
     287             :   // Gather answers received from other processors
     288         815 :   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        5990 :     for (const auto i : index_range(elem_ids))
     293             :     {
     294        5175 :       const auto elem_id = elem_ids[i];
     295        5175 :       const auto & [Ae, be] = ab_pairs[i];
     296        5175 :       _Ae[elem_id] = Ae;
     297        5175 :       _be[elem_id] = be;
     298             :     }
     299         815 :   };
     300             : 
     301             :   // the send and receive are called inside the pull_parallel_vector_data function
     302        1978 :   libMesh::Parallel::pull_parallel_vector_data<AbPair>(
     303        1978 :       _communicator, query_ids, gather_data, act_on_data, 0);
     304        1978 : }
     305             : 
     306             : void
     307       75943 : NodalPatchRecoveryBase::addToQuery(
     308             :     const libMesh::Elem * elem,
     309             :     std::unordered_map<processor_id_type, std::vector<dof_id_type>> & query_ids) const
     310             : {
     311       75943 :   if (hasBlocks(elem->subdomain_id()) && elem->processor_id() != processor_id())
     312        4464 :     query_ids[elem->processor_id()].push_back(elem->id());
     313       75943 : }

Generated by: LCOV version 1.14