https://mooseframework.inl.gov
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
25 namespace nanoflann
26 {
27 typedef SearchParams SearchParameters;
28 
29 template <typename T, typename U>
30 using ResultItem = std::pair<T, U>;
31 }
32 #endif
33 
35 
36 // specialization for PointListAdaptor<RadialAverage::QPData>
37 template <>
38 const Point &
40 {
41  return item._q_point;
42 }
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 
86 void
88 {
89  // set up processor boundary node list
90  meshChanged();
91 }
92 
93 void
95 {
96  _qp_data.clear();
97 }
98 
99 void
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 
116 void
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 
204 void
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 
212 void
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();
222  MeshTools::build_nodes_to_elem_map(_mesh.getMesh(), _nodes_to_elem_map);
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 
247 void
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
268  _boundary_data_indices.clear();
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 
306 namespace 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);
321  StandardType<dof_id_type> d2(&example->_elem_id);
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 Real _radius
cut-off radius
Definition: RadialAverage.h:87
virtual void initialSetup() override
Gets called at the beginning of the simulation before this object is asked to do its job...
Definition: RadialAverage.C:87
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
Real _volume
current value * _JxW
Definition: RadialAverage.h:58
std::set< Point > _boundary_nodes
set of nodes on the boundary of the current processor domain
std::vector< std::set< std::size_t > > _communication_lists
QPData indices to send to the various processors.
A MultiMooseEnum object to hold "execute_on" flags.
Definition: ExecFlagEnum.h:21
unsigned int n_threads()
const MaterialProperty< Real > & _prop
material to be averaged
Definition: RadialAverage.h:84
const MooseArray< Point > & _q_point
const MooseArray< Real > & _coord
MPI_Datatype data_type
std::unique_ptr< KDTreeType > _kd_tree
spatial index (nanoflann guarantees this to be threadsafe under read-only operations) ...
virtual void finalize() override
Finalize.
static InputParameters validParams()
virtual void execute() override
Execute method.
MessageTag get_unique_tag(int tagvalue=MessageTag::invalid_tag) const
virtual void threadJoin(const UserObject &y) override
Must override.
quadrature point data
Definition: RadialAverage.h:49
registerMooseObject("MooseApp", RadialAverage)
T & set(const std::string &name, bool quiet_mode=false)
Returns a writable reference to the named parameters.
const Point & getPoint(const PointObject &item) const
get a Point reference from the PointData object at index idx in the list
WeightsType
distance based weight function
Definition: RadialAverage.h:76
MeshBase & mesh
Real _value
variable value
Definition: RadialAverage.h:60
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::set< std::size_t > _boundary_data_indices
set of all _qp_data indices that are within _radius of any _boundary_nodes
processor_id_type _my_pid
StoredRange< std::vector< RadialAverage::QPData >::const_iterator, RadialAverage::QPData > QPDataRange
const Parallel::Communicator & _communicator
void updateCommunicationLists()
virtual void initialize() override
Called before execute() is ever called so that data can be cleared.
Definition: RadialAverage.C:94
const Real _padding
communication padding distance
Definition: RadialAverage.h:90
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.
StandardType(const T *example=nullptr)
std::vector< std::vector< const Elem * > > _nodes_to_elem_map
The data structure used to find neighboring elements give a node ID.
static InputParameters validParams()
Definition: RadialAverage.C:45
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...
short _qp
index of the quadrature point
Definition: RadialAverage.h:56
MPI_Status status
virtual void meshChanged() override
Called on this object when the mesh changes.
RadialAverage(const InputParameters &parameters)
Definition: RadialAverage.C:72
PerfID _perf_updatelists
processor_id_type n_processors() const
void libmesh_ignore(const Args &...)
Status receive(const unsigned int dest_processor_id, T &buf, const MessageTag &tag=any_tag) const
MeshBase & getMesh()
Accessor for the underlying libMesh Mesh object.
Definition: MooseMesh.C:3448
std::vector< QPData > _qp_data
gathered data
Definition: RadialAverage.h:93
const ExecFlagType EXEC_TIMESTEP_BEGIN
Definition: Moose.C:37
status probe(const unsigned int src_processor_id, const MessageTag &tag=any_tag) const
bool _update_communication_lists
PerfID _perf_meshchanged
RadialAverage threaded loop.
data_type _datatype
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:33
MooseApp & _app
The MOOSE application this is associated with.
Definition: MooseBase.h:353
Point _q_point
physical coordinates of the quadrature point
Definition: RadialAverage.h:52
std::pair< T, U > ResultItem
Definition: KDTree.h:24
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual std::shared_ptr< const DisplacedProblem > getDisplacedProblem() const
const QBase *const & _qrule
const Elem *const & _current_elem
The current element pointer (available during execute())
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this user object.
Definition: UserObject.h:211
void send(const unsigned int dest_processor_id, const T &buf, const MessageTag &tag=no_tag) const
const MooseArray< Real > & _JxW
Result _average
average result
Definition: RadialAverage.h:96
dof_id_type _elem_id
element id
Definition: RadialAverage.h:54
IntRange< T > make_range(T beg, T end)
libMesh::BoundingBox getInflatedProcessorBoundingBox(Real inflation_multiplier=0.01) const
Get a (slightly inflated) processor bounding box.
Definition: MooseMesh.C:3419
Gather and communicate a full list of all quadrature points and the values of a selected material pro...
Definition: RadialAverage.h:33
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...
PerfID _perf_finalize
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
SearchParams SearchParameters
auto index_range(const T &sizable)
Base class for user-specific data.
Definition: UserObject.h:40
std::vector< processor_id_type > _candidate_procs
processors to send (potentially empty) data to
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template pow< 2 >(tan(_arg))+1.0) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(sqrt
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...