https://mooseframework.inl.gov
NodalPatchRecoveryBase.C
Go to the documentation of this file.
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 removeDuplicateEntries(const std::vector<dof_id_type> & ids)
28 {
29  std::vector<dof_id_type> key = ids;
30  std::sort(key.begin(), key.end());
31  key.erase(std::unique(key.begin(), key.end()), key.end());
32  return key;
33 }
34 
37 {
39 
40  MooseEnum orders("CONSTANT FIRST SECOND THIRD FOURTH");
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  params.addRelationshipManager("ElementSideNeighborLayers",
49  [](const InputParameters &, InputParameters & rm_params)
50  {
51  rm_params.set<bool>("use_point_neighbors") = true;
52  rm_params.set<unsigned short>("layers") = 1;
53  });
54 
55  params.addParamNamesToGroup("patch_polynomial_order", "Advanced");
56 
57  return params;
58 }
59 
61  : ElementUserObject(parameters),
62  _qp(0),
63  _patch_polynomial_order(
64  static_cast<unsigned int>(getParam<MooseEnum>("patch_polynomial_order"))),
65  _multi_index(MathUtils::multiIndex(_mesh.dimension(), _patch_polynomial_order)),
66  _q(_multi_index.size()),
67  _distributed_mesh(_mesh.isDistributedMesh()),
68  _proc_ids(n_processors())
69 {
70  std::iota(_proc_ids.begin(), _proc_ids.end(), 0);
71 }
72 
73 Real
75  const std::vector<dof_id_type> & elem_ids) const
76 {
77  const RealEigenVector coef = getCoefficients(elem_ids); // const version
78  // Compute the fitted nodal value
80  return p.dot(coef);
81 }
82 
83 const RealEigenVector
84 NodalPatchRecoveryBase::getCoefficients(const std::vector<dof_id_type> & elem_ids) const
85 {
86  auto elem_ids_reduced = removeDuplicateEntries(elem_ids);
87 
88  RealEigenVector coef = RealEigenVector::Zero(_q);
89  // Before we go, check if we have enough sample points for solving the least square fitting
90  if (_q_point.size() * elem_ids_reduced.size() < _q)
91  mooseError("There are not enough sample points to recover the nodal value, try reducing the "
92  "polynomial order or using a higher-order quadrature scheme.");
93 
94  // Assemble the least squares problem over the patch
95  RealEigenMatrix A = RealEigenMatrix::Zero(_q, _q);
96  RealEigenVector b = RealEigenVector::Zero(_q);
97  for (auto elem_id : elem_ids_reduced)
98  {
99  const auto elem = _mesh.elemPtr(elem_id);
100  if (elem /*prevent segmentation fault in distributed mesh*/)
101  if (!hasBlocks(elem->subdomain_id()))
102  mooseError("Element with id = ",
103  elem_id,
104  " is not in the block. "
105  "Please use nodalPatchRecovery with elements in the block only.");
106 
107  if (_Ae.find(elem_id) == _Ae.end())
108  mooseError("Missing entry for elem_id = ", elem_id, " in _Ae.");
109  if (_be.find(elem_id) == _be.end())
110  mooseError("Missing entry for elem_id = ", elem_id, " in _be.");
111 
112  A += libmesh_map_find(_Ae, elem_id);
113  b += libmesh_map_find(_be, elem_id);
114  }
115 
116  // Solve the least squares fitting
117  coef = A.completeOrthogonalDecomposition().solve(b);
118 
119  return coef;
120 }
121 
122 const RealEigenVector
123 NodalPatchRecoveryBase::getCachedCoefficients(const std::vector<dof_id_type> & elem_ids)
124 {
125  // Check cache
126  auto key = removeDuplicateEntries(elem_ids);
127 
128  if (key == _cached_elem_ids)
129  return _cached_coef;
130  else
131  {
132  const auto coef = getCoefficients(key); // const version
133 
134  _cached_elem_ids = key; // Update the cached element IDs
135  _cached_coef = coef; // Update the cached coefficients
136 
137  return coef;
138  }
139 }
140 
143 {
144  RealEigenVector p(_q);
146  for (unsigned int r = 0; r < _multi_index.size(); r++)
147  {
148  polynomial = 1.0;
149  mooseAssert(_multi_index[r].size() == _mesh.dimension(), "Wrong multi-index size.");
150  for (unsigned int c = 0; c < _multi_index[r].size(); c++)
151  for (unsigned int p = 0; p < _multi_index[r][c]; p++)
152  polynomial *= q_point(c);
153  p(r) = polynomial;
154  }
155  return p;
156 }
157 
158 void
160 {
161  // Clear cached data to ensure coefficients are correctly recomputed in the next patch recovery
162  // iteration. _Ae and _be must also be reset, as the associated patch elements may differ in the
163  // upcoming iteration.
164 
165  _cached_elem_ids.clear();
166  _cached_coef = RealEigenVector::Zero(_q);
167  _Ae.clear();
168  _be.clear();
169 }
170 
171 void
173 {
174  RealEigenMatrix Ae = RealEigenMatrix::Zero(_q, _q);
175  RealEigenVector be = RealEigenVector::Zero(_q);
176  for (_qp = 0; _qp < _qrule->n_points(); _qp++)
177  {
179  Ae += p * p.transpose();
180  be += computeValue() * p;
181  }
182 
183  dof_id_type elem_id = _current_elem->id();
184 
185  _Ae[elem_id] = Ae;
186  _be[elem_id] = be;
187 }
188 
189 void
191 {
192  const auto & npr = static_cast<const NodalPatchRecoveryBase &>(uo);
193  _Ae.insert(npr._Ae.begin(), npr._Ae.end());
194  _be.insert(npr._be.begin(), npr._be.end());
195 }
196 
197 void
199 {
200  // When calling nodalPatchRecovery, we may need to know _Ae and _be on algebraically ghosted
201  // elements. However, this userobject is only run on local elements, so we need to query those
202  // information from other processors in this finalize() method.
203  sync();
204 }
205 
206 std::unordered_map<processor_id_type, std::vector<dof_id_type>>
207 NodalPatchRecoveryBase::gatherRequestList(const std::vector<dof_id_type> & specific_elems)
208 {
209  std::unordered_map<processor_id_type, std::vector<dof_id_type>> query_ids;
210 
211  typedef std::pair<processor_id_type, dof_id_type> PidElemPair;
212  std::unordered_map<processor_id_type, std::vector<PidElemPair>> push_data;
213 
214  for (const auto & entry : specific_elems)
215  {
216  const auto * elem = _mesh.elemPtr(entry);
217  if (_distributed_mesh)
218  {
219  if (!elem)
220  continue; // Prevent segmentation fault in distributed mesh
221 
222  if (hasBlocks(elem->subdomain_id()))
223  for (processor_id_type pid = 0; pid < n_processors(); ++pid)
224  if (pid != processor_id())
225  push_data[pid].push_back(std::make_pair(elem->processor_id(), elem->id()));
226  }
227  else
228  // Add to query_ids if this element's data is on a different processor
229  addToQuery(elem, query_ids);
230  }
231 
232  if (_distributed_mesh)
233  {
234  auto push_receiver =
235  [&](const processor_id_type, const std::vector<PidElemPair> & received_data)
236  {
237  for (const auto & [pid, id] : received_data)
238  query_ids[pid].push_back(id);
239  };
240 
241  Parallel::push_parallel_vector_data(_mesh.comm(), push_data, push_receiver);
242  }
243 
244  return query_ids;
245 }
246 
247 std::unordered_map<processor_id_type, std::vector<dof_id_type>>
249 {
250  std::unordered_map<processor_id_type, std::vector<dof_id_type>> query_ids;
251 
252  typedef std::pair<processor_id_type, dof_id_type> PidElemPair;
253  std::unordered_map<processor_id_type, std::vector<PidElemPair>> push_data;
254 
255  for (const auto & elem : _fe_problem.getEvaluableElementRange())
256  addToQuery(elem, query_ids);
257 
258  return query_ids;
259 }
260 
261 void
263 {
264  const auto query_ids = gatherRequestList();
265  syncHelper(query_ids);
266 }
267 
268 void
269 NodalPatchRecoveryBase::sync(const std::vector<dof_id_type> & specific_elems)
270 {
271  const auto query_ids = gatherRequestList(specific_elems);
272  syncHelper(query_ids);
273 }
274 
275 void
277  const std::unordered_map<processor_id_type, std::vector<dof_id_type>> & query_ids)
278 {
279  typedef std::pair<RealEigenMatrix, RealEigenVector> AbPair;
280 
281  // Answer queries received from other processors
282  auto gather_data = [this](const processor_id_type /*pid*/,
283  const std::vector<dof_id_type> & elem_ids,
284  std::vector<AbPair> & ab_pairs)
285  {
286  for (const auto & elem_id : elem_ids)
287  ab_pairs.emplace_back(libmesh_map_find(_Ae, elem_id), libmesh_map_find(_be, elem_id));
288  };
289 
290  // Gather answers received from other processors
291  auto act_on_data = [this](const processor_id_type /*pid*/,
292  const std::vector<dof_id_type> & elem_ids,
293  const std::vector<AbPair> & ab_pairs)
294  {
295  for (const auto i : index_range(elem_ids))
296  {
297  const auto elem_id = elem_ids[i];
298  const auto & [Ae, be] = ab_pairs[i];
299  _Ae[elem_id] = Ae;
300  _be[elem_id] = be;
301  }
302  };
303 
304  // the send and receive are called inside the pull_parallel_vector_data function
305  libMesh::Parallel::pull_parallel_vector_data<AbPair>(
306  _communicator, query_ids, gather_data, act_on_data, 0);
307 }
308 
309 void
311  const libMesh::Elem * elem,
312  std::unordered_map<processor_id_type, std::vector<dof_id_type>> & query_ids) const
313 {
314  if (hasBlocks(elem->subdomain_id()) && elem->processor_id() != processor_id())
315  query_ids[elem->processor_id()].push_back(elem->id());
316 }
void syncHelper(const std::unordered_map< processor_id_type, std::vector< dof_id_type >> &query_ids)
Helper function to perform the actual communication of _Ae and _be.
std::vector< int > _proc_ids
The processor IDs vector in the running.
std::map< dof_id_type, RealEigenVector > _be
The element-level b vector.
static InputParameters validParams()
void addToQuery(const libMesh::Elem *elem, std::unordered_map< processor_id_type, std::vector< dof_id_type >> &query_ids) const
Adds an element to the map provided in query_ids if it belongs to a different processor.
void initialize() override
Called before execute() is ever called so that data can be cleared.
const MooseArray< Point > & _q_point
virtual Elem * elemPtr(const dof_id_type i)
Definition: MooseMesh.C:3213
static InputParameters validParams()
std::vector< dof_id_type > _cached_elem_ids
Cache for least-squares coefficients used in nodal patch recovery.
void sync()
Synchronizes local matrices and vectors (_Ae, _be) across processors.
std::map< dof_id_type, RealEigenMatrix > _Ae
The element-level A matrix.
RealEigenVector evaluateBasisFunctions(const Point &q_point) const
Compute the P vector at a given point i.e.
T & set(const std::string &name, bool quiet_mode=false)
Returns a writable reference to the named parameters.
static std::vector< dof_id_type > removeDuplicateEntries(const std::vector< dof_id_type > &ids)
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
const Parallel::Communicator & comm() const
const Parallel::Communicator & _communicator
void addRelationshipManager(const std::string &name, Moose::RelationshipManagerType rm_type, Moose::RelationshipManagerInputParameterCallback input_parameter_callback=nullptr)
Tells MOOSE about a RelationshipManager that this object needs.
virtual Real nodalPatchRecovery(const Point &p, const std::vector< dof_id_type > &elem_ids) const
Solve the least-squares problem.
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
void execute() override
Execute method.
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this user object.
uint8_t processor_id_type
processor_id_type n_processors() const
unsigned int size() const
The number of elements that can currently be stored in the array.
Definition: MooseArray.h:259
dof_id_type id() const
virtual unsigned int dimension() const
Returns MeshBase::mesh_dimension(), (not MeshBase::spatial_dimension()!) of the underlying libMesh me...
Definition: MooseMesh.C:2985
NodalPatchRecoveryBase(const InputParameters &parameters)
const std::vector< std::vector< unsigned int > > _multi_index
Multi-index table for a polynomial basis.
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
bool _distributed_mesh
Whether the mesh is distributed.
R polynomial(const C &c, const T x)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:277
virtual Real computeValue()=0
Compute the quantity to recover using nodal patch recovery.
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic > RealEigenMatrix
Definition: MooseTypes.h:151
void finalize() override
Finalize.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
subdomain_id_type subdomain_id() const
const QBase *const & _qrule
const Elem *const & _current_elem
The current element pointer (available during execute())
std::vector< std::vector< unsigned int > > multiIndex(unsigned int dim, unsigned int order)
generate a complete multi index table for given dimension and order i.e.
Definition: MathUtils.C:186
void threadJoin(const UserObject &) override
Must override.
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:271
Eigen::Matrix< Real, Eigen::Dynamic, 1 > RealEigenVector
Definition: MooseTypes.h:147
const libMesh::ConstElemRange & getEvaluableElementRange()
In general, {evaluable elements} >= {local elements} U {algebraic ghosting elements}.
const RealEigenVector getCoefficients(const std::vector< dof_id_type > &elem_ids) const
Compute coefficients without reading or writing cached values The coefficients returned by this funct...
std::unordered_map< processor_id_type, std::vector< dof_id_type > > gatherRequestList()
Builds a query map of element IDs that require data from other processors.
bool hasBlocks(const SubdomainName &name) const
Test if the supplied block name is valid for this object.
processor_id_type processor_id() const
processor_id_type processor_id() const
const RealEigenVector getCachedCoefficients(const std::vector< dof_id_type > &elem_ids)
Compute coefficients, using cached values if available, and store any newly computed coefficients in ...
const unsigned int _q
Number of basis functions.
void ErrorVector unsigned int
auto index_range(const T &sizable)
Base class for user-specific data.
Definition: UserObject.h:19
uint8_t dof_id_type
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
This method takes a space delimited list of parameter names and adds them to the specified group name...