https://mooseframework.inl.gov
MultiAppGeneralFieldKDTreeTransferBase.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 
12 // MOOSE includes
13 #include "FEProblem.h"
14 #include "MooseMesh.h"
15 #include "MooseTypes.h"
16 #include "MooseVariableFE.h"
17 #include "SystemBase.h"
18 #include "Positions.h"
19 #include "MooseAppCoordTransform.h"
20 
21 #include "libmesh/system.h"
22 
23 using namespace libMesh;
24 
27 {
29 
30  params.addParam<unsigned int>("num_nearest_points",
31  1,
32  "Number of nearest source (from) points will be chosen to "
33  "construct a value for the target point. All points will be "
34  "selected from the same origin mesh!");
35 
36  // choose whether to include data from multiple apps when performing nearest-position/
37  // mesh-divisions based transfers
38  params.addParam<bool>("group_subapps",
39  false,
40  "Whether to group source locations and values from all subapps "
41  "when working with a nearest-position or source mesh-division");
42 
43  return params;
44 }
45 
47  const InputParameters & parameters)
48  : MultiAppGeneralFieldTransfer(parameters),
49  _num_nearest_points(getParam<unsigned int>("num_nearest_points")),
50  _group_subapps(getParam<bool>("group_subapps"))
51 {
53  paramError("use_nearest_position",
54  "We do not support using both nearest positions matching and checking if target "
55  "points are within an app domain because the KDTrees for nearest-positions matching "
56  "are (currently) built with data from multiple applications.");
58  (isParamValid("from_mesh_divisions") || isParamValid("to_mesh_divisions")))
59  paramError("use_nearest_position", "Cannot use nearest positions with mesh divisions");
60 
61  // Parameter checks on grouping subapp values
63  paramError(
64  "group_subapps",
65  "This option is only available for using mesh divisions or nearest positions regions");
66  else if (_group_subapps &&
67  (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
68  _from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX))
69  paramError("group_subapps",
70  "Cannot group subapps when considering nearest-location data as we would lose "
71  "track of the division index of the source locations");
72  else if (_group_subapps && _use_nearest_app)
73  paramError(
74  "group_subapps",
75  "When using the 'nearest child application' data, the source data (positions and values) "
76  "are grouped on a per-application basis, so it cannot be agglomerated over all child "
77  "applications.\nNote that the option to use nearest applications for source restrictions, "
78  "but further split each child application's domain by regions closest to each position "
79  "(here the the child application's centroid), which could be conceived when "
80  "'group_subapps' = false, is also not available.");
81 }
82 
83 void
85 {
87 
88  // We need to improve the indexing if we are to allow this
89  if (!_from_mesh_divisions.empty())
90  for (const auto mesh_div : _from_mesh_divisions)
91  if (mesh_div->getNumDivisions() != _from_mesh_divisions[0]->getNumDivisions())
92  paramError("from_mesh_division",
93  "This transfer has only been implemented with a uniform number of source mesh "
94  "divisions across all source applications");
95 }
96 
97 void
99  const unsigned int var_index)
100 {
101  _local_kdtrees.clear();
102  _local_points.clear();
103  _local_values.clear();
104  buildKDTrees(var_index);
105 }
106 
107 bool
109  const MooseMesh & mesh,
110  const Elem * elem) const
111 {
112  // We need to override the definition of block restriction for an element
113  // because we have to consider whether each node of an element is adjacent to a block
114  for (const auto & i_node : make_range(elem->n_nodes()))
115  {
116  const auto & node = elem->node_ptr(i_node);
117  const auto & node_blocks = mesh.getNodeBlockIds(*node);
118  std::set<SubdomainID> u;
119  std::set_intersection(blocks.begin(),
120  blocks.end(),
121  node_blocks.begin(),
122  node_blocks.end(),
123  std::inserter(u, u.begin()));
124  if (!u.empty())
125  return true;
126  }
127  return false;
128 }
129 
130 void
132 {
133  // Number of source = number of KDTrees.
134  // Using mesh divisions or nearest-positions, for every app we use 1 tree per division
135  if (!_from_mesh_divisions.empty() ||
138  // If we group apps, then we only use one tree per division (nearest-position region)
141  EXEC_INITIAL);
142  // Regular case: 1 KDTree per app
143  // Also if use_nearest_app = true, the number of problems is better than the number of positions,
144  // because some of the positions are positions of child applications that are not local
145  else
146  _num_sources = _from_problems.size();
147 }
148 
149 unsigned int
151  unsigned int nested_loop_on_app_index) const
152 {
153  // Each app is mapped to a single KD Tree
154  if (_use_nearest_app)
155  return kdtree_index;
156  // We are looping over all the apps that are grouped together
157  else if (_group_subapps)
158  return nested_loop_on_app_index;
159  // There are num_divisions trees for each app, inner ordering is divisions, so dividing by the
160  // number of divisions gets us the index of the application
161  else
162  return kdtree_index / getNumDivisions();
163 }
164 
165 unsigned int
167 {
168  if (_use_nearest_app)
169  return 1;
170  else if (_group_subapps)
171  return _from_meshes.size();
172  else
173  return 1;
174 }
175 
176 unsigned int
178 {
179  // This is not used currently, but conceptually it is better to only divide the domain with the
180  // local of local applications rather than the global number of positions (# global applications
181  // here)
182  if (_use_nearest_app)
183  return _from_meshes.size();
184  // Each nearest-position region is a division
187  EXEC_INITIAL);
188  // Assume all mesh divisions (on each sub-app) has the same number of divisions. This is checked
189  else if (!_from_mesh_divisions.empty())
191  // Grouping subapps or no special mode, we do not subdivide
192  else
193  return 1;
194 }
195 
196 Point
198  const Point & pt) const
199 {
201  // KD-trees are built in global coords when grouping subapps with nearest-positions
202  return pt;
203  else
204  return getPointInSourceAppFrame(pt, i_source, "KD-tree neighbor search");
205 }
206 
207 bool
209  const unsigned int mesh_div,
210  const unsigned int i_from) const
211 {
212  // Only use the KDTree from the closest position if in "nearest-position" mode
214  {
215  // See computeNumSources for the number of sources. i_from is the index in the source loop
216  // i_from is local if looping on _from_problems as sources, positions are indexed globally
217  // i_from is already indexing in positions if using group_subapps
218  auto position_index = i_from; // if _group_subapps
219  if (_use_nearest_app)
220  position_index = getGlobalSourceAppIndex(i_from);
221  else if (!_group_subapps)
222  position_index = i_from % getNumDivisions();
223 
224  // NOTE: if two positions are equi-distant to the point, this will chose one
225  // This problem is detected if using search_value_conflicts in this call
226  if (!closestToPosition(position_index, pt))
227  return false;
228  }
229 
230  // Application index depends on which source/grouping mode we are using
231  const unsigned int app_index = getAppIndex(i_from, i_from / getNumDivisions());
232 
233  // Check mesh restriction before anything
235  {
237  mooseError("Nearest-positions + source_app_must_contain_point not implemented");
238  // Transform the point to place it in the local coordinate system
239  const auto local_pt = getPointInSourceAppFrame(pt, app_index, "Source mesh containment check");
240  if (!inMesh(_from_point_locators[app_index].get(), local_pt))
241  return false;
242  }
243 
244  // Check the mesh division. We have handled the restriction of the source locations when
245  // building the nearest-neighbor trees. We only need to check that we meet the required
246  // source division index.
247  if (!_from_mesh_divisions.empty())
248  {
249  mooseAssert(mesh_div != MooseMeshDivision::INVALID_DIVISION_INDEX,
250  "We should not be receiving point requests with an invalid "
251  "source mesh division index");
252  const unsigned int kd_div_index = i_from % getNumDivisions();
253 
254  // If matching source mesh divisions to target apps, we check that the index of the target
255  // application, which was passed in the point request, is equal to the current mesh division
256  if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
257  mesh_div != kd_div_index)
258  return false;
259  // If matching source mesh divisions to target mesh divisions, we check that the index of the
260  // target mesh division, which was passed in the point request, is equal to the current mesh
261  // division
262  else if ((_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
263  _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX) &&
264  mesh_div != kd_div_index)
265  return false;
266  }
267 
268  // If matching target apps to source mesh divisions, we check that the global index of the
269  // application is equal to the target mesh division index, which was passed in the point request
270  if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
271  mesh_div != getGlobalSourceAppIndex(app_index))
272  return false;
273 
274  return true;
275 }
276 
277 void
279  const Point & pt,
280  unsigned int source_index,
281  std::pair<Real, Real> & outgoing_val,
282  bool & point_found)
283 {
284  // First pass: find the nearest neighbor value across all local sources
285  for (const auto i_from : make_range(_num_sources))
286  {
287  if (!checkRestrictionsForSource(pt, source_index, i_from))
288  continue;
289 
290  // TODO: Pre-allocate these two work arrays. They will be regularly resized by the searches
291  std::vector<std::size_t> return_index(_num_nearest_points);
292  std::vector<Real> return_dist_sqr(_num_nearest_points);
293 
294  // KD-Tree can be empty if no points are within block/boundary/bounding box restrictions
295  if (_local_kdtrees[i_from]->numberCandidatePoints())
296  {
297  point_found = true;
298  // Note that we do not need to use the transformed_pt (in the source app frame)
299  // because the KDTree has been created in the reference frame
300  _local_kdtrees[i_from]->neighborSearch(
301  pt, _num_nearest_points, return_index, return_dist_sqr);
302  Real val_sum = 0, dist_sum = 0;
303  for (const auto index : return_index)
304  {
305  val_sum += _local_values[i_from][index];
306  dist_sum += (_local_points[i_from][index] - pt).norm();
307  }
308 
309  // If the new value found is closer than for other sources, use it
310  const auto new_distance = dist_sum / return_dist_sqr.size();
311  if (new_distance < outgoing_val.second)
312  outgoing_val = {val_sum / return_index.size(), new_distance};
313  }
314  }
315 
316  // Second pass: search-value-conflict detection
317  if (point_found && _search_value_conflicts)
318  {
319  unsigned int num_equidistant_problems = 0;
320 
321  for (const auto i_from : make_range(_num_sources))
322  {
323  if (!checkRestrictionsForSource(pt, source_index, i_from))
324  continue;
325 
326  // TODO: Pre-allocate these two work arrays. They will be regularly resized by the searches
327  std::vector<std::size_t> return_index(_num_nearest_points + 1);
328  std::vector<Real> return_dist_sqr(_num_nearest_points + 1);
329 
330  const auto app_index = getAppIndex(i_from, i_from / getNumDivisions());
331  const auto num_search = _num_nearest_points + 1;
332 
333  if (_local_kdtrees[i_from]->numberCandidatePoints())
334  {
335  _local_kdtrees[i_from]->neighborSearch(pt, num_search, return_index, return_dist_sqr);
336  auto num_found = return_dist_sqr.size();
337 
338  // Local coordinates only accessible when not using nearest-position,
339  // as we did not keep the index of the source app, only the position index
340  const Point local_pt = getPointInSourceKDTreeFrame(app_index, pt);
341 
342  if (!_nearest_positions_obj &&
343  _from_transforms[getGlobalSourceAppIndex(app_index)]->hasCoordinateSystemTypeChange())
345  mooseInfo("Search value conflict cannot find the origin point due to the "
346  "non-uniqueness of the coordinate collapsing reverse mapping");
347 
348  // Look for too many equidistant nodes within a problem. First zip then sort by distance
349  std::vector<std::pair<Real, std::size_t>> zipped_nearest_points;
350  for (const auto i : make_range(num_found))
351  zipped_nearest_points.push_back(std::make_pair(return_dist_sqr[i], return_index[i]));
352  std::sort(zipped_nearest_points.begin(), zipped_nearest_points.end());
353 
354  // If two furthest are equally far from target point, then we have an indetermination in
355  // what is sent in this communication round from this process. However, it may not
356  // materialize to an actual conflict, as values sent from another process for the
357  // desired target point could be closer (nearest). There is no way to know at this point
358  // in the communication that a closer value exists somewhere else
359  if (num_found > 1 && num_found == num_search &&
360  MooseUtils::absoluteFuzzyEqual(zipped_nearest_points[num_found - 1].first,
361  zipped_nearest_points[num_found - 2].first))
362  {
364  registerConflict(app_index, 0, pt, outgoing_val.second, true);
365  else
366  registerConflict(app_index, 0, local_pt, outgoing_val.second, true);
367  }
368 
369  // Recompute the distance for this problem. If it matches the cached value more than
370  // once it means multiple problems provide equidistant values for this point
371  Real dist_sum = 0;
372  for (const auto i : make_range(num_search - 1))
373  {
374  auto index = zipped_nearest_points[i].second;
375  dist_sum += (_local_points[i_from][index] - pt).norm();
376  }
377 
378  // Compare to the selected value found after looking at all the problems
379  if (MooseUtils::absoluteFuzzyEqual(dist_sum / return_dist_sqr.size(), outgoing_val.second))
380  {
381  num_equidistant_problems++;
382  if (num_equidistant_problems > 1)
383  {
385  registerConflict(app_index, 0, pt, outgoing_val.second, true);
386  else
387  registerConflict(app_index, 0, local_pt, outgoing_val.second, true);
388  }
389  }
390  }
391  }
392  }
393 }
void mooseInfo(Args &&... args) const
Definition: MooseBase.h:344
void initialSetup() override
Method called at the beginning of the simulation for checking integrity or doing one-time setup...
unsigned int getNumDivisions() const
Number of divisions (nearest-positions or source mesh divisions) used when building KD-Trees...
const bool _group_subapps
Whether to group data when creating the nearest-point regions.
void computeNumSources()
Pre-compute the number of sources Number of KDTrees used to hold the locations and variable value dat...
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:467
bool _source_app_must_contain_point
Whether the source app mesh must actually contain the points for them to be considered or whether the...
const bool _skip_coordinate_collapsing
Whether to skip coordinate collapsing (transformations of coordinates between applications using diff...
char ** blocks
Point getPointInSourceAppFrame(const Point &p, unsigned int local_i_from, const std::string &phase) const
Get the source app point from a point in the reference frame.
Point getPointInSourceKDTreeFrame(unsigned int i_source, const Point &pt) const
Transform a point into the frame used for KD-tree queries.
void registerConflict(unsigned int problem, dof_id_type dof_id, Point p, Real dist, bool local)
Register a potential value conflict, e.g.
const ExecFlagType & getCurrentExecuteOnFlag() const
Return/set the current execution flag.
unsigned int getAppIndex(unsigned int kdtree_index, unsigned int app_index_in_tree) const
Get the index of the app when inside of a KD-Tree source loop, where multiple applications could be l...
std::vector< std::unique_ptr< libMesh::PointLocatorBase > > _from_point_locators
Point locators, useful to examine point location with regards to domain restriction.
MeshBase & mesh
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
bool inBlocks(const std::set< SubdomainID > &blocks, const MooseMesh &mesh, const Elem *elem) const override
FEProblemBase & _fe_problem
Definition: Transfer.h:97
std::vector< const MeshDivision * > _from_mesh_divisions
Division of the origin mesh.
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
bool inMesh(const libMesh::PointLocatorBase *const pl, const Point &pt) const
bool closestToPosition(unsigned int pos_index, const Point &pt) const
Whether a point is closest to a position at the index specified than any other position.
unsigned int _num_sources
Number of KD-Trees to create.
MultiAppGeneralFieldKDTreeTransferBase(const InputParameters &parameters)
void evaluateNearestNodeFromKDTrees(const Point &pt, unsigned int source_index, std::pair< Real, Real > &outgoing_val, bool &point_found)
Search all local KD-trees for the nearest node/element and update outgoing_val.
unsigned int getNumAppsPerTree() const
Number of applications which contributed nearest-locations to each KD-tree.
virtual void prepareEvaluationOfInterpValues(const unsigned int var_index) override
std::vector< MooseMesh * > _from_meshes
virtual unsigned int n_nodes() const=0
MooseMesh wraps a libMesh::Mesh object and enhances its capabilities by caching additional data and s...
Definition: MooseMesh.h:93
bool _search_value_conflicts
Whether to look for conflicts between origin points, multiple valid values for a target point...
unsigned int getGlobalSourceAppIndex(unsigned int i_from) const
Return the global app index from the local index in the "from-multiapp" transfer direction.
unsigned int getNumPositions(bool initial=false) const
}
Definition: Positions.h:37
virtual void buildKDTrees(const unsigned int var_index)=0
unsigned int INVALID_DIVISION_INDEX
Invalid subdomain id to return when outside the mesh division.
Definition: MeshDivision.h:28
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const MooseEnum & _to_mesh_division_behavior
How to use the target mesh divisions to restrict the transfer.
auto norm(const T &a)
const Node * node_ptr(const unsigned int i) const
std::vector< std::unique_ptr< MultiAppCoordTransform > > _from_transforms
IntRange< T > make_range(T beg, T end)
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:281
bool checkRestrictionsForSource(const Point &pt, const unsigned int valid_mesh_div, const unsigned int i_from) const
Examine all spatial restrictions that could preclude this source from being a valid source for this p...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:209
unsigned int _num_nearest_points
Number of points to consider.
virtual void initialSetup() override
Method called at the beginning of the simulation for checking integrity or doing one-time setup...
std::vector< std::vector< Real > > _local_values
Values of the variable being transferred at all the points in _local_points.
std::vector< std::vector< Point > > _local_points
All the nodes that meet the spatial restrictions in all the local source apps.
std::vector< FEProblemBase * > _from_problems
void ErrorVector unsigned int
std::vector< std::shared_ptr< KDTree > > _local_kdtrees
KD-Trees for all the local source apps.
It is a general field transfer.
const MooseEnum & _from_mesh_division_behavior
How to use the origin mesh divisions to restrict the transfer.
const bool _use_nearest_app
Whether to keep track of the distance from the requested point to the app position.
const ExecFlagType EXEC_INITIAL
Definition: Moose.C:30