https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
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.
26static std::vector<dof_id_type>
27removeDuplicateEntries(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
73Real
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
79 RealEigenVector p = evaluateBasisFunctions(x);
80 return p.dot(coef);
81}
82
83const RealEigenVector
84NodalPatchRecoveryBase::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
122const RealEigenVector
123NodalPatchRecoveryBase::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
141RealEigenVector
143{
144 RealEigenVector p(_q);
145 Real polynomial;
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
158void
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
171void
173{
174 RealEigenMatrix Ae = RealEigenMatrix::Zero(_q, _q);
175 RealEigenVector be = RealEigenVector::Zero(_q);
176 for (_qp = 0; _qp < _qrule->n_points(); _qp++)
177 {
178 RealEigenVector p = evaluateBasisFunctions(_q_point[_qp]);
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
189void
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
197void
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
206std::unordered_map<processor_id_type, std::vector<dof_id_type>>
207NodalPatchRecoveryBase::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);
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
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
247std::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
261void
263{
264 const auto query_ids = gatherRequestList();
265 syncHelper(query_ids);
266}
267
268void
269NodalPatchRecoveryBase::sync(const std::vector<dof_id_type> & specific_elems)
270{
271 const auto query_ids = gatherRequestList(specific_elems);
272 syncHelper(query_ids);
273}
274
275void
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
309void
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 mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
static std::vector< dof_id_type > removeDuplicateEntries(const std::vector< dof_id_type > &ids)
void ErrorVector unsigned int
bool hasBlocks(const SubdomainName &name) const
Test if the supplied block name is valid for this object.
static InputParameters validParams()
const QBase *const & _qrule
const Elem *const & _current_elem
The current element pointer (available during execute())
const MooseArray< Point > & _q_point
const libMesh::ConstElemRange & getEvaluableElementRange()
In general, {evaluable elements} >= {local elements} U {algebraic ghosting elements}.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
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...
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 addRelationshipManager(const std::string &name, Moose::RelationshipManagerType rm_type, Moose::RelationshipManagerInputParameterCallback input_parameter_callback=nullptr)
Tells MOOSE about a RelationshipManager that this object needs.
T & set(const std::string &name, bool quiet_mode=false)
Returns a writable reference to the named parameters.
unsigned int size() const
The number of elements that can currently be stored in the array.
Definition MooseArray.h:259
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
virtual unsigned int dimension() const
Returns MeshBase::mesh_dimension(), (not MeshBase::spatial_dimension()!) of the underlying libMesh me...
Definition MooseMesh.C:2986
virtual Elem * elemPtr(const dof_id_type i)
Definition MooseMesh.C:3214
NodalPatchRecoveryBase(const InputParameters &parameters)
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 ...
void execute() override
Execute method.
std::vector< dof_id_type > _cached_elem_ids
Cache for least-squares coefficients used in nodal patch recovery.
static InputParameters validParams()
void threadJoin(const UserObject &) override
Must override.
std::vector< int > _proc_ids
The processor IDs vector in the running.
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.
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.
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.
void initialize() override
Called before execute() is ever called so that data can be cleared.
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.
const std::vector< std::vector< unsigned int > > _multi_index
Multi-index table for a polynomial basis.
std::map< dof_id_type, RealEigenVector > _be
The element-level b vector.
void finalize() override
Finalize.
const unsigned int _q
Number of basis functions.
bool _distributed_mesh
Whether the mesh is distributed.
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...
virtual Real nodalPatchRecovery(const Point &p, const std::vector< dof_id_type > &elem_ids) const
Solve the least-squares problem.
virtual Real computeValue()=0
Compute the quantity to recover using nodal patch recovery.
void sync()
Synchronizes local matrices and vectors (_Ae, _be) across processors.
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this user object.
Base class for user-specific data.
Definition UserObject.h:20
processor_id_type processor_id() const
dof_id_type id() const
subdomain_id_type subdomain_id() const
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
processor_id_type n_processors() const