LCOV - code coverage report
Current view: top level - src/transfers - MultiAppGeneralFieldTransfer.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 896 1011 88.6 %
Date: 2026-05-29 20:35:17 Functions: 43 44 97.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "MultiAppGeneralFieldTransfer.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "DisplacedProblem.h"
      14             : #include "FEProblem.h"
      15             : #include "MooseMesh.h"
      16             : #include "MooseTypes.h"
      17             : #include "MooseVariableFE.h"
      18             : #include "MeshDivision.h"
      19             : #include "Positions.h"
      20             : #include "Factory.h"
      21             : #include "MooseAppCoordTransform.h"
      22             : #include "MultiAppGeneralFieldFunctorTransfer.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             : 
      32             : using namespace libMesh;
      33             : 
      34             : namespace GeneralFieldTransfer
      35             : {
      36             : Number OutOfMeshValue = std::numeric_limits<Real>::infinity();
      37             : }
      38             : 
      39             : InputParameters
      40       30274 : MultiAppGeneralFieldTransfer::validParams()
      41             : {
      42       30274 :   InputParameters params = MultiAppConservativeTransfer::validParams();
      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      151370 :   params.addRangeCheckedParam<Real>("bbox_factor",
      47       60548 :                                     1.00000001,
      48             :                                     "bbox_factor>0",
      49             :                                     "Factor to inflate or deflate the source app bounding boxes");
      50      181644 :   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       90822 :   params.addParam<Real>(
      56             :       "extrapolation_constant",
      57       60548 :       0,
      58             :       "Constant to use when no source app can provide a valid value for a target location.");
      59      121096 :   MooseEnum extrap_options("none nearest-valid-target", "none");
      60      121096 :   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      121096 :   params.addParam<std::vector<SubdomainName>>(
      66             :       "from_blocks",
      67             :       "Subdomain restriction to transfer from (defaults to all the origin app domain)");
      68      121096 :   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      121096 :   params.addParam<std::vector<BoundaryName>>(
      73             :       "from_boundaries",
      74             :       "The boundary we are transferring from (if not specified, whole domain is used).");
      75      121096 :   params.addParam<std::vector<BoundaryName>>(
      76             :       "to_boundaries",
      77             :       "The boundary we are transferring to (if not specified, whole domain is used).");
      78      121096 :   MooseEnum nodes_or_sides("nodes sides", "sides");
      79      121096 :   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      121096 :   params.addParam<MeshDivisionName>("from_mesh_division",
      86             :                                     "Mesh division object on the origin application");
      87      121096 :   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      121096 :                                "none");
      91      121096 :   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      121096 :   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       90822 :   params.addParam<std::vector<unsigned int>>("source_variable_components",
     102       60548 :                                              std::vector<unsigned int>(),
     103             :                                              "The source array or vector variable component(s).");
     104       90822 :   params.addParam<std::vector<unsigned int>>("target_variable_components",
     105       60548 :                                              std::vector<unsigned int>(),
     106             :                                              "The target array or vector variable component(s).");
     107             : 
     108             :   // Search options
     109       90822 :   params.addParam<bool>(
     110             :       "greedy_search",
     111       60548 :       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       90822 :   params.addParam<bool>(
     116             :       "error_on_miss",
     117       60548 :       true,
     118             :       "Whether or not to error in the case that a target point is not found in the source domain.");
     119       90822 :   params.addParam<bool>("use_bounding_boxes",
     120       60548 :                         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       90822 :   params.addParam<bool>(
     125             :       "use_nearest_app",
     126       60548 :       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      121096 :   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       90822 :   params.addParam<bool>(
     135             :       "from_app_must_contain_point",
     136       60548 :       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       90822 :   params.addParam<bool>("search_value_conflicts",
     141       60548 :                         false,
     142             :                         "Whether to look for potential conflicts between two valid and different "
     143             :                         "source values for any target point");
     144       90822 :   params.addParam<unsigned int>(
     145             :       "value_conflicts_output",
     146       60548 :       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             : 
     150      121096 :   params.addParamNamesToGroup(
     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");
     154      121096 :   params.addParamNamesToGroup(
     155             :       "greedy_search use_bounding_boxes use_nearest_app use_nearest_position "
     156             :       "search_value_conflicts",
     157             :       "Search algorithm");
     158      121096 :   params.addParamNamesToGroup("error_on_miss from_app_must_contain_point extrapolation_constant",
     159             :                               "Extrapolation behavior");
     160       90822 :   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!
     165       90822 :   params.addRelationshipManager(
     166             :       "ElementSideNeighborLayers",
     167             :       Moose::RelationshipManagerType::GEOMETRIC | Moose::RelationshipManagerType::ALGEBRAIC,
     168           0 :       [](const InputParameters & obj_params, InputParameters & rm_params)
     169             :       {
     170       98223 :         if (!obj_params.isParamValid("from_multi_app") &&
     171       31173 :             obj_params.get<MooseEnum>("post_transfer_extrapolation") == "nearest-valid-target")
     172           0 :           rm_params.set<unsigned short>("layers") = 2;
     173       44700 :         rm_params.set<bool>("use_displaced_mesh") = obj_params.get<bool>("displaced_target_mesh");
     174       22350 :       });
     175             : 
     176       60548 :   return params;
     177       30274 : }
     178             : 
     179        7486 : MultiAppGeneralFieldTransfer::MultiAppGeneralFieldTransfer(const InputParameters & parameters)
     180             :   : MultiAppConservativeTransfer(parameters),
     181        7486 :     _from_var_components(getParam<std::vector<unsigned int>>("source_variable_components")),
     182       14972 :     _to_var_components(getParam<std::vector<unsigned int>>("target_variable_components")),
     183       14972 :     _use_bounding_boxes(getParam<bool>("use_bounding_boxes")),
     184       14972 :     _use_nearest_app(getParam<bool>("use_nearest_app")),
     185        7486 :     _nearest_positions_obj(
     186       14972 :         isParamValid("use_nearest_position")
     187        8032 :             ? &_fe_problem.getPositionsObject(getParam<PositionsName>("use_nearest_position"))
     188             :             : nullptr),
     189       14972 :     _source_app_must_contain_point(getParam<bool>("from_app_must_contain_point")),
     190       14972 :     _from_mesh_division_behavior(getParam<MooseEnum>("from_mesh_division_usage")),
     191       14972 :     _to_mesh_division_behavior(getParam<MooseEnum>("to_mesh_division_usage")),
     192        7486 :     _elemental_boundary_restriction_on_sides(
     193       14972 :         getParam<MooseEnum>("elemental_boundary_restriction") == "sides"),
     194       14972 :     _greedy_search(getParam<bool>("greedy_search")),
     195       14972 :     _search_value_conflicts(getParam<bool>("search_value_conflicts")),
     196        7486 :     _already_output_search_value_conflicts(false),
     197       14972 :     _search_value_conflicts_max_log(getParam<unsigned int>("value_conflicts_output")),
     198       14972 :     _post_transfer_extrapolation(getParam<MooseEnum>("post_transfer_extrapolation")),
     199       14972 :     _error_on_miss(getParam<bool>("error_on_miss")),
     200       14972 :     _default_extrapolation_value(getParam<Real>("extrapolation_constant")),
     201       14972 :     _bbox_factor(getParam<Real>("bbox_factor")),
     202       37430 :     _fixed_bbox_size(isParamValid("fixed_bounding_box_size")
     203        7486 :                          ? getParam<std::vector<Real>>("fixed_bounding_box_size")
     204       37430 :                          : std::vector<Real>(3, 0))
     205             : {
     206        7486 :   _var_size = _to_var_names.size();
     207       11680 :   if (_to_var_names.size() != _from_var_names.size() && !parameters.isPrivate("source_variable"))
     208           6 :     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        7483 :   if (_from_var_names.size() != _from_var_components.size() && _from_var_components.size() > 0)
     212           0 :     paramError("source_variable_components",
     213             :                "This parameter must be equal to the number of source variables");
     214        7483 :   if (_to_var_names.size() != _to_var_components.size() && _to_var_components.size() > 0)
     215           0 :     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
     219        7483 :   if (_use_nearest_app)
     220             :   {
     221         141 :     if (_nearest_positions_obj)
     222           6 :       paramError("use_nearest_app", "Cannot use nearest-app and nearest-position together");
     223         138 :     if (!hasFromMultiApp())
     224           6 :       paramError("use_nearest_app",
     225             :                  "Should have a 'from_multiapp' when using the nearest-app informed search");
     226         270 :     auto pos_params = _app.getFactory().getValidParams("MultiAppPositions");
     227         540 :     pos_params.set<std::vector<MultiAppName>>("multiapps") = {getFromMultiApp()->name()};
     228         405 :     _fe_problem.addReporter("MultiAppPositions", "_created_for_" + name(), pos_params);
     229         135 :     _nearest_positions_obj = &_fe_problem.getPositionsObject("_created_for_" + name());
     230         135 :   }
     231             : 
     232             :   // Dont let users get wrecked by bounding boxes if it looks like they are trying to extrapolate
     233       14873 :   if (!_source_app_must_contain_point && _use_bounding_boxes &&
     234       28861 :       (_nearest_positions_obj || isParamSetByUser("from_app_must_contain_point")))
     235        1938 :     if (!isParamSetByUser("bbox_factor") && !isParamSetByUser("fixed_bounding_box_size"))
     236           3 :       mooseWarning(
     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        7549 :   if (!_use_bounding_boxes &&
     241        7849 :       (isParamValid("fixed_bounding_box_size") || isParamSetByUser("bbox_factor")))
     242           0 :     paramError("use_bounding_boxes",
     243             :                "Cannot pass additional bounding box parameters (sizes, expansion, etc) if we are "
     244             :                "not using bounding boxes");
     245        7744 : }
     246             : 
     247             : void
     248        7309 : MultiAppGeneralFieldTransfer::initialSetup()
     249             : {
     250        7309 :   MultiAppConservativeTransfer::initialSetup();
     251             : 
     252             :   // Use IDs for block and boundary restriction
     253             :   // Loop over all source problems
     254       17278 :   for (const auto i_from : index_range(_from_problems))
     255             :   {
     256        9996 :     const auto & from_moose_mesh = _from_problems[i_from]->mesh(_displaced_source_mesh);
     257       29988 :     if (isParamValid("from_blocks"))
     258             :     {
     259        3438 :       const auto & block_names = getParam<std::vector<SubdomainName>>("from_blocks");
     260             : 
     261        3747 :       for (const auto & b : block_names)
     262        2031 :         if (!MooseMeshUtils::hasSubdomainName(from_moose_mesh.getMesh(), b))
     263           6 :           paramError("from_blocks", "The block '", b, "' was not found in the mesh");
     264             : 
     265        1716 :       if (!block_names.empty())
     266             :       {
     267        1716 :         const auto ids = from_moose_mesh.getSubdomainIDs(block_names);
     268        1716 :         _from_blocks.insert(ids.begin(), ids.end());
     269        1716 :       }
     270             :     }
     271             : 
     272       29979 :     if (isParamValid("from_boundaries"))
     273             :     {
     274         354 :       const auto & boundary_names = getParam<std::vector<BoundaryName>>("from_boundaries");
     275         507 :       for (const auto & bn : boundary_names)
     276         333 :         if (!MooseMeshUtils::hasBoundaryName(from_moose_mesh.getMesh(), bn))
     277           6 :           paramError("from_boundaries", "The boundary '", bn, "' was not found in the mesh");
     278             : 
     279         174 :       if (!boundary_names.empty())
     280             :       {
     281         174 :         const auto boundary_ids = from_moose_mesh.getBoundaryIDs(boundary_names);
     282         174 :         _from_boundaries.insert(boundary_ids.begin(), boundary_ids.end());
     283         174 :       }
     284             :     }
     285             : 
     286       29970 :     if (isParamValid("from_mesh_division"))
     287             :     {
     288         708 :       const auto & mesh_div_name = getParam<MeshDivisionName>("from_mesh_division");
     289         354 :       _from_mesh_divisions.push_back(&_from_problems[i_from]->getMeshDivision(mesh_div_name));
     290             :       // Check that the behavior set makes sense
     291         354 :       if (_from_mesh_division_behavior == MeshDivisionTransferUse::RESTRICTION)
     292             :       {
     293         144 :         if (_from_mesh_divisions[i_from]->coversEntireMesh())
     294           0 :           mooseInfo("'from_mesh_division_usage' is set to use a spatial restriction but the "
     295           0 :                     "'from_mesh_division' for source app of global index " +
     296           0 :                     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             :       }
     300         309 :       else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX &&
     301         507 :                !isParamValid("to_mesh_division"))
     302           0 :         paramError("to_mesh_division_usage",
     303             :                    "Source mesh division cannot match target mesh division if no target mesh "
     304             :                    "division is specified");
     305         210 :       else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
     306             :       {
     307         111 :         if (!hasToMultiApp())
     308           6 :           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         216 :         else if (getToMultiApp()->numGlobalApps() !=
     312         108 :                  _from_mesh_divisions[i_from]->getNumDivisions())
     313           3 :           mooseWarning("Attempting to match target subapp index with the number of source mesh "
     314           3 :                        "divisions, which is " +
     315           6 :                        std::to_string(_from_mesh_divisions[i_from]->getNumDivisions()) +
     316           6 :                        " while there are " + std::to_string(getToMultiApp()->numGlobalApps()) +
     317             :                        " target subapps");
     318         105 :         if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX)
     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           6 :           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          99 :       else if (_from_mesh_division_behavior == "none")
     326           0 :         paramError("from_mesh_division_usage", "User must specify a 'from_mesh_division_usage'");
     327             :     }
     328        9636 :     else if (_from_mesh_division_behavior != "none")
     329           6 :       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       17237 :   for (const auto i_to : index_range(_to_problems))
     335             :   {
     336        9973 :     const auto & to_moose_mesh = _to_problems[i_to]->mesh(_displaced_target_mesh);
     337       29919 :     if (isParamValid("to_blocks"))
     338             :     {
     339        2910 :       const auto & block_names = getParam<std::vector<SubdomainName>>("to_blocks");
     340        3219 :       for (const auto & b : block_names)
     341        1767 :         if (!MooseMeshUtils::hasSubdomainName(to_moose_mesh.getMesh(), b))
     342           6 :           paramError("to_blocks", "The block '", b, "' was not found in the mesh");
     343             : 
     344        1452 :       if (!block_names.empty())
     345             :       {
     346        1452 :         const auto ids = to_moose_mesh.getSubdomainIDs(block_names);
     347        1452 :         _to_blocks.insert(ids.begin(), ids.end());
     348        1452 :       }
     349             :     }
     350             : 
     351       29910 :     if (isParamValid("to_boundaries"))
     352             :     {
     353        3282 :       const auto & boundary_names = getParam<std::vector<BoundaryName>>("to_boundaries");
     354        5493 :       for (const auto & bn : boundary_names)
     355        3855 :         if (!MooseMeshUtils::hasBoundaryName(to_moose_mesh.getMesh(), bn))
     356           6 :           paramError("to_boundaries", "The boundary '", bn, "' was not found in the mesh");
     357             : 
     358        1638 :       if (!boundary_names.empty())
     359             :       {
     360        1638 :         const auto boundary_ids = to_moose_mesh.getBoundaryIDs(boundary_names);
     361        1638 :         _to_boundaries.insert(boundary_ids.begin(), boundary_ids.end());
     362        1638 :       }
     363             :     }
     364             : 
     365       29901 :     if (isParamValid("to_mesh_division"))
     366             :     {
     367         702 :       const auto & mesh_div_name = getParam<MeshDivisionName>("to_mesh_division");
     368         351 :       _to_mesh_divisions.push_back(&_to_problems[i_to]->getMeshDivision(mesh_div_name));
     369             :       // Check that the behavior set makes sense
     370         351 :       if (_to_mesh_division_behavior == MeshDivisionTransferUse::RESTRICTION)
     371             :       {
     372         144 :         if (_to_mesh_divisions[i_to]->coversEntireMesh())
     373           0 :           mooseInfo("'to_mesh_division_usage' is set to use a spatial restriction but the "
     374           0 :                     "'to_mesh_division' for target application of global index " +
     375           0 :                     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             :       }
     379         207 :       else if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX)
     380             :       {
     381         297 :         if (!isParamValid("from_mesh_division"))
     382           0 :           paramError("to_mesh_division_usage",
     383             :                      "Target mesh division cannot match source mesh division if no source mesh "
     384             :                      "division is specified");
     385         198 :         else if ((*_from_mesh_divisions.begin())->getNumDivisions() !=
     386          99 :                  _to_mesh_divisions[i_to]->getNumDivisions())
     387           3 :           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             :       }
     391         108 :       else if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
     392             :       {
     393         108 :         if (!hasFromMultiApp())
     394           6 :           paramError(
     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         105 :         else if (getFromMultiApp()->numGlobalApps() != _to_mesh_divisions[i_to]->getNumDivisions())
     399           3 :           mooseWarning("Attempting to match source subapp index with the number of target mesh "
     400           3 :                        "divisions, which is " +
     401           6 :                        std::to_string(_to_mesh_divisions[i_to]->getNumDivisions()) +
     402           6 :                        " while there are " + std::to_string(getFromMultiApp()->numGlobalApps()) +
     403             :                        " source subapps");
     404         102 :         if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX)
     405           0 :           paramError(
     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           0 :       else if (_to_mesh_division_behavior == "none")
     411           0 :         paramError("to_mesh_division_usage", "User must specify a 'to_mesh_division_usage'");
     412             :     }
     413        9616 :     else if (_to_mesh_division_behavior != "none")
     414           3 :       paramError("to_mesh_division",
     415           3 :                  "'to_mesh_division' must be specified if usage method '" +
     416           6 :                      Moose::stringify(_to_mesh_division_behavior) + "' is specified");
     417             :   }
     418             : 
     419             :   // Check if components are set correctly if using an array variable
     420       17206 :   for (const auto i_from : index_range(_from_problems))
     421             :   {
     422       17268 :     for (const auto var_index : make_range(_from_var_names.size()))
     423             :     {
     424             :       MooseVariableFieldBase & from_var =
     425       14652 :           _from_problems[i_from]->getVariable(0,
     426        7326 :                                               _from_var_names[var_index],
     427             :                                               Moose::VarKindType::VAR_ANY,
     428             :                                               Moose::VarFieldType::VAR_FIELD_ANY);
     429        7326 :       if (from_var.count() > 1 && _from_var_components.empty())
     430           6 :         paramError("source_variable_components", "Component must be passed for an array variable");
     431        7323 :       if (_from_var_components.size() && from_var.count() < _from_var_components[var_index])
     432           6 :         paramError("source_variable_components",
     433             :                    "Component passed is larger than size of variable");
     434             :     }
     435             :   }
     436       17201 :   for (const auto i_to : index_range(_to_problems))
     437             :   {
     438       20183 :     for (const auto var_index : make_range(_to_var_names.size()))
     439             :     {
     440             :       MooseVariableFieldBase & to_var =
     441       20480 :           _to_problems[i_to]->getVariable(0,
     442       10240 :                                           _to_var_names[var_index],
     443             :                                           Moose::VarKindType::VAR_ANY,
     444             :                                           Moose::VarFieldType::VAR_FIELD_ANY);
     445       10240 :       if (to_var.count() > 1 && _to_var_components.empty())
     446           6 :         paramError("target_variable_components", "Component must be passed for an array variable");
     447       10237 :       if (_to_var_components.size() && to_var.count() < _to_var_components[var_index])
     448           6 :         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        7252 :   if (_to_problems.size())
     455             :   {
     456        7234 :     _to_variables.resize(_to_var_names.size());
     457       14636 :     for (const auto i_var : index_range(_to_var_names))
     458       14804 :       _to_variables[i_var] = &_to_problems[0]->getVariable(
     459        7402 :           0, _to_var_names[i_var], Moose::VarKindType::VAR_ANY, Moose::VarFieldType::VAR_FIELD_ANY);
     460             :   }
     461        7252 : }
     462             : 
     463             : void
     464       59651 : MultiAppGeneralFieldTransfer::getAppInfo()
     465             : {
     466       59651 :   MultiAppFieldTransfer::getAppInfo();
     467             : 
     468             :   // Create the point locators to locate evaluation points in the origin mesh(es)
     469       59651 :   _from_point_locators.resize(_from_problems.size());
     470      124361 :   for (const auto i_from : index_range(_from_problems))
     471             :   {
     472       64710 :     const auto & from_moose_mesh = _from_problems[i_from]->mesh(_displaced_source_mesh);
     473       64710 :     _from_point_locators[i_from] =
     474      129420 :         PointLocatorBase::build(TREE_LOCAL_ELEMENTS, from_moose_mesh.getMesh());
     475       64710 :     _from_point_locators[i_from]->enable_out_of_mesh_mode();
     476             :   }
     477       59651 : }
     478             : 
     479             : void
     480       52342 : MultiAppGeneralFieldTransfer::execute()
     481             : {
     482       52342 :   TIME_SECTION(
     483             :       "MultiAppGeneralFieldTransfer::execute()_" + name(), 5, "Transfer execution " + name());
     484       52342 :   getAppInfo();
     485             : 
     486             :   // Set up bounding boxes, etc
     487       52342 :   prepareToTransfer();
     488             : 
     489             :   // loop over the vector of variables and make the transfer one by one
     490      104778 :   for (const auto i : make_range(_var_size))
     491       52476 :     transferVariable(i);
     492             : 
     493       52302 :   postExecute();
     494       52302 : }
     495             : 
     496             : void
     497       52342 : MultiAppGeneralFieldTransfer::prepareToTransfer()
     498             : {
     499             :   // Get the bounding boxes for the "from" domains.
     500             :   // Clean up _from_bboxes from the previous transfer execution
     501       52342 :   _from_bboxes.clear();
     502             : 
     503             :   // NOTE: This ignores the app's bounding box inflation and padding
     504       52342 :   _from_bboxes = getRestrictedFromBoundingBoxes();
     505             : 
     506             :   // Expand bounding boxes. Some desired points might be excluded
     507             :   // without an expansion
     508       52342 :   extendBoundingBoxes(_bbox_factor, _from_bboxes);
     509             : 
     510             :   // Figure out how many "from" domains each processor owns.
     511       52342 :   _froms_per_proc.clear();
     512       52342 :   _froms_per_proc = getFromsPerProc();
     513             : 
     514             :   // Get the index for the first source app every processor owns
     515       52342 :   _global_app_start_per_proc = getGlobalStartAppPerProc();
     516             : 
     517             :   // No need to keep searching for conflicts if the mesh has not changed
     518       52342 :   if (_already_output_search_value_conflicts && !_displaced_source_mesh && !_displaced_target_mesh)
     519           0 :     _search_value_conflicts = false;
     520       52342 : }
     521             : 
     522             : void
     523       52302 : MultiAppGeneralFieldTransfer::postExecute()
     524             : {
     525       52302 :   MultiAppConservativeTransfer::postExecute();
     526       52302 :   if (_search_value_conflicts)
     527         259 :     _already_output_search_value_conflicts = true;
     528       52302 : }
     529             : 
     530             : void
     531       52476 : MultiAppGeneralFieldTransfer::transferVariable(unsigned int i)
     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       52476 :   ProcessorToPointVec outgoing_points;
     540       52476 :   extractOutgoingPoints(i, outgoing_points);
     541             : 
     542       52470 :   if (_from_var_names.size() || dynamic_cast<MultiAppGeneralFieldFunctorTransfer *>(this))
     543       51258 :     prepareEvaluationOfInterpValues(i);
     544             :   else
     545        1212 :     prepareEvaluationOfInterpValues(-1);
     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       81100 :       [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       81100 :     outgoing_vals.resize(
     555             :         incoming_locations.size(),
     556             :         {GeneralFieldTransfer::OutOfMeshValue, GeneralFieldTransfer::OutOfMeshValue});
     557             :     // Evaluate interpolation values for these incoming points
     558       81100 :     evaluateInterpValues(i, incoming_locations, outgoing_vals);
     559      133570 :   };
     560             : 
     561      104940 :   DofobjectToInterpValVec dofobject_to_valsvec(_to_problems.size());
     562       52470 :   InterpCaches interp_caches(_to_problems.size(), getMaxToProblemsBBoxDimensions());
     563       52470 :   InterpCaches distance_caches(_to_problems.size(), getMaxToProblemsBBoxDimensions());
     564             : 
     565             :   // Copy data out to incoming_vals_ids
     566       81100 :   auto action_functor = [this, &i, &dofobject_to_valsvec, &interp_caches, &distance_caches](
     567             :                             processor_id_type pid,
     568             :                             const std::vector<std::pair<Point, unsigned int>> & my_outgoing_points,
     569             :                             const std::vector<std::pair<Real, Real>> & incoming_vals)
     570             :   {
     571       81100 :     auto & pointInfoVec = _processor_to_pointInfoVec[pid];
     572             : 
     573             :     // Cache interpolation values for each dof object / points
     574       81100 :     cacheIncomingInterpVals(pid,
     575             :                             i,
     576             :                             pointInfoVec,
     577             :                             my_outgoing_points,
     578             :                             incoming_vals,
     579             :                             dofobject_to_valsvec,
     580             :                             interp_caches,
     581             :                             distance_caches);
     582      133570 :   };
     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       52470 :   const std::pair<Real, Real> * ex = nullptr;
     587       52470 :   libMesh::Parallel::pull_parallel_vector_data(
     588       52470 :       comm(), outgoing_points, gather_functor, action_functor, ex);
     589             : 
     590             :   // Check for conflicts and overlaps from the maps that were built during the transfer
     591       52470 :   if (_search_value_conflicts)
     592         287 :     outputValueConflicts(i, dofobject_to_valsvec, distance_caches);
     593             : 
     594             :   // Set cached values into solution vector
     595       52442 :   setSolutionVectorValues(i, dofobject_to_valsvec, interp_caches);
     596             : 
     597             :   // Modify solution vector values (notably extrapolation options in functor transfer)
     598       52436 :   correctSolutionVectorValues(i, dofobject_to_valsvec, interp_caches);
     599       52436 : }
     600             : 
     601             : void
     602     5360367 : MultiAppGeneralFieldTransfer::locatePointReceivers(const Point point,
     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     5360367 :   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
     617     5360367 :   if (_use_nearest_app)
     618             :   {
     619             :     // Find the nearest position for the point
     620        7919 :     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        7919 :     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       18559 :     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       10640 :       unsigned int from0 = _global_app_start_per_proc[i_proc];
     630       30014 :       for (unsigned int i_from = from0; i_from < from0 + _froms_per_proc[i_proc]; ++i_from)
     631             :       {
     632       19374 :         if (_greedy_search || _search_value_conflicts || i_from == nearest_index)
     633             :         {
     634       10534 :           processors.insert(i_proc);
     635       10534 :           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) ||
     641             :                     _greedy_search || _search_value_conflicts,
     642             :                 "Should only be one source processor when using more processors than source apps");
     643             :   }
     644     5352448 :   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     5350165 :     Real nearest_max_distance = std::numeric_limits<Real>::max();
     653    13335380 :     for (const auto & bbox : _from_bboxes)
     654             :     {
     655     7985215 :       Real distance = bboxMaxDistance(point, bbox);
     656     7985215 :       if (distance < nearest_max_distance)
     657     6589925 :         nearest_max_distance = distance;
     658             :     }
     659             : 
     660     5350165 :     unsigned int from0 = 0;
     661    12785553 :     for (processor_id_type i_proc = 0; i_proc < n_processors();
     662     7435388 :          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    15420603 :       for (unsigned int i_from = from0; i_from < from0 + _froms_per_proc[i_proc]; ++i_from)
     665             :       {
     666     7985215 :         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     8164185 :         if (_greedy_search || distance <= nearest_max_distance ||
     669      178970 :             _from_bboxes[i_from].contains_point(point))
     670             :         {
     671     7806963 :           processors.insert(i_proc);
     672     7806963 :           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        2283 :   else if (_greedy_search)
     679             :   {
     680        2080 :     found = true;
     681        4940 :     for (const auto i_proc : make_range(n_processors()))
     682        2860 :       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         403 :   else if (!_to_mesh_divisions.empty() &&
     687         200 :            _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
     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         200 :     auto saved_target_div = MooseMeshDivision::INVALID_DIVISION_INDEX;
     692         400 :     for (const auto i_to : index_range(_to_meshes))
     693             :     {
     694         400 :       const auto target_div = _to_mesh_divisions[i_to]->divisionIndex(
     695         200 :           _to_transforms[getGlobalTargetAppIndex(i_to)]->mapBack(point));
     696             :       // If it's the same division index, do not redo the search
     697         200 :       if (target_div == saved_target_div)
     698           0 :         continue;
     699             :       else
     700         200 :         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         475 :       for (const auto i_proc : make_range(n_processors()))
     704        1075 :         for (const auto i_from : make_range(_froms_per_proc[i_proc]))
     705         800 :           if (target_div == _global_app_start_per_proc[i_proc] + i_from)
     706             :           {
     707         200 :             processors.insert(i_proc);
     708         200 :             found = true;
     709             :           }
     710             :     }
     711             :   }
     712             :   else
     713           3 :     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     5360364 :   if (!found && _error_on_miss)
     719           0 :     mooseError(
     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     5360364 : }
     729             : 
     730             : void
     731     5360367 : MultiAppGeneralFieldTransfer::cacheOutgoingPointInfo(const Point point,
     732             :                                                      const dof_id_type dof_object_id,
     733             :                                                      const unsigned int problem_id,
     734             :                                                      ProcessorToPointVec & outgoing_points)
     735             : {
     736     5360367 :   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     5360367 :   processors.clear();
     741     5360367 :   locatePointReceivers(point, processors);
     742             : 
     743             :   // We need to send this location data to these processors so they can send back values
     744    12766729 :   for (const auto pid : processors)
     745             :   {
     746             :     // Select which from_mesh_division the source data must come from for this point
     747     7406365 :     unsigned int required_source_division = 0;
     748     7406365 :     if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
     749        9452 :       required_source_division = getGlobalTargetAppIndex(problem_id);
     750     7396913 :     else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     751    14790966 :              _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     752     7394053 :              _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX)
     753        7866 :       required_source_division = _to_mesh_divisions[problem_id]->divisionIndex(
     754        7866 :           _to_transforms[getGlobalTargetAppIndex(problem_id)]->mapBack(point));
     755             : 
     756             :     // Skip if we already know we don't want the point
     757     7406365 :     if (required_source_division == MooseMeshDivision::INVALID_DIVISION_INDEX)
     758           0 :       continue;
     759             : 
     760             :     // Store outgoing information for every source process
     761     7406365 :     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     7406365 :     pointinfo.problem_id = problem_id;
     767     7406365 :     pointinfo.dof_object_id = dof_object_id;
     768     7406365 :     _processor_to_pointInfoVec[pid].push_back(pointinfo);
     769             :   }
     770     5360364 : }
     771             : 
     772             : void
     773       52476 : MultiAppGeneralFieldTransfer::extractOutgoingPoints(const unsigned int var_index,
     774             :                                                     ProcessorToPointVec & outgoing_points)
     775             : {
     776             :   // Get the variable name, with the accommodation for array/vector names
     777       52476 :   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
     781       52476 :   _processor_to_pointInfoVec.clear();
     782             : 
     783             :   // Loop over all problems
     784      107298 :   for (const auto i_to : index_range(_to_problems))
     785             :   {
     786       54828 :     const auto global_i_to = getGlobalTargetAppIndex(i_to);
     787             : 
     788             :     // libMesh EquationSystems
     789       54828 :     auto & es = getEquationSystem(*_to_problems[i_to], _displaced_target_mesh);
     790             :     // libMesh system that has this variable
     791       54828 :     System * to_sys = find_sys(es, var_name);
     792       54828 :     auto sys_num = to_sys->number();
     793       54828 :     auto var_num = _to_variables[var_index]->number();
     794       54828 :     auto & fe_type = _to_variables[var_index]->feType();
     795       54828 :     bool is_nodal = _to_variables[var_index]->isNodal();
     796             : 
     797             :     // Moose mesh
     798       54828 :     const auto & to_moose_mesh = _to_problems[i_to]->mesh(_displaced_target_mesh);
     799       54828 :     const auto & to_mesh = to_moose_mesh.getMesh();
     800             : 
     801             :     // We support more general variables via libMesh GenericProjector
     802       54828 :     if (fe_type.order > CONSTANT && !is_nodal)
     803             :     {
     804         404 :       GeneralFieldTransfer::RecordRequests<Number> f;
     805         404 :       GeneralFieldTransfer::RecordRequests<Gradient> g;
     806         404 :       GeneralFieldTransfer::NullAction<Number> nullsetter;
     807         404 :       const std::vector<unsigned int> varvec(1, var_num);
     808             : 
     809             :       libMesh::GenericProjector<GeneralFieldTransfer::RecordRequests<Number>,
     810             :                                 GeneralFieldTransfer::RecordRequests<Gradient>,
     811             :                                 Number,
     812             :                                 GeneralFieldTransfer::NullAction<Number>>
     813         404 :           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         404 :       if (_to_boundaries.size() && (_to_variables[var_index]->getContinuity() == DISCONTINUOUS))
     817           3 :         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         401 :       if (!_to_mesh_divisions.empty() && !_to_mesh_divisions[i_to]->coversEntireMesh())
     824           0 :         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         401 :       const auto & to_begin = _to_blocks.empty()
     831         662 :                                   ? to_mesh.active_local_elements_begin()
     832         401 :                                   : to_mesh.active_local_subdomain_set_elements_begin(_to_blocks);
     833             : 
     834         401 :       const auto & to_end = _to_blocks.empty()
     835         662 :                                 ? to_mesh.active_local_elements_end()
     836         401 :                                 : to_mesh.active_local_subdomain_set_elements_end(_to_blocks);
     837             : 
     838         401 :       ConstElemRange to_elem_range(to_begin, to_end);
     839             : 
     840         401 :       request_gather.project(to_elem_range);
     841             : 
     842         401 :       dof_id_type point_id = 0;
     843      283601 :       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
     846      283200 :         this->cacheOutgoingPointInfo(
     847      283200 :             (*_to_transforms[global_i_to])(p), point_id++, i_to, outgoing_points);
     848             : 
     849             :       // This is going to require more complicated transfer work
     850         401 :       if (!g.points_requested().empty())
     851           0 :         mooseError("We don't currently support variables with gradient degrees of freedom");
     852         401 :     }
     853       54424 :     else if (is_nodal)
     854             :     {
     855     5271772 :       for (const auto & node : to_mesh.local_node_ptr_range())
     856             :       {
     857             :         // Skip this node if the variable has no dofs at it.
     858     5221047 :         if (node->n_dofs(sys_num, var_num) < 1)
     859      301176 :           continue;
     860             : 
     861             :         // Skip if it is a block restricted transfer and current node does not have
     862             :         // specified blocks
     863     4919871 :         if (!_to_blocks.empty() && !inBlocks(_to_blocks, to_moose_mesh, node))
     864       17076 :           continue;
     865             : 
     866     4902795 :         if (!_to_boundaries.empty() && !onBoundaries(_to_boundaries, to_moose_mesh, node))
     867      215568 :           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     4687227 :         if (!_to_mesh_divisions.empty() && _to_mesh_divisions[i_to]->divisionIndex(*node) ==
     873             :                                                MooseMeshDivision::INVALID_DIVISION_INDEX)
     874        4400 :           continue;
     875             : 
     876             :         // Cache point information
     877             :         // We will use this information later for setting values back to solution vectors
     878     4682827 :         cacheOutgoingPointInfo(
     879     4682827 :             (*_to_transforms[global_i_to])(*node), node->id(), i_to, outgoing_points);
     880       50725 :       }
     881             :     }
     882             :     else // Elemental, constant monomial
     883             :     {
     884        3696 :       for (const auto & elem :
     885      462384 :            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      454992 :         if (elem->n_dofs(sys_num, var_num) < 1)
     889        2512 :           continue;
     890             : 
     891             :         // Skip if the element is not inside the block restriction
     892      452480 :         if (!_to_blocks.empty() && !inBlocks(_to_blocks, elem))
     893        5668 :           continue;
     894             : 
     895             :         // Skip if the element does not have a side on the boundary
     896      446812 :         if (!_to_boundaries.empty() && !onBoundaries(_to_boundaries, to_moose_mesh, elem))
     897       49480 :           continue;
     898             : 
     899             :         // Skip if the element is not indexed within the mesh division
     900      397332 :         if (!_to_mesh_divisions.empty() && _to_mesh_divisions[i_to]->divisionIndex(*elem) ==
     901             :                                                MooseMeshDivision::INVALID_DIVISION_INDEX)
     902        2992 :           continue;
     903             : 
     904             :         // Cache point information
     905             :         // We will use this information later for setting values back to solution vectors
     906      394340 :         cacheOutgoingPointInfo((*_to_transforms[global_i_to])(elem->vertex_average()),
     907      394340 :                                elem->id(),
     908             :                                i_to,
     909             :                                outgoing_points);
     910        3696 :       } // for
     911             :     } // else
     912             :   } // for
     913       52470 : }
     914             : 
     915             : void
     916        3934 : MultiAppGeneralFieldTransfer::extractLocalFromBoundingBoxes(std::vector<BoundingBox> & local_bboxes)
     917             : {
     918        3934 :   local_bboxes.resize(_froms_per_proc[processor_id()]);
     919             :   // Find the index to the first of this processor's local bounding boxes.
     920        3934 :   unsigned int local_start = 0;
     921        4984 :   for (processor_id_type i_proc = 0; i_proc < n_processors() && i_proc != processor_id(); ++i_proc)
     922        1050 :     local_start += _froms_per_proc[i_proc];
     923             : 
     924             :   // Extract the local bounding boxes.
     925        9078 :   for (const auto i_from : make_range(_froms_per_proc[processor_id()]))
     926        5144 :     local_bboxes[i_from] = _from_bboxes[local_start + i_from];
     927        3934 : }
     928             : 
     929             : void
     930       81100 : MultiAppGeneralFieldTransfer::cacheIncomingInterpVals(
     931             :     processor_id_type pid,
     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       81100 :   dof_id_type val_offset = 0;
     944     7487465 :   for (const auto & pointinfo : pointInfoVec)
     945             :   {
     946             :     // Retrieve target information from cached point infos
     947     7406365 :     const auto problem_id = pointinfo.problem_id;
     948     7406365 :     const auto dof_object_id = pointinfo.dof_object_id;
     949             : 
     950     7406365 :     auto & fe_type = _to_variables[var_index]->feType();
     951     7406365 :     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     7406365 :     if (fe_type.order > CONSTANT && !is_nodal)
     958             :     {
     959             :       // Cache solution on target mesh in its local frame of reference
     960      383613 :       InterpCache & value_cache = interp_caches[problem_id];
     961      383613 :       InterpCache & distance_cache = distance_caches[problem_id];
     962      383613 :       Point p = _to_transforms[getGlobalTargetAppIndex(problem_id)]->mapBack(
     963      383613 :           point_requests[val_offset].first);
     964      383613 :       const Number val = incoming_vals[val_offset].first;
     965             : 
     966             :       // Initialize distance to be able to compare
     967      383613 :       if (!distance_cache.hasKey(p))
     968      283200 :         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
     973      383613 :       if (_search_value_conflicts && !GeneralFieldTransfer::isOutOfMeshValue(val) &&
     974      383613 :           value_cache.hasKey(p) != 0 && !MooseUtils::absoluteFuzzyEqual(value_cache[p], val) &&
     975           0 :           MooseUtils::absoluteFuzzyEqual(distance_cache[p], incoming_vals[val_offset].second))
     976           0 :         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
     980      467038 :       if ((!GeneralFieldTransfer::isOutOfMeshValue(val) || _use_nearest_app) &&
     981       83425 :           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       75700 :         value_cache[p] = val;
     987       75700 :         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     7022752 :       auto & dofobject_to_val = dofobject_to_valsvec[problem_id];
    1017             : 
    1018             :       // Check if we visited this dof object earlier
    1019     7022752 :       auto values_ptr = dofobject_to_val.find(dof_object_id);
    1020             :       // We did not visit this
    1021     7022752 :       if (values_ptr == dofobject_to_val.end())
    1022             :       {
    1023             :         // Values for this dof object
    1024     5077164 :         auto & val = dofobject_to_val[dof_object_id];
    1025             :         // Interpolation value
    1026     5077164 :         val.interp = incoming_vals[val_offset].first;
    1027             :         // Where this value came from
    1028     5077164 :         val.pid = pid;
    1029             :         // Distance
    1030     5077164 :         val.distance = incoming_vals[val_offset].second;
    1031             :       }
    1032             :       else
    1033             :       {
    1034     1945588 :         auto & val = values_ptr->second;
    1035             : 
    1036             :         // Look for value conflicts
    1037     1945588 :         if (detectConflict(val.interp,
    1038     1945588 :                            incoming_vals[val_offset].first,
    1039             :                            val.distance,
    1040     1945588 :                            incoming_vals[val_offset].second))
    1041             :         {
    1042             :           // Keep track of distance and value
    1043             :           const Point p =
    1044           0 :               getPointInTargetAppFrame(point_requests[val_offset].first,
    1045             :                                        problem_id,
    1046             :                                        "Registration of received equi-distant value conflict");
    1047           0 :           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     3891176 :         if ((!GeneralFieldTransfer::isOutOfMeshValue(incoming_vals[val_offset].first) ||
    1057     3667601 :              _use_nearest_app) &&
    1058     1722013 :             (MooseUtils::absoluteFuzzyGreaterThan(val.distance, incoming_vals[val_offset].second) ||
    1059     1382629 :              ((val.pid > pid) &&
    1060      594040 :               MooseUtils::absoluteFuzzyEqual(val.distance, incoming_vals[val_offset].second))))
    1061             :         {
    1062      342941 :           val.interp = incoming_vals[val_offset].first;
    1063      342941 :           val.pid = pid;
    1064      342941 :           val.distance = incoming_vals[val_offset].second;
    1065             :         }
    1066             :       }
    1067             :     }
    1068             : 
    1069             :     // Move it to next position
    1070     7406365 :     val_offset++;
    1071             :   }
    1072       81100 : }
    1073             : 
    1074             : void
    1075         363 : MultiAppGeneralFieldTransfer::registerConflict(
    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         363 :   if (local)
    1080         363 :     _local_conflicts.push_back(std::make_tuple(problem, dof_id, p, dist));
    1081             :   else
    1082           0 :     _received_conflicts.push_back(std::make_tuple(problem, dof_id, p, dist));
    1083         363 : }
    1084             : 
    1085             : void
    1086         287 : MultiAppGeneralFieldTransfer::examineReceivedValueConflicts(
    1087             :     const unsigned int var_index,
    1088             :     const DofobjectToInterpValVec & dofobject_to_valsvec,
    1089             :     const InterpCaches & distance_caches)
    1090             : {
    1091         287 :   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         287 :   for (auto conflict_it = _received_conflicts.begin(); conflict_it != _received_conflicts.end();)
    1098             :   {
    1099           0 :     const auto potential_conflict = *conflict_it;
    1100           0 :     bool overlap_found = false;
    1101             : 
    1102             :     // Extract info for the potential conflict
    1103           0 :     const unsigned int problem_id = std::get<0>(potential_conflict);
    1104           0 :     const dof_id_type dof_object_id = std::get<1>(potential_conflict);
    1105           0 :     const Point p = std::get<2>(potential_conflict);
    1106           0 :     const Real distance = std::get<3>(potential_conflict);
    1107             : 
    1108             :     // Extract target variable info
    1109           0 :     auto & es = getEquationSystem(*_to_problems[problem_id], _displaced_target_mesh);
    1110           0 :     System * to_sys = find_sys(es, var_name);
    1111           0 :     auto var_num = to_sys->variable_number(var_name);
    1112           0 :     auto & fe_type = to_sys->variable_type(var_num);
    1113           0 :     bool is_nodal = _to_variables[var_index]->isNodal();
    1114             : 
    1115             :     // Higher order elemental
    1116           0 :     if (fe_type.order > CONSTANT && !is_nodal)
    1117             :     {
    1118           0 :       auto cached_distance = distance_caches[problem_id].find(p);
    1119           0 :       if (cached_distance == distance_caches[problem_id].end())
    1120           0 :         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           0 :       if (MooseUtils::absoluteFuzzyEqual(cached_distance->second, distance))
    1123           0 :         overlap_found = true;
    1124             :     }
    1125             :     // Nodal and const monomial variable
    1126           0 :     else if (MooseUtils::absoluteFuzzyEqual(
    1127           0 :                  dofobject_to_valsvec[problem_id].find(dof_object_id)->second.distance, distance))
    1128           0 :       overlap_found = true;
    1129             : 
    1130             :     // Map will only keep the actual overlaps
    1131           0 :     if (!overlap_found)
    1132           0 :       _received_conflicts.erase(conflict_it);
    1133             :     else
    1134           0 :       ++conflict_it;
    1135             :   }
    1136         287 : }
    1137             : 
    1138             : void
    1139         287 : MultiAppGeneralFieldTransfer::examineLocalValueConflicts(
    1140             :     const unsigned int var_index,
    1141             :     const DofobjectToInterpValVec & dofobject_to_valsvec,
    1142             :     const InterpCaches & distance_caches)
    1143             : {
    1144         287 :   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         287 :   std::vector<std::tuple<Point, Real>> potential_conflicts;
    1156         287 :   potential_conflicts.reserve(_local_conflicts.size());
    1157             : 
    1158             :   // Loop over potential conflicts to broadcast all the conflicts
    1159         650 :   for (auto conflict_it = _local_conflicts.begin(); conflict_it != _local_conflicts.end();
    1160         363 :        ++conflict_it)
    1161             :   {
    1162             :     // Extract info for the potential conflict
    1163         363 :     const auto potential_conflict = *conflict_it;
    1164         363 :     const unsigned int i_from = std::get<0>(potential_conflict);
    1165         363 :     Point p = std::get<2>(potential_conflict);
    1166         363 :     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
    1169         363 :     if (!_nearest_positions_obj)
    1170             :     {
    1171         327 :       const auto from_global_num = getGlobalSourceAppIndex(i_from);
    1172         327 :       p = (*_from_transforms[from_global_num])(p);
    1173             :     }
    1174             : 
    1175             :     // Send data in the global frame of reference
    1176         363 :     potential_conflicts.push_back(std::make_tuple(p, distance));
    1177             :   }
    1178         287 :   _communicator.allgather(potential_conflicts, false);
    1179             :   // conflicts could have been reported multiple times within a tolerance
    1180         287 :   std::sort(potential_conflicts.begin(), potential_conflicts.end());
    1181         287 :   potential_conflicts.erase(unique(potential_conflicts.begin(),
    1182             :                                    potential_conflicts.end(),
    1183         335 :                                    [](auto l, auto r)
    1184             :                                    {
    1185         368 :                                      return std::get<0>(l).absolute_fuzzy_equals(std::get<0>(r)) &&
    1186         368 :                                             std::abs(std::get<1>(l) - std::get<1>(r)) < TOLERANCE;
    1187             :                                    }),
    1188         287 :                             potential_conflicts.end());
    1189             : 
    1190         287 :   std::vector<std::tuple<Point, Real>> real_conflicts;
    1191         287 :   real_conflicts.reserve(potential_conflicts.size());
    1192             : 
    1193             :   // For each potential conflict, we need to identify what problem asked for that value
    1194         617 :   for (auto conflict_it = potential_conflicts.begin(); conflict_it != potential_conflicts.end();
    1195         330 :        ++conflict_it)
    1196             :   {
    1197             :     // Extract info for the potential conflict
    1198         330 :     auto potential_conflict = *conflict_it;
    1199         330 :     const Point p = std::get<0>(potential_conflict);
    1200         330 :     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         330 :     bool target_found = false;
    1205         330 :     bool conflict_real = false;
    1206         660 :     for (const auto i_to : index_range(_to_problems))
    1207             :     {
    1208             :       // Extract variable info
    1209         330 :       auto & es = getEquationSystem(*_to_problems[i_to], _displaced_target_mesh);
    1210         330 :       System * to_sys = find_sys(es, var_name);
    1211         330 :       auto var_num = to_sys->variable_number(var_name);
    1212         330 :       auto & fe_type = to_sys->variable_type(var_num);
    1213         330 :       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         660 :           getPointInTargetAppFrame(p, i_to, "Resolution of local value conflicts detected");
    1218             : 
    1219             :       // Higher order elemental
    1220         330 :       if (fe_type.order > CONSTANT && !is_nodal)
    1221             :       {
    1222             :         // distance_caches finds use a binned floating point search
    1223           0 :         auto cached_distance = distance_caches[i_to].find(local_p);
    1224           0 :         if (cached_distance != distance_caches[i_to].end())
    1225             :         {
    1226           0 :           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           0 :           if (MooseUtils::absoluteFuzzyEqual(cached_distance->second, distance))
    1230           0 :             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         330 :         dof_id_type dof_object_id = std::numeric_limits<dof_id_type>::max();
    1238         330 :         auto pl = _to_problems[i_to]->mesh().getPointLocator();
    1239         330 :         pl->enable_out_of_mesh_mode();
    1240         330 :         if (is_nodal)
    1241             :         {
    1242         309 :           auto node = pl->locate_node(local_p);
    1243         309 :           if (node)
    1244             :             // this is not the dof_id for the variable, but the dof_object_id
    1245         309 :             dof_object_id = node->id();
    1246             :         }
    1247             :         else
    1248             :         {
    1249          21 :           auto elem = (*pl)(local_p);
    1250          21 :           if (elem)
    1251          21 :             dof_object_id = elem->id();
    1252             :         }
    1253         330 :         pl->disable_out_of_mesh_mode();
    1254             : 
    1255             :         // point isn't even in mesh
    1256         330 :         if (dof_object_id == std::numeric_limits<dof_id_type>::max())
    1257           0 :           continue;
    1258             : 
    1259             :         // this dof was not requested by this problem on this process
    1260         330 :         if (dofobject_to_valsvec[i_to].find(dof_object_id) == dofobject_to_valsvec[i_to].end())
    1261           0 :           continue;
    1262             : 
    1263         330 :         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         660 :         if (MooseUtils::absoluteFuzzyEqual(
    1267         330 :                 dofobject_to_valsvec[i_to].find(dof_object_id)->second.distance, distance))
    1268         330 :           conflict_real = true;
    1269         330 :       }
    1270             :     }
    1271             :     // Only keep the actual conflicts / overlaps
    1272         330 :     if (target_found && conflict_real)
    1273         330 :       real_conflicts.push_back(potential_conflict);
    1274             :   }
    1275             : 
    1276             :   // Communicate real conflicts to all so they can be checked by every process
    1277         287 :   _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         650 :   for (auto conflict_it = _local_conflicts.begin(); conflict_it != _local_conflicts.end();)
    1284             :   {
    1285             :     // Extract info for the potential conflict
    1286         363 :     const auto potential_conflict = *conflict_it;
    1287         363 :     const unsigned int i_from = std::get<0>(potential_conflict);
    1288         363 :     Point p = std::get<2>(potential_conflict);
    1289         363 :     const Real distance = std::get<3>(potential_conflict);
    1290         363 :     if (!_nearest_positions_obj)
    1291             :     {
    1292         327 :       const auto from_global_num = getGlobalSourceAppIndex(i_from);
    1293         327 :       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         363 :     if (std::find_if(real_conflicts.begin(),
    1298             :                      real_conflicts.end(),
    1299        5094 :                      [p, distance](const auto & item)
    1300             :                      {
    1301        5457 :                        return std::get<0>(item).absolute_fuzzy_equals(p) &&
    1302        5457 :                               std::abs(std::get<1>(item) - distance) < TOLERANCE;
    1303         726 :                      }) == real_conflicts.end())
    1304           0 :       _local_conflicts.erase(conflict_it);
    1305             :     else
    1306         363 :       ++conflict_it;
    1307             :   }
    1308         287 : }
    1309             : 
    1310             : void
    1311         287 : MultiAppGeneralFieldTransfer::outputValueConflicts(
    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         287 :   examineReceivedValueConflicts(var_index, dofobject_to_valsvec, distance_caches);
    1318         287 :   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         287 :   const std::string rank_str = std::to_string(_communicator.rank());
    1323         287 :   if (_local_conflicts.size())
    1324             :   {
    1325          28 :     unsigned int num_outputs = 0;
    1326          56 :     std::string local_conflicts_string = "";
    1327             :     std::string potential_reasons =
    1328             :         "Are some points in target mesh equidistant from the sources "
    1329          28 :         "(nodes/centroids/apps/positions, depending on transfer) in origin mesh(es)?\n";
    1330          28 :     if (hasFromMultiApp() && _from_problems.size() > 1)
    1331          22 :       potential_reasons += "Are multiple subapps overlapping?\n";
    1332         391 :     for (const auto & conflict : _local_conflicts)
    1333             :     {
    1334         363 :       const unsigned int problem_id = std::get<0>(conflict);
    1335         363 :       Point p = std::get<2>(conflict);
    1336         363 :       num_outputs++;
    1337             : 
    1338         363 :       std::string origin_domain_message;
    1339         363 :       if (hasFromMultiApp() && !_nearest_positions_obj)
    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         327 :         const auto app_id = _from_local2global_map[problem_id];
    1347         327 :         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
    1352          36 :       else if (hasFromMultiApp() && _nearest_positions_obj)
    1353             :       {
    1354          36 :         if (_to_problems.size() == 1 || _skip_coordinate_collapsing)
    1355             :         {
    1356          36 :           p = (*_to_transforms[0])(p);
    1357          36 :           origin_domain_message = "In target app mesh,";
    1358             :         }
    1359             :         else
    1360           0 :           origin_domain_message = "In reference (post-coordinate collapse) mesh,";
    1361             :       }
    1362             :       else
    1363           0 :         origin_domain_message = "In source parent app mesh,";
    1364             : 
    1365         363 :       if (num_outputs < _search_value_conflicts_max_log)
    1366         330 :         local_conflicts_string += origin_domain_message + " point: (" + std::to_string(p(0)) +
    1367         660 :                                   ", " + std::to_string(p(1)) + ", " + std::to_string(p(2)) +
    1368         660 :                                   "), equi-distance: " + std::to_string(std::get<3>(conflict)) +
    1369         165 :                                   "\n";
    1370         198 :       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           6 :             "will not be output.\nIncrease 'search_value_conflicts_max_log' to output more.";
    1374         363 :     }
    1375             :     // Explicitly name source to give more context
    1376          28 :     const std::string source_str = getDataSourceName(var_index);
    1377             : 
    1378          56 :     mooseWarning("On rank " + rank_str +
    1379             :                  ", multiple valid values from equidistant points were "
    1380          28 :                  "found in the origin mesh for source " +
    1381          56 :                  source_str + " for " + std::to_string(_local_conflicts.size()) +
    1382          28 :                  " target points.\n" + potential_reasons + "Conflicts detected at :\n" +
    1383             :                  local_conflicts_string);
    1384           0 :   }
    1385             : 
    1386             :   // Output the conflicts discovered when receiving values from multiple origin problems
    1387         259 :   if (_received_conflicts.size())
    1388             :   {
    1389           0 :     unsigned int num_outputs = 0;
    1390           0 :     std::string received_conflicts_string = "";
    1391             :     std::string potential_reasons =
    1392             :         "Are some points in target mesh equidistant from the sources "
    1393           0 :         "(nodes/centroids/apps/positions, depending on transfer) in origin mesh(es)?\n";
    1394           0 :     if (hasToMultiApp() && _to_problems.size() > 1)
    1395           0 :       potential_reasons += "Are multiple subapps overlapping?\n";
    1396           0 :     for (const auto & conflict : _received_conflicts)
    1397             :     {
    1398             :       // Extract info for the potential overlap
    1399           0 :       const unsigned int problem_id = std::get<0>(conflict);
    1400           0 :       const Point p = std::get<2>(conflict);
    1401           0 :       num_outputs++;
    1402             : 
    1403           0 :       std::string target_domain_message;
    1404           0 :       if (hasToMultiApp())
    1405             :       {
    1406           0 :         const auto app_id = _to_local2global_map[problem_id];
    1407           0 :         target_domain_message = "In target child app " + std::to_string(app_id) + " mesh,";
    1408             :       }
    1409             :       else
    1410           0 :         target_domain_message = "In target parent app mesh,";
    1411             : 
    1412           0 :       if (num_outputs < _search_value_conflicts_max_log)
    1413           0 :         received_conflicts_string += target_domain_message + " point: (" + std::to_string(p(0)) +
    1414           0 :                                      ", " + std::to_string(p(1)) + ", " + std::to_string(p(2)) +
    1415           0 :                                      "), equi-distance: " + std::to_string(std::get<3>(conflict)) +
    1416           0 :                                      "\n";
    1417           0 :       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           0 :             "will not be output.\nIncrease 'search_value_conflicts_max_log' to output more.";
    1421           0 :     }
    1422           0 :     mooseWarning("On rank " + rank_str +
    1423             :                  ", multiple valid values from equidistant points were "
    1424           0 :                  "received for target variable '" +
    1425           0 :                  getToVarName(var_index) + "' for " + std::to_string(_received_conflicts.size()) +
    1426           0 :                  " target points.\n" + potential_reasons + "Conflicts detected at :\n" +
    1427             :                  received_conflicts_string);
    1428           0 :   }
    1429             : 
    1430         259 :   if (_local_conflicts.empty() && _received_conflicts.empty())
    1431             :   {
    1432         777 :     if (isParamSetByUser("search_value_conflict"))
    1433           0 :       mooseInfo("Automated diagnosis did not detect floating point indetermination in transfer");
    1434         259 :     else if (_to_problems.size() > 10 || _from_problems.size() > 10 || _communicator.size() > 10)
    1435           0 :       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         259 :   _local_conflicts.clear();
    1444         259 :   _received_conflicts.clear();
    1445         259 : }
    1446             : 
    1447             : void
    1448       52442 : MultiAppGeneralFieldTransfer::setSolutionVectorValues(
    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       52442 :   const auto & var_name = getToVarName(var_index);
    1455             : 
    1456      107230 :   for (const auto problem_id : index_range(_to_problems))
    1457             :   {
    1458       54794 :     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       54794 :     auto & es = getEquationSystem(*_to_problems[problem_id], false);
    1463             : 
    1464             :     // libMesh system
    1465       54794 :     System * to_sys = find_sys(es, var_name);
    1466             : 
    1467             :     // libMesh mesh
    1468       54794 :     const MeshBase & to_mesh = _to_problems[problem_id]->mesh(_displaced_target_mesh).getMesh();
    1469       54794 :     auto var_num = to_sys->variable_number(var_name);
    1470       54794 :     auto sys_num = to_sys->number();
    1471             : 
    1472       54794 :     auto & fe_type = _to_variables[var_index]->feType();
    1473       54794 :     bool is_nodal = _to_variables[var_index]->isNodal();
    1474             : 
    1475       54794 :     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         401 :       MeshFunction to_func(es, *to_sys->current_local_solution, to_sys->get_dof_map(), var_num);
    1480         401 :       to_func.init();
    1481             : 
    1482             :       GeneralFieldTransfer::CachedData<Number> f(
    1483         401 :           interp_caches[problem_id], to_func, _default_extrapolation_value);
    1484         401 :       libMesh::VectorSetAction<Number> setter(*to_sys->solution);
    1485         401 :       const std::vector<unsigned int> varvec(1, var_num);
    1486             : 
    1487             :       libMesh::GenericProjector<GeneralFieldTransfer::CachedData<Number>,
    1488             :                                 GeneralFieldTransfer::CachedData<Gradient>,
    1489             :                                 Number,
    1490             :                                 libMesh::VectorSetAction<Number>>
    1491         401 :           set_solution(*to_sys, f, nullptr, setter, varvec);
    1492             : 
    1493             :       // We dont look at boundary restriction, not supported for higher order target variables
    1494         401 :       const auto & to_begin = _to_blocks.empty()
    1495         662 :                                   ? to_mesh.active_local_elements_begin()
    1496         401 :                                   : to_mesh.active_local_subdomain_set_elements_begin(_to_blocks);
    1497             : 
    1498         401 :       const auto & to_end = _to_blocks.empty()
    1499         662 :                                 ? to_mesh.active_local_elements_end()
    1500         401 :                                 : to_mesh.active_local_subdomain_set_elements_end(_to_blocks);
    1501             : 
    1502         401 :       ConstElemRange active_local_elem_range(to_begin, to_end);
    1503             : 
    1504         401 :       set_solution.project(active_local_elem_range);
    1505         401 :     }
    1506             :     else
    1507             :     {
    1508     5127704 :       for (const auto & val_pair : dofobject_to_val)
    1509             :       {
    1510     5073317 :         const auto dof_object_id = val_pair.first;
    1511             : 
    1512     5073317 :         const DofObject * dof_object = nullptr;
    1513     5073317 :         if (is_nodal)
    1514     4679754 :           dof_object = to_mesh.node_ptr(dof_object_id);
    1515             :         else
    1516      393563 :           dof_object = to_mesh.elem_ptr(dof_object_id);
    1517             : 
    1518     5073317 :         const auto dof = dof_object->dof_number(sys_num, var_num, 0);
    1519     5073317 :         const auto val = val_pair.second.interp;
    1520             : 
    1521             :         // This will happen if meshes are mismatched
    1522     5073317 :         if (_error_on_miss && GeneralFieldTransfer::isOutOfMeshValue(val))
    1523             :         {
    1524             :           const auto target_location =
    1525           6 :               hasToMultiApp()
    1526           6 :                   ? " on target app " + std::to_string(getGlobalTargetAppIndex(problem_id))
    1527          15 :                   : " on parent app";
    1528           6 :           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           6 :           if (is_nodal)
    1532           3 :             mooseError("No source value for node ",
    1533             :                        dof_object_id,
    1534             :                        target_location,
    1535             :                        " could be located. Node details:\n",
    1536           3 :                        _to_meshes[problem_id]->nodePtr(dof_object_id)->get_info(),
    1537             :                        "\n",
    1538             :                        info_msg);
    1539             :           else
    1540           3 :             mooseError("No source value for element ",
    1541             :                        dof_object_id,
    1542             :                        target_location,
    1543             :                        " could be located. Element details:\n",
    1544           3 :                        _to_meshes[problem_id]->elemPtr(dof_object_id)->get_info(),
    1545             :                        "\n",
    1546             :                        info_msg);
    1547           0 :         }
    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
    1553     5073311 :         if (GeneralFieldTransfer::isOutOfMeshValue(val))
    1554             :         {
    1555      566504 :           if (!GeneralFieldTransfer::isOutOfMeshValue(_default_extrapolation_value))
    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      566056 :             const auto missing_value = _post_transfer_extrapolation == "nearest-valid-target"
    1563      566056 :                                            ? GeneralFieldTransfer::OutOfMeshValue
    1564      566056 :                                            : _default_extrapolation_value;
    1565      566056 :             to_sys->solution->set(dof, missing_value);
    1566             :           }
    1567      566504 :           continue;
    1568      566504 :         }
    1569     4506807 :         to_sys->solution->set(dof, val);
    1570             :       }
    1571             :     }
    1572             : 
    1573       54788 :     to_sys->solution->close();
    1574             :     // Sync local solutions
    1575       54788 :     to_sys->update();
    1576             :   }
    1577       52436 : }
    1578             : 
    1579             : bool
    1580     1825856 : MultiAppGeneralFieldTransfer::acceptPointInOriginMesh(unsigned int i_from,
    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     1825856 :   if (_use_bounding_boxes && !local_bboxes[i_from].contains_point(pt))
    1587     1484626 :     return false;
    1588             :   else
    1589             :   {
    1590      341230 :     auto * pl = _from_point_locators[i_from].get();
    1591      341230 :     const auto from_global_num = getGlobalSourceAppIndex(i_from);
    1592             :     const auto transformed_pt =
    1593      682460 :         getPointInSourceAppFrame(pt, i_from, "Source point acceptance check");
    1594             : 
    1595             :     // Check point against source block restriction
    1596      341230 :     if (!_from_blocks.empty() && !inBlocks(_from_blocks, pl, transformed_pt))
    1597       12313 :       return false;
    1598             : 
    1599             :     // Check point against source boundary restriction. Block restriction will speed up the search
    1600      341034 :     if (!_from_boundaries.empty() &&
    1601           0 :         !onBoundaries(_from_boundaries, _from_blocks, *_from_meshes[i_from], pl, transformed_pt))
    1602           0 :       return false;
    1603             : 
    1604             :     // Check point against the source mesh division
    1605      347798 :     if ((!_from_mesh_divisions.empty() || !_to_mesh_divisions.empty()) &&
    1606        6764 :         !acceptPointMeshDivision(transformed_pt, i_from, only_from_mesh_div))
    1607        3796 :       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      337238 :     Point nearest_position_source;
    1612      337238 :     if (_nearest_positions_obj)
    1613             :     {
    1614       14399 :       const bool initial = _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL;
    1615             :       // The search for the nearest position is done in the reference frame
    1616       14399 :       const Point nearest_position = _nearest_positions_obj->getNearestPosition(pt, initial);
    1617       14399 :       nearest_position_source = _nearest_positions_obj->getNearestPosition(
    1618       14399 :           (*_from_transforms[from_global_num])(Point(0, 0, 0)), initial);
    1619             : 
    1620       14399 :       if (!_skip_coordinate_collapsing &&
    1621           0 :           _from_transforms[from_global_num]->hasNonTranslationTransformation())
    1622           0 :         mooseError("Rotation and scaling currently unsupported with nearest positions transfer.");
    1623             : 
    1624             :       // Compute distance to nearest position and nearest position source
    1625       14399 :       const Real distance_to_position_nearest_source = (pt - nearest_position_source).norm();
    1626       14399 :       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       14399 :       if (!MooseUtils::absoluteFuzzyEqual(distance_to_position_nearest_source,
    1633             :                                           distance_to_nearest_position))
    1634        8201 :         return false;
    1635             : 
    1636             :       // Set the distance as the distance from the nearest position to the target point
    1637        6198 :       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
    1642      329157 :     if (_from_blocks.empty() && _from_boundaries.empty() && _source_app_must_contain_point &&
    1643         120 :         !inMesh(pl, transformed_pt))
    1644         120 :       return false;
    1645             :   }
    1646      328917 :   return true;
    1647             : }
    1648             : 
    1649             : void
    1650       52436 : MultiAppGeneralFieldTransfer::correctSolutionVectorValues(
    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       52436 :   const auto & var_name = getToVarName(var_index);
    1659             : 
    1660      107224 :   for (const auto problem_id : index_range(_to_problems))
    1661             :   {
    1662       54788 :     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       54788 :     auto & es = getEquationSystem(*_to_problems[problem_id], false);
    1667             : 
    1668             :     // libMesh system
    1669       54788 :     System * to_sys = find_sys(es, var_name);
    1670             : 
    1671             :     // libMesh mesh
    1672       54788 :     const MeshBase & to_mesh = _to_problems[problem_id]->mesh(_displaced_target_mesh).getMesh();
    1673       54788 :     auto var_num = to_sys->variable_number(var_name);
    1674       54788 :     auto sys_num = to_sys->number();
    1675             : 
    1676       54788 :     auto & fe_type = getToVariable(var_index)->feType();
    1677       54788 :     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       54788 :     if (_post_transfer_extrapolation == "nearest-valid-target")
    1685             :     {
    1686          44 :       if (fe_type.order > CONSTANT && !is_nodal)
    1687           0 :         paramError("post_transfer_extrapolation",
    1688             :                    "Nearest-valid-target is not implemented for higher order elemental variables");
    1689             :       const auto & node_to_elem_map =
    1690          44 :           _to_problems[problem_id]->mesh(_displaced_target_mesh).nodeToElemMap();
    1691             : 
    1692         732 :       for (const auto & val_pair : dofobject_to_val)
    1693             :       {
    1694         688 :         const auto dof_object_id = val_pair.first;
    1695             : 
    1696             :         // Check that the value was out of bounds
    1697         688 :         const DofObject * dof_object = nullptr;
    1698         688 :         if (is_nodal)
    1699         416 :           dof_object = to_mesh.node_ptr(dof_object_id);
    1700             :         else
    1701         272 :           dof_object = to_mesh.elem_ptr(dof_object_id);
    1702         688 :         const auto dof = dof_object->dof_number(sys_num, var_num, 0);
    1703         688 :         const auto val = val_pair.second.interp;
    1704         688 :         if (GeneralFieldTransfer::isOutOfMeshValue(val))
    1705             :         {
    1706         328 :           Real nearest_value = 0.;
    1707         328 :           dof_id_type min_dist_id = std::numeric_limits<dof_id_type>::max();
    1708             : 
    1709             :           // Find the nearest valid value
    1710         328 :           if (is_nodal)
    1711             :           {
    1712         184 :             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         184 :             Real min_distance_sq = std::numeric_limits<Real>::max();
    1716         568 :             for (const auto & elem_id : libmesh_map_find(node_to_elem_map, node->id()))
    1717             :             {
    1718         384 :               const auto elem = to_mesh.elem_ptr(elem_id);
    1719        1920 :               for (const auto & elem_node : elem->node_ref_range())
    1720             :               {
    1721        1536 :                 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        1536 :                 if (distance_sq < min_distance_sq && elem_node.id() != node->id())
    1729             :                 {
    1730         851 :                   if (auto it = dofobject_to_val.find(elem_node.id());
    1731        1652 :                       it != dofobject_to_val.end() &&
    1732         801 :                       !GeneralFieldTransfer::isOutOfMeshValue(it->second.interp))
    1733             :                   {
    1734         172 :                     min_distance_sq = distance_sq;
    1735         172 :                     min_dist_id = elem_node.id();
    1736         172 :                     nearest_value = it->second.interp;
    1737             :                   }
    1738         679 :                   else if (elem_node.n_dofs(sys_num, var_num) > 0)
    1739             :                   {
    1740         679 :                     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         679 :                       if (const auto sol_val = (*to_sys->current_local_solution)(other_dof);
    1748         679 :                           !GeneralFieldTransfer::isOutOfMeshValue(sol_val))
    1749             :                       {
    1750          20 :                         min_distance_sq = distance_sq;
    1751          20 :                         min_dist_id = elem_node.id();
    1752          20 :                         nearest_value = sol_val;
    1753             :                       }
    1754             :                     }
    1755           0 :                     catch (...)
    1756             :                     {
    1757             :                       // Access in ghosted vector failed, just keep going
    1758           0 :                     }
    1759             :                   }
    1760             :                 }
    1761             :               }
    1762             :             }
    1763             :           }
    1764             :           else
    1765             :           {
    1766         144 :             const auto elem = to_mesh.elem_ptr(dof_object_id);
    1767         144 :             Real min_distance_sq = std::numeric_limits<Real>::max();
    1768         720 :             for (const auto neigh : elem->neighbor_ptr_range())
    1769             :             {
    1770         576 :               if (!neigh || neigh == libMesh::remote_elem)
    1771         168 :                 continue;
    1772         408 :               Real distance_sq = (neigh->vertex_average() - elem->vertex_average()).norm_sq();
    1773         408 :               if (distance_sq < min_distance_sq)
    1774             :               {
    1775         352 :                 if (auto it = dofobject_to_val.find(neigh->id());
    1776         681 :                     it != dofobject_to_val.end() &&
    1777         329 :                     !GeneralFieldTransfer::isOutOfMeshValue(it->second.interp))
    1778             :                 {
    1779          79 :                   min_distance_sq = distance_sq;
    1780          79 :                   min_dist_id = neigh->id();
    1781          79 :                   nearest_value = it->second.interp;
    1782             :                 }
    1783             :                 // Access into ghosted solution vector. See comments for node
    1784         273 :                 else if (neigh->n_dofs(sys_num, var_num) > 0)
    1785             :                 {
    1786         273 :                   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         273 :                     if (const auto sol_val = (*to_sys->current_local_solution)(other_dof);
    1792         273 :                         !GeneralFieldTransfer::isOutOfMeshValue(sol_val))
    1793             :                     {
    1794           9 :                       nearest_value = sol_val;
    1795           9 :                       min_distance_sq = distance_sq;
    1796           9 :                       min_dist_id = neigh->id();
    1797             :                     }
    1798             :                   }
    1799           0 :                   catch (...)
    1800             :                   {
    1801             :                     // Access in ghosted vector failed, just keep going
    1802           0 :                   }
    1803             :                 }
    1804             :               }
    1805             :             }
    1806             :           }
    1807         328 :           nearest_value = (min_dist_id != std::numeric_limits<dof_id_type>::max())
    1808         328 :                               ? nearest_value
    1809             :                               : _default_extrapolation_value;
    1810             : 
    1811         328 :           if (min_dist_id != std::numeric_limits<dof_id_type>::max())
    1812         224 :             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         104 :             to_sys->solution->set(dof, _default_extrapolation_value);
    1818         126 :             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          44 :       to_sys->solution->close();
    1826             :       // Sync local solutions
    1827          44 :       to_sys->update();
    1828             :     }
    1829             :   }
    1830       52436 : }
    1831             : 
    1832             : bool
    1833         120 : MultiAppGeneralFieldTransfer::inMesh(const PointLocatorBase * const pl, const Point & point) const
    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         120 :   const Elem * elem = (*pl)(point);
    1838         120 :   return (elem != nullptr);
    1839             : }
    1840             : 
    1841             : bool
    1842       49736 : MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
    1843             :                                        const Elem * elem) const
    1844             : {
    1845       49736 :   return blocks.find(elem->subdomain_id()) != blocks.end();
    1846             : }
    1847             : 
    1848             : bool
    1849       30400 : MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
    1850             :                                        const MooseMesh & /* mesh */,
    1851             :                                        const Elem * elem) const
    1852             : {
    1853       30400 :   return inBlocks(blocks, elem);
    1854             : }
    1855             : 
    1856             : bool
    1857       45172 : MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
    1858             :                                        const MooseMesh & mesh,
    1859             :                                        const Node * node) const
    1860             : {
    1861       45172 :   const auto & node_blocks = mesh.getNodeBlockIds(*node);
    1862       45172 :   std::set<SubdomainID> u;
    1863       45172 :   std::set_intersection(blocks.begin(),
    1864             :                         blocks.end(),
    1865             :                         node_blocks.begin(),
    1866             :                         node_blocks.end(),
    1867             :                         std::inserter(u, u.begin()));
    1868       90344 :   return !u.empty();
    1869       45172 : }
    1870             : 
    1871             : bool
    1872        9324 : MultiAppGeneralFieldTransfer::inBlocks(const std::set<SubdomainID> & blocks,
    1873             :                                        const PointLocatorBase * const pl,
    1874             :                                        const Point & point) const
    1875             : {
    1876        9324 :   const Elem * elem = (*pl)(point, &blocks);
    1877        9324 :   return (elem != nullptr);
    1878             : }
    1879             : 
    1880             : bool
    1881      512656 : MultiAppGeneralFieldTransfer::onBoundaries(const std::set<BoundaryID> & boundaries,
    1882             :                                            const MooseMesh & mesh,
    1883             :                                            const Node * node) const
    1884             : {
    1885      512656 :   const BoundaryInfo & bnd_info = mesh.getMesh().get_boundary_info();
    1886      512656 :   std::vector<BoundaryID> vec_to_fill;
    1887      512656 :   bnd_info.boundary_ids(node, vec_to_fill);
    1888      512656 :   std::set<BoundaryID> vec_to_fill_set(vec_to_fill.begin(), vec_to_fill.end());
    1889      512656 :   std::set<BoundaryID> u;
    1890      512656 :   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     1025312 :   return !u.empty();
    1896      512656 : }
    1897             : 
    1898             : bool
    1899       88612 : MultiAppGeneralFieldTransfer::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       88612 :   const BoundaryInfo & bnd_info = mesh.getMesh().get_boundary_info();
    1905       88612 :   std::vector<BoundaryID> vec_to_fill;
    1906       88612 :   std::vector<BoundaryID> vec_to_fill_temp;
    1907       88612 :   if (_elemental_boundary_restriction_on_sides)
    1908      536284 :     for (const auto side : make_range(elem->n_sides()))
    1909             :     {
    1910      459672 :       bnd_info.boundary_ids(elem, side, vec_to_fill_temp);
    1911      459672 :       vec_to_fill.insert(vec_to_fill.end(), vec_to_fill_temp.begin(), vec_to_fill_temp.end());
    1912             :     }
    1913             :   else
    1914      108000 :     for (const auto node_index : make_range(elem->n_nodes()))
    1915             :     {
    1916       96000 :       bnd_info.boundary_ids(elem->node_ptr(node_index), vec_to_fill_temp);
    1917       96000 :       vec_to_fill.insert(vec_to_fill.end(), vec_to_fill_temp.begin(), vec_to_fill_temp.end());
    1918             :     }
    1919       88612 :   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       88612 :   std::set<BoundaryID> u;
    1923       88612 :   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      177224 :   return !u.empty();
    1929       88612 : }
    1930             : 
    1931             : bool
    1932           0 : MultiAppGeneralFieldTransfer::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           0 :   if (block_restriction.empty())
    1941           0 :     elem = (*pl)(point);
    1942             :   else
    1943           0 :     elem = (*pl)(point, &block_restriction);
    1944             : 
    1945           0 :   if (!elem)
    1946           0 :     return false;
    1947           0 :   return onBoundaries(boundaries, mesh, elem);
    1948             : }
    1949             : 
    1950             : bool
    1951        6764 : MultiAppGeneralFieldTransfer::acceptPointMeshDivision(
    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        6764 :   unsigned int source_mesh_div = MooseMeshDivision::INVALID_DIVISION_INDEX - 1;
    1957        6764 :   if (!_from_mesh_divisions.empty())
    1958        4824 :     source_mesh_div = _from_mesh_divisions[i_local]->divisionIndex(pt);
    1959             : 
    1960             :   // If the point is not indexed in the source division
    1961        6764 :   if (!_from_mesh_divisions.empty() && source_mesh_div == MooseMeshDivision::INVALID_DIVISION_INDEX)
    1962        2896 :     return false;
    1963             :   // If the point is not the at the same index in the target and the origin meshes, reject
    1964        3868 :   else if ((_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
    1965        3868 :             _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX) &&
    1966             :            source_mesh_div != only_from_this_mesh_div)
    1967           0 :     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
    1970        3868 :   else if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
    1971             :            source_mesh_div != only_from_this_mesh_div)
    1972         462 :     return false;
    1973        4044 :   else if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
    1974         638 :            only_from_this_mesh_div != getGlobalSourceAppIndex(i_local))
    1975         438 :     return false;
    1976             :   else
    1977        2968 :     return true;
    1978             : }
    1979             : 
    1980             : bool
    1981      146289 : MultiAppGeneralFieldTransfer::closestToPosition(unsigned int pos_index, const Point & pt) const
    1982             : {
    1983             :   mooseAssert(_nearest_positions_obj, "Should not be here without a positions object");
    1984      146289 :   if (!_skip_coordinate_collapsing)
    1985           0 :     paramError("skip_coordinate_collapsing", "Coordinate collapsing not implemented");
    1986      146289 :   bool initial = _fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL;
    1987      146289 :   if (!_search_value_conflicts)
    1988             :     // Faster to just compare the index
    1989       17017 :     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      129272 :     const auto nearest_position = _nearest_positions_obj->getNearestPosition(pt, initial);
    1995      129272 :     const auto nearest_position_at_index = _nearest_positions_obj->getPosition(pos_index, initial);
    1996      129272 :     Real distance_to_position_at_index = (pt - nearest_position_at_index).norm();
    1997      129272 :     const Real distance_to_nearest_position = (pt - nearest_position).norm();
    1998             : 
    1999      129272 :     if (!MooseUtils::absoluteFuzzyEqual(distance_to_position_at_index,
    2000             :                                         distance_to_nearest_position))
    2001       84053 :       return false;
    2002             :     // Actually the same position (point)
    2003       45219 :     else if (nearest_position == nearest_position_at_index)
    2004       45219 :       return true;
    2005             :     else
    2006             :     {
    2007           0 :       mooseWarning("Two equidistant positions ",
    2008             :                    nearest_position,
    2009             :                    " and ",
    2010             :                    nearest_position_at_index,
    2011             :                    " detected near point ",
    2012             :                    pt);
    2013           0 :       return true;
    2014             :     }
    2015             :   }
    2016             : }
    2017             : 
    2018             : Real
    2019     7985215 : MultiAppGeneralFieldTransfer::bboxMaxDistance(const Point & p, const BoundingBox & bbox) const
    2020             : {
    2021     7985215 :   std::array<Point, 2> source_points = {{bbox.first, bbox.second}};
    2022             : 
    2023     7985215 :   std::array<Point, 8> all_points;
    2024    23955645 :   for (unsigned int x = 0; x < 2; x++)
    2025    47911290 :     for (unsigned int y = 0; y < 2; y++)
    2026    95822580 :       for (unsigned int z = 0; z < 2; z++)
    2027    63881720 :         all_points[x + 2 * y + 4 * z] =
    2028   127763440 :             Point(source_points[x](0), source_points[y](1), source_points[z](2));
    2029             : 
    2030     7985215 :   Real max_distance = 0.;
    2031             : 
    2032    71866935 :   for (unsigned int i = 0; i < 8; i++)
    2033             :   {
    2034    63881720 :     Real distance = (p - all_points[i]).norm();
    2035    63881720 :     if (distance > max_distance)
    2036    17095062 :       max_distance = distance;
    2037             :   }
    2038             : 
    2039     7985215 :   return max_distance;
    2040             : }
    2041             : 
    2042             : Real
    2043     7985215 : MultiAppGeneralFieldTransfer::bboxMinDistance(const Point & p, const BoundingBox & bbox) const
    2044             : {
    2045     7985215 :   std::array<Point, 2> source_points = {{bbox.first, bbox.second}};
    2046             : 
    2047     7985215 :   std::array<Point, 8> all_points;
    2048    23955645 :   for (unsigned int x = 0; x < 2; x++)
    2049    47911290 :     for (unsigned int y = 0; y < 2; y++)
    2050    95822580 :       for (unsigned int z = 0; z < 2; z++)
    2051    63881720 :         all_points[x + 2 * y + 4 * z] =
    2052   127763440 :             Point(source_points[x](0), source_points[y](1), source_points[z](2));
    2053             : 
    2054     7985215 :   Real min_distance = std::numeric_limits<Real>::max();
    2055             : 
    2056    71866935 :   for (unsigned int i = 0; i < 8; i++)
    2057             :   {
    2058    63881720 :     Real distance = (p - all_points[i]).norm();
    2059    63881720 :     if (distance < min_distance)
    2060    17632467 :       min_distance = distance;
    2061             :   }
    2062             : 
    2063     7985215 :   return min_distance;
    2064             : }
    2065             : 
    2066             : std::vector<BoundingBox>
    2067       52342 : MultiAppGeneralFieldTransfer::getRestrictedFromBoundingBoxes() const
    2068             : {
    2069       52342 :   std::vector<std::pair<Point, Point>> bb_points(_from_meshes.size());
    2070       52342 :   const Real min_r = std::numeric_limits<Real>::lowest();
    2071       52342 :   const Real max_r = std::numeric_limits<Real>::max();
    2072             : 
    2073      107032 :   for (const auto j : make_range(_from_meshes.size()))
    2074             :   {
    2075       54690 :     Point min(max_r, max_r, max_r);
    2076       54690 :     Point max(min_r, min_r, min_r);
    2077       54690 :     bool at_least_one = false;
    2078       54690 :     const auto & from_mesh = _from_problems[j]->mesh(_displaced_source_mesh);
    2079             : 
    2080      109380 :     for (const auto & elem : as_range(from_mesh.getMesh().local_elements_begin(),
    2081     4760836 :                                       from_mesh.getMesh().local_elements_end()))
    2082             :     {
    2083     4596766 :       if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, elem))
    2084       20496 :         continue;
    2085             : 
    2086    26492626 :       for (const auto & node : elem->node_ref_range())
    2087             :       {
    2088    21916356 :         if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, &node))
    2089      161592 :           continue;
    2090             : 
    2091    21754764 :         at_least_one = true;
    2092    87019056 :         for (const auto i : make_range(LIBMESH_DIM))
    2093             :         {
    2094    65264292 :           min(i) = std::min(min(i), node(i));
    2095    65264292 :           max(i) = std::max(max(i), node(i));
    2096             :         }
    2097             :       }
    2098       54690 :     }
    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       54690 :     if ((from_mesh.getUniqueCoordSystem() == Moose::COORD_RZ) && (LIBMESH_DIM == 3))
    2105             :     {
    2106         119 :       min(0) = -max(0);
    2107         119 :       min(2) = -max(0);
    2108         119 :       max(2) = max(0);
    2109             :     }
    2110             : 
    2111       54690 :     BoundingBox bbox(min, max);
    2112       54690 :     if (!at_least_one)
    2113          31 :       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       54659 :       const auto from_global_num = getGlobalSourceAppIndex(j);
    2119       54659 :       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       54690 :     bb_points[j] = static_cast<std::pair<Point, Point>>(bbox);
    2125             :   }
    2126             : 
    2127             :   // Serialize the bounding box points.
    2128       52342 :   _communicator.allgather(bb_points);
    2129             : 
    2130             :   // Recast the points back into bounding boxes and return.
    2131       52342 :   std::vector<BoundingBox> bboxes(bb_points.size());
    2132      136401 :   for (const auto i : make_range(bb_points.size()))
    2133       84059 :     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       52342 :   if (_fixed_bbox_size != std::vector<Real>(3, 0))
    2138        1260 :     for (const auto i : make_range(LIBMESH_DIM))
    2139         945 :       if (!MooseUtils::absoluteFuzzyEqual(_fixed_bbox_size[i], 0))
    2140        1986 :         for (const auto j : make_range(bboxes.size()))
    2141             :         {
    2142        1245 :           const auto current_width = (bboxes[j].second - bboxes[j].first)(i);
    2143        1245 :           bboxes[j].first(i) -= (_fixed_bbox_size[i] - current_width) / 2;
    2144        1245 :           bboxes[j].second(i) += (_fixed_bbox_size[i] - current_width) / 2;
    2145             :         }
    2146             : 
    2147      104684 :   return bboxes;
    2148       52342 : }
    2149             : 
    2150             : std::vector<unsigned int>
    2151       52342 : MultiAppGeneralFieldTransfer::getGlobalStartAppPerProc() const
    2152             : {
    2153       52342 :   std::vector<unsigned int> global_app_start_per_proc(1, -1);
    2154       52342 :   if (_from_local2global_map.size())
    2155       27537 :     global_app_start_per_proc[0] = _from_local2global_map[0];
    2156       52342 :   _communicator.allgather(global_app_start_per_proc, true);
    2157       52342 :   return global_app_start_per_proc;
    2158           0 : }
    2159             : 
    2160             : std::string
    2161          19 : MultiAppGeneralFieldTransfer::getDataSourceName(unsigned int var_index) const
    2162             : {
    2163             :   mooseAssert(var_index < _from_var_names.size(), "No source variable at this index");
    2164          19 :   return "variable '" + getFromVarName(var_index) + "'";
    2165             : }
    2166             : 
    2167             : VariableName
    2168       54851 : MultiAppGeneralFieldTransfer::getFromVarName(unsigned int var_index) const
    2169             : {
    2170             :   mooseAssert(var_index < _from_var_names.size(), "No source variable at this index");
    2171       54851 :   VariableName var_name = _from_var_names[var_index];
    2172       54851 :   if (_from_var_components.size())
    2173         280 :     var_name += "_" + std::to_string(_from_var_components[var_index]);
    2174       54851 :   return var_name;
    2175           0 : }
    2176             : 
    2177             : VariableName
    2178      157928 : MultiAppGeneralFieldTransfer::getToVarName(unsigned int var_index)
    2179             : {
    2180             :   mooseAssert(var_index < _to_var_names.size(), "No target variable at this index");
    2181      157928 :   VariableName var_name = _to_var_names[var_index];
    2182      157928 :   if (_to_var_components.size())
    2183         528 :     var_name += "_" + std::to_string(_to_var_components[var_index]);
    2184      157928 :   return var_name;
    2185           0 : }
    2186             : 
    2187             : Point
    2188      104940 : MultiAppGeneralFieldTransfer::getMaxToProblemsBBoxDimensions() const
    2189             : {
    2190             :   Point max_dimension = {std::numeric_limits<Real>::min(),
    2191             :                          std::numeric_limits<Real>::min(),
    2192      104940 :                          std::numeric_limits<Real>::min()};
    2193             : 
    2194      214584 :   for (const auto & to_mesh : _to_meshes)
    2195             :   {
    2196      109644 :     const auto bbox = to_mesh->getInflatedProcessorBoundingBox();
    2197      438576 :     for (const auto dim : make_range(LIBMESH_DIM))
    2198      328932 :       max_dimension(dim) = std::max(
    2199      657864 :           max_dimension(dim), std::max(std::abs(bbox.first(dim)), std::abs(bbox.second(dim))));
    2200             :   }
    2201             : 
    2202      104940 :   return max_dimension;
    2203             : }
    2204             : 
    2205             : bool
    2206     2274505 : MultiAppGeneralFieldTransfer::detectConflict(Real current_value,
    2207             :                                              Real new_value,
    2208             :                                              Real current_distance,
    2209             :                                              Real new_distance) const
    2210             : {
    2211             :   // No conflict if we're not looking for them
    2212     2274505 :   if (_search_value_conflicts)
    2213             :     // Only consider conflicts if the values are valid and different
    2214        4545 :     if (current_value != GeneralFieldTransfer::OutOfMeshValue &&
    2215        7066 :         new_value != GeneralFieldTransfer::OutOfMeshValue &&
    2216        2521 :         !MooseUtils::absoluteFuzzyEqual(current_value, new_value))
    2217             :       // Conflict only occurs if the origin points are equidistant
    2218        2269 :       if (MooseUtils::absoluteFuzzyEqual(current_distance, new_distance))
    2219          57 :         return true;
    2220     2274448 :   return false;
    2221             : }

Generated by: LCOV version 1.14