LCOV - code coverage report
Current view: top level - src/auxkernels - NodalPatchRecovery.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 0 128 0.0 %
Date: 2025-07-17 01:28:37 Functions: 0 9 0.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 "NodalPatchRecovery.h"
      11             : #include "SwapBackSentinel.h"
      12             : 
      13             : InputParameters
      14           0 : NodalPatchRecovery::validParams()
      15             : {
      16           0 :   InputParameters params = AuxKernel::validParams();
      17           0 :   MooseEnum orders("CONSTANT FIRST SECOND THIRD FOURTH");
      18           0 :   params.addParam<MooseEnum>("patch_polynomial_order",
      19             :                              orders,
      20             :                              "Polynomial order used in least squares fitting of material property "
      21             :                              "over the local patch of elements connected to a given node");
      22           0 :   params.addParamNamesToGroup("patch_polynomial_order", "Advanced");
      23             : 
      24             :   // TODO make this class work with relationship manager
      25             :   // params.registerRelationshipManagers("ElementSideNeighborLayers");
      26             :   // params.addPrivateParam<unsigned int short>("element_side_neighbor_layers", 2);
      27             : 
      28           0 :   return params;
      29           0 : }
      30             : 
      31           0 : NodalPatchRecovery::NodalPatchRecovery(const InputParameters & parameters)
      32             :   : AuxKernel(parameters),
      33           0 :     _patch_polynomial_order(
      34           0 :         isParamValid("patch_polynomial_order")
      35           0 :             ? static_cast<unsigned int>(getParam<MooseEnum>("patch_polynomial_order"))
      36           0 :             : static_cast<unsigned int>(_var.order())),
      37           0 :     _fe_problem(*parameters.get<FEProblemBase *>("_fe_problem_base")),
      38           0 :     _multi_index(multiIndex(_mesh.dimension(), _patch_polynomial_order))
      39             : {
      40             :   // if the patch polynomial order is lower than the variable order,
      41             :   // it is very likely that the patch recovery is not used at its max accuracy
      42           0 :   if (_patch_polynomial_order < static_cast<unsigned int>(_var.order()))
      43           0 :     mooseWarning("Specified 'patch_polynomial_order' is lower than the AuxVariable's order");
      44             : 
      45             :   // TODO remove the manual ghosting once relationship manager is working correctly
      46             :   // no need to ghost if this aux is elemental
      47           0 :   if (isNodal())
      48             :   {
      49           0 :     MeshBase & meshhelper = _mesh.getMesh();
      50           0 :     meshhelper.allow_renumbering(false);
      51           0 :     for (const auto & elem :
      52           0 :          as_range(meshhelper.semilocal_elements_begin(), meshhelper.semilocal_elements_end()))
      53           0 :       _fe_problem.addGhostedElem(elem->id());
      54             :   }
      55           0 : }
      56             : 
      57             : std::vector<std::vector<unsigned int>>
      58           0 : NodalPatchRecovery::nChooseK(unsigned int N, unsigned int K)
      59             : {
      60           0 :   std::vector<std::vector<unsigned int>> n_choose_k;
      61           0 :   std::vector<unsigned int> row;
      62           0 :   std::string bitmask(K, 1); // K leading 1's
      63           0 :   bitmask.resize(N, 0);      // N-K trailing 0's
      64             : 
      65             :   do
      66             :   {
      67           0 :     row.clear();
      68           0 :     row.push_back(0);
      69           0 :     for (unsigned int i = 0; i < N; ++i) // [0..N-1] integers
      70           0 :       if (bitmask[i])
      71           0 :         row.push_back(i + 1);
      72           0 :     row.push_back(N + 1);
      73           0 :     n_choose_k.push_back(row);
      74           0 :   } while (std::prev_permutation(bitmask.begin(), bitmask.end()));
      75             : 
      76           0 :   return n_choose_k;
      77           0 : }
      78             : 
      79             : std::vector<std::vector<unsigned int>>
      80           0 : NodalPatchRecovery::multiIndex(unsigned int dim, unsigned int order)
      81             : {
      82             :   // first row all zero
      83           0 :   std::vector<std::vector<unsigned int>> multi_index;
      84           0 :   std::vector<std::vector<unsigned int>> n_choose_k;
      85           0 :   std::vector<unsigned int> row(dim, 0);
      86           0 :   multi_index.push_back(row);
      87             : 
      88           0 :   if (dim == 1)
      89           0 :     for (unsigned int q = 1; q <= order; q++)
      90             :     {
      91           0 :       row[0] = q;
      92           0 :       multi_index.push_back(row);
      93             :     }
      94             :   else
      95           0 :     for (unsigned int q = 1; q <= order; q++)
      96             :     {
      97           0 :       n_choose_k = nChooseK(dim + q - 1, dim - 1);
      98           0 :       for (unsigned int r = 0; r < n_choose_k.size(); r++)
      99             :       {
     100           0 :         row.clear();
     101           0 :         for (unsigned int c = 1; c < n_choose_k[0].size(); c++)
     102           0 :           row.push_back(n_choose_k[r][c] - n_choose_k[r][c - 1] - 1);
     103           0 :         multi_index.push_back(row);
     104             :       }
     105             :     }
     106             : 
     107           0 :   return multi_index;
     108           0 : }
     109             : 
     110             : void
     111           0 : NodalPatchRecovery::computePVector(Point q_point)
     112             : {
     113           0 :   _P.resize(_multi_index.size());
     114             :   Real polynomial;
     115           0 :   for (unsigned int r = 0; r < _multi_index.size(); r++)
     116             :   {
     117           0 :     polynomial = 1.0;
     118           0 :     for (unsigned int c = 0; c < _multi_index[0].size(); c++)
     119           0 :       for (unsigned int p = 0; p < _multi_index[r][c]; p++)
     120           0 :         polynomial *= q_point(c);
     121           0 :     _P(r) = polynomial;
     122             :   }
     123           0 : }
     124             : 
     125             : void
     126           0 : NodalPatchRecovery::accumulateAMatrix()
     127             : {
     128           0 :   DenseMatrix<Number> PxP;
     129           0 :   PxP.resize(_multi_index.size(), _multi_index.size());
     130           0 :   PxP.outer_product(_P, _P);
     131           0 :   _A.add(1.0, PxP);
     132           0 : }
     133             : 
     134             : void
     135           0 : NodalPatchRecovery::accumulateBVector(Real val)
     136             : {
     137           0 :   _B.add(val, _P);
     138           0 : }
     139             : 
     140             : void
     141           0 : NodalPatchRecovery::reinitPatch()
     142             : {
     143             :   // clean and resize P, A, B, coef
     144           0 :   _P.resize(_multi_index.size());
     145           0 :   _A.resize(_multi_index.size(), _multi_index.size());
     146           0 :   _B.resize(_multi_index.size());
     147           0 :   _coef.resize(_multi_index.size());
     148             : 
     149             :   // activate dependent material properties
     150           0 :   std::unordered_set<unsigned int> needed_mat_props;
     151           0 :   const auto & mp_deps = getMatPropDependencies();
     152           0 :   needed_mat_props.insert(mp_deps.begin(), mp_deps.end());
     153           0 :   _fe_problem.setActiveMaterialProperties(needed_mat_props, _tid);
     154           0 : }
     155             : 
     156             : void
     157           0 : NodalPatchRecovery::compute()
     158             : {
     159             :   // Fall back on standard procedure for element variables
     160           0 :   if (!isNodal())
     161             :   {
     162           0 :     AuxKernel::compute();
     163           0 :     return;
     164             :   }
     165             : 
     166             :   // Limit current use of NodalPatchRecovery to a single processor
     167           0 :   if (_communicator.size() > 1)
     168           0 :     mooseError("The nodal patch recovery option, which calculates the Zienkiewicz-Zhu patch "
     169             :                "recovery for nodal variables (family = LAGRANGE), is not currently implemented for "
     170             :                "parallel runs. Run in serial if you must use the nodal patch capability");
     171             : 
     172             :   // Use Zienkiewicz-Zhu patch recovery for nodal variables
     173           0 :   reinitPatch();
     174             : 
     175             :   // get node-to-conneted-elem map
     176           0 :   const std::map<dof_id_type, std::vector<dof_id_type>> & node_to_elem_map = _mesh.nodeToElemMap();
     177           0 :   auto node_to_elem_pair = node_to_elem_map.find(_current_node->id());
     178             :   mooseAssert(node_to_elem_pair != node_to_elem_map.end(), "Missing entry in node to elem map");
     179           0 :   std::vector<dof_id_type> elem_ids = node_to_elem_pair->second;
     180             : 
     181             :   // consider the case for corner node
     182           0 :   if (elem_ids.size() == 1)
     183             :   {
     184           0 :     dof_id_type elem_id = elem_ids[0];
     185           0 :     for (auto & n : _mesh.elemPtr(elem_id)->node_ref_range())
     186             :     {
     187           0 :       node_to_elem_pair = node_to_elem_map.find(n.id());
     188           0 :       std::vector<dof_id_type> elem_ids_candidate = node_to_elem_pair->second;
     189           0 :       if (elem_ids_candidate.size() > elem_ids.size())
     190           0 :         elem_ids = elem_ids_candidate;
     191           0 :     }
     192             :   }
     193             : 
     194             :   // before we go, check if we have enough sample points for solving the least square fitting
     195           0 :   if (_q_point.size() * elem_ids.size() < _multi_index.size())
     196           0 :     mooseError("There are not enough sample points to recover the nodal value, try reducing the "
     197             :                "polynomial order or using a higher-order quadrature scheme.");
     198             : 
     199             :   // general treatment for side nodes and internal nodes
     200           0 :   for (auto elem_id : elem_ids)
     201             :   {
     202           0 :     const Elem * elem = _mesh.elemPtr(elem_id);
     203             : 
     204           0 :     if (!elem->is_semilocal(_mesh.processor_id()))
     205           0 :       mooseError("skipped non local elem!");
     206             : 
     207           0 :     _fe_problem.prepare(elem, _tid);
     208           0 :     _fe_problem.reinitElem(elem, _tid);
     209             : 
     210             :     // Set up Sentinel class so that, even if reinitMaterials() throws, we
     211             :     // still remember to swap back during stack unwinding.
     212           0 :     SwapBackSentinel sentinel(_fe_problem, &FEProblem::swapBackMaterials, _tid);
     213           0 :     _fe_problem.reinitMaterials(elem->subdomain_id(), _tid);
     214             : 
     215           0 :     for (_qp = 0; _qp < _q_point.size(); _qp++)
     216             :     {
     217           0 :       computePVector(_q_point[_qp]);
     218           0 :       accumulateAMatrix();
     219           0 :       accumulateBVector(computeValue());
     220             :     }
     221           0 :   }
     222             : 
     223           0 :   _fe_problem.clearActiveMaterialProperties(_tid);
     224             : 
     225             :   // solve for the coefficients of least square fitting
     226             :   // as we are on the physical coordinates, A is not always SPD
     227           0 :   _A.svd_solve(_B, _coef);
     228             : 
     229             :   // compute fitted nodal value
     230           0 :   computePVector(*_current_node);
     231           0 :   Real nodal_value = _P.dot(_coef);
     232             : 
     233             :   // set nodal value
     234           0 :   _fe_problem.reinitNode(_current_node, _tid);
     235           0 :   dof_id_type dof = _var.nodalDofIndex();
     236             :   {
     237           0 :     Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
     238           0 :     _solution.set(dof, nodal_value);
     239           0 :   }
     240           0 :   _var.setNodalValue(nodal_value);
     241           0 : }

Generated by: LCOV version 1.14