https://mooseframework.inl.gov
MultiAppGeneralFieldTransfer.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 "DisplacedProblem.h"
14 #include "FEProblem.h"
15 #include "MooseMesh.h"
16 #include "MooseTypes.h"
17 #include "MooseVariableFE.h"
18 #include "MeshDivision.h"
19 #include "Positions.h"
20 #include "Factory.h"
21 #include "MooseAppCoordTransform.h"
22 
23 // libmesh includes
24 #include "libmesh/point_locator_base.h"
25 #include "libmesh/enum_point_locator_type.h"
26 
27 // TIMPI includes
28 #include "timpi/communicator.h"
29 #include "timpi/parallel_sync.h"
30 
31 using namespace libMesh;
32 
33 namespace GeneralFieldTransfer
34 {
35 Number BetterOutOfMeshValue = std::numeric_limits<Real>::infinity();
36 }
37 
40 {
42  // Expansion default to make a node in the target mesh overlapping with a node in the origin
43  // mesh always register as being inside the origin application bounding box. The contains_point
44  // bounding box checks uses exact comparisons
45  params.addRangeCheckedParam<Real>("bbox_factor",
46  1.00000001,
47  "bbox_factor>0",
48  "Factor to inflate or deflate the source app bounding boxes");
49  params.addRangeCheckedParam<std::vector<Real>>(
50  "fixed_bounding_box_size",
51  "fixed_bounding_box_size >= 0",
52  "Override source app bounding box size(s) for searches. App bounding boxes will still be "
53  "centered on the same coordinates. Only non-zero components passed will override.");
54  params.addParam<Real>(
55  "extrapolation_constant",
56  0,
57  "Constant to use when no source app can provide a valid value for a target location.");
58 
59  // Block restrictions
60  params.addParam<std::vector<SubdomainName>>(
61  "from_blocks",
62  "Subdomain restriction to transfer from (defaults to all the origin app domain)");
63  params.addParam<std::vector<SubdomainName>>(
64  "to_blocks", "Subdomain restriction to transfer to, (defaults to all the target app domain)");
65 
66  // Boundary restrictions
67  params.addParam<std::vector<BoundaryName>>(
68  "from_boundaries",
69  "The boundary we are transferring from (if not specified, whole domain is used).");
70  params.addParam<std::vector<BoundaryName>>(
71  "to_boundaries",
72  "The boundary we are transferring to (if not specified, whole domain is used).");
73  MooseEnum nodes_or_sides("nodes sides", "sides");
74  params.addParam<MooseEnum>("elemental_boundary_restriction",
75  nodes_or_sides,
76  "Whether elemental variable boundary restriction is considered by "
77  "element side or element nodes");
78 
79  // Mesh division restriction
80  params.addParam<MeshDivisionName>("from_mesh_division",
81  "Mesh division object on the origin application");
82  params.addParam<MeshDivisionName>("to_mesh_division",
83  "Mesh division object on the target application");
84  MooseEnum mesh_division_uses("spatial_restriction matching_division matching_subapp_index none",
85  "none");
86  params.addParam<MooseEnum>("from_mesh_division_usage",
87  mesh_division_uses,
88  "How to use the source mesh division in the transfer. See object "
89  "documentation for description of each option");
90  params.addParam<MooseEnum>("to_mesh_division_usage",
91  mesh_division_uses,
92  "How to use the target mesh division in the transfer. See object "
93  "documentation for description of each option");
94 
95  // Array and vector variables
96  params.addParam<std::vector<unsigned int>>("source_variable_components",
97  std::vector<unsigned int>(),
98  "The source array or vector variable component(s).");
99  params.addParam<std::vector<unsigned int>>("target_variable_components",
100  std::vector<unsigned int>(),
101  "The target array or vector variable component(s).");
102 
103  // Search options
104  params.addParam<bool>(
105  "greedy_search",
106  false,
107  "Whether or not to send a point to all the domains. If true, all the processors will be "
108  "checked for a given point."
109  "The code will be slow if this flag is on but it will give a better solution.");
110  params.addParam<bool>(
111  "error_on_miss",
112  true,
113  "Whether or not to error in the case that a target point is not found in the source domain.");
114  params.addParam<bool>("use_bounding_boxes",
115  true,
116  "When set to false, bounding boxes will not be used to restrict the source "
117  "of the transfer. Either source applications must be set using the "
118  "from_mesh_division parameter, or a greedy search must be used.");
119  params.addParam<bool>(
120  "use_nearest_app",
121  false,
122  "When True, transfers from a child application will work by finding the nearest (using "
123  "the `position` + mesh centroid) sub-app and query that app for the value to transfer.");
124  params.addParam<PositionsName>(
125  "use_nearest_position",
126  "Name of the the Positions object (in main app) such that transfers to/from a child "
127  "application will work by finding the nearest position to a target and query only the "
128  "app / points closer to this position than to any other position for the value to transfer.");
129  params.addParam<bool>(
130  "from_app_must_contain_point",
131  false,
132  "Wether on not the origin mesh must contain the point to evaluate data at. If false, this "
133  "allows for interpolation between origin app meshes. Origin app bounding boxes are still "
134  "considered so you may want to increase them with 'fixed_bounding_box_size'");
135  params.addParam<bool>("search_value_conflicts",
136  false,
137  "Whether to look for potential conflicts between two valid and different "
138  "source values for any target point");
139  params.addParam<unsigned int>(
140  "value_conflicts_output",
141  10,
142  "Maximum number of conflicts to output if value-conflicts, from equidistant sources to a "
143  "given transfer target location, search is turned on");
144 
145  params.addParamNamesToGroup(
146  "to_blocks from_blocks to_boundaries from_boundaries elemental_boundary_restriction "
147  "from_mesh_division from_mesh_division_usage to_mesh_division to_mesh_division_usage",
148  "Transfer spatial restriction");
149  params.addParamNamesToGroup(
150  "greedy_search use_bounding_boxes use_nearest_app use_nearest_position "
151  "search_value_conflicts",
152  "Search algorithm");
153  params.addParamNamesToGroup("error_on_miss from_app_must_contain_point extrapolation_constant",
154  "Extrapolation behavior");
155  params.addParamNamesToGroup("bbox_factor fixed_bounding_box_size", "Source app bounding box");
156  return params;
157 }
158 
160  : MultiAppConservativeTransfer(parameters),
161  _from_var_components(getParam<std::vector<unsigned int>>("source_variable_components")),
162  _to_var_components(getParam<std::vector<unsigned int>>("target_variable_components")),
163  _use_bounding_boxes(getParam<bool>("use_bounding_boxes")),
164  _use_nearest_app(getParam<bool>("use_nearest_app")),
165  _nearest_positions_obj(
166  isParamValid("use_nearest_position")
167  ? &_fe_problem.getPositionsObject(getParam<PositionsName>("use_nearest_position"))
168  : nullptr),
169  _source_app_must_contain_point(getParam<bool>("from_app_must_contain_point")),
170  _from_mesh_division_behavior(getParam<MooseEnum>("from_mesh_division_usage")),
171  _to_mesh_division_behavior(getParam<MooseEnum>("to_mesh_division_usage")),
172  _elemental_boundary_restriction_on_sides(
173  getParam<MooseEnum>("elemental_boundary_restriction") == "sides"),
174  _greedy_search(getParam<bool>("greedy_search")),
175  _search_value_conflicts(getParam<bool>("search_value_conflicts")),
176  _already_output_search_value_conflicts(false),
177  _search_value_conflicts_max_log(getParam<unsigned int>("value_conflicts_output")),
178  _error_on_miss(getParam<bool>("error_on_miss")),
179  _default_extrapolation_value(getParam<Real>("extrapolation_constant")),
180  _bbox_factor(getParam<Real>("bbox_factor")),
181  _fixed_bbox_size(isParamValid("fixed_bounding_box_size")
182  ? getParam<std::vector<Real>>("fixed_bounding_box_size")
183  : std::vector<Real>(3, 0))
184 {
185  _var_size = _to_var_names.size();
186  if (_to_var_names.size() != _from_var_names.size() && !parameters.isPrivate("source_variable"))
187  paramError("variable", "The number of variables to transfer to and from should be equal");
188 
189  // Check the parameters of the components of the array / vector variable
190  if (_from_var_names.size() != _from_var_components.size() && _from_var_components.size() > 0)
191  paramError("source_variable_components",
192  "This parameter must be equal to the number of source variables");
193  if (_to_var_names.size() != _to_var_components.size() && _to_var_components.size() > 0)
194  paramError("target_variable_components",
195  "This parameter must be equal to the number of target variables");
196 
197  // Make simple 'use_nearest_app' parameter rely on Positions
198  if (_use_nearest_app)
199  {
201  paramError("use_nearest_app", "Cannot use nearest-app and nearest-position together");
202  if (!hasFromMultiApp())
203  paramError("use_nearest_app",
204  "Should have a 'from_multiapp' when using the nearest-app informed search");
205  auto pos_params = _app.getFactory().getValidParams("MultiAppPositions");
206  pos_params.set<std::vector<MultiAppName>>("multiapps") = {getFromMultiApp()->name()};
207  _fe_problem.addReporter("MultiAppPositions", "_created_for_" + name(), pos_params);
209  }
210 
211  // Dont let users get wrecked by bounding boxes if it looks like they are trying to extrapolate
213  (_nearest_positions_obj || isParamSetByUser("from_app_must_contain_point")))
214  if (!isParamSetByUser("bbox_factor") && !isParamSetByUser("fixed_bounding_box_size"))
215  mooseWarning(
216  "Extrapolation (nearest-source options, outside-app source) parameters have "
217  "been passed, but no subapp bounding box expansion parameters have been passed.");
218 
219  if (!_use_bounding_boxes &&
220  (isParamValid("fixed_bounding_box_size") || isParamSetByUser("bbox_factor")))
221  paramError("use_bounding_boxes",
222  "Cannot pass additional bounding box parameters (sizes, expansion, etc) if we are "
223  "not using bounding boxes");
224 }
225 
226 void
228 {
230 
231  // Use IDs for block and boundary restriction
232  // Loop over all source problems
233  for (const auto i_from : index_range(_from_problems))
234  {
235  const auto & from_moose_mesh = _from_problems[i_from]->mesh(_displaced_source_mesh);
236  if (isParamValid("from_blocks"))
237  {
238  const auto & block_names = getParam<std::vector<SubdomainName>>("from_blocks");
239 
240  for (const auto & b : block_names)
241  if (!MooseMeshUtils::hasSubdomainName(from_moose_mesh.getMesh(), b))
242  paramError("from_blocks", "The block '", b, "' was not found in the mesh");
243 
244  if (!block_names.empty())
245  {
246  const auto ids = from_moose_mesh.getSubdomainIDs(block_names);
247  _from_blocks.insert(ids.begin(), ids.end());
248  }
249  }
250 
251  if (isParamValid("from_boundaries"))
252  {
253  const auto & boundary_names = getParam<std::vector<BoundaryName>>("from_boundaries");
254  for (const auto & bn : boundary_names)
255  if (!MooseMeshUtils::hasBoundaryName(from_moose_mesh.getMesh(), bn))
256  paramError("from_boundaries", "The boundary '", bn, "' was not found in the mesh");
257 
258  if (!boundary_names.empty())
259  {
260  const auto boundary_ids = from_moose_mesh.getBoundaryIDs(boundary_names);
261  _from_boundaries.insert(boundary_ids.begin(), boundary_ids.end());
262  }
263  }
264 
265  if (isParamValid("from_mesh_division"))
266  {
267  const auto & mesh_div_name = getParam<MeshDivisionName>("from_mesh_division");
268  _from_mesh_divisions.push_back(&_from_problems[i_from]->getMeshDivision(mesh_div_name));
269  // Check that the behavior set makes sense
270  if (_from_mesh_division_behavior == MeshDivisionTransferUse::RESTRICTION)
271  {
272  if (_from_mesh_divisions[i_from]->coversEntireMesh())
273  mooseInfo("'from_mesh_division_usage' is set to use a spatial restriction but the "
274  "'from_mesh_division' for source app of global index " +
275  std::to_string(getGlobalSourceAppIndex(i_from)) +
276  " covers the entire mesh. Do not expect any restriction from a mesh "
277  "division that covers the entire mesh");
278  }
279  else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX &&
280  !isParamValid("to_mesh_division"))
281  paramError("to_mesh_division_usage",
282  "Source mesh division cannot match target mesh division if no target mesh "
283  "division is specified");
284  else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
285  {
286  if (!hasToMultiApp())
287  paramError("from_mesh_division_usage",
288  "Cannot match source mesh division index to target subapp index if there is "
289  "only one target: the parent app (not a subapp)");
290  else if (getToMultiApp()->numGlobalApps() !=
291  _from_mesh_divisions[i_from]->getNumDivisions())
292  mooseWarning("Attempting to match target subapp index with the number of source mesh "
293  "divisions, which is " +
294  std::to_string(_from_mesh_divisions[i_from]->getNumDivisions()) +
295  " while there are " + std::to_string(getToMultiApp()->numGlobalApps()) +
296  " target subapps");
297  if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX)
298  // We do not support it because it would require sending the point + target app index +
299  // target app division index, and we only send the Point + one number
300  paramError("from_mesh_division_usage",
301  "We do not support using target subapp index for source division behavior and "
302  "matching the division index for the target mesh division behavior.");
303  }
304  else if (_from_mesh_division_behavior == "none")
305  paramError("from_mesh_division_usage", "User must specify a 'from_mesh_division_usage'");
306  }
307  else if (_from_mesh_division_behavior != "none")
308  paramError("from_mesh_division",
309  "'from_mesh_division' must be specified if the usage method is specified");
310  }
311 
312  // Loop over all target problems
313  for (const auto i_to : index_range(_to_problems))
314  {
315  const auto & to_moose_mesh = _to_problems[i_to]->mesh(_displaced_target_mesh);
316  if (isParamValid("to_blocks"))
317  {
318  const auto & block_names = getParam<std::vector<SubdomainName>>("to_blocks");
319  for (const auto & b : block_names)
320  if (!MooseMeshUtils::hasSubdomainName(to_moose_mesh.getMesh(), b))
321  paramError("to_blocks", "The block '", b, "' was not found in the mesh");
322 
323  if (!block_names.empty())
324  {
325  const auto ids = to_moose_mesh.getSubdomainIDs(block_names);
326  _to_blocks.insert(ids.begin(), ids.end());
327  }
328  }
329 
330  if (isParamValid("to_boundaries"))
331  {
332  const auto & boundary_names = getParam<std::vector<BoundaryName>>("to_boundaries");
333  for (const auto & bn : boundary_names)
334  if (!MooseMeshUtils::hasBoundaryName(to_moose_mesh.getMesh(), bn))
335  paramError("to_boundaries", "The boundary '", bn, "' was not found in the mesh");
336 
337  if (!boundary_names.empty())
338  {
339  const auto boundary_ids = to_moose_mesh.getBoundaryIDs(boundary_names);
340  _to_boundaries.insert(boundary_ids.begin(), boundary_ids.end());
341  }
342  }
343 
344  if (isParamValid("to_mesh_division"))
345  {
346  const auto & mesh_div_name = getParam<MeshDivisionName>("to_mesh_division");
347  _to_mesh_divisions.push_back(&_to_problems[i_to]->getMeshDivision(mesh_div_name));
348  // Check that the behavior set makes sense
349  if (_to_mesh_division_behavior == MeshDivisionTransferUse::RESTRICTION)
350  {
351  if (_to_mesh_divisions[i_to]->coversEntireMesh())
352  mooseInfo("'to_mesh_division_usage' is set to use a spatial restriction but the "
353  "'to_mesh_division' for target application of global index " +
354  std::to_string(getGlobalSourceAppIndex(i_to)) +
355  " covers the entire mesh. Do not expect any restriction from a mesh "
356  "division that covers the entire mesh");
357  }
358  else if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX)
359  {
360  if (!isParamValid("from_mesh_division"))
361  paramError("to_mesh_division_usage",
362  "Target mesh division cannot match source mesh division if no source mesh "
363  "division is specified");
364  else if ((*_from_mesh_divisions.begin())->getNumDivisions() !=
365  _to_mesh_divisions[i_to]->getNumDivisions())
366  mooseWarning("Source and target mesh divisions do not have the same number of bins. If "
367  "this is what you expect, please reach out to a MOOSE or app developer to "
368  "ensure appropriate use");
369  }
370  else if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
371  {
372  if (!hasFromMultiApp())
373  paramError(
374  "to_mesh_division_usage",
375  "Cannot match target mesh division index to source subapp index if there is only one "
376  "source: the parent app (not a subapp)");
377  else if (getFromMultiApp()->numGlobalApps() != _to_mesh_divisions[i_to]->getNumDivisions())
378  mooseWarning("Attempting to match source subapp index with the number of target mesh "
379  "divisions, which is " +
380  std::to_string(_to_mesh_divisions[i_to]->getNumDivisions()) +
381  " while there are " + std::to_string(getFromMultiApp()->numGlobalApps()) +
382  " source subapps");
383  if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX)
384  paramError(
385  "from_mesh_division_usage",
386  "We do not support using source subapp index for the target division behavior and "
387  "matching the division index for the source mesh division behavior.");
388  }
389  else if (_to_mesh_division_behavior == "none")
390  paramError("to_mesh_division_usage", "User must specify a 'to_mesh_division_usage'");
391  }
392  else if (_to_mesh_division_behavior != "none")
393  paramError("to_mesh_division",
394  "'to_mesh_division' must be specified if usage method '" +
395  Moose::stringify(_to_mesh_division_behavior) + "' is specified");
396  }
397 
398  // Check if components are set correctly if using an array variable
399  for (const auto i_from : index_range(_from_problems))
400  {
401  for (const auto var_index : make_range(_from_var_names.size()))
402  {
403  MooseVariableFieldBase & from_var =
404  _from_problems[i_from]->getVariable(0,
405  _from_var_names[var_index],
408  if (from_var.count() > 1 && _from_var_components.empty())
409  paramError("source_variable_components", "Component must be passed for an array variable");
410  if (_from_var_components.size() && from_var.count() < _from_var_components[var_index])
411  paramError("source_variable_components",
412  "Component passed is larger than size of variable");
413  }
414  }
415  for (const auto i_to : index_range(_to_problems))
416  {
417  for (const auto var_index : make_range(_to_var_names.size()))
418  {
419  MooseVariableFieldBase & to_var =
420  _to_problems[i_to]->getVariable(0,
421  _to_var_names[var_index],
424  if (to_var.count() > 1 && _to_var_components.empty())
425  paramError("target_variable_components", "Component must be passed for an array variable");
426  if (_to_var_components.size() && to_var.count() < _to_var_components[var_index])
427  paramError("target_variable_components",
428  "Component passed is larger than size of variable");
429  }
430  }
431 
432  // Cache some quantities to avoid having to get them on every transferred point
433  if (_to_problems.size())
434  {
435  _to_variables.resize(_to_var_names.size());
436  for (const auto i_var : index_range(_to_var_names))
437  _to_variables[i_var] = &_to_problems[0]->getVariable(
439  }
440 }
441 
442 void
444 {
446 
447  // Create the point locators to locate evaluation points in the origin mesh(es)
448  _from_point_locators.resize(_from_problems.size());
449  for (const auto i_from : index_range(_from_problems))
450  {
451  const auto & from_moose_mesh = _from_problems[i_from]->mesh(_displaced_source_mesh);
452  _from_point_locators[i_from] =
453  PointLocatorBase::build(TREE_LOCAL_ELEMENTS, from_moose_mesh.getMesh());
454  _from_point_locators[i_from]->enable_out_of_mesh_mode();
455  }
456 }
457 
458 void
460 {
461  TIME_SECTION(
462  "MultiAppGeneralFieldTransfer::execute()_" + name(), 5, "Transfer execution " + name());
463  getAppInfo();
464 
465  // Set up bounding boxes, etc
467 
468  // loop over the vector of variables and make the transfer one by one
469  for (const auto i : make_range(_var_size))
470  transferVariable(i);
471 
472  postExecute();
473 }
474 
475 void
477 {
478  // Get the bounding boxes for the "from" domains.
479  // Clean up _from_bboxes from the previous transfer execution
480  _from_bboxes.clear();
481 
482  // NOTE: This ignores the app's bounding box inflation and padding
484 
485  // Expand bounding boxes. Some desired points might be excluded
486  // without an expansion
488 
489  // Figure out how many "from" domains each processor owns.
490  _froms_per_proc.clear();
492 
493  // Get the index for the first source app every processor owns
495 
496  // No need to keep searching for conflicts if the mesh has not changed
498  _search_value_conflicts = false;
499 }
500 
501 void
503 {
507 }
508 
509 void
511 {
512  mooseAssert(i < _var_size, "The variable of index " << i << " does not exist");
513 
514  // Find outgoing target points
515  // We need to know what points we need to send which processors
516  // One processor will receive many points from many processors
517  // One point may go to different processors
518  ProcessorToPointVec outgoing_points;
519  extractOutgoingPoints(i, outgoing_points);
520 
521  if (_from_var_names.size())
523  else
525 
526  // Fill values and app ids for incoming points
527  // We are responsible to compute values for these incoming points
528  auto gather_functor =
529  [this](processor_id_type /*pid*/,
530  const std::vector<std::pair<Point, unsigned int>> & incoming_locations,
531  std::vector<std::pair<Real, Real>> & outgoing_vals)
532  {
533  outgoing_vals.resize(
534  incoming_locations.size(),
536  // Evaluate interpolation values for these incoming points
537  evaluateInterpValues(incoming_locations, outgoing_vals);
538  };
539 
540  DofobjectToInterpValVec dofobject_to_valsvec(_to_problems.size());
542  InterpCaches distance_caches(_to_problems.size(), getMaxToProblemsBBoxDimensions());
543 
544  // Copy data out to incoming_vals_ids
545  auto action_functor = [this, &i, &dofobject_to_valsvec, &interp_caches, &distance_caches](
546  processor_id_type pid,
547  const std::vector<std::pair<Point, unsigned int>> & my_outgoing_points,
548  const std::vector<std::pair<Real, Real>> & incoming_vals)
549  {
550  auto & pointInfoVec = _processor_to_pointInfoVec[pid];
551 
552  // Cache interpolation values for each dof object / points
554  i,
555  pointInfoVec,
556  my_outgoing_points,
557  incoming_vals,
558  dofobject_to_valsvec,
559  interp_caches,
560  distance_caches);
561  };
562 
563  // We assume incoming_vals_ids is ordered in the same way as outgoing_points
564  // Hopefully, pull_parallel_vector_data will not mess up this
565  const std::pair<Real, Real> * ex = nullptr;
566  libMesh::Parallel::pull_parallel_vector_data(
567  comm(), outgoing_points, gather_functor, action_functor, ex);
568 
569  // Check for conflicts and overlaps from the maps that were built during the transfer
571  outputValueConflicts(i, dofobject_to_valsvec, distance_caches);
572 
573  // Set cached values into solution vector
574  setSolutionVectorValues(i, dofobject_to_valsvec, interp_caches);
575 }
576 
577 void
579  std::set<processor_id_type> & processors)
580 {
581  // Check which processors have apps that may include or be near this point
582  // A point may be close enough to several problems, hosted on several processes
583  bool found = false;
584 
585  // Additional process-restriction techniques we could use (TODOs):
586  // - create a heuristic for using nearest-positions
587  // - from_mesh_divisions could be polled for which divisions they possess on each
588  // process, depending on the behavior chosen. This could limit potential senders.
589  // This should be done ahead of this function call, for all points at once
590 
591  // Determine the apps which will be receiving points (then sending values) using various
592  // heuristics
593  if (_use_nearest_app)
594  {
595  // Find the nearest position for the point
596  const bool initial = _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL;
597  // The apps form the nearest positions here, this is the index of the nearest app
598  const auto nearest_index = _nearest_positions_obj->getNearestPositionIndex(point, initial);
599 
600  // Find the apps that are nearest to the same position
601  // Global search over all applications
602  for (processor_id_type i_proc = 0; i_proc < n_processors(); ++i_proc)
603  {
604  // We need i_from to correspond to the global app index
605  unsigned int from0 = _global_app_start_per_proc[i_proc];
606  for (unsigned int i_from = from0; i_from < from0 + _froms_per_proc[i_proc]; ++i_from)
607  {
608  if (_greedy_search || _search_value_conflicts || i_from == nearest_index)
609  {
610  processors.insert(i_proc);
611  found = true;
612  }
613  mooseAssert(i_from < getFromMultiApp()->numGlobalApps(), "We should not reach this");
614  }
615  }
616  mooseAssert((getFromMultiApp()->numGlobalApps() < n_processors() || processors.size() == 1) ||
618  "Should only be one source processor when using more processors than source apps");
619  }
620  else if (_use_bounding_boxes)
621  {
622  // We examine all (global) bounding boxes and find the minimum of the maximum distances within a
623  // bounding box from the point. This creates a sphere around the point of interest. Any app
624  // with a bounding box that intersects this sphere (with a bboxMinDistance <
625  // nearest_max_distance) will be considered a potential source
626  // NOTE: This is a heuristic. We could try others
627  // NOTE: from_bboxes are in the reference space, as is the point.
628  Real nearest_max_distance = std::numeric_limits<Real>::max();
629  for (const auto & bbox : _from_bboxes)
630  {
632  if (distance < nearest_max_distance)
633  nearest_max_distance = distance;
634  }
635 
636  unsigned int from0 = 0;
637  for (processor_id_type i_proc = 0; i_proc < n_processors();
638  from0 += _froms_per_proc[i_proc], ++i_proc)
639  // i_from here is a hybrid index based on the cumulative sum of the apps per processor
640  for (unsigned int i_from = from0; i_from < from0 + _froms_per_proc[i_proc]; ++i_from)
641  {
643  // We will not break here because we want to send a point to all possible source domains
644  if (_greedy_search || distance <= nearest_max_distance ||
645  _from_bboxes[i_from].contains_point(point))
646  {
647  processors.insert(i_proc);
648  found = true;
649  }
650  }
651  }
652  // Greedy search will contact every single processor. It's not scalable, but if there's valid data
653  // on any subapp on any process, it will find it
654  else if (_greedy_search)
655  {
656  found = true;
657  for (const auto i_proc : make_range(n_processors()))
658  processors.insert(i_proc);
659  }
660  // Since we indicated that we only wanted values from a subapp with the same global index as the
661  // target mesh division, we might as well only communicate with the process that owns this app
662  else if (!_to_mesh_divisions.empty() &&
663  _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
664  {
665  // The target point could have a different index in each target mesh division. So on paper, we
666  // would need to check all of them.
667  auto saved_target_div = MooseMeshDivision::INVALID_DIVISION_INDEX;
668  for (const auto i_to : index_range(_to_meshes))
669  {
670  const auto target_div = _to_mesh_divisions[i_to]->divisionIndex(
671  _to_transforms[getGlobalTargetAppIndex(i_to)]->mapBack(point));
672  // If it's the same division index, do not redo the search
673  if (target_div == saved_target_div)
674  continue;
675  else
676  saved_target_div = target_div;
677 
678  // Look for the processors owning a source-app with an index equal to the target mesh division
679  for (const auto i_proc : make_range(n_processors()))
680  for (const auto i_from : make_range(_froms_per_proc[i_proc]))
681  if (target_div == _global_app_start_per_proc[i_proc] + i_from)
682  {
683  processors.insert(i_proc);
684  found = true;
685  }
686  }
687  }
688  else
689  mooseError("No algorithm were selected to find which processes may send value data "
690  "for a each target point. Please either specify using bounding boxes, "
691  "greedy search, or to_mesh_division-based parameters");
692 
693  // Error out if we could not find this point when ask us to do so
694  if (!found && _error_on_miss)
695  mooseError(
696  "Cannot find a source application to provide a value at point: ",
697  point,
698  " \n ",
699  "It must be that mismatched meshes, between the source and target application, are being "
700  "used.\nIf you are using the bounding boxes or nearest-app heuristics, or mesh-divisions, "
701  "please consider using the greedy_search to confirm. Then consider choosing a different "
702  "transfer type.\nThis check can be turned off by setting 'error_on_miss' to false. The "
703  "'extrapolation_constant' parameter will be used to set the local value at missed points.");
704 }
705 
706 void
708  const dof_id_type dof_object_id,
709  const unsigned int problem_id,
710  ProcessorToPointVec & outgoing_points)
711 {
712  std::set<processor_id_type> processors;
713  // Find which processors will receive point data so they can send back value data
714  // The list can be larger than needed, depending on the heuristic / algorithm used to make
715  // the call on whether a processor (and the apps it runs) should be involved
716  processors.clear();
717  locatePointReceivers(point, processors);
718 
719  // We need to send this location data to these processors so they can send back values
720  for (const auto pid : processors)
721  {
722  // Select which from_mesh_division the source data must come from for this point
723  unsigned int required_source_division = 0;
724  if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
725  required_source_division = getGlobalTargetAppIndex(problem_id);
726  else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
727  _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
728  _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
729  required_source_division = _to_mesh_divisions[problem_id]->divisionIndex(
730  _to_transforms[getGlobalTargetAppIndex(problem_id)]->mapBack(point));
731 
732  // Skip if we already know we don't want the point
733  if (required_source_division == MooseMeshDivision::INVALID_DIVISION_INDEX)
734  continue;
735 
736  // Store outgoing information for every source process
737  outgoing_points[pid].push_back(std::pair<Point, unsigned int>(point, required_source_division));
738 
739  // Store point information locally for processing received data
740  // We can use these information when inserting values into the solution vector
741  PointInfo pointinfo;
742  pointinfo.problem_id = problem_id;
743  pointinfo.dof_object_id = dof_object_id;
744  _processor_to_pointInfoVec[pid].push_back(pointinfo);
745  }
746 }
747 
748 void
750  ProcessorToPointVec & outgoing_points)
751 {
752  // Get the variable name, with the accommodation for array/vector names
753  const auto & var_name = getToVarName(var_index);
754 
755  // Clean up the map from processor to pointInfo vector
756  // This map should be consistent with outgoing_points
758 
759  // Loop over all problems
760  for (const auto i_to : index_range(_to_problems))
761  {
762  const auto global_i_to = getGlobalTargetAppIndex(i_to);
763 
764  // libMesh EquationSystems
766  // libMesh system that has this variable
767  System * to_sys = find_sys(es, var_name);
768  auto sys_num = to_sys->number();
769  auto var_num = _to_variables[var_index]->number();
770  auto & fe_type = _to_variables[var_index]->feType();
771  bool is_nodal = _to_variables[var_index]->isNodal();
772 
773  // Moose mesh
774  const auto & to_moose_mesh = _to_problems[i_to]->mesh(_displaced_target_mesh);
775  const auto & to_mesh = to_moose_mesh.getMesh();
776 
777  // We support more general variables via libMesh GenericProjector
778  if (fe_type.order > CONSTANT && !is_nodal)
779  {
783  const std::vector<unsigned int> varvec(1, var_num);
784 
787  Number,
789  request_gather(*to_sys, f, &g, nullsetter, varvec);
790 
791  // Defining only boundary values will not be enough to describe the variable, disallow it
792  if (_to_boundaries.size() && (_to_variables[var_index]->getContinuity() == DISCONTINUOUS))
793  mooseError("Higher order discontinuous elemental variables are not supported for "
794  "target-boundary "
795  "restricted transfers");
796 
797  // Not implemented as the target mesh division could similarly be cutting elements in an
798  // arbitrary way with not enough requested points to describe the target variable
799  if (!_to_mesh_divisions.empty() && !_to_mesh_divisions[i_to]->coversEntireMesh())
800  mooseError("Higher order variable support not implemented for target mesh division "
801  "unless the mesh is fully covered / indexed in the mesh division. This must be "
802  "set programmatically in the MeshDivision object used.");
803 
804  // We dont look at boundary restriction, not supported for higher order target variables
805  // Same for mesh divisions
806  const auto & to_begin = _to_blocks.empty()
807  ? to_mesh.active_local_elements_begin()
808  : to_mesh.active_local_subdomain_set_elements_begin(_to_blocks);
809 
810  const auto & to_end = _to_blocks.empty()
811  ? to_mesh.active_local_elements_end()
812  : to_mesh.active_local_subdomain_set_elements_end(_to_blocks);
813 
814  ConstElemRange to_elem_range(to_begin, to_end);
815 
816  request_gather.project(to_elem_range);
817 
818  dof_id_type point_id = 0;
819  for (const Point & p : f.points_requested())
820  // using the point number as a "dof_object_id" will serve to identify the point if we ever
821  // rework interp/distance_cache into the dof_id_to_value maps
823  (*_to_transforms[global_i_to])(p), point_id++, i_to, outgoing_points);
824 
825  // This is going to require more complicated transfer work
826  if (!g.points_requested().empty())
827  mooseError("We don't currently support variables with gradient degrees of freedom");
828  }
829  else if (is_nodal)
830  {
831  for (const auto & node : to_mesh.local_node_ptr_range())
832  {
833  // Skip this node if the variable has no dofs at it.
834  if (node->n_dofs(sys_num, var_num) < 1)
835  continue;
836 
837  // Skip if it is a block restricted transfer and current node does not have
838  // specified blocks
839  if (!_to_blocks.empty() && !inBlocks(_to_blocks, to_moose_mesh, node))
840  continue;
841 
842  if (!_to_boundaries.empty() && !onBoundaries(_to_boundaries, to_moose_mesh, node))
843  continue;
844 
845  // Skip if the node does not meet the target mesh division behavior
846  // We cannot know from which app the data will come from so we cannot know
847  // the source mesh division index and the source app global index
848  if (!_to_mesh_divisions.empty() && _to_mesh_divisions[i_to]->divisionIndex(*node) ==
850  continue;
851 
852  // Cache point information
853  // We will use this information later for setting values back to solution vectors
855  (*_to_transforms[global_i_to])(*node), node->id(), i_to, outgoing_points);
856  }
857  }
858  else // Elemental, constant monomial
859  {
860  for (const auto & elem :
861  as_range(to_mesh.local_elements_begin(), to_mesh.local_elements_end()))
862  {
863  // Skip this element if the variable has no dofs at it.
864  if (elem->n_dofs(sys_num, var_num) < 1)
865  continue;
866 
867  // Skip if the element is not inside the block restriction
868  if (!_to_blocks.empty() && !inBlocks(_to_blocks, elem))
869  continue;
870 
871  // Skip if the element does not have a side on the boundary
872  if (!_to_boundaries.empty() && !onBoundaries(_to_boundaries, to_moose_mesh, elem))
873  continue;
874 
875  // Skip if the element is not indexed within the mesh division
876  if (!_to_mesh_divisions.empty() && _to_mesh_divisions[i_to]->divisionIndex(*elem) ==
878  continue;
879 
880  // Cache point information
881  // We will use this information later for setting values back to solution vectors
882  cacheOutgoingPointInfo((*_to_transforms[global_i_to])(elem->vertex_average()),
883  elem->id(),
884  i_to,
885  outgoing_points);
886  } // for
887  } // else
888  } // for
889 }
890 
891 void
892 MultiAppGeneralFieldTransfer::extractLocalFromBoundingBoxes(std::vector<BoundingBox> & local_bboxes)
893 {
894  local_bboxes.resize(_froms_per_proc[processor_id()]);
895  // Find the index to the first of this processor's local bounding boxes.
896  unsigned int local_start = 0;
897  for (processor_id_type i_proc = 0; i_proc < n_processors() && i_proc != processor_id(); ++i_proc)
898  local_start += _froms_per_proc[i_proc];
899 
900  // Extract the local bounding boxes.
901  for (const auto i_from : make_range(_froms_per_proc[processor_id()]))
902  local_bboxes[i_from] = _from_bboxes[local_start + i_from];
903 }
904 
905 void
907  processor_id_type pid,
908  const unsigned int var_index,
909  std::vector<PointInfo> & pointInfoVec,
910  const std::vector<std::pair<Point, unsigned int>> & point_requests,
911  const std::vector<std::pair<Real, Real>> & incoming_vals,
912  DofobjectToInterpValVec & dofobject_to_valsvec,
913  InterpCaches & interp_caches,
914  InterpCaches & distance_caches)
915 {
916  mooseAssert(pointInfoVec.size() == incoming_vals.size(),
917  "Number of dof objects does not equal to the number of incoming values");
918 
919  dof_id_type val_offset = 0;
920  for (const auto & pointinfo : pointInfoVec)
921  {
922  // Retrieve target information from cached point infos
923  const auto problem_id = pointinfo.problem_id;
924  const auto dof_object_id = pointinfo.dof_object_id;
925 
926  auto & fe_type = _to_variables[var_index]->feType();
927  bool is_nodal = _to_variables[var_index]->isNodal();
928 
929  // In the higher order elemental variable case, we receive point values, not nodal or
930  // elemental. We use an InterpCache to store the values. The distance_cache is necessary to
931  // choose between multiple origin problems sending values. This code could be unified with the
932  // lower order order case by using the dofobject_to_valsvec
933  if (fe_type.order > CONSTANT && !is_nodal)
934  {
935  // Cache solution on target mesh in its local frame of reference
936  InterpCache & value_cache = interp_caches[problem_id];
937  InterpCache & distance_cache = distance_caches[problem_id];
938  Point p = _to_transforms[getGlobalTargetAppIndex(problem_id)]->mapBack(
939  point_requests[val_offset].first);
940  const Number val = incoming_vals[val_offset].first;
941 
942  // Initialize distance to be able to compare
943  if (!distance_cache.hasKey(p))
944  distance_cache[p] = std::numeric_limits<Real>::max();
945 
946  // We should only have one closest value for each variable at any given point.
947  // While there are shared Qps, on vertices for higher order variables usually,
948  // the generic projector only queries each point once
950  value_cache.hasKey(p) != 0 && !MooseUtils::absoluteFuzzyEqual(value_cache[p], val) &&
951  MooseUtils::absoluteFuzzyEqual(distance_cache[p], incoming_vals[val_offset].second))
952  registerConflict(problem_id, dof_object_id, p, incoming_vals[val_offset].second, false);
953 
954  // if we use the nearest app, even if the value is bad we want to save the distance because
955  // it's the distance to the app, if that's the closest app then so be it with the bad value
957  MooseUtils::absoluteFuzzyGreaterThan(distance_cache[p], incoming_vals[val_offset].second))
958  {
959  // NOTE: We store the distance as well as the value. We really only need the
960  // value to construct the variable, but the distance is used to make decisions in nearest
961  // node schemes on which value to use
962  value_cache[p] = val;
963  distance_cache[p] = incoming_vals[val_offset].second;
964  }
965  }
966  else
967  {
968  // Using the dof object pointer, so we can handle
969  // both element and node using the same code
970 #ifndef NDEBUG
971  auto var_num = _to_variables[var_index]->number();
972  auto & to_sys = _to_variables[var_index]->sys();
973 
974  const MeshBase & to_mesh = _to_problems[problem_id]->mesh(_displaced_target_mesh).getMesh();
975  const DofObject * dof_object_ptr = nullptr;
976  const auto sys_num = to_sys.number();
977  // It is a node
978  if (is_nodal)
979  dof_object_ptr = to_mesh.node_ptr(dof_object_id);
980  // It is an element
981  else
982  dof_object_ptr = to_mesh.elem_ptr(dof_object_id);
983 
984  // We should only be supporting nodal and constant elemental
985  // variables in this code path; if we see multiple DoFs on one
986  // object we should have been using GenericProjector
987  mooseAssert(dof_object_ptr->n_dofs(sys_num, var_num) == 1,
988  "Unexpectedly found " << dof_object_ptr->n_dofs(sys_num, var_num)
989  << "dofs instead of 1");
990 #endif
991 
992  auto & dofobject_to_val = dofobject_to_valsvec[problem_id];
993 
994  // Check if we visited this dof object earlier
995  auto values_ptr = dofobject_to_val.find(dof_object_id);
996  // We did not visit this
997  if (values_ptr == dofobject_to_val.end())
998  {
999  // Values for this dof object
1000  auto & val = dofobject_to_val[dof_object_id];
1001  // Interpolation value
1002  val.interp = incoming_vals[val_offset].first;
1003  // Where this value came from
1004  val.pid = pid;
1005  // Distance
1006  val.distance = incoming_vals[val_offset].second;
1007  }
1008  else
1009  {
1010  auto & val = values_ptr->second;
1011 
1012  // Look for value conflicts
1013  if (detectConflict(val.interp,
1014  incoming_vals[val_offset].first,
1015  val.distance,
1016  incoming_vals[val_offset].second))
1017  {
1018  // Keep track of distance and value
1019  const Point p =
1020  getPointInTargetAppFrame(point_requests[val_offset].first,
1021  problem_id,
1022  "Registration of received equi-distant value conflict");
1023  registerConflict(problem_id, dof_object_id, p, incoming_vals[val_offset].second, false);
1024  }
1025 
1026  // We adopt values that are, in order of priority
1027  // - valid (or from nearest app)
1028  // - closest distance
1029  // - the smallest rank with the same distance
1030  // It is debatable whether we want invalid values from the nearest app. It could just be
1031  // that the app position was closer but the extent of another child app was large enough
1032  if ((!GeneralFieldTransfer::isBetterOutOfMeshValue(incoming_vals[val_offset].first) ||
1033  _use_nearest_app) &&
1034  (MooseUtils::absoluteFuzzyGreaterThan(val.distance, incoming_vals[val_offset].second) ||
1035  ((val.pid > pid) &&
1036  MooseUtils::absoluteFuzzyEqual(val.distance, incoming_vals[val_offset].second))))
1037  {
1038  val.interp = incoming_vals[val_offset].first;
1039  val.pid = pid;
1040  val.distance = incoming_vals[val_offset].second;
1041  }
1042  }
1043  }
1044 
1045  // Move it to next position
1046  val_offset++;
1047  }
1048 }
1049 
1050 void
1052  unsigned int problem, dof_id_type dof_id, Point p, Real dist, bool local)
1053 {
1054  // NOTE We could be registering the same conflict several times, we could count them instead
1055  if (local)
1056  _local_conflicts.push_back(std::make_tuple(problem, dof_id, p, dist));
1057  else
1058  _received_conflicts.push_back(std::make_tuple(problem, dof_id, p, dist));
1059 }
1060 
1061 void
1063  const unsigned int var_index,
1064  const DofobjectToInterpValVec & dofobject_to_valsvec,
1065  const InterpCaches & distance_caches)
1066 {
1067  const auto var_name = getToVarName(var_index);
1068  // We must check a posteriori because we could have two
1069  // equidistant points with different values from two different problems, but a third point from
1070  // another problem is actually closer, so there is no conflict because only that last one
1071  // matters We check here whether the potential conflicts actually were the nearest points Loop
1072  // over potential conflicts
1073  for (auto conflict_it = _received_conflicts.begin(); conflict_it != _received_conflicts.end();)
1074  {
1075  const auto potential_conflict = *conflict_it;
1076  bool overlap_found = false;
1077 
1078  // Extract info for the potential conflict
1079  const unsigned int problem_id = std::get<0>(potential_conflict);
1080  const dof_id_type dof_object_id = std::get<1>(potential_conflict);
1081  const Point p = std::get<2>(potential_conflict);
1082  const Real distance = std::get<3>(potential_conflict);
1083 
1084  // Extract target variable info
1085  auto & es = getEquationSystem(*_to_problems[problem_id], _displaced_target_mesh);
1086  System * to_sys = find_sys(es, var_name);
1087  auto var_num = to_sys->variable_number(var_name);
1088  auto & fe_type = to_sys->variable_type(var_num);
1089  bool is_nodal = _to_variables[var_index]->isNodal();
1090 
1091  // Higher order elemental
1092  if (fe_type.order > CONSTANT && !is_nodal)
1093  {
1094  auto cached_distance = distance_caches[problem_id].find(p);
1095  if (cached_distance == distance_caches[problem_id].end())
1096  mooseError("Conflict point was not found in the map of all origin-target distances");
1097  // Distance is still the distance when we detected a potential overlap
1098  if (MooseUtils::absoluteFuzzyEqual(cached_distance->second, distance))
1099  overlap_found = true;
1100  }
1101  // Nodal and const monomial variable
1103  dofobject_to_valsvec[problem_id].find(dof_object_id)->second.distance, distance))
1104  overlap_found = true;
1105 
1106  // Map will only keep the actual overlaps
1107  if (!overlap_found)
1108  _received_conflicts.erase(conflict_it);
1109  else
1110  ++conflict_it;
1111  }
1112 }
1113 
1114 void
1116  const unsigned int var_index,
1117  const DofobjectToInterpValVec & dofobject_to_valsvec,
1118  const InterpCaches & distance_caches)
1119 {
1120  const auto var_name = getToVarName(var_index);
1121  // We must check a posteriori because we could have:
1122  // - two equidistant points with different values from two different problems
1123  // - two (or more) equidistant points with different values from the same problem
1124  // but a third point/value couple from another problem is actually closer, so there is no
1125  // conflict because only that last one matters. We check here whether the potential conflicts
1126  // actually were the nearest points. We use several global reductions. If there are not too many
1127  // potential conflicts (and there should not be in a well-posed problem) it should be manageably
1128  // expensive
1129 
1130  // Move relevant conflict info (location, distance) to a smaller data structure
1131  std::vector<std::tuple<Point, Real>> potential_conflicts;
1132  potential_conflicts.reserve(_local_conflicts.size());
1133 
1134  // Loop over potential conflicts to broadcast all the conflicts
1135  for (auto conflict_it = _local_conflicts.begin(); conflict_it != _local_conflicts.end();
1136  ++conflict_it)
1137  {
1138  // Extract info for the potential conflict
1139  const auto potential_conflict = *conflict_it;
1140  const unsigned int i_from = std::get<0>(potential_conflict);
1141  Point p = std::get<2>(potential_conflict);
1142  const Real distance = std::get<3>(potential_conflict);
1143  // If not using nearest-positions: potential conflict was saved in the source frame
1144  // If using nearest-positions: potential conflict was saved in the reference frame
1146  {
1147  const auto from_global_num = getGlobalSourceAppIndex(i_from);
1148  p = (*_from_transforms[from_global_num])(p);
1149  }
1150 
1151  // Send data in the global frame of reference
1152  potential_conflicts.push_back(std::make_tuple(p, distance));
1153  }
1154  _communicator.allgather(potential_conflicts, false);
1155  // conflicts could have been reported multiple times within a tolerance
1156  std::sort(potential_conflicts.begin(), potential_conflicts.end());
1157  potential_conflicts.erase(unique(potential_conflicts.begin(),
1158  potential_conflicts.end(),
1159  [](auto l, auto r)
1160  {
1161  return std::get<0>(l).absolute_fuzzy_equals(std::get<0>(r)) &&
1162  std::abs(std::get<1>(l) - std::get<1>(r)) < TOLERANCE;
1163  }),
1164  potential_conflicts.end());
1165 
1166  std::vector<std::tuple<Point, Real>> real_conflicts;
1167  real_conflicts.reserve(potential_conflicts.size());
1168 
1169  // For each potential conflict, we need to identify what problem asked for that value
1170  for (auto conflict_it = potential_conflicts.begin(); conflict_it != potential_conflicts.end();
1171  ++conflict_it)
1172  {
1173  // Extract info for the potential conflict
1174  auto potential_conflict = *conflict_it;
1175  const Point p = std::get<0>(potential_conflict);
1176  const Real distance = std::get<1>(potential_conflict);
1177 
1178  // Check all the problems to try to find this requested point in the data structures filled
1179  // with the received information
1180  bool target_found = false;
1181  bool conflict_real = false;
1182  for (const auto i_to : index_range(_to_problems))
1183  {
1184  // Extract variable info
1186  System * to_sys = find_sys(es, var_name);
1187  auto var_num = to_sys->variable_number(var_name);
1188  auto & fe_type = to_sys->variable_type(var_num);
1189  bool is_nodal = _to_variables[var_index]->isNodal();
1190 
1191  // Move to the local frame of reference for the target problem
1192  Point local_p =
1193  getPointInTargetAppFrame(p, i_to, "Resolution of local value conflicts detected");
1194 
1195  // Higher order elemental
1196  if (fe_type.order > CONSTANT && !is_nodal)
1197  {
1198  // distance_caches finds use a binned floating point search
1199  auto cached_distance = distance_caches[i_to].find(local_p);
1200  if (cached_distance != distance_caches[i_to].end())
1201  {
1202  target_found = true;
1203  // Distance between source & target is still the distance we found in the sending
1204  // process when we detected a potential overlap while gathering values to send
1205  if (MooseUtils::absoluteFuzzyEqual(cached_distance->second, distance))
1206  conflict_real = true;
1207  }
1208  }
1209  // Nodal-value-dof-only and const monomial variable
1210  else
1211  {
1212  // Find the dof id for the variable to be set
1214  auto pl = _to_problems[i_to]->mesh().getPointLocator();
1215  pl->enable_out_of_mesh_mode();
1216  if (is_nodal)
1217  {
1218  auto node = pl->locate_node(local_p);
1219  if (node)
1220  // this is not the dof_id for the variable, but the dof_object_id
1221  dof_object_id = node->id();
1222  }
1223  else
1224  {
1225  auto elem = (*pl)(local_p);
1226  if (elem)
1227  dof_object_id = elem->id();
1228  }
1229  pl->disable_out_of_mesh_mode();
1230 
1231  // point isn't even in mesh
1232  if (dof_object_id == std::numeric_limits<dof_id_type>::max())
1233  continue;
1234 
1235  // this dof was not requested by this problem on this process
1236  if (dofobject_to_valsvec[i_to].find(dof_object_id) == dofobject_to_valsvec[i_to].end())
1237  continue;
1238 
1239  target_found = true;
1240  // Check the saved distance in the vector of saved results. If the same, then the local
1241  // conflict we detected with that distance is still an issue after receiving all values
1243  dofobject_to_valsvec[i_to].find(dof_object_id)->second.distance, distance))
1244  conflict_real = true;
1245  }
1246  }
1247  // Only keep the actual conflicts / overlaps
1248  if (target_found && conflict_real)
1249  real_conflicts.push_back(potential_conflict);
1250  }
1251 
1252  // Communicate real conflicts to all so they can be checked by every process
1253  _communicator.allgather(real_conflicts, false);
1254 
1255  // Delete potential conflicts that were resolved
1256  // Each local list of conflicts will now be updated. It's important to keep conflict lists local
1257  // so we can give more context like the sending processor id (the domain of which can be
1258  // inspected by the user)
1259  for (auto conflict_it = _local_conflicts.begin(); conflict_it != _local_conflicts.end();)
1260  {
1261  // Extract info for the potential conflict
1262  const auto potential_conflict = *conflict_it;
1263  const unsigned int i_from = std::get<0>(potential_conflict);
1264  Point p = std::get<2>(potential_conflict);
1265  const Real distance = std::get<3>(potential_conflict);
1267  {
1268  const auto from_global_num = getGlobalSourceAppIndex(i_from);
1269  p = (*_from_transforms[from_global_num])(p);
1270  }
1271 
1272  // If not in the vector of real conflicts, was not real so delete it
1273  if (std::find_if(real_conflicts.begin(),
1274  real_conflicts.end(),
1275  [p, distance](const auto & item)
1276  {
1277  return std::get<0>(item).absolute_fuzzy_equals(p) &&
1278  std::abs(std::get<1>(item) - distance) < TOLERANCE;
1279  }) == real_conflicts.end())
1280  _local_conflicts.erase(conflict_it);
1281  else
1282  ++conflict_it;
1283  }
1284 }
1285 
1286 void
1288  const unsigned int var_index,
1289  const DofobjectToInterpValVec & dofobject_to_valsvec,
1290  const InterpCaches & distance_caches)
1291 {
1292  // Remove potential conflicts that did not materialize, the value did not end up being used
1293  examineReceivedValueConflicts(var_index, dofobject_to_valsvec, distance_caches);
1294  examineLocalValueConflicts(var_index, dofobject_to_valsvec, distance_caches);
1295 
1296  // Output the conflicts from the selection of local values (evaluateInterpValues-type routines)
1297  // to send in response to value requests at target points
1298  const std::string rank_str = std::to_string(_communicator.rank());
1299  if (_local_conflicts.size())
1300  {
1301  unsigned int num_outputs = 0;
1302  std::string local_conflicts_string = "";
1303  std::string potential_reasons =
1304  "Are some points in target mesh equidistant from the sources "
1305  "(nodes/centroids/apps/positions, depending on transfer) in origin mesh(es)?\n";
1306  if (hasFromMultiApp() && _from_problems.size() > 1)
1307  potential_reasons += "Are multiple subapps overlapping?\n";
1308  for (const auto & conflict : _local_conflicts)
1309  {
1310  const unsigned int problem_id = std::get<0>(conflict);
1311  Point p = std::get<2>(conflict);
1312  num_outputs++;
1313 
1314  std::string origin_domain_message;
1316  {
1317  // NOTES:
1318  // - The origin app for a conflict may not be unique.
1319  // - The conflicts vectors only store the conflictual points, not the original one
1320  // The original value found with a given distance could be retrieved from the main
1321  // caches
1322  const auto app_id = _from_local2global_map[problem_id];
1323  origin_domain_message = "In source child app " + std::to_string(app_id) + " mesh,";
1324  }
1325  // We can't locate the source app when considering nearest positions, so we saved the data
1326  // in the reference space. So we return the conflict location in the target app (parent or
1327  // sibling) instead
1329  {
1330  if (_to_problems.size() == 1 || _skip_coordinate_collapsing)
1331  {
1332  p = (*_to_transforms[0])(p);
1333  origin_domain_message = "In target app mesh,";
1334  }
1335  else
1336  origin_domain_message = "In reference (post-coordinate collapse) mesh,";
1337  }
1338  else
1339  origin_domain_message = "In source parent app mesh,";
1340 
1341  if (num_outputs < _search_value_conflicts_max_log)
1342  local_conflicts_string += origin_domain_message + " point: (" + std::to_string(p(0)) +
1343  ", " + std::to_string(p(1)) + ", " + std::to_string(p(2)) +
1344  "), equi-distance: " + std::to_string(std::get<3>(conflict)) +
1345  "\n";
1346  else if (num_outputs == _search_value_conflicts_max_log)
1347  local_conflicts_string +=
1348  "Maximum output of the search for value conflicts has been reached. Further conflicts "
1349  "will not be output.\nIncrease 'search_value_conflicts_max_log' to output more.";
1350  }
1351  // Explicitly name source to give more context
1352  std::string source_str = "unknown";
1353  if (_from_var_names.size())
1354  source_str = "variable '" + getFromVarName(var_index) + "'";
1355  else if (isParamValid("source_user_object"))
1356  source_str = "user object '" + getParam<UserObjectName>("source_user_object") + "'";
1357 
1358  mooseWarning("On rank " + rank_str +
1359  ", multiple valid values from equidistant points were "
1360  "found in the origin mesh for source " +
1361  source_str + " for " + std::to_string(_local_conflicts.size()) +
1362  " target points.\n" + potential_reasons + "Conflicts detected at :\n" +
1363  local_conflicts_string);
1364  }
1365 
1366  // Output the conflicts discovered when receiving values from multiple origin problems
1367  if (_received_conflicts.size())
1368  {
1369  unsigned int num_outputs = 0;
1370  std::string received_conflicts_string = "";
1371  std::string potential_reasons =
1372  "Are some points in target mesh equidistant from the sources "
1373  "(nodes/centroids/apps/positions, depending on transfer) in origin mesh(es)?\n";
1374  if (hasToMultiApp() && _to_problems.size() > 1)
1375  potential_reasons += "Are multiple subapps overlapping?\n";
1376  for (const auto & conflict : _received_conflicts)
1377  {
1378  // Extract info for the potential overlap
1379  const unsigned int problem_id = std::get<0>(conflict);
1380  const Point p = std::get<2>(conflict);
1381  num_outputs++;
1382 
1383  std::string target_domain_message;
1384  if (hasToMultiApp())
1385  {
1386  const auto app_id = _to_local2global_map[problem_id];
1387  target_domain_message = "In target child app " + std::to_string(app_id) + " mesh,";
1388  }
1389  else
1390  target_domain_message = "In target parent app mesh,";
1391 
1392  if (num_outputs < _search_value_conflicts_max_log)
1393  received_conflicts_string += target_domain_message + " point: (" + std::to_string(p(0)) +
1394  ", " + std::to_string(p(1)) + ", " + std::to_string(p(2)) +
1395  "), equi-distance: " + std::to_string(std::get<3>(conflict)) +
1396  "\n";
1397  else if (num_outputs == _search_value_conflicts_max_log)
1398  received_conflicts_string +=
1399  "Maximum output of the search for value conflicts has been reached. Further conflicts "
1400  "will not be output.\nIncrease 'search_value_conflicts_max_log' to output more.";
1401  }
1402  mooseWarning("On rank " + rank_str +
1403  ", multiple valid values from equidistant points were "
1404  "received for target variable '" +
1405  getToVarName(var_index) + "' for " + std::to_string(_received_conflicts.size()) +
1406  " target points.\n" + potential_reasons + "Conflicts detected at :\n" +
1407  received_conflicts_string);
1408  }
1409 
1410  if (_local_conflicts.empty() && _received_conflicts.empty())
1411  {
1412  if (isParamSetByUser("search_value_conflict"))
1413  mooseInfo("Automated diagnosis did not detect floating point indetermination in transfer");
1414  else if (_to_problems.size() > 10 || _from_problems.size() > 10 || _communicator.size() > 10)
1415  mooseInfo(
1416  "Automated diagnosis did not detect any floating point indetermination in "
1417  "the transfer. You may consider turning it off using `search_value_conflicts=false` "
1418  "to improve performance/scalability.");
1419  }
1420 
1421  // Reset the conflicts vectors, to be used for checking conflicts when transferring the next
1422  // variable
1423  _local_conflicts.clear();
1424  _received_conflicts.clear();
1425 }
1426 
1427 void
1429  const unsigned int var_index,
1430  const DofobjectToInterpValVec & dofobject_to_valsvec,
1431  const InterpCaches & interp_caches)
1432 {
1433  // Get the variable name, with the accommodation for array/vector names
1434  const auto & var_name = getToVarName(var_index);
1435 
1436  for (const auto problem_id : index_range(_to_problems))
1437  {
1438  auto & dofobject_to_val = dofobject_to_valsvec[problem_id];
1439 
1440  // libMesh EquationSystems
1441  // NOTE: we would expect to set variables from the displaced equation system here
1442  auto & es = getEquationSystem(*_to_problems[problem_id], false);
1443 
1444  // libMesh system
1445  System * to_sys = find_sys(es, var_name);
1446 
1447  // libMesh mesh
1448  const MeshBase & to_mesh = _to_problems[problem_id]->mesh(_displaced_target_mesh).getMesh();
1449  auto var_num = to_sys->variable_number(var_name);
1450  auto sys_num = to_sys->number();
1451 
1452  auto & fe_type = _to_variables[var_index]->feType();
1453  bool is_nodal = _to_variables[var_index]->isNodal();
1454 
1455  if (fe_type.order > CONSTANT && !is_nodal)
1456  {
1457  // We may need to use existing data values in places where the
1458  // from app domain doesn't overlap
1459  MeshFunction to_func(es, *to_sys->current_local_solution, to_sys->get_dof_map(), var_num);
1460  to_func.init();
1461 
1463  interp_caches[problem_id], to_func, _default_extrapolation_value);
1464  libMesh::VectorSetAction<Number> setter(*to_sys->solution);
1465  const std::vector<unsigned int> varvec(1, var_num);
1466 
1469  Number,
1471  set_solution(*to_sys, f, nullptr, setter, varvec);
1472 
1473  // We dont look at boundary restriction, not supported for higher order target variables
1474  const auto & to_begin = _to_blocks.empty()
1475  ? to_mesh.active_local_elements_begin()
1476  : to_mesh.active_local_subdomain_set_elements_begin(_to_blocks);
1477 
1478  const auto & to_end = _to_blocks.empty()
1479  ? to_mesh.active_local_elements_end()
1480  : to_mesh.active_local_subdomain_set_elements_end(_to_blocks);
1481 
1482  ConstElemRange active_local_elem_range(to_begin, to_end);
1483 
1484  set_solution.project(active_local_elem_range);
1485  }
1486  else
1487  {
1488  for (const auto & val_pair : dofobject_to_val)
1489  {
1490  const auto dof_object_id = val_pair.first;
1491 
1492  const DofObject * dof_object = nullptr;
1493  if (is_nodal)
1494  dof_object = to_mesh.node_ptr(dof_object_id);
1495  else
1496  dof_object = to_mesh.elem_ptr(dof_object_id);
1497 
1498  const auto dof = dof_object->dof_number(sys_num, var_num, 0);
1499  const auto val = val_pair.second.interp;
1500 
1501  // This will happen if meshes are mismatched
1503  {
1504  const auto target_location =
1505  hasToMultiApp()
1506  ? " on target app " + std::to_string(getGlobalTargetAppIndex(problem_id))
1507  : " on parent app";
1508  const auto info_msg = "\nThis check can be turned off by setting 'error_on_miss' to "
1509  "false. The 'extrapolation_constant' parameter will be used to set "
1510  "the local value at missed points.";
1511  if (is_nodal)
1512  mooseError("No source value for node ",
1513  dof_object_id,
1514  target_location,
1515  " could be located. Node details:\n",
1516  _to_meshes[problem_id]->nodePtr(dof_object_id)->get_info(),
1517  "\n",
1518  info_msg);
1519  else
1520  mooseError("No source value for element ",
1521  dof_object_id,
1522  target_location,
1523  " could be located. Element details:\n",
1524  _to_meshes[problem_id]->elemPtr(dof_object_id)->get_info(),
1525  "\n",
1526  info_msg);
1527  }
1528 
1529  // We should not put garbage into our solution vector
1530  // but it can be that we want to set it to a different value than what was already there
1531  // for example: the source app has been displaced and was sending an indicator of its
1532  // position
1534  {
1536  to_sys->solution->set(dof, _default_extrapolation_value);
1537  continue;
1538  }
1539 
1540  to_sys->solution->set(dof, val);
1541  }
1542  }
1543 
1544  to_sys->solution->close();
1545  // Sync local solutions
1546  to_sys->update();
1547  }
1548 }
1549 
1550 bool
1552  const std::vector<BoundingBox> & local_bboxes,
1553  const Point & pt,
1554  const unsigned int only_from_mesh_div,
1555  Real & distance) const
1556 {
1557  if (_use_bounding_boxes && !local_bboxes[i_from].contains_point(pt))
1558  return false;
1559  else
1560  {
1561  auto * pl = _from_point_locators[i_from].get();
1562  const auto from_global_num = getGlobalSourceAppIndex(i_from);
1563  const auto transformed_pt = _from_transforms[from_global_num]->mapBack(pt);
1564 
1565  // Check point against source block restriction
1566  if (!_from_blocks.empty() && !inBlocks(_from_blocks, pl, transformed_pt))
1567  return false;
1568 
1569  // Check point against source boundary restriction. Block restriction will speed up the search
1570  if (!_from_boundaries.empty() &&
1571  !onBoundaries(_from_boundaries, _from_blocks, *_from_meshes[i_from], pl, transformed_pt))
1572  return false;
1573 
1574  // Check point against the source mesh division
1575  if ((!_from_mesh_divisions.empty() || !_to_mesh_divisions.empty()) &&
1576  !acceptPointMeshDivision(transformed_pt, i_from, only_from_mesh_div))
1577  return false;
1578 
1579  // Get nearest position (often a subapp position) for the target point
1580  // We want values from the child app that is closest to the same position as the target
1581  Point nearest_position_source;
1583  {
1584  const bool initial = _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL;
1585  // The search for the nearest position is done in the reference frame
1586  const Point nearest_position = _nearest_positions_obj->getNearestPosition(pt, initial);
1587  nearest_position_source = _nearest_positions_obj->getNearestPosition(
1588  (*_from_transforms[from_global_num])(Point(0, 0, 0)), initial);
1589 
1591  _from_transforms[from_global_num]->hasNonTranslationTransformation())
1592  mooseError("Rotation and scaling currently unsupported with nearest positions transfer.");
1593 
1594  // Compute distance to nearest position and nearest position source
1595  const Real distance_to_position_nearest_source = (pt - nearest_position_source).norm();
1596  const Real distance_to_nearest_position = (pt - nearest_position).norm();
1597 
1598  // Source (usually app position) is not closest to the same positions as the target, dont
1599  // send values. We check the distance instead of the positions because if they are the same
1600  // that means there's two equidistant positions and we would want to capture that as a "value
1601  // conflict"
1602  if (!MooseUtils::absoluteFuzzyEqual(distance_to_position_nearest_source,
1603  distance_to_nearest_position))
1604  return false;
1605 
1606  // Set the distance as the distance from the nearest position to the target point
1607  distance = distance_to_position_nearest_source;
1608  }
1609 
1610  // Check that the app actually contains the origin point
1611  // We dont need to check if we already found it in a block or a boundary
1612  if (_from_blocks.empty() && _from_boundaries.empty() && _source_app_must_contain_point &&
1613  !inMesh(pl, transformed_pt))
1614  return false;
1615  }
1616  return true;
1617 }
1618 
1619 bool
1621 {
1622  // Note: we do not take advantage of a potential block restriction of the mesh here. This is
1623  // because we can avoid this routine by calling inBlocks() instead
1624  const Elem * elem = (*pl)(point);
1625  return (elem != nullptr);
1626 }
1627 
1628 bool
1629 MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
1630  const Elem * elem) const
1631 {
1632  return blocks.find(elem->subdomain_id()) != blocks.end();
1633 }
1634 
1635 bool
1636 MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
1637  const MooseMesh & /* mesh */,
1638  const Elem * elem) const
1639 {
1640  return inBlocks(blocks, elem);
1641 }
1642 
1643 bool
1644 MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
1645  const MooseMesh & mesh,
1646  const Node * node) const
1647 {
1648  const auto & node_blocks = mesh.getNodeBlockIds(*node);
1649  std::set<SubdomainID> u;
1650  std::set_intersection(blocks.begin(),
1651  blocks.end(),
1652  node_blocks.begin(),
1653  node_blocks.end(),
1654  std::inserter(u, u.begin()));
1655  return !u.empty();
1656 }
1657 
1658 bool
1659 MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
1660  const PointLocatorBase * const pl,
1661  const Point & point) const
1662 {
1663  const Elem * elem = (*pl)(point, &blocks);
1664  return (elem != nullptr);
1665 }
1666 
1667 bool
1668 MultiAppGeneralFieldTransfer::onBoundaries(const std::set<BoundaryID> & boundaries,
1669  const MooseMesh & mesh,
1670  const Node * node) const
1671 {
1672  const BoundaryInfo & bnd_info = mesh.getMesh().get_boundary_info();
1673  std::vector<BoundaryID> vec_to_fill;
1674  bnd_info.boundary_ids(node, vec_to_fill);
1675  std::set<BoundaryID> vec_to_fill_set(vec_to_fill.begin(), vec_to_fill.end());
1676  std::set<BoundaryID> u;
1677  std::set_intersection(boundaries.begin(),
1678  boundaries.end(),
1679  vec_to_fill_set.begin(),
1680  vec_to_fill_set.end(),
1681  std::inserter(u, u.begin()));
1682  return !u.empty();
1683 }
1684 
1685 bool
1686 MultiAppGeneralFieldTransfer::onBoundaries(const std::set<BoundaryID> & boundaries,
1687  const MooseMesh & mesh,
1688  const Elem * elem) const
1689 {
1690  // Get all boundaries each side of the element is part of
1691  const BoundaryInfo & bnd_info = mesh.getMesh().get_boundary_info();
1692  std::vector<BoundaryID> vec_to_fill;
1693  std::vector<BoundaryID> vec_to_fill_temp;
1695  for (const auto side : make_range(elem->n_sides()))
1696  {
1697  bnd_info.boundary_ids(elem, side, vec_to_fill_temp);
1698  vec_to_fill.insert(vec_to_fill.end(), vec_to_fill_temp.begin(), vec_to_fill_temp.end());
1699  }
1700  else
1701  for (const auto node_index : make_range(elem->n_nodes()))
1702  {
1703  bnd_info.boundary_ids(elem->node_ptr(node_index), vec_to_fill_temp);
1704  vec_to_fill.insert(vec_to_fill.end(), vec_to_fill_temp.begin(), vec_to_fill_temp.end());
1705  }
1706  std::set<BoundaryID> vec_to_fill_set(vec_to_fill.begin(), vec_to_fill.end());
1707 
1708  // Look for a match between the boundaries from the restriction and those near the element
1709  std::set<BoundaryID> u;
1710  std::set_intersection(boundaries.begin(),
1711  boundaries.end(),
1712  vec_to_fill_set.begin(),
1713  vec_to_fill_set.end(),
1714  std::inserter(u, u.begin()));
1715  return !u.empty();
1716 }
1717 
1718 bool
1719 MultiAppGeneralFieldTransfer::onBoundaries(const std::set<BoundaryID> & boundaries,
1720  const std::set<SubdomainID> & block_restriction,
1721  const MooseMesh & mesh,
1722  const PointLocatorBase * const pl,
1723  const Point & point) const
1724 {
1725  // Find the element containing the point and use the block restriction if known for speed
1726  const Elem * elem;
1727  if (block_restriction.empty())
1728  elem = (*pl)(point);
1729  else
1730  elem = (*pl)(point, &block_restriction);
1731 
1732  if (!elem)
1733  return false;
1734  return onBoundaries(boundaries, mesh, elem);
1735 }
1736 
1737 bool
1739  const Point & pt, const unsigned int i_local, const unsigned int only_from_this_mesh_div) const
1740 {
1741  // This routine can also be called to examine if the to_mesh_division index matches the current
1742  // source subapp index
1743  unsigned int source_mesh_div = MooseMeshDivision::INVALID_DIVISION_INDEX - 1;
1744  if (!_from_mesh_divisions.empty())
1745  source_mesh_div = _from_mesh_divisions[i_local]->divisionIndex(pt);
1746 
1747  // If the point is not indexed in the source division
1748  if (!_from_mesh_divisions.empty() && source_mesh_div == MooseMeshDivision::INVALID_DIVISION_INDEX)
1749  return false;
1750  // If the point is not the at the same index in the target and the origin meshes, reject
1751  else if ((_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
1752  _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX) &&
1753  source_mesh_div != only_from_this_mesh_div)
1754  return false;
1755  // If the point is at a certain division index that is not the same as the index of the subapp
1756  // we wanted the information to be from for that point, reject
1757  else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
1758  source_mesh_div != only_from_this_mesh_div)
1759  return false;
1760  else if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
1761  only_from_this_mesh_div != getGlobalSourceAppIndex(i_local))
1762  return false;
1763  else
1764  return true;
1765 }
1766 
1767 bool
1768 MultiAppGeneralFieldTransfer::closestToPosition(unsigned int pos_index, const Point & pt) const
1769 {
1770  mooseAssert(_nearest_positions_obj, "Should not be here without a positions object");
1772  paramError("skip_coordinate_collapsing", "Coordinate collapsing not implemented");
1773  bool initial = _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL;
1775  // Faster to just compare the index
1776  return pos_index == _nearest_positions_obj->getNearestPositionIndex(pt, initial);
1777  else
1778  {
1779  // Get the distance to the position and see if we are missing a value just because the position
1780  // is not officially the closest, but it is actually at the same distance
1781  const auto nearest_position = _nearest_positions_obj->getNearestPosition(pt, initial);
1782  const auto nearest_position_at_index = _nearest_positions_obj->getPosition(pos_index, initial);
1783  Real distance_to_position_at_index = (pt - nearest_position_at_index).norm();
1784  const Real distance_to_nearest_position = (pt - nearest_position).norm();
1785 
1786  if (!MooseUtils::absoluteFuzzyEqual(distance_to_position_at_index,
1787  distance_to_nearest_position))
1788  return false;
1789  // Actually the same position (point)
1790  else if (nearest_position == nearest_position_at_index)
1791  return true;
1792  else
1793  {
1794  mooseWarning("Two equidistant positions ",
1795  nearest_position,
1796  " and ",
1797  nearest_position_at_index,
1798  " detected near point ",
1799  pt);
1800  return true;
1801  }
1802  }
1803 }
1804 
1805 Real
1807 {
1808  std::array<Point, 2> source_points = {{bbox.first, bbox.second}};
1809 
1810  std::array<Point, 8> all_points;
1811  for (unsigned int x = 0; x < 2; x++)
1812  for (unsigned int y = 0; y < 2; y++)
1813  for (unsigned int z = 0; z < 2; z++)
1814  all_points[x + 2 * y + 4 * z] =
1815  Point(source_points[x](0), source_points[y](1), source_points[z](2));
1816 
1817  Real max_distance = 0.;
1818 
1819  for (unsigned int i = 0; i < 8; i++)
1820  {
1821  Real distance = (p - all_points[i]).norm();
1822  if (distance > max_distance)
1823  max_distance = distance;
1824  }
1825 
1826  return max_distance;
1827 }
1828 
1829 Real
1831 {
1832  std::array<Point, 2> source_points = {{bbox.first, bbox.second}};
1833 
1834  std::array<Point, 8> all_points;
1835  for (unsigned int x = 0; x < 2; x++)
1836  for (unsigned int y = 0; y < 2; y++)
1837  for (unsigned int z = 0; z < 2; z++)
1838  all_points[x + 2 * y + 4 * z] =
1839  Point(source_points[x](0), source_points[y](1), source_points[z](2));
1840 
1841  Real min_distance = std::numeric_limits<Real>::max();
1842 
1843  for (unsigned int i = 0; i < 8; i++)
1844  {
1845  Real distance = (p - all_points[i]).norm();
1846  if (distance < min_distance)
1847  min_distance = distance;
1848  }
1849 
1850  return min_distance;
1851 }
1852 
1853 std::vector<BoundingBox>
1855 {
1856  std::vector<std::pair<Point, Point>> bb_points(_from_meshes.size());
1857  const Real min_r = std::numeric_limits<Real>::lowest();
1858  const Real max_r = std::numeric_limits<Real>::max();
1859 
1860  for (const auto j : make_range(_from_meshes.size()))
1861  {
1862  Point min(max_r, max_r, max_r);
1863  Point max(min_r, min_r, min_r);
1864  bool at_least_one = false;
1865  const auto & from_mesh = _from_problems[j]->mesh(_displaced_source_mesh);
1866 
1867  for (const auto & elem : as_range(from_mesh.getMesh().local_elements_begin(),
1868  from_mesh.getMesh().local_elements_end()))
1869  {
1870  if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, elem))
1871  continue;
1872 
1873  for (const auto & node : elem->node_ref_range())
1874  {
1875  if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, &node))
1876  continue;
1877 
1878  at_least_one = true;
1879  for (const auto i : make_range(LIBMESH_DIM))
1880  {
1881  min(i) = std::min(min(i), node(i));
1882  max(i) = std::max(max(i), node(i));
1883  }
1884  }
1885  }
1886 
1887  // For 2D RZ problems, we need to amend the bounding box to cover the whole XYZ projection
1888  // - The XYZ-Y axis is assumed aligned with the RZ-Z axis
1889  // - RZ systems also cover negative coordinates hence the use of the maximum R
1890  // NOTE: We will only support the case where there is only one coordinate system
1891  if ((from_mesh.getUniqueCoordSystem() == Moose::COORD_RZ) && (LIBMESH_DIM == 3))
1892  {
1893  min(0) = -max(0);
1894  min(2) = -max(0);
1895  max(2) = max(0);
1896  }
1897 
1898  BoundingBox bbox(min, max);
1899  if (!at_least_one)
1900  bbox.min() = max; // If we didn't hit any nodes, this will be _the_ minimum bbox
1901  else
1902  {
1903  // Translate the bounding box to the from domain's position. We may have rotations so we
1904  // must be careful in constructing the new min and max (first and second)
1905  const auto from_global_num = getGlobalSourceAppIndex(j);
1906  transformBoundingBox(bbox, *_from_transforms[from_global_num]);
1907  }
1908 
1909  // Cast the bounding box into a pair of points (so it can be put through
1910  // MPI communication).
1911  bb_points[j] = static_cast<std::pair<Point, Point>>(bbox);
1912  }
1913 
1914  // Serialize the bounding box points.
1915  _communicator.allgather(bb_points);
1916 
1917  // Recast the points back into bounding boxes and return.
1918  std::vector<BoundingBox> bboxes(bb_points.size());
1919  for (const auto i : make_range(bb_points.size()))
1920  bboxes[i] = static_cast<BoundingBox>(bb_points[i]);
1921 
1922  // TODO move up
1923  // Check for a user-set fixed bounding box size and modify the sizes as appropriate
1924  if (_fixed_bbox_size != std::vector<Real>(3, 0))
1925  for (const auto i : make_range(LIBMESH_DIM))
1927  for (const auto j : make_range(bboxes.size()))
1928  {
1929  const auto current_width = (bboxes[j].second - bboxes[j].first)(i);
1930  bboxes[j].first(i) -= (_fixed_bbox_size[i] - current_width) / 2;
1931  bboxes[j].second(i) += (_fixed_bbox_size[i] - current_width) / 2;
1932  }
1933 
1934  return bboxes;
1935 }
1936 
1937 std::vector<unsigned int>
1939 {
1940  std::vector<unsigned int> global_app_start_per_proc(1, -1);
1941  if (_from_local2global_map.size())
1942  global_app_start_per_proc[0] = _from_local2global_map[0];
1943  _communicator.allgather(global_app_start_per_proc, true);
1944  return global_app_start_per_proc;
1945 }
1946 
1947 VariableName
1949 {
1950  mooseAssert(var_index < _from_var_names.size(), "No source variable at this index");
1951  VariableName var_name = _from_var_names[var_index];
1952  if (_from_var_components.size())
1953  var_name += "_" + std::to_string(_from_var_components[var_index]);
1954  return var_name;
1955 }
1956 
1957 VariableName
1959 {
1960  mooseAssert(var_index < _to_var_names.size(), "No target variable at this index");
1961  VariableName var_name = _to_var_names[var_index];
1962  if (_to_var_components.size())
1963  var_name += "_" + std::to_string(_to_var_components[var_index]);
1964  return var_name;
1965 }
1966 
1967 Point
1969 {
1970  Point max_dimension = {std::numeric_limits<Real>::min(),
1973 
1974  for (const auto & to_mesh : _to_meshes)
1975  {
1976  const auto bbox = to_mesh->getInflatedProcessorBoundingBox();
1977  for (const auto dim : make_range(LIBMESH_DIM))
1978  max_dimension(dim) = std::max(
1979  max_dimension(dim), std::max(std::abs(bbox.first(dim)), std::abs(bbox.second(dim))));
1980  }
1981 
1982  return max_dimension;
1983 }
1984 
1985 bool
1987  Real new_value,
1988  Real current_distance,
1989  Real new_distance) const
1990 {
1991  // No conflict if we're not looking for them
1993  // Only consider conflicts if the values are valid and different
1994  if (current_value != GeneralFieldTransfer::BetterOutOfMeshValue &&
1996  !MooseUtils::absoluteFuzzyEqual(current_value, new_value))
1997  // Conflict only occurs if the origin points are equidistant
1998  if (MooseUtils::absoluteFuzzyEqual(current_distance, new_distance))
1999  return true;
2000  return false;
2001 }
std::vector< BoundingBox > _from_bboxes
Bounding boxes for all source applications.
void mooseInfo(Args &&... args) const
Definition: MooseBase.h:317
std::vector< std::unique_ptr< MultiAppCoordTransform > > _to_transforms
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
virtual void evaluateInterpValues(const std::vector< std::pair< Point, unsigned int >> &incoming_points, std::vector< std::pair< Real, Real >> &outgoing_vals)=0
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
void examineReceivedValueConflicts(const unsigned int var_index, const DofobjectToInterpValVec &dofobject_to_valsvec, const InterpCaches &distance_caches)
Remove potential value conflicts that did not materialize because another source was closer Several e...
dof_id_type dof_number(const unsigned int s, const unsigned int var, const unsigned int comp) const
const Point & getNearestPosition(const Point &target, bool initial) const
Find the nearest Position for a given point.
Definition: Positions.C:88
void cacheOutgoingPointInfo(const Point point, const dof_id_type dof_object_id, const unsigned int problem_id, ProcessorToPointVec &outgoing_points)
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: MooseUtils.h:380
static void transformBoundingBox(libMesh::BoundingBox &box, const MultiAppCoordTransform &transform)
Transform a bounding box according to the transformations in the provided coordinate transformation o...
const std::shared_ptr< MultiApp > getFromMultiApp() const
Get the MultiApp to transfer data from.
bool hasBoundaryName(const MeshBase &input_mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
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:435
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
void registerConflict(unsigned int problem, dof_id_type dof_id, Point p, Real dist, bool local)
Register a potential value conflict, e.g.
VariableName getFromVarName(unsigned int var_index)
Get the source variable name, with the suffix for array/vector variables.
const ExecFlagType & getCurrentExecuteOnFlag() const
Return/set the current execution flag.
const InputParameters & parameters() const
Get the parameters of the object.
Definition: MooseBase.h:127
void locatePointReceivers(const Point point, std::set< processor_id_type > &processors)
std::vector< std::unique_ptr< libMesh::PointLocatorBase > > _from_point_locators
Point locators, useful to examine point location with regards to domain restriction.
unsigned int count() const
Get the number of components Note: For standard and vector variables, the number is one...
const std::vector< VariableName > _from_var_names
Name of variables transferring from.
virtual void execute() override
Execute the transfer.
MultiAppGeneralFieldTransfer(const InputParameters &parameters)
MeshBase & mesh
std::vector< FEProblemBase * > _to_problems
processor_id_type rank() const
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
static constexpr std::size_t dim
This is the dimension of all vector and tensor datastructures used in MOOSE.
Definition: Moose.h:159
const Parallel::Communicator & comm() const
bool acceptPointMeshDivision(const Point &pt, const unsigned int i_local, const unsigned int only_from_this_mesh_div) const
Whether a point lies inside the mesh division delineated by the MeshDivision object.
virtual void postExecute()
Add some extra work if necessary after execute().
std::vector< unsigned int > getGlobalStartAppPerProc() const
Get global index for the first app each processes owns Requires a global communication, must be called on every domain simultaneously.
FEProblemBase & _fe_problem
Definition: Transfer.h:97
const std::shared_ptr< MultiApp > getToMultiApp() const
Get the MultiApp to transfer data to.
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
This class provides an interface for common operations on field variables of both FE and FV types wit...
const Point & getPosition(unsigned int index, bool initial) const
Getter for a single position at a known index.
Definition: Positions.C:59
const Parallel::Communicator & _communicator
std::vector< Real > _fixed_bbox_size
Set the bounding box sizes manually.
std::vector< const MeshDivision * > _from_mesh_divisions
Division of the origin mesh.
bool isPrivate(const std::string &name) const
Returns a Boolean indicating whether the specified parameter is private or not.
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
std::vector< const MeshDivision * > _to_mesh_divisions
Division of the target mesh.
unsigned int _var_size
The number of variables to transfer.
const BoundaryInfo & get_boundary_info() const
const Real _default_extrapolation_value
Value to use when no received data is valid for a target location.
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.
bool hasFromMultiApp() const
Whether the transfer owns a non-null from_multi_app.
const Positions & getPositionsObject(const std::string &name) const
Get the Positions object by its name.
const std::vector< unsigned int > _from_var_components
Origin array/vector variable components.
Real distance(const Point &p)
virtual void getAppInfo() override
This method will fill information into the convenience member variables (_to_problems, _from_meshes, etc.)
Point getPointInTargetAppFrame(const Point &p, unsigned int local_i_to, const std::string &phase) const
Get the target app point from a point in the reference frame.
VariableName getToVarName(unsigned int var_index)
Get the target variable name, with the suffix for array/vector variables.
std::set< BoundaryID > _from_boundaries
Origin boundary(ies) restriction.
void outputValueConflicts(const unsigned int var_index, const DofobjectToInterpValVec &dofobject_to_valsvec, const InterpCaches &distance_caches)
Report on conflicts between overlapping child apps, equidistant origin points etc.
DISCONTINUOUS
Factory & getFactory()
Retrieve a writable reference to the Factory associated with this App.
Definition: MooseApp.h:394
auto max(const L &left, const R &right)
std::vector< unsigned int > _to_local2global_map
Given local app index, returns global app index.
unsigned int variable_number(std::string_view var) const
Real bboxMinDistance(const Point &p, const BoundingBox &bbox) const
Compute minimum distance.
unsigned int getGlobalTargetAppIndex(unsigned int i_to) const
Return the global app index from the local index in the "to-multiapp" transfer direction.
bool _displaced_target_mesh
True if displaced mesh is used for the target mesh, otherwise false.
void extractOutgoingPoints(const unsigned int var_index, ProcessorToPointVec &outgoing_points)
std::set< SubdomainID > _to_blocks
Target block(s) restriction.
unsigned int n_dofs(const unsigned int s, const unsigned int var=libMesh::invalid_uint) const
processor_id_type size() const
std::unordered_map< processor_id_type, std::vector< std::pair< Point, unsigned int > > > ProcessorToPointVec
A map from pid to a set of points.
Point getMaxToProblemsBBoxDimensions() const
Obtains the max dimensions to scale all points in the mesh.
virtual void initialSetup() override
Method called at the beginning of the simulation for checking integrity or doing one-time setup...
uint8_t processor_id_type
virtual void prepareEvaluationOfInterpValues(const unsigned int var_index)=0
const std::vector< unsigned int > _to_var_components
Target array/vector variable components.
processor_id_type n_processors() const
Real bboxMaxDistance(const Point &p, const BoundingBox &bbox) const
Compute max distance.
CONSTANT
unsigned int getNearestPositionIndex(const Point &target, bool initial) const
Find the nearest Position index for a given point.
Definition: Positions.C:96
unsigned int number() const
const std::string & name() const
Get the name of the class.
Definition: MooseBase.h:99
libMesh::EquationSystems & getEquationSystem(FEProblemBase &problem, bool use_displaced) const
Returns the Problem&#39;s equation system, displaced or not Be careful! If you transfer TO a displaced sy...
std::vector< MooseMesh * > _from_meshes
std::vector< std::tuple< unsigned int, dof_id_type, Point, Real > > _local_conflicts
Keeps track of all local equidistant points to requested points, creating an indetermination in which...
const bool _use_bounding_boxes
Whether to use bounding boxes to determine the applications that may receive point requests then send...
Value request recording base class.
bool acceptPointInOriginMesh(unsigned int i_from, const std::vector< BoundingBox > &local_bboxes, const Point &pt, const unsigned int mesh_div, Real &distance) const
void project(const ConstElemRange &range)
virtual unsigned int n_nodes() const=0
std::set< BoundaryID > _to_boundaries
Target boundary(ies) restriction.
std::vector< unsigned int > _froms_per_proc
Number of source/from applications per processor. This vector is indexed by processor id...
std::vector< std::tuple< unsigned int, dof_id_type, Point, Real > > _received_conflicts
Keeps track of all received conflicts.
const Point & min() const
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
std::vector< std::unordered_map< dof_id_type, InterpInfo > > DofobjectToInterpValVec
A vector, indexed by to-problem id, of maps from dof object to interpolation values.
Real _bbox_factor
How much we should relax bounding boxes.
bool _error_on_miss
Error out when some points can not be located.
const std::vector< AuxVariableName > _to_var_names
Name of variables transferring to.
static libMesh::System * find_sys(libMesh::EquationSystems &es, const std::string &var_name)
Small helper function for finding the system containing the variable.
Definition: Transfer.C:91
std::unique_ptr< NumericVector< Number > > solution
MooseMesh wraps a libMesh::Mesh object and enhances its capabilities by caching additional data and s...
Definition: MooseMesh.h:88
std::vector< unsigned int > getFromsPerProc()
Return the number of "from" domains that each processor owns.
virtual void init() override
Transfers variables on possibly different meshes while conserving a user defined property (Postproces...
bool _search_value_conflicts
Whether to look for conflicts between origin points, multiple valid values for a target point...
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:33
unsigned int getGlobalSourceAppIndex(unsigned int i_from) const
Return the global app index from the local index in the "from-multiapp" transfer direction.
MooseApp & _app
The MOOSE application this is associated with.
Definition: MooseBase.h:353
auto norm(const T &a) -> decltype(std::abs(a))
An unordered map indexed by Point, eg 3 floating point numbers Because floating point rounding errors...
void transferVariable(unsigned int i)
Performs the transfer for the variable of index i.
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
unsigned int INVALID_DIVISION_INDEX
Invalid subdomain id to return when outside the mesh division.
Definition: MeshDivision.h:28
void examineLocalValueConflicts(const unsigned int var_index, const DofobjectToInterpValVec &dofobject_to_valsvec, const InterpCaches &distance_caches)
Remove potential value conflicts that did not materialize because another source was closer Several e...
bool detectConflict(Real value_1, Real value_2, Real distance_1, Real distance_2) const
Detects whether two source values are valid and equidistant for a desired target location.
bool hasSubdomainName(const MeshBase &input_mesh, const SubdomainName &name)
Whether a particular subdomain name exists in the mesh.
std::vector< BoundingBox > getRestrictedFromBoundingBoxes() const
Get from bounding boxes for given domains and boundaries.
SimpleRange< NodeRefIter > node_ref_range()
virtual const Elem * elem_ptr(const dof_id_type i) const=0
virtual unsigned int n_sides() const=0
virtual void addReporter(const std::string &type, const std::string &name, InputParameters &parameters)
Add a Reporter object to the simulation.
virtual void update()
bool absolute_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
const FEType & variable_type(const unsigned int i) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
subdomain_id_type subdomain_id() const
bool _already_output_search_value_conflicts
Whether we already output the search value conflicts.
const MooseEnum & _to_mesh_division_behavior
How to use the target mesh divisions to restrict the transfer.
virtual void postExecute() override
Add some extra work if necessary after execute().
const Node * node_ptr(const unsigned int i) const
void mooseWarning(Args &&... args) const
Emits a warning prefixed with object name and type.
Definition: MooseBase.h:295
std::vector< std::unique_ptr< MultiAppCoordTransform > > _from_transforms
IntRange< T > make_range(T beg, T end)
TREE_LOCAL_ELEMENTS
bool hasToMultiApp() const
Whether the transfer owns a non-null to_multi_app.
Value request response base class.
bool _displaced_source_mesh
True if displaced mesh is used for the source mesh, otherwise false.
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:267
std::set< SubdomainID > _from_blocks
Origin block(s) restriction.
std::vector< unsigned int > _from_local2global_map
Given local app index, returns global app index.
const unsigned int _search_value_conflicts_max_log
How many conflicts are output to console.
std::vector< InterpCache > InterpCaches
A vector of such caches, indexed by to_problem.
std::unique_ptr< NumericVector< Number > > current_local_solution
void extractLocalFromBoundingBoxes(std::vector< BoundingBox > &local_bboxes)
void extendBoundingBoxes(const Real factor, std::vector< libMesh::BoundingBox > &bboxes) const
Extends bounding boxes to avoid missing points.
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...
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
std::vector< MooseVariableFieldBase * > _to_variables
The target variables.
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:195
Real Number
ProcessorToPointInfoVec _processor_to_pointInfoVec
A map from processor to pointInfo vector.
const bool _elemental_boundary_restriction_on_sides
Whether elemental variable boundary restriction is considered by element side or element nodes...
virtual void initialSetup() override
Method called at the beginning of the simulation for checking integrity or doing one-time setup...
void prepareToTransfer()
Initialize supporting attributes like bounding boxes, processor app indexes etc.
virtual const Node * node_ptr(const dof_id_type i) const=0
processor_id_type processor_id() const
virtual void getAppInfo()
This method will fill information into the convenience member variables (_to_problems, _from_meshes, etc.)
auto min(const L &left, const R &right)
const DofMap & get_dof_map() const
void setSolutionVectorValues(const unsigned int var_index, const DofobjectToInterpValVec &dofobject_to_valsvec, const InterpCaches &interp_caches)
unsigned int hasKey(Point p)
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition: MooseBase.h:201
bool onBoundaries(const std::set< BoundaryID > &boundaries, const MooseMesh &mesh, const Node *node) const
std::vector< FEProblemBase * > _from_problems
void ErrorVector unsigned int
auto index_range(const T &sizable)
bool absoluteFuzzyGreaterThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is greater than another variable within an absolute tolerance...
Definition: MooseUtils.h:428
std::vector< MooseMesh * > _to_meshes
bool _greedy_search
Whether or not a greedy strategy will be used If true, all the partitions will be checked for a given...
void cacheIncomingInterpVals(processor_id_type pid, const unsigned int var_index, std::vector< PointInfo > &pointInfoVec, const std::vector< std::pair< Point, unsigned int >> &point_requests, const std::vector< std::pair< Real, Real >> &incoming_vals, DofobjectToInterpValVec &dofobject_to_valsvec, InterpCaches &interp_caches, InterpCaches &distance_caches)
std::vector< unsigned int > _global_app_start_per_proc
First app each processor owns, indexed by processor If no app on the processor, will have a -1 for th...
uint8_t dof_id_type
const MooseEnum & _from_mesh_division_behavior
How to use the origin mesh divisions to restrict the transfer.
bool inBlocks(const std::set< SubdomainID > &blocks, const Elem *elem) const
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...
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