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