LCOV - code coverage report
Current view: top level - src/transfers - MultiAppGeneralFieldNearestLocationTransfer.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 291 309 94.2 %
Date: 2025-07-17 01:28:37 Functions: 14 14 100.0 %
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 "MultiAppGeneralFieldNearestLocationTransfer.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "FEProblem.h"
      14             : #include "MooseMesh.h"
      15             : #include "MooseTypes.h"
      16             : #include "MooseVariableFE.h"
      17             : #include "SystemBase.h"
      18             : #include "Positions.h"
      19             : #include "MooseAppCoordTransform.h"
      20             : 
      21             : #include "libmesh/system.h"
      22             : 
      23             : using namespace libMesh;
      24             : 
      25             : registerMooseObject("MooseApp", MultiAppGeneralFieldNearestLocationTransfer);
      26             : registerMooseObjectRenamed("MooseApp",
      27             :                            MultiAppGeneralFieldNearestNodeTransfer,
      28             :                            "12/31/2024 24:00",
      29             :                            MultiAppGeneralFieldNearestLocationTransfer);
      30             : 
      31             : InputParameters
      32       35254 : MultiAppGeneralFieldNearestLocationTransfer::validParams()
      33             : {
      34       35254 :   InputParameters params = MultiAppGeneralFieldTransfer::validParams();
      35       35254 :   params.addClassDescription(
      36             :       "Transfers field data at the MultiApp position by finding the value at the nearest "
      37             :       "neighbor(s) in the origin application.");
      38      105762 :   params.addParam<unsigned int>("num_nearest_points",
      39       70508 :                                 1,
      40             :                                 "Number of nearest source (from) points will be chosen to "
      41             :                                 "construct a value for the target point. All points will be "
      42             :                                 "selected from the same origin mesh!");
      43             : 
      44             :   // Nearest node is historically more an extrapolation transfer
      45       35254 :   params.set<Real>("extrapolation_constant") = GeneralFieldTransfer::BetterOutOfMeshValue;
      46       35254 :   params.suppressParameter<Real>("extrapolation_constant");
      47             :   // We dont keep track of both point distance to app and to nearest node, so we cannot guarantee
      48             :   // that the nearest app (among the local apps, not globally) will hold the nearest location.
      49             :   // However, if the user knows this is true, we can use this heuristic to reduce the number of apps
      50             :   // that are requested to provide a candidate value. If the user is wrong, then the nearest
      51             :   // location is used, which can be from the non-nearest app.
      52       35254 :   params.renameParam("use_nearest_app", "assume_nearest_app_holds_nearest_location", "");
      53             : 
      54             :   // the default of node/centroid switching based on the variable is causing lots of mistakes and
      55             :   // bad results
      56             :   std::vector<MooseEnum> source_types = {
      57       70508 :       MooseEnum("nodes centroids variable_default", "variable_default")};
      58       35254 :   params.addParam<std::vector<MooseEnum>>(
      59             :       "source_type", source_types, "Where to get the source values from for each source variable");
      60             : 
      61             :   // choose whether to include data from multiple apps when performing nearest-position/
      62             :   // mesh-divisions based transfers
      63      105762 :   params.addParam<bool>("group_subapps",
      64       70508 :                         false,
      65             :                         "Whether to group source locations and values from all subapps "
      66             :                         "when considering a nearest-position algorithm");
      67             : 
      68       70508 :   return params;
      69      105762 : }
      70             : 
      71        3362 : MultiAppGeneralFieldNearestLocationTransfer::MultiAppGeneralFieldNearestLocationTransfer(
      72        3362 :     const InputParameters & parameters)
      73             :   : MultiAppGeneralFieldTransfer(parameters),
      74             :     SolutionInvalidInterface(this),
      75        3362 :     _num_nearest_points(getParam<unsigned int>("num_nearest_points")),
      76        6724 :     _group_subapps(getParam<bool>("group_subapps"))
      77             : {
      78        3362 :   if (_source_app_must_contain_point && _nearest_positions_obj)
      79           4 :     paramError("use_nearest_position",
      80             :                "We do not support using both nearest positions matching and checking if target "
      81             :                "points are within an app domain because the KDTrees for nearest-positions matching "
      82             :                "are (currently) built with data from multiple applications.");
      83        3578 :   if (_nearest_positions_obj &&
      84        3578 :       (isParamValid("from_mesh_divisions") || isParamValid("to_mesh_divisions")))
      85           0 :     paramError("use_nearest_position", "Cannot use nearest positions with mesh divisions");
      86             : 
      87             :   // Parameter checks on grouping subapp values
      88        3358 :   if (_group_subapps && _from_mesh_divisions.empty() && !_nearest_positions_obj)
      89           0 :     paramError(
      90             :         "group_subapps",
      91             :         "This option is only available for using mesh divisions or nearest positions regions");
      92        3434 :   else if (_group_subapps &&
      93          76 :            (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
      94          76 :             _from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX))
      95           0 :     paramError("group_subapps",
      96             :                "Cannot group subapps when considering nearest-location data as we would lose "
      97             :                "track of the division index of the source locations");
      98        3358 :   else if (_group_subapps && _use_nearest_app)
      99           0 :     paramError(
     100             :         "group_subapps",
     101             :         "When using the 'nearest child application' data, the source data (positions and values) "
     102             :         "are grouped on a per-application basis, so it cannot be agglomerated over all child "
     103             :         "applications.\nNote that the option to use nearest applications for source restrictions, "
     104             :         "but further split each child application's domain by regions closest to each position "
     105             :         "(here the the child application's centroid), which could be conceived when "
     106             :         "'group_subapps' = false, is also not available.");
     107        3358 : }
     108             : 
     109             : void
     110        3258 : MultiAppGeneralFieldNearestLocationTransfer::initialSetup()
     111             : {
     112        3258 :   MultiAppGeneralFieldTransfer::initialSetup();
     113             : 
     114             :   // Handle the source types ahead of time
     115        3226 :   const auto & source_types = getParam<std::vector<MooseEnum>>("source_type");
     116        3226 :   _source_is_nodes.resize(_from_var_names.size());
     117        3226 :   _use_zero_dof_for_value.resize(_from_var_names.size());
     118        3226 :   if (source_types.size() != _from_var_names.size())
     119           0 :     mooseError("Not enough source types specified for this number of variables. Source types must "
     120             :                "be specified for transfers with multiple variables");
     121        6494 :   for (const auto var_index : index_range(_from_var_names))
     122             :   {
     123             :     // Local app does not own any of the source problems
     124        3274 :     if (_from_problems.empty())
     125           3 :       break;
     126             : 
     127             :     // Get some info on the source variable
     128        3271 :     FEProblemBase & from_problem = *_from_problems[0];
     129             :     MooseVariableFieldBase & from_var =
     130        3271 :         from_problem.getVariable(0,
     131        3271 :                                  _from_var_names[var_index],
     132             :                                  Moose::VarKindType::VAR_ANY,
     133             :                                  Moose::VarFieldType::VAR_FIELD_ANY);
     134        3271 :     System & from_sys = from_var.sys().system();
     135        3271 :     const auto & fe_type = from_sys.variable_type(from_var.number());
     136             : 
     137             :     // Select where to get the origin values
     138        3271 :     if (source_types[var_index] == "nodes")
     139          48 :       _source_is_nodes[var_index] = true;
     140        3223 :     else if (source_types[var_index] == "centroids")
     141          48 :       _source_is_nodes[var_index] = false;
     142             :     else
     143             :     {
     144             :       // "Nodal" variables are continuous for sure at nodes
     145        3175 :       if (from_var.isNodal())
     146        2391 :         _source_is_nodes[var_index] = true;
     147             :       // for everyone else, we know they are continuous at centroids
     148             :       else
     149         784 :         _source_is_nodes[var_index] = false;
     150             :     }
     151             : 
     152             :     // Some variables can be sampled directly at their 0 dofs
     153             :     // - lagrange at nodes on a first order mesh
     154             :     // - anything constant and elemental obviously has the 0-dof value at the centroid (or
     155             :     // vertex-average). However, higher order elemental, even monomial, do not hold the centroid
     156             :     // value at dof index 0 For example: pyramid has dof 0 at the center of the base, prism has dof
     157             :     // 0 on an edge etc
     158        5710 :     if ((_source_is_nodes[var_index] && fe_type.family == LAGRANGE &&
     159        6746 :          !from_problem.mesh().hasSecondOrderElements()) ||
     160        4307 :         (!_source_is_nodes[var_index] && fe_type.order == CONSTANT))
     161        3019 :       _use_zero_dof_for_value[var_index] = true;
     162             :     else
     163         252 :       _use_zero_dof_for_value[var_index] = false;
     164             : 
     165             :     // Check with the source variable that it is not discontinuous at the source
     166        3271 :     if (_source_is_nodes[var_index])
     167        4878 :       if (from_var.getContinuity() == DISCONTINUOUS ||
     168        2439 :           from_var.getContinuity() == SIDE_DISCONTINUOUS)
     169           0 :         mooseError("Source variable cannot be sampled at nodes as it is discontinuous");
     170             : 
     171             :     // Local app does not own any of the target problems
     172        3271 :     if (_to_problems.empty())
     173           3 :       break;
     174             : 
     175             :     // Check with the target variable that we are not doing awful projections
     176             :     MooseVariableFieldBase & to_var =
     177        6536 :         _to_problems[0]->getVariable(0,
     178        3268 :                                      _to_var_names[var_index],
     179             :                                      Moose::VarKindType::VAR_ANY,
     180             :                                      Moose::VarFieldType::VAR_FIELD_ANY);
     181        3268 :     System & to_sys = to_var.sys().system();
     182        3268 :     const auto & to_fe_type = to_sys.variable_type(to_var.number());
     183        3268 :     if (_source_is_nodes[var_index])
     184             :     {
     185        2436 :       if (to_fe_type.order == CONSTANT)
     186         108 :         mooseWarning(
     187             :             "Transfer is projecting from nearest-nodes to centroids. This is likely causing "
     188             :             "floating point indetermination in the results because multiple nodes are 'nearest' to "
     189             :             "a centroid. Please consider using a ProjectionAux to build an elemental source "
     190             :             "variable (for example constant monomial) before transferring");
     191             :     }
     192         832 :     else if (to_var.isNodal())
     193          72 :       mooseWarning(
     194             :           "Transfer is projecting from nearest-centroids to nodes. This is likely causing "
     195             :           "floating point indetermination in the results because multiple centroids are "
     196             :           "'nearest' to a node. Please consider using a ProjectionAux to build a nodal source "
     197             :           "variable (for example linear Lagrange) before transferring");
     198             :   }
     199             : 
     200             :   // We need to improve the indexing if we are to allow this
     201        3226 :   if (!_from_mesh_divisions.empty())
     202         384 :     for (const auto mesh_div : _from_mesh_divisions)
     203         240 :       if (mesh_div->getNumDivisions() != _from_mesh_divisions[0]->getNumDivisions())
     204           0 :         paramError("from_mesh_division",
     205             :                    "This transfer has only been implemented with a uniform number of source mesh "
     206             :                    "divisions across all source applications");
     207        3226 : }
     208             : 
     209             : void
     210       51420 : MultiAppGeneralFieldNearestLocationTransfer::prepareEvaluationOfInterpValues(
     211             :     const unsigned int var_index)
     212             : {
     213       51420 :   _local_kdtrees.clear();
     214       51420 :   _local_points.clear();
     215       51420 :   _local_values.clear();
     216       51420 :   buildKDTrees(var_index);
     217       51420 : }
     218             : 
     219             : void
     220       51420 : MultiAppGeneralFieldNearestLocationTransfer::buildKDTrees(const unsigned int var_index)
     221             : {
     222       51420 :   computeNumSources();
     223       51420 :   const auto num_apps_per_tree = getNumAppsPerTree();
     224       51420 :   _local_kdtrees.resize(_num_sources);
     225       51420 :   _local_points.resize(_num_sources);
     226       51420 :   _local_values.resize(_num_sources);
     227       51420 :   unsigned int max_leaf_size = 0;
     228             : 
     229             :   // Construct a local KDTree for each source. A source can be a single app or multiple apps
     230             :   // combined (option for nearest-position / mesh-divisions)
     231      107048 :   for (const auto i_source : make_range(_num_sources))
     232             :   {
     233             :     // Nest a loop on apps in case multiple apps contribute to the same KD-Tree source
     234      111496 :     for (const auto app_i : make_range(num_apps_per_tree))
     235             :     {
     236             :       // Get the current app index
     237       55868 :       const auto i_from = getAppIndex(i_source, app_i);
     238             :       // Current position index, if using nearest positions (not used for use_nearest_app)
     239       55868 :       const auto i_pos = _group_subapps ? i_source : (i_source % getNumDivisions());
     240             : 
     241             :       // Get access to the variable and some variable information
     242       55868 :       FEProblemBase & from_problem = *_from_problems[i_from];
     243       55868 :       auto & from_mesh = from_problem.mesh(_displaced_source_mesh);
     244             :       MooseVariableFieldBase & from_var =
     245       55868 :           from_problem.getVariable(0,
     246       55868 :                                    _from_var_names[var_index],
     247             :                                    Moose::VarKindType::VAR_ANY,
     248             :                                    Moose::VarFieldType::VAR_FIELD_ANY);
     249       55868 :       System & from_sys = from_var.sys().system();
     250       55868 :       const unsigned int from_var_num = from_sys.variable_number(getFromVarName(var_index));
     251             : 
     252             :       // Build KDTree from nodes and nodal values
     253       55868 :       if (_source_is_nodes[var_index])
     254             :       {
     255     9119456 :         for (const auto & node : from_mesh.getMesh().local_node_ptr_range())
     256             :         {
     257     4533646 :           if (node->n_dofs(from_sys.number(), from_var_num) < 1)
     258      289704 :             continue;
     259             : 
     260     4412134 :           if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, node))
     261        5152 :             continue;
     262             : 
     263     4406982 :           if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, node))
     264       34544 :             continue;
     265             : 
     266             :           // Handle the various source mesh divisions behaviors
     267             :           // NOTE: This could be more efficient, as instead of rejecting points in the
     268             :           // wrong division, we could just be adding them to the tree for the right division
     269     4372438 :           if (!_from_mesh_divisions.empty())
     270             :           {
     271       87080 :             const auto tree_division_index = i_source % getNumDivisions();
     272       87080 :             const auto node_div_index = _from_mesh_divisions[i_from]->divisionIndex(*node);
     273             : 
     274             :             // Spatial restriction is always active
     275       87080 :             if (node_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
     276       56552 :               continue;
     277             :             // We fill one tree per division index for matching subapp index or division index. We
     278             :             // only accept source data from the division index
     279       30528 :             else if ((_from_mesh_division_behavior ==
     280        3328 :                           MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     281        3328 :                       _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     282        3328 :                       _from_mesh_division_behavior ==
     283       33856 :                           MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
     284             :                      tree_division_index != node_div_index)
     285       26880 :               continue;
     286             :           }
     287             : 
     288             :           // Transformed node is in the reference space, as is the _nearest_positions_obj
     289     4289006 :           const auto transformed_node = (*_from_transforms[getGlobalSourceAppIndex(i_from)])(*node);
     290             : 
     291             :           // Only add to the KDTree nodes that are closest to the 'position'
     292             :           // When querying values at a target point, the KDTree associated to the closest
     293             :           // position to the target point is queried
     294             :           // We do not need to check the positions when using nearest app as we will assume
     295             :           // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
     296             :           // than to any other
     297     4359598 :           if (!_use_nearest_app && _nearest_positions_obj &&
     298       70592 :               !closestToPosition(i_pos, transformed_node))
     299       45064 :             continue;
     300             : 
     301     4243942 :           _local_points[i_source].push_back(transformed_node);
     302     4243942 :           const auto dof = node->dof_number(from_sys.number(), from_var_num, 0);
     303     4243942 :           _local_values[i_source].push_back((*from_sys.solution)(dof));
     304     4243942 :           if (!_use_zero_dof_for_value[var_index])
     305       53920 :             flagInvalidSolution(
     306             :                 "Nearest-location is not implemented for this source variable type on "
     307             :                 "this mesh. Returning value at dof 0");
     308       52164 :         }
     309             :       }
     310             :       // Build KDTree from centroids and value at centroids
     311             :       else
     312             :       {
     313        7408 :         for (auto & elem : as_range(from_mesh.getMesh().local_elements_begin(),
     314      376616 :                                     from_mesh.getMesh().local_elements_end()))
     315             :         {
     316      182752 :           if (elem->n_dofs(from_sys.number(), from_var_num) < 1)
     317       99608 :             continue;
     318             : 
     319      180736 :           if (!_from_blocks.empty() && !inBlocks(_from_blocks, elem))
     320        3560 :             continue;
     321             : 
     322      177176 :           if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, elem))
     323        2048 :             continue;
     324             : 
     325             :           // Handle the various source mesh divisions behaviors
     326      175128 :           if (!_from_mesh_divisions.empty())
     327             :           {
     328       67424 :             const auto tree_division_index = i_source % getNumDivisions();
     329       67424 :             const auto elem_div_index = _from_mesh_divisions[i_from]->divisionIndex(*elem);
     330             : 
     331             :             // Spatial restriction is always active
     332       67424 :             if (elem_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
     333       39744 :               continue;
     334             :             // We fill one tree per division index for matching subapp index or division index. We
     335             :             // only accept source data from the division index
     336       27680 :             else if ((_from_mesh_division_behavior ==
     337        2880 :                           MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     338        2880 :                       _from_mesh_division_behavior ==
     339       30560 :                           MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
     340             :                      tree_division_index != elem_div_index)
     341       24240 :               continue;
     342             :           }
     343             : 
     344      111144 :           const auto vertex_average = elem->vertex_average();
     345             :           // Transformed element vertex average is in the reference space, as is the
     346             :           // _nearest_positions_obj
     347             :           const auto transformed_vertex_average =
     348      111144 :               (*_from_transforms[getGlobalSourceAppIndex(i_from)])(vertex_average);
     349             : 
     350             :           // We do not need to check the positions when using nearest app as we will assume
     351             :           // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
     352      155944 :           if (!_use_nearest_app && _nearest_positions_obj &&
     353       44800 :               !closestToPosition(i_pos, transformed_vertex_average))
     354       28000 :             continue;
     355             : 
     356       83144 :           _local_points[i_source].push_back(transformed_vertex_average);
     357       83144 :           if (_use_zero_dof_for_value[var_index])
     358             :           {
     359       79704 :             auto dof = elem->dof_number(from_sys.number(), from_var_num, 0);
     360       79704 :             _local_values[i_source].push_back((*from_sys.solution)(dof));
     361             :           }
     362             :           else
     363        3440 :             _local_values[i_source].push_back(
     364        3440 :                 from_sys.point_value(from_var_num, vertex_average, elem));
     365        3704 :         }
     366             :       }
     367       55868 :       max_leaf_size = std::max(max_leaf_size, from_mesh.getMaxLeafSize());
     368             :     }
     369             : 
     370             :     // Make a KDTree from the accumulated points data
     371             :     std::shared_ptr<KDTree> _kd_tree =
     372       55628 :         std::make_shared<KDTree>(_local_points[i_source], max_leaf_size);
     373       55628 :     _local_kdtrees[i_source] = _kd_tree;
     374       55628 :   }
     375       51420 : }
     376             : 
     377             : void
     378       79120 : MultiAppGeneralFieldNearestLocationTransfer::evaluateInterpValues(
     379             :     const std::vector<std::pair<Point, unsigned int>> & incoming_points,
     380             :     std::vector<std::pair<Real, Real>> & outgoing_vals)
     381             : {
     382       79120 :   evaluateInterpValuesNearestNode(incoming_points, outgoing_vals);
     383       79120 : }
     384             : 
     385             : void
     386       79120 : MultiAppGeneralFieldNearestLocationTransfer::evaluateInterpValuesNearestNode(
     387             :     const std::vector<std::pair<Point, unsigned int>> & incoming_points,
     388             :     std::vector<std::pair<Real, Real>> & outgoing_vals)
     389             : {
     390       79120 :   dof_id_type i_pt = 0;
     391     6198407 :   for (const auto & [pt, mesh_div] : incoming_points)
     392             :   {
     393             :     // Reset distance
     394     6119287 :     outgoing_vals[i_pt].second = std::numeric_limits<Real>::max();
     395     6119287 :     bool point_found = false;
     396             : 
     397             :     // Loop on all sources
     398    12553132 :     for (const auto i_from : make_range(_num_sources))
     399             :     {
     400             :       // Examine all restrictions for the point. This source (KDTree+values) could be ruled out
     401     6433845 :       if (!checkRestrictionsForSource(pt, mesh_div, i_from))
     402      206880 :         continue;
     403             : 
     404             :       // TODO: Pre-allocate these two work arrays. They will be regularly resized by the
     405             :       // searches
     406     6226965 :       std::vector<std::size_t> return_index(_num_nearest_points);
     407     6226965 :       std::vector<Real> return_dist_sqr(_num_nearest_points);
     408             : 
     409             :       // KD Tree can be empty if no points are within block/boundary/bounding box restrictions
     410     6226965 :       if (_local_kdtrees[i_from]->numberCandidatePoints())
     411             :       {
     412     6213028 :         point_found = true;
     413             :         // Note that we do not need to use the transformed_pt (in the source app frame)
     414             :         // because the KDTree has been created in the reference frame
     415     6213028 :         _local_kdtrees[i_from]->neighborSearch(
     416             :             pt, _num_nearest_points, return_index, return_dist_sqr);
     417     6213028 :         Real val_sum = 0, dist_sum = 0;
     418    12426056 :         for (const auto index : return_index)
     419             :         {
     420     6213028 :           val_sum += _local_values[i_from][index];
     421     6213028 :           dist_sum += (_local_points[i_from][index] - pt).norm();
     422             :         }
     423             : 
     424             :         // If the new value found is closer than for other sources, use it
     425     6213028 :         const auto new_distance = dist_sum / return_dist_sqr.size();
     426     6213028 :         if (new_distance < outgoing_vals[i_pt].second)
     427     6149277 :           outgoing_vals[i_pt] = {val_sum / return_index.size(), new_distance};
     428             :       }
     429     6226965 :     }
     430             : 
     431             :     // none of the source problem meshes were within the restrictions set
     432     6119287 :     if (!point_found)
     433        5143 :       outgoing_vals[i_pt] = {GeneralFieldTransfer::BetterOutOfMeshValue,
     434       10286 :                              GeneralFieldTransfer::BetterOutOfMeshValue};
     435     6114144 :     else if (_search_value_conflicts)
     436             :     {
     437             :       // Local source meshes conflicts can happen two ways:
     438             :       // - two (or more) problems' nearest nodes are equidistant to the target point
     439             :       // - within a problem, the num_nearest_points'th closest node is as close to the target
     440             :       //   point as the num_nearest_points + 1'th closest node
     441       14084 :       unsigned int num_equidistant_problems = 0;
     442             : 
     443       45776 :       for (const auto i_from : make_range(_num_sources))
     444             :       {
     445             :         // Examine all restrictions for the point. This source (KDTree+values) could be ruled out
     446       31692 :         if (!checkRestrictionsForSource(pt, mesh_div, i_from))
     447       15660 :           continue;
     448             : 
     449             :         // TODO: Pre-allocate these two work arrays. They will be regularly resized by the searches
     450       16032 :         std::vector<std::size_t> return_index(_num_nearest_points + 1);
     451       16032 :         std::vector<Real> return_dist_sqr(_num_nearest_points + 1);
     452             : 
     453             :         // NOTE: app_index is not valid if _group_subapps = true
     454       16032 :         const auto app_index = i_from / getNumDivisions();
     455       16032 :         const auto num_search = _num_nearest_points + 1;
     456             : 
     457       16032 :         if (_local_kdtrees[i_from]->numberCandidatePoints())
     458             :         {
     459       14568 :           _local_kdtrees[i_from]->neighborSearch(pt, num_search, return_index, return_dist_sqr);
     460       14568 :           auto num_found = return_dist_sqr.size();
     461             : 
     462             :           // Local coordinates only accessible when not using nearest-position
     463             :           // as we did not keep the index of the source app, only the position index
     464       14568 :           const Point local_pt = getPointInLocalSourceFrame(app_index, pt);
     465             : 
     466       19320 :           if (!_nearest_positions_obj &&
     467        4752 :               _from_transforms[getGlobalSourceAppIndex(app_index)]->hasCoordinateSystemTypeChange())
     468           0 :             if (!_skip_coordinate_collapsing)
     469           0 :               mooseInfo("Search value conflict cannot find the origin point due to the "
     470             :                         "non-uniqueness of the coordinate collapsing reverse mapping");
     471             : 
     472             :           // Look for too many equidistant nodes within a problem. First zip then sort by distance
     473       14568 :           std::vector<std::pair<Real, std::size_t>> zipped_nearest_points;
     474       43704 :           for (const auto i : make_range(num_found))
     475       29136 :             zipped_nearest_points.push_back(std::make_pair(return_dist_sqr[i], return_index[i]));
     476       14568 :           std::sort(zipped_nearest_points.begin(), zipped_nearest_points.end());
     477             : 
     478             :           // If two furthest are equally far from target point, then we have an indetermination in
     479             :           // what is sent in this communication round from this process. However, it may not
     480             :           // materialize to an actual conflict, as values sent from another process for the
     481             :           // desired target point could be closer (nearest). There is no way to know at this point
     482             :           // in the communication that a closer value exists somewhere else
     483       29136 :           if (num_found > 1 && num_found == num_search &&
     484       14568 :               MooseUtils::absoluteFuzzyEqual(zipped_nearest_points[num_found - 1].first,
     485       14568 :                                              zipped_nearest_points[num_found - 2].first))
     486             :           {
     487         352 :             if (_nearest_positions_obj)
     488          16 :               registerConflict(app_index, 0, pt, outgoing_vals[i_pt].second, true);
     489             :             else
     490         336 :               registerConflict(app_index, 0, local_pt, outgoing_vals[i_pt].second, true);
     491             :           }
     492             : 
     493             :           // Recompute the distance for this problem. If it matches the cached value more than
     494             :           // once it means multiple problems provide equidistant values for this point
     495       14568 :           Real dist_sum = 0;
     496       29136 :           for (const auto i : make_range(num_search - 1))
     497             :           {
     498       14568 :             auto index = zipped_nearest_points[i].second;
     499       14568 :             dist_sum += (_local_points[i_from][index] - pt).norm();
     500             :           }
     501             : 
     502             :           // Compare to the selected value found after looking at all the problems
     503       14568 :           if (MooseUtils::absoluteFuzzyEqual(dist_sum / return_dist_sqr.size(),
     504       14568 :                                              outgoing_vals[i_pt].second))
     505             :           {
     506          88 :             num_equidistant_problems++;
     507          88 :             if (num_equidistant_problems > 1)
     508             :             {
     509           0 :               if (_nearest_positions_obj)
     510           0 :                 registerConflict(app_index, 0, pt, outgoing_vals[i_pt].second, true);
     511             :               else
     512           0 :                 registerConflict(app_index, 0, local_pt, outgoing_vals[i_pt].second, true);
     513             :             }
     514             :           }
     515       14568 :         }
     516       16032 :       }
     517             :     }
     518             : 
     519             :     // Move to next point
     520     6119287 :     i_pt++;
     521             :   }
     522       79120 : }
     523             : 
     524             : bool
     525       16000 : MultiAppGeneralFieldNearestLocationTransfer::inBlocks(const std::set<SubdomainID> & blocks,
     526             :                                                       const MooseMesh & mesh,
     527             :                                                       const Elem * elem) const
     528             : {
     529             :   // We need to override the definition of block restriction for an element
     530             :   // because we have to consider whether each node of an element is adjacent to a block
     531       44704 :   for (const auto & i_node : make_range(elem->n_nodes()))
     532             :   {
     533       40672 :     const auto & node = elem->node_ptr(i_node);
     534       40672 :     const auto & node_blocks = mesh.getNodeBlockIds(*node);
     535       40672 :     std::set<SubdomainID> u;
     536       40672 :     std::set_intersection(blocks.begin(),
     537             :                           blocks.end(),
     538             :                           node_blocks.begin(),
     539             :                           node_blocks.end(),
     540             :                           std::inserter(u, u.begin()));
     541       40672 :     if (!u.empty())
     542       11968 :       return true;
     543       40672 :   }
     544        4032 :   return false;
     545             : }
     546             : 
     547             : void
     548       51420 : MultiAppGeneralFieldNearestLocationTransfer::computeNumSources()
     549             : {
     550             :   // Number of source = number of KDTrees.
     551             :   // Using mesh divisions or nearest-positions, for every app we use 1 tree per division
     552      102708 :   if (!_from_mesh_divisions.empty() ||
     553       51288 :       (!_use_nearest_app && _nearest_positions_obj && !_group_subapps))
     554         418 :     _num_sources = _from_problems.size() * getNumDivisions();
     555             :   // If we group apps, then we only use one tree per division (nearest-position region)
     556       51002 :   else if (_nearest_positions_obj && _group_subapps)
     557         290 :     _num_sources = _nearest_positions_obj->getNumPositions(_fe_problem.getCurrentExecuteOnFlag() ==
     558             :                                                            EXEC_INITIAL);
     559             :   // Regular case: 1 KDTree per app
     560             :   // Also if use_nearest_app = true, the number of problems is better than the number of positions,
     561             :   // because some of the positions are positions of child applications that are not local
     562             :   else
     563       50712 :     _num_sources = _from_problems.size();
     564       51420 : }
     565             : 
     566             : unsigned int
     567     6449171 : MultiAppGeneralFieldNearestLocationTransfer::getAppIndex(
     568             :     unsigned int kdtree_index, unsigned int nested_loop_on_app_index) const
     569             : {
     570             :   // Each app is mapped to a single KD Tree
     571     6449171 :   if (_use_nearest_app)
     572       16092 :     return kdtree_index;
     573             :   // We are looping over all the apps that are grouped together
     574     6433079 :   else if (_group_subapps)
     575       25315 :     return nested_loop_on_app_index;
     576             :   // There are num_divisions trees for each app, inner ordering is divisions, so dividing by the
     577             :   // number of divisions gets us the index of the application
     578             :   else
     579     6407764 :     return kdtree_index / getNumDivisions();
     580             : }
     581             : 
     582             : unsigned int
     583       51420 : MultiAppGeneralFieldNearestLocationTransfer::getNumAppsPerTree() const
     584             : {
     585       51420 :   if (_use_nearest_app)
     586         286 :     return 1;
     587       51134 :   else if (_group_subapps)
     588         290 :     return _from_meshes.size();
     589             :   else
     590       50844 :     return 1;
     591             : }
     592             : 
     593             : unsigned int
     594    13275189 : MultiAppGeneralFieldNearestLocationTransfer::getNumDivisions() const
     595             : {
     596             :   // This is not used currently, but conceptually it is better to only divide the domain with the
     597             :   // local of local applications rather than the global number of positions (# global applications
     598             :   // here)
     599    13275189 :   if (_use_nearest_app)
     600       18288 :     return _from_meshes.size();
     601             :   // Each nearest-position region is a division
     602    13256901 :   else if (_nearest_positions_obj && !_group_subapps)
     603      110522 :     return _nearest_positions_obj->getNumPositions(_fe_problem.getCurrentExecuteOnFlag() ==
     604      110522 :                                                    EXEC_INITIAL);
     605             :   // Assume all mesh divisions (on each sub-app) has the same number of divisions. This is checked
     606    13146379 :   else if (!_from_mesh_divisions.empty())
     607      747124 :     return _from_mesh_divisions[0]->getNumDivisions();
     608             :   // Grouping subapps or no special mode, we do not subdivide
     609             :   else
     610    12399255 :     return 1;
     611             : }
     612             : 
     613             : Point
     614       14568 : MultiAppGeneralFieldNearestLocationTransfer::getPointInLocalSourceFrame(unsigned int i_from,
     615             :                                                                         const Point & pt) const
     616             : {
     617             : 
     618       19320 :   if (!_nearest_positions_obj &&
     619        4752 :       (!_from_transforms[getGlobalSourceAppIndex(i_from)]->hasCoordinateSystemTypeChange() ||
     620           0 :        _skip_coordinate_collapsing))
     621        4752 :     return _from_transforms[getGlobalSourceAppIndex(i_from)]->mapBack(pt);
     622        9816 :   else if (!_nearest_positions_obj || !_group_subapps)
     623        4392 :     return pt - _from_positions[i_from];
     624             :   else
     625        5424 :     return pt;
     626             : }
     627             : 
     628             : bool
     629     6465537 : MultiAppGeneralFieldNearestLocationTransfer::checkRestrictionsForSource(
     630             :     const Point & pt, const unsigned int mesh_div, const unsigned int i_from) const
     631             : {
     632             :   // Only use the KDTree from the closest position if in "nearest-position" mode
     633     6465537 :   if (_nearest_positions_obj)
     634             :   {
     635             :     // See computeNumSources for the number of sources. i_from is the index in the source loop
     636             :     // i_from is local if looping on _from_problems as sources, positions are indexed globally
     637             :     // i_from is already indexing in positions if using group_subapps
     638      138581 :     auto position_index = i_from; // if _group_subapps
     639      138581 :     if (_use_nearest_app)
     640       26297 :       position_index = getGlobalSourceAppIndex(i_from);
     641      112284 :     else if (!_group_subapps)
     642       52456 :       position_index = i_from % getNumDivisions();
     643             : 
     644             :     // NOTE: if two positions are equi-distant to the point, this will chose one
     645             :     // This problem is detected if using search_value_conflicts in this call
     646      138581 :     if (!closestToPosition(position_index, pt))
     647       72234 :       return false;
     648             :   }
     649             : 
     650             :   // Application index depends on which source/grouping mode we are using
     651     6393303 :   const unsigned int app_index = getAppIndex(i_from, i_from / getNumDivisions());
     652             : 
     653             :   // Check mesh restriction before anything
     654     6393303 :   if (_source_app_must_contain_point)
     655             :   {
     656             :     // We have to be careful that getPointInLocalSourceFrame returns in the reference frame
     657           0 :     if (_nearest_positions_obj)
     658           0 :       mooseError("Nearest-positions + source_app_must_contain_point not implemented");
     659             :     // Transform the point to place it in the local coordinate system
     660           0 :     const auto local_pt = getPointInLocalSourceFrame(app_index, pt);
     661           0 :     if (!inMesh(_from_point_locators[app_index].get(), local_pt))
     662           0 :       return false;
     663             :   }
     664             : 
     665             :   // Check the mesh division. We have handled the restriction of the source locations when
     666             :   // building the nearest-neighbor trees. We only need to check that we meet the required
     667             :   // source division index.
     668     6393303 :   if (!_from_mesh_divisions.empty())
     669             :   {
     670             :     mooseAssert(mesh_div != MooseMeshDivision::INVALID_DIVISION_INDEX,
     671             :                 "We should not be receiving point requests with an invalid "
     672             :                 "source mesh division index");
     673      195716 :     const unsigned int kd_div_index = i_from % getNumDivisions();
     674             : 
     675             :     // If matching source mesh divisions to target apps, we check that the index of the target
     676             :     // application, which was passed in the point request, is equal to the current mesh division
     677      195716 :     if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
     678             :         mesh_div != kd_div_index)
     679       16104 :       return false;
     680             :     // If matching source mesh divisions to target mesh divisions, we check that the index of the
     681             :     // target mesh division, which was passed in the point request, is equal to the current mesh
     682             :     // division
     683      179612 :     else if ((_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     684      179612 :               _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX) &&
     685             :              mesh_div != kd_div_index)
     686      133152 :       return false;
     687             :   }
     688             : 
     689             :   // If matching target apps to source mesh divisions, we check that the global index of the
     690             :   // application is equal to the target mesh division index, which was passed in the point request
     691     6245497 :   if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
     692        1450 :       mesh_div != getGlobalSourceAppIndex(app_index))
     693        1050 :     return false;
     694             : 
     695     6242997 :   return true;
     696             : }

Generated by: LCOV version 1.14