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