https://mooseframework.inl.gov
Loading...
Searching...
No Matches
RadialAverage.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 "RadialAverage.h"
12#include "FEProblemBase.h"
13
14#include "libmesh/nanoflann.hpp"
15#include "libmesh/parallel_algebra.h"
16#include "libmesh/mesh_tools.h"
17#include "libmesh/int_range.h"
18
19#include <list>
20#include <iterator>
21#include <algorithm>
22
23// Make newer nanoflann API compatible with older nanoflann versions
24#if NANOFLANN_VERSION < 0x150
25namespace nanoflann
26{
27typedef SearchParams SearchParameters;
28
29template <typename T, typename U>
30using ResultItem = std::pair<T, U>;
31}
32#endif
33
35
36// specialization for PointListAdaptor<RadialAverage::QPData>
37template <>
38const Point &
43
46{
48 params.addClassDescription("Perform a radial average of a material property");
49 params.addRequiredParam<MaterialPropertyName>("prop_name",
50 "Name of the material property to average");
51 MooseEnum weights_type("constant linear cosine", "linear");
52 params.addRequiredParam<MooseEnum>("weights", weights_type, "Distance based weight function");
53 params.addRequiredParam<Real>("radius", "Cut-off radius for the averaging");
54 params.addRangeCheckedParam<Real>(
55 "padding",
56 0.0,
57 "padding >= 0",
58 "Padding for communication. This gets added to the radius when determining which QPs to send "
59 "to other processors. Increase this gradually if inconsistent parallel results occur.");
60
61 // we run this object once at the beginning of the timestep by default
62 params.set<ExecFlagEnum>("execute_on") = EXEC_TIMESTEP_BEGIN;
63
64 // make sure we always have geometric point neighbors ghosted
65 params.addRelationshipManager("ElementPointNeighborLayers",
68 params.addParamNamesToGroup("padding", "Advanced");
69 return params;
70}
71
73 : ElementUserObject(parameters),
74 _weights_type(getParam<MooseEnum>("weights").getEnum<WeightsType>()),
75 _prop(getMaterialProperty<Real>("prop_name")),
76 _radius(getParam<Real>("radius")),
77 _padding(getParam<Real>("padding")),
78 _update_communication_lists(false),
79 _my_pid(processor_id()),
80 _perf_meshchanged(registerTimedSection("meshChanged", 3)),
81 _perf_updatelists(registerTimedSection("updateCommunicationLists", 3)),
82 _perf_finalize(registerTimedSection("finalize", 2))
83{
84}
85
86void
88{
89 // set up processor boundary node list
91}
92
93void
95{
96 _qp_data.clear();
97}
98
99void
101{
102 auto id = _current_elem->id();
103
104 // collect all QP data
105 for (const auto qp : make_range(_qrule->n_points()))
106 _qp_data.emplace_back(_q_point[qp], id, qp, _JxW[qp] * _coord[qp], _prop[qp]);
107
108 // make sure the result map entry for the current element is sized correctly
109 auto i = _average.find(id);
110 if (i == _average.end())
111 _average.insert(std::make_pair(id, std::vector<Real>(_qrule->n_points())));
112 else
113 i->second.resize(_qrule->n_points());
114}
115
116void
118{
119 TIME_SECTION(_perf_finalize);
120
121 // the first chunk of data is always the local data - remember its size
122 unsigned int local_size = _qp_data.size();
123
124 // communicate the qp data list if n_proc > 1
125 if (_app.n_processors() > 1)
126 {
127 // !!!!!!!!!!!
128 // !!CAREFUL!! Is it guaranteed that _qp_data is in the same order if the mesh has not changed?
129 // According to @friedmud it is not guaranteed if threads are used
130 // !!!!!!!!!!!
131
132 // update after mesh changes and/or if a displaced problem exists
134 libMesh::n_threads() > 1)
136
137 // data structures for sparse point to point communication
138 std::vector<std::vector<QPData>> send(_candidate_procs.size());
139 std::vector<Parallel::Request> send_requests(_candidate_procs.size());
140 Parallel::MessageTag send_tag = _communicator.get_unique_tag(4711);
141 std::vector<QPData> receive;
142
143 const auto item_type = TIMPI::StandardType<QPData>(&(_qp_data[0]));
144
145 // fill buffer and send structures
146 for (const auto i : index_range(_candidate_procs))
147 {
148 const auto pid = _candidate_procs[i];
149 const auto & list = _communication_lists[pid];
150
151 // fill send buffer for transfer to pid
152 send[i].reserve(list.size());
153 for (const auto & item : list)
154 send[i].push_back(_qp_data[item]);
155
156 // issue non-blocking send
157 _communicator.send(pid, send[i], send_requests[i], send_tag);
158 }
159
160 // receive messages - we assume that we receive as many messages as we send!
161 // bounding box overlapp is transitive, but data exhange between overlapping procs could still
162 // be unidirectional!
163 for (const auto i : index_range(_candidate_procs))
164 {
165 libmesh_ignore(i);
166
167 // inspect incoming message
168 Parallel::Status status(_communicator.probe(Parallel::any_source, send_tag));
169 const auto source_pid = TIMPI::cast_int<processor_id_type>(status.source());
170 const auto message_size = status.size(item_type);
171
172 // resize receive buffer accordingly and receive data
173 receive.resize(message_size);
174 _communicator.receive(source_pid, receive, send_tag);
175
176 // append communicated data
177 _qp_data.insert(_qp_data.end(), receive.begin(), receive.end());
178 }
179
180 // wait until all send requests are at least buffered and we can destroy
181 // the send buffers by going out of scope
182 Parallel::wait(send_requests);
183 }
184
185 // build KD-Tree using data we just received
186 const unsigned int max_leaf_size = 20; // slightly affects runtime
187 auto point_list = PointListAdaptor<QPData>(_qp_data.begin(), _qp_data.end());
188 _kd_tree = std::make_unique<KDTreeType>(
189 LIBMESH_DIM, point_list, nanoflann::KDTreeSingleIndexAdaptorParams(max_leaf_size));
190
191 mooseAssert(_kd_tree != nullptr, "KDTree was not properly initialized.");
192 _kd_tree->buildIndex();
193
194 // build thread loop functor
195 ThreadedRadialAverageLoop rgcl(*this);
196
197 // run threads
198 auto local_range_begin = _qp_data.begin();
199 auto local_range_end = local_range_begin;
200 std::advance(local_range_end, local_size);
201 Threads::parallel_reduce(QPDataRange(local_range_begin, local_range_end), rgcl);
202}
203
204void
206{
207 const auto & uo = static_cast<const RadialAverage &>(y);
208 _qp_data.insert(_qp_data.begin(), uo._qp_data.begin(), uo._qp_data.end());
209 _average.insert(uo._average.begin(), uo._average.end());
210}
211
212void
214{
215 TIME_SECTION(_perf_meshchanged);
216
217 // get underlying libMesh mesh
218 auto & mesh = _mesh.getMesh();
219
220 // Build a new node to element map
221 _nodes_to_elem_map.clear();
223
224 // clear procesor boundary nodes set
225 _boundary_nodes.clear();
226
227 //
228 // iterate over active local elements and store all processor boundary node locations
229 //
230 const auto end = mesh.active_local_elements_end();
231 for (auto it = mesh.active_local_elements_begin(); it != end; ++it)
232 // find faces at processor boundaries
233 for (const auto s : make_range((*it)->n_sides()))
234 {
235 const auto * neighbor = (*it)->neighbor_ptr(s);
236 if (neighbor && neighbor->processor_id() != _my_pid)
237 // add all nodes on the processor boundary
238 for (const auto n : make_range((*it)->n_nodes()))
239 if ((*it)->is_node_on_side(n, s))
240 _boundary_nodes.insert((*it)->node_ref(n));
241
242 // request communication list update
244 }
245}
246
247void
249{
250 TIME_SECTION(_perf_updatelists);
251
252 // clear communication lists
253 _communication_lists.clear();
255
256 // build KD-Tree using local qpoint data
257 const unsigned int max_leaf_size = 20; // slightly affects runtime
258 auto point_list = PointListAdaptor<QPData>(_qp_data.begin(), _qp_data.end());
259 auto kd_tree = std::make_unique<KDTreeType>(
260 LIBMESH_DIM, point_list, nanoflann::KDTreeSingleIndexAdaptorParams(max_leaf_size));
261 mooseAssert(kd_tree != nullptr, "KDTree was not properly initialized.");
262 kd_tree->buildIndex();
263
264 std::vector<nanoflann::ResultItem<std::size_t, Real>> ret_matches;
265 nanoflann::SearchParameters search_params;
266
267 // iterate over all boundary nodes and collect all boundary-near data points
269 for (const auto & bn : _boundary_nodes)
270 {
271 ret_matches.clear();
272 kd_tree->radiusSearch(
273 &(bn(0)), Utility::pow<2>(_radius + _padding), ret_matches, search_params);
274 for (auto & match : ret_matches)
275 _boundary_data_indices.insert(match.first);
276 }
277
278 // gather all processor bounding boxes (communicate as pairs)
279 std::vector<std::pair<Point, Point>> pps(n_processors());
280 const auto mybb = _mesh.getInflatedProcessorBoundingBox(0);
281 std::pair<Point, Point> mypp = mybb;
282 _communicator.allgather(mypp, pps);
283
284 // inflate all processor bounding boxes by radius (no padding)
285 const auto rpoint = Point(1, 1, 1) * _radius;
286 std::vector<BoundingBox> bbs;
287 for (const auto & pp : pps)
288 bbs.emplace_back(pp.first - rpoint, pp.second + rpoint);
289
290 // get candidate processors (overlapping bounding boxes)
291 _candidate_procs.clear();
292 for (const auto pid : index_range(bbs))
293 if (pid != _my_pid && bbs[pid].intersects(mypp))
294 _candidate_procs.push_back(pid);
295
296 // go over all boundary data items and send them to the proc they overlap with
297 for (const auto i : _boundary_data_indices)
298 for (const auto pid : _candidate_procs)
299 if (bbs[pid].contains_point(_qp_data[i]._q_point))
300 _communication_lists[pid].insert(i);
301
302 // done
304}
305
306namespace TIMPI
307{
308
310{
311 // We need an example for MPI_Address to use
312 static const RadialAverage::QPData p;
313 if (!example)
314 example = &p;
315
316#ifdef LIBMESH_HAVE_MPI
317
318 // Get the sub-data-types, and make sure they live long enough
319 // to construct the derived type
320 StandardType<Point> d1(&example->_q_point);
322 StandardType<short> d3(&example->_qp);
323 StandardType<Real> d4(&example->_volume);
324 StandardType<Real> d5(&example->_value);
325
326 MPI_Datatype types[] = {
327 (data_type)d1, (data_type)d2, (data_type)d3, (data_type)d4, (data_type)d5};
328 int blocklengths[] = {1, 1, 1, 1, 1};
329 MPI_Aint displs[5], start;
330
331 libmesh_call_mpi(MPI_Get_address(const_cast<RadialAverage::QPData *>(example), &start));
332 libmesh_call_mpi(MPI_Get_address(const_cast<Point *>(&example->_q_point), &displs[0]));
333 libmesh_call_mpi(MPI_Get_address(const_cast<dof_id_type *>(&example->_elem_id), &displs[1]));
334 libmesh_call_mpi(MPI_Get_address(const_cast<short *>(&example->_qp), &displs[2]));
335 libmesh_call_mpi(MPI_Get_address(const_cast<Real *>(&example->_volume), &displs[3]));
336 libmesh_call_mpi(MPI_Get_address(const_cast<Real *>(&example->_value), &displs[4]));
337
338 for (std::size_t i = 0; i < 5; ++i)
339 displs[i] -= start;
340
341 // create a prototype structure
342 MPI_Datatype tmptype;
343 libmesh_call_mpi(MPI_Type_create_struct(5, blocklengths, displs, types, &tmptype));
344 libmesh_call_mpi(MPI_Type_commit(&tmptype));
345
346 // resize the structure type to account for padding, if any
347 libmesh_call_mpi(MPI_Type_create_resized(tmptype, 0, sizeof(RadialAverage::QPData), &_datatype));
348 libmesh_call_mpi(MPI_Type_free(&tmptype));
349
350 this->commit();
351
352#endif // LIBMESH_HAVE_MPI
353}
354
356 : DataType(t._datatype)
357{
358#ifdef LIBMESH_HAVE_MPI
359 libmesh_call_mpi(MPI_Type_dup(t._datatype, &_datatype));
360#endif
361}
362
363} // namespace TIMPI
const ExecFlagType EXEC_TIMESTEP_BEGIN
Definition Moose.C:37
registerMooseObject("MooseApp", RadialAverage)
StoredRange< std::vector< RadialAverage::QPData >::const_iterator, RadialAverage::QPData > QPDataRange
static InputParameters validParams()
const QBase *const & _qrule
const Elem *const & _current_elem
The current element pointer (available during execute())
const MooseArray< Real > & _coord
const MooseArray< Real > & _JxW
const MooseArray< Point > & _q_point
A MultiMooseEnum object to hold "execute_on" flags.
virtual std::shared_ptr< const DisplacedProblem > getDisplacedProblem() const
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.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
T & set(const std::string &name, bool quiet_mode=false)
Returns a writable reference to the named parameters.
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
libMesh::BoundingBox getInflatedProcessorBoundingBox(Real inflation_multiplier=0.01) const
Get a (slightly inflated) processor bounding box.
Definition MooseMesh.C:3520
MeshBase & getMesh()
Accessor for the underlying libMesh Mesh object.
Definition MooseMesh.C:3549
MooseApp & _app
The MOOSE application this is associated with.
Definition MooseBase.h:375
const Point & getPoint(const PointObject &item) const
get a Point reference from the PointData object at index idx in the list
Gather and communicate a full list of all quadrature points and the values of a selected material pro...
virtual void initialSetup() override
Gets called at the beginning of the simulation before this object is asked to do its job.
std::vector< std::vector< const Elem * > > _nodes_to_elem_map
The data structure used to find neighboring elements give a node ID.
PerfID _perf_updatelists
virtual void initialize() override
Called before execute() is ever called so that data can be cleared.
const Real _radius
cut-off radius
std::vector< std::set< std::size_t > > _communication_lists
QPData indices to send to the various processors.
std::set< Point > _boundary_nodes
set of nodes on the boundary of the current processor domain
const Real _padding
communication padding distance
virtual void execute() override
Execute method.
processor_id_type _my_pid
bool _update_communication_lists
RadialAverage(const InputParameters &parameters)
std::unique_ptr< KDTreeType > _kd_tree
spatial index (nanoflann guarantees this to be threadsafe under read-only operations)
virtual void threadJoin(const UserObject &y) override
Must override.
std::vector< processor_id_type > _candidate_procs
processors to send (potentially empty) data to
PerfID _perf_finalize
virtual void meshChanged() override
Called on this object when the mesh changes.
void updateCommunicationLists()
static InputParameters validParams()
PerfID _perf_meshchanged
Result _average
average result
WeightsType
distance based weight function
virtual void finalize() override
Finalize.
std::set< std::size_t > _boundary_data_indices
set of all _qp_data indices that are within _radius of any _boundary_nodes
const MaterialProperty< Real > & _prop
material to be averaged
std::vector< QPData > _qp_data
gathered data
MessageTag get_unique_tag(int tagvalue=MessageTag::invalid_tag) const
status probe(const unsigned int src_processor_id, const MessageTag &tag=any_tag) const
Status receive(const unsigned int dest_processor_id, T &buf, const MessageTag &tag=any_tag) const
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
void send(const unsigned int dest_processor_id, const T &buf, const MessageTag &tag=no_tag) const
StandardType(const T *example=nullptr)
RadialAverage threaded loop.
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this user object.
Base class for user-specific data.
Definition UserObject.h:20
const Parallel::Communicator & _communicator
processor_id_type n_processors() const
MeshBase & mesh
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type > > &nodes_to_elem_map)
void parallel_reduce(const Range &range, Body &body, unsigned int n_threads=libMesh::n_threads())
unsigned int n_threads()
std::pair< T, U > ResultItem
Definition KDTree.h:24
SearchParams SearchParameters
quadrature point data
dof_id_type _elem_id
element id
Real _volume
current value * _JxW
short _qp
index of the quadrature point
Point _q_point
physical coordinates of the quadrature point
Real _value
variable value