LCOV - code coverage report
Current view: top level - src/transfers - MultiAppGeneralFieldNearestLocationTransfer.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #31653 (2d163b) with base 0cc44f Lines: 291 309 94.2 %
Date: 2025-11-04 20:38:02 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       37332 : MultiAppGeneralFieldNearestLocationTransfer::validParams()
      33             : {
      34       37332 :   InputParameters params = MultiAppGeneralFieldTransfer::validParams();
      35       74664 :   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       74664 :   params.addParam<unsigned int>("num_nearest_points",
      39       74664 :                                 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      111996 :   params.set<Real>("extrapolation_constant") = GeneralFieldTransfer::BetterOutOfMeshValue;
      46       74664 :   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      186660 :   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      111996 :       MooseEnum("nodes centroids variable_default", "variable_default")};
      58      149328 :   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       74664 :   params.addParam<bool>("group_subapps",
      64       74664 :                         false,
      65             :                         "Whether to group source locations and values from all subapps "
      66             :                         "when considering a nearest-position algorithm");
      67             : 
      68       74664 :   return params;
      69      186660 : }
      70             : 
      71        3675 : MultiAppGeneralFieldNearestLocationTransfer::MultiAppGeneralFieldNearestLocationTransfer(
      72        3675 :     const InputParameters & parameters)
      73             :   : MultiAppGeneralFieldTransfer(parameters),
      74        7350 :     _num_nearest_points(getParam<unsigned int>("num_nearest_points")),
      75       14700 :     _group_subapps(getParam<bool>("group_subapps"))
      76             : {
      77        3675 :   if (_source_app_must_contain_point && _nearest_positions_obj)
      78           8 :     paramError("use_nearest_position",
      79             :                "We do not support using both nearest positions matching and checking if target "
      80             :                "points are within an app domain because the KDTrees for nearest-positions matching "
      81             :                "are (currently) built with data from multiple applications.");
      82        3909 :   if (_nearest_positions_obj &&
      83        4861 :       (isParamValid("from_mesh_divisions") || isParamValid("to_mesh_divisions")))
      84           0 :     paramError("use_nearest_position", "Cannot use nearest positions with mesh divisions");
      85             : 
      86             :   // Parameter checks on grouping subapp values
      87        3671 :   if (_group_subapps && _from_mesh_divisions.empty() && !_nearest_positions_obj)
      88           0 :     paramError(
      89             :         "group_subapps",
      90             :         "This option is only available for using mesh divisions or nearest positions regions");
      91        3753 :   else if (_group_subapps &&
      92          82 :            (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
      93          82 :             _from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX))
      94           0 :     paramError("group_subapps",
      95             :                "Cannot group subapps when considering nearest-location data as we would lose "
      96             :                "track of the division index of the source locations");
      97        3671 :   else if (_group_subapps && _use_nearest_app)
      98           0 :     paramError(
      99             :         "group_subapps",
     100             :         "When using the 'nearest child application' data, the source data (positions and values) "
     101             :         "are grouped on a per-application basis, so it cannot be agglomerated over all child "
     102             :         "applications.\nNote that the option to use nearest applications for source restrictions, "
     103             :         "but further split each child application's domain by regions closest to each position "
     104             :         "(here the the child application's centroid), which could be conceived when "
     105             :         "'group_subapps' = false, is also not available.");
     106        3671 : }
     107             : 
     108             : void
     109        3523 : MultiAppGeneralFieldNearestLocationTransfer::initialSetup()
     110             : {
     111        3523 :   MultiAppGeneralFieldTransfer::initialSetup();
     112             : 
     113             :   // Handle the source types ahead of time
     114        6950 :   const auto & source_types = getParam<std::vector<MooseEnum>>("source_type");
     115        3475 :   _source_is_nodes.resize(_from_var_names.size());
     116        3475 :   _use_zero_dof_for_value.resize(_from_var_names.size());
     117        3475 :   if (source_types.size() != _from_var_names.size())
     118           0 :     mooseError("Not enough source types specified for this number of variables. Source types must "
     119             :                "be specified for transfers with multiple variables");
     120        6996 :   for (const auto var_index : index_range(_from_var_names))
     121             :   {
     122             :     // Local app does not own any of the source problems
     123        3527 :     if (_from_problems.empty())
     124           3 :       break;
     125             : 
     126             :     // Get some info on the source variable
     127        3524 :     FEProblemBase & from_problem = *_from_problems[0];
     128             :     MooseVariableFieldBase & from_var =
     129        3524 :         from_problem.getVariable(0,
     130        3524 :                                  _from_var_names[var_index],
     131             :                                  Moose::VarKindType::VAR_ANY,
     132             :                                  Moose::VarFieldType::VAR_FIELD_ANY);
     133        3524 :     System & from_sys = from_var.sys().system();
     134        3524 :     const auto & fe_type = from_sys.variable_type(from_var.number());
     135             : 
     136             :     // Select where to get the origin values
     137        3524 :     if (source_types[var_index] == "nodes")
     138          52 :       _source_is_nodes[var_index] = true;
     139        3472 :     else if (source_types[var_index] == "centroids")
     140          52 :       _source_is_nodes[var_index] = false;
     141             :     else
     142             :     {
     143             :       // "Nodal" variables are continuous for sure at nodes
     144        3420 :       if (from_var.isNodal())
     145        2572 :         _source_is_nodes[var_index] = true;
     146             :       // for everyone else, we know they are continuous at centroids
     147             :       else
     148         848 :         _source_is_nodes[var_index] = false;
     149             :     }
     150             : 
     151             :     // Some variables can be sampled directly at their 0 dofs
     152             :     // - lagrange at nodes on a first order mesh
     153             :     // - anything constant and elemental obviously has the 0-dof value at the centroid (or
     154             :     // vertex-average). However, higher order elemental, even monomial, do not hold the centroid
     155             :     // value at dof index 0 For example: pyramid has dof 0 at the center of the base, prism has dof
     156             :     // 0 on an edge etc
     157        6148 :     if ((_source_is_nodes[var_index] && fe_type.family == LAGRANGE &&
     158        7269 :          !from_problem.mesh().hasSecondOrderElements()) ||
     159        4645 :         (!_source_is_nodes[var_index] && fe_type.order == CONSTANT))
     160        3251 :       _use_zero_dof_for_value[var_index] = true;
     161             :     else
     162         273 :       _use_zero_dof_for_value[var_index] = false;
     163             : 
     164             :     // Check with the source variable that it is not discontinuous at the source
     165        3524 :     if (_source_is_nodes[var_index])
     166        5248 :       if (from_var.getContinuity() == DISCONTINUOUS ||
     167        2624 :           from_var.getContinuity() == SIDE_DISCONTINUOUS)
     168           0 :         mooseError("Source variable cannot be sampled at nodes as it is discontinuous");
     169             : 
     170             :     // Local app does not own any of the target problems
     171        3524 :     if (_to_problems.empty())
     172           3 :       break;
     173             : 
     174             :     // Check with the target variable that we are not doing awful projections
     175             :     MooseVariableFieldBase & to_var =
     176        7042 :         _to_problems[0]->getVariable(0,
     177        3521 :                                      _to_var_names[var_index],
     178             :                                      Moose::VarKindType::VAR_ANY,
     179             :                                      Moose::VarFieldType::VAR_FIELD_ANY);
     180        3521 :     System & to_sys = to_var.sys().system();
     181        3521 :     const auto & to_fe_type = to_sys.variable_type(to_var.number());
     182        3521 :     if (_source_is_nodes[var_index])
     183             :     {
     184        2621 :       if (to_fe_type.order == CONSTANT)
     185         117 :         mooseWarning(
     186             :             "Transfer is projecting from nearest-nodes to centroids. This is likely causing "
     187             :             "floating point indetermination in the results because multiple nodes are 'nearest' to "
     188             :             "a centroid. Please consider using a ProjectionAux to build an elemental source "
     189             :             "variable (for example constant monomial) before transferring");
     190             :     }
     191         900 :     else if (to_var.isNodal())
     192          78 :       mooseWarning(
     193             :           "Transfer is projecting from nearest-centroids to nodes. This is likely causing "
     194             :           "floating point indetermination in the results because multiple centroids are "
     195             :           "'nearest' to a node. Please consider using a ProjectionAux to build a nodal source "
     196             :           "variable (for example linear Lagrange) before transferring");
     197             :   }
     198             : 
     199             :   // We need to improve the indexing if we are to allow this
     200        3475 :   if (!_from_mesh_divisions.empty())
     201         420 :     for (const auto mesh_div : _from_mesh_divisions)
     202         264 :       if (mesh_div->getNumDivisions() != _from_mesh_divisions[0]->getNumDivisions())
     203           0 :         paramError("from_mesh_division",
     204             :                    "This transfer has only been implemented with a uniform number of source mesh "
     205             :                    "divisions across all source applications");
     206        3475 : }
     207             : 
     208             : void
     209       52520 : MultiAppGeneralFieldNearestLocationTransfer::prepareEvaluationOfInterpValues(
     210             :     const unsigned int var_index)
     211             : {
     212       52520 :   _local_kdtrees.clear();
     213       52520 :   _local_points.clear();
     214       52520 :   _local_values.clear();
     215       52520 :   buildKDTrees(var_index);
     216       52520 : }
     217             : 
     218             : void
     219       52520 : MultiAppGeneralFieldNearestLocationTransfer::buildKDTrees(const unsigned int var_index)
     220             : {
     221       52520 :   computeNumSources();
     222       52520 :   const auto num_apps_per_tree = getNumAppsPerTree();
     223       52520 :   _local_kdtrees.resize(_num_sources);
     224       52520 :   _local_points.resize(_num_sources);
     225       52520 :   _local_values.resize(_num_sources);
     226       52520 :   unsigned int max_leaf_size = 0;
     227             : 
     228             :   // Construct a local KDTree for each source. A source can be a single app or multiple apps
     229             :   // combined (option for nearest-position / mesh-divisions)
     230      108967 :   for (const auto i_source : make_range(_num_sources))
     231             :   {
     232             :     // Nest a loop on apps in case multiple apps contribute to the same KD-Tree source
     233      112942 :     for (const auto app_i : make_range(num_apps_per_tree))
     234             :     {
     235             :       // Get the current app index
     236       56495 :       const auto i_from = getAppIndex(i_source, app_i);
     237             :       // Current position index, if using nearest positions (not used for use_nearest_app)
     238       56495 :       const auto i_pos = _group_subapps ? i_source : (i_source % getNumDivisions());
     239             : 
     240             :       // Get access to the variable and some variable information
     241       56495 :       FEProblemBase & from_problem = *_from_problems[i_from];
     242       56495 :       auto & from_mesh = from_problem.mesh(_displaced_source_mesh);
     243             :       MooseVariableFieldBase & from_var =
     244       56495 :           from_problem.getVariable(0,
     245       56495 :                                    _from_var_names[var_index],
     246             :                                    Moose::VarKindType::VAR_ANY,
     247             :                                    Moose::VarFieldType::VAR_FIELD_ANY);
     248       56495 :       System & from_sys = from_var.sys().system();
     249       56495 :       const unsigned int from_var_num = from_sys.variable_number(getFromVarName(var_index));
     250             : 
     251             :       // Build KDTree from nodes and nodal values
     252       56495 :       if (_source_is_nodes[var_index])
     253             :       {
     254     4953021 :         for (const auto & node : from_mesh.getMesh().local_node_ptr_range())
     255             :         {
     256     4899277 :           if (node->n_dofs(from_sys.number(), from_var_num) < 1)
     257      310284 :             continue;
     258             : 
     259     4764126 :           if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, node))
     260        5796 :             continue;
     261             : 
     262     4758330 :           if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, node))
     263       38960 :             continue;
     264             : 
     265             :           // Handle the various source mesh divisions behaviors
     266             :           // NOTE: This could be more efficient, as instead of rejecting points in the
     267             :           // wrong division, we could just be adding them to the tree for the right division
     268     4719370 :           if (!_from_mesh_divisions.empty())
     269             :           {
     270       97965 :             const auto tree_division_index = i_source % getNumDivisions();
     271       97965 :             const auto node_div_index = _from_mesh_divisions[i_from]->divisionIndex(*node);
     272             : 
     273             :             // Spatial restriction is always active
     274       97965 :             if (node_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
     275       63621 :               continue;
     276             :             // We fill one tree per division index for matching subapp index or division index. We
     277             :             // only accept source data from the division index
     278       34344 :             else if ((_from_mesh_division_behavior ==
     279        3744 :                           MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     280        3744 :                       _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     281        3744 :                       _from_mesh_division_behavior ==
     282       38088 :                           MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
     283             :                      tree_division_index != node_div_index)
     284       30240 :               continue;
     285             :           }
     286             : 
     287             :           // Transformed node is in the reference space, as is the _nearest_positions_obj
     288     4625509 :           const auto transformed_node = (*_from_transforms[getGlobalSourceAppIndex(i_from)])(*node);
     289             : 
     290             :           // Only add to the KDTree nodes that are closest to the 'position'
     291             :           // When querying values at a target point, the KDTree associated to the closest
     292             :           // position to the target point is queried
     293             :           // We do not need to check the positions when using nearest app as we will assume
     294             :           // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
     295             :           // than to any other
     296     4677377 :           if (!_use_nearest_app && _nearest_positions_obj &&
     297       51868 :               !closestToPosition(i_pos, transformed_node))
     298       36516 :             continue;
     299             : 
     300     4588993 :           _local_points[i_source].push_back(transformed_node);
     301     4588993 :           const auto dof = node->dof_number(from_sys.number(), from_var_num, 0);
     302     4588993 :           _local_values[i_source].push_back((*from_sys.solution)(dof));
     303     4588993 :           if (!_use_zero_dof_for_value[var_index])
     304       61363 :             flagInvalidSolution(
     305             :                 "Nearest-location is not implemented for this source variable type on "
     306             :                 "this mesh. Returning value at dof 0");
     307       53744 :         }
     308             :       }
     309             :       // Build KDTree from centroids and value at centroids
     310             :       else
     311             :       {
     312        5502 :         for (auto & elem : as_range(from_mesh.getMesh().local_elements_begin(),
     313      185829 :                                     from_mesh.getMesh().local_elements_end()))
     314             :         {
     315      177576 :           if (elem->n_dofs(from_sys.number(), from_var_num) < 1)
     316      101169 :             continue;
     317             : 
     318      177198 :           if (!_from_blocks.empty() && !inBlocks(_from_blocks, elem))
     319        4005 :             continue;
     320             : 
     321      173193 :           if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, elem))
     322        2304 :             continue;
     323             : 
     324             :           // Handle the various source mesh divisions behaviors
     325      170889 :           if (!_from_mesh_divisions.empty())
     326             :           {
     327       75852 :             const auto tree_division_index = i_source % getNumDivisions();
     328       75852 :             const auto elem_div_index = _from_mesh_divisions[i_from]->divisionIndex(*elem);
     329             : 
     330             :             // Spatial restriction is always active
     331       75852 :             if (elem_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
     332       44712 :               continue;
     333             :             // We fill one tree per division index for matching subapp index or division index. We
     334             :             // only accept source data from the division index
     335       31140 :             else if ((_from_mesh_division_behavior ==
     336        3240 :                           MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     337        3240 :                       _from_mesh_division_behavior ==
     338       34380 :                           MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
     339             :                      tree_division_index != elem_div_index)
     340       27270 :               continue;
     341             :           }
     342             : 
     343       98907 :           const auto vertex_average = elem->vertex_average();
     344             :           // Transformed element vertex average is in the reference space, as is the
     345             :           // _nearest_positions_obj
     346             :           const auto transformed_vertex_average =
     347       98907 :               (*_from_transforms[getGlobalSourceAppIndex(i_from)])(vertex_average);
     348             : 
     349             :           // We do not need to check the positions when using nearest app as we will assume
     350             :           // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
     351      131307 :           if (!_use_nearest_app && _nearest_positions_obj &&
     352       32400 :               !closestToPosition(i_pos, transformed_vertex_average))
     353       22500 :             continue;
     354             : 
     355       76407 :           _local_points[i_source].push_back(transformed_vertex_average);
     356       76407 :           if (_use_zero_dof_for_value[var_index])
     357             :           {
     358       72537 :             auto dof = elem->dof_number(from_sys.number(), from_var_num, 0);
     359       72537 :             _local_values[i_source].push_back((*from_sys.solution)(dof));
     360             :           }
     361             :           else
     362        3870 :             _local_values[i_source].push_back(
     363        3870 :                 from_sys.point_value(from_var_num, vertex_average, elem));
     364        2751 :         }
     365             :       }
     366       56495 :       max_leaf_size = std::max(max_leaf_size, from_mesh.getMaxLeafSize());
     367             :     }
     368             : 
     369             :     // Make a KDTree from the accumulated points data
     370             :     std::shared_ptr<KDTree> _kd_tree =
     371       56447 :         std::make_shared<KDTree>(_local_points[i_source], max_leaf_size);
     372       56447 :     _local_kdtrees[i_source] = _kd_tree;
     373       56447 :   }
     374       52520 : }
     375             : 
     376             : void
     377       78836 : MultiAppGeneralFieldNearestLocationTransfer::evaluateInterpValues(
     378             :     const std::vector<std::pair<Point, unsigned int>> & incoming_points,
     379             :     std::vector<std::pair<Real, Real>> & outgoing_vals)
     380             : {
     381       78836 :   evaluateInterpValuesNearestNode(incoming_points, outgoing_vals);
     382       78836 : }
     383             : 
     384             : void
     385       78836 : MultiAppGeneralFieldNearestLocationTransfer::evaluateInterpValuesNearestNode(
     386             :     const std::vector<std::pair<Point, unsigned int>> & incoming_points,
     387             :     std::vector<std::pair<Real, Real>> & outgoing_vals)
     388             : {
     389       78836 :   dof_id_type i_pt = 0;
     390     6518166 :   for (const auto & [pt, mesh_div] : incoming_points)
     391             :   {
     392             :     // Reset distance
     393     6439330 :     outgoing_vals[i_pt].second = std::numeric_limits<Real>::max();
     394     6439330 :     bool point_found = false;
     395             : 
     396             :     // Loop on all sources
     397    13188086 :     for (const auto i_from : make_range(_num_sources))
     398             :     {
     399             :       // Examine all restrictions for the point. This source (KDTree+values) could be ruled out
     400     6748756 :       if (!checkRestrictionsForSource(pt, mesh_div, i_from))
     401      190872 :         continue;
     402             : 
     403             :       // TODO: Pre-allocate these two work arrays. They will be regularly resized by the
     404             :       // searches
     405    13115768 :       std::vector<std::size_t> return_index(_num_nearest_points);
     406     6557884 :       std::vector<Real> return_dist_sqr(_num_nearest_points);
     407             : 
     408             :       // KD Tree can be empty if no points are within block/boundary/bounding box restrictions
     409     6557884 :       if (_local_kdtrees[i_from]->numberCandidatePoints())
     410             :       {
     411     6549788 :         point_found = true;
     412             :         // Note that we do not need to use the transformed_pt (in the source app frame)
     413             :         // because the KDTree has been created in the reference frame
     414     6549788 :         _local_kdtrees[i_from]->neighborSearch(
     415             :             pt, _num_nearest_points, return_index, return_dist_sqr);
     416     6549788 :         Real val_sum = 0, dist_sum = 0;
     417    13099576 :         for (const auto index : return_index)
     418             :         {
     419     6549788 :           val_sum += _local_values[i_from][index];
     420     6549788 :           dist_sum += (_local_points[i_from][index] - pt).norm();
     421             :         }
     422             : 
     423             :         // If the new value found is closer than for other sources, use it
     424     6549788 :         const auto new_distance = dist_sum / return_dist_sqr.size();
     425     6549788 :         if (new_distance < outgoing_vals[i_pt].second)
     426     6475022 :           outgoing_vals[i_pt] = {val_sum / return_index.size(), new_distance};
     427             :       }
     428     6557884 :     }
     429             : 
     430             :     // none of the source problem meshes were within the restrictions set
     431     6439330 :     if (!point_found)
     432        4769 :       outgoing_vals[i_pt] = {GeneralFieldTransfer::BetterOutOfMeshValue,
     433        9538 :                              GeneralFieldTransfer::BetterOutOfMeshValue};
     434     6434561 :     else if (_search_value_conflicts)
     435             :     {
     436             :       // Local source meshes conflicts can happen two ways:
     437             :       // - two (or more) problems' nearest nodes are equidistant to the target point
     438             :       // - within a problem, the num_nearest_points'th closest node is as close to the target
     439             :       //   point as the num_nearest_points + 1'th closest node
     440       14605 :       unsigned int num_equidistant_problems = 0;
     441             : 
     442       47481 :       for (const auto i_from : make_range(_num_sources))
     443             :       {
     444             :         // Examine all restrictions for the point. This source (KDTree+values) could be ruled out
     445       32876 :         if (!checkRestrictionsForSource(pt, mesh_div, i_from))
     446       16323 :           continue;
     447             : 
     448             :         // TODO: Pre-allocate these two work arrays. They will be regularly resized by the searches
     449       33106 :         std::vector<std::size_t> return_index(_num_nearest_points + 1);
     450       16553 :         std::vector<Real> return_dist_sqr(_num_nearest_points + 1);
     451             : 
     452             :         // NOTE: app_index is not valid if _group_subapps = true
     453       16553 :         const auto app_index = i_from / getNumDivisions();
     454       16553 :         const auto num_search = _num_nearest_points + 1;
     455             : 
     456       16553 :         if (_local_kdtrees[i_from]->numberCandidatePoints())
     457             :         {
     458       15089 :           _local_kdtrees[i_from]->neighborSearch(pt, num_search, return_index, return_dist_sqr);
     459       15089 :           auto num_found = return_dist_sqr.size();
     460             : 
     461             :           // Local coordinates only accessible when not using nearest-position
     462             :           // as we did not keep the index of the source app, only the position index
     463       15089 :           const Point local_pt = getPointInLocalSourceFrame(app_index, pt);
     464             : 
     465       20141 :           if (!_nearest_positions_obj &&
     466        5052 :               _from_transforms[getGlobalSourceAppIndex(app_index)]->hasCoordinateSystemTypeChange())
     467           0 :             if (!_skip_coordinate_collapsing)
     468           0 :               mooseInfo("Search value conflict cannot find the origin point due to the "
     469             :                         "non-uniqueness of the coordinate collapsing reverse mapping");
     470             : 
     471             :           // Look for too many equidistant nodes within a problem. First zip then sort by distance
     472       15089 :           std::vector<std::pair<Real, std::size_t>> zipped_nearest_points;
     473       45267 :           for (const auto i : make_range(num_found))
     474       30178 :             zipped_nearest_points.push_back(std::make_pair(return_dist_sqr[i], return_index[i]));
     475       15089 :           std::sort(zipped_nearest_points.begin(), zipped_nearest_points.end());
     476             : 
     477             :           // If two furthest are equally far from target point, then we have an indetermination in
     478             :           // what is sent in this communication round from this process. However, it may not
     479             :           // materialize to an actual conflict, as values sent from another process for the
     480             :           // desired target point could be closer (nearest). There is no way to know at this point
     481             :           // in the communication that a closer value exists somewhere else
     482       30178 :           if (num_found > 1 && num_found == num_search &&
     483       15089 :               MooseUtils::absoluteFuzzyEqual(zipped_nearest_points[num_found - 1].first,
     484       15089 :                                              zipped_nearest_points[num_found - 2].first))
     485             :           {
     486         352 :             if (_nearest_positions_obj)
     487          16 :               registerConflict(app_index, 0, pt, outgoing_vals[i_pt].second, true);
     488             :             else
     489         336 :               registerConflict(app_index, 0, local_pt, outgoing_vals[i_pt].second, true);
     490             :           }
     491             : 
     492             :           // Recompute the distance for this problem. If it matches the cached value more than
     493             :           // once it means multiple problems provide equidistant values for this point
     494       15089 :           Real dist_sum = 0;
     495       30178 :           for (const auto i : make_range(num_search - 1))
     496             :           {
     497       15089 :             auto index = zipped_nearest_points[i].second;
     498       15089 :             dist_sum += (_local_points[i_from][index] - pt).norm();
     499             :           }
     500             : 
     501             :           // Compare to the selected value found after looking at all the problems
     502       15089 :           if (MooseUtils::absoluteFuzzyEqual(dist_sum / return_dist_sqr.size(),
     503       15089 :                                              outgoing_vals[i_pt].second))
     504             :           {
     505          88 :             num_equidistant_problems++;
     506          88 :             if (num_equidistant_problems > 1)
     507             :             {
     508           0 :               if (_nearest_positions_obj)
     509           0 :                 registerConflict(app_index, 0, pt, outgoing_vals[i_pt].second, true);
     510             :               else
     511           0 :                 registerConflict(app_index, 0, local_pt, outgoing_vals[i_pt].second, true);
     512             :             }
     513             :           }
     514       15089 :         }
     515       16553 :       }
     516             :     }
     517             : 
     518             :     // Move to next point
     519     6439330 :     i_pt++;
     520             :   }
     521       78836 : }
     522             : 
     523             : bool
     524       18000 : MultiAppGeneralFieldNearestLocationTransfer::inBlocks(const std::set<SubdomainID> & blocks,
     525             :                                                       const MooseMesh & mesh,
     526             :                                                       const Elem * elem) const
     527             : {
     528             :   // We need to override the definition of block restriction for an element
     529             :   // because we have to consider whether each node of an element is adjacent to a block
     530       50292 :   for (const auto & i_node : make_range(elem->n_nodes()))
     531             :   {
     532       45756 :     const auto & node = elem->node_ptr(i_node);
     533       45756 :     const auto & node_blocks = mesh.getNodeBlockIds(*node);
     534       45756 :     std::set<SubdomainID> u;
     535       45756 :     std::set_intersection(blocks.begin(),
     536             :                           blocks.end(),
     537             :                           node_blocks.begin(),
     538             :                           node_blocks.end(),
     539             :                           std::inserter(u, u.begin()));
     540       45756 :     if (!u.empty())
     541       13464 :       return true;
     542       45756 :   }
     543        4536 :   return false;
     544             : }
     545             : 
     546             : void
     547       52520 : MultiAppGeneralFieldNearestLocationTransfer::computeNumSources()
     548             : {
     549             :   // Number of source = number of KDTrees.
     550             :   // Using mesh divisions or nearest-positions, for every app we use 1 tree per division
     551      104896 :   if (!_from_mesh_divisions.empty() ||
     552       52376 :       (!_use_nearest_app && _nearest_positions_obj && !_group_subapps))
     553         216 :     _num_sources = _from_problems.size() * getNumDivisions();
     554             :   // If we group apps, then we only use one tree per division (nearest-position region)
     555       52304 :   else if (_nearest_positions_obj && _group_subapps)
     556          76 :     _num_sources = _nearest_positions_obj->getNumPositions(_fe_problem.getCurrentExecuteOnFlag() ==
     557             :                                                            EXEC_INITIAL);
     558             :   // Regular case: 1 KDTree per app
     559             :   // Also if use_nearest_app = true, the number of problems is better than the number of positions,
     560             :   // because some of the positions are positions of child applications that are not local
     561             :   else
     562       52228 :     _num_sources = _from_problems.size();
     563       52520 : }
     564             : 
     565             : unsigned int
     566     6798308 : MultiAppGeneralFieldNearestLocationTransfer::getAppIndex(
     567             :     unsigned int kdtree_index, unsigned int nested_loop_on_app_index) const
     568             : {
     569             :   // Each app is mapped to a single KD Tree
     570     6798308 :   if (_use_nearest_app)
     571        6489 :     return kdtree_index;
     572             :   // We are looping over all the apps that are grouped together
     573     6791819 :   else if (_group_subapps)
     574       11965 :     return nested_loop_on_app_index;
     575             :   // There are num_divisions trees for each app, inner ordering is divisions, so dividing by the
     576             :   // number of divisions gets us the index of the application
     577             :   else
     578     6779854 :     return kdtree_index / getNumDivisions();
     579             : }
     580             : 
     581             : unsigned int
     582       52520 : MultiAppGeneralFieldNearestLocationTransfer::getNumAppsPerTree() const
     583             : {
     584       52520 :   if (_use_nearest_app)
     585          72 :     return 1;
     586       52448 :   else if (_group_subapps)
     587          76 :     return _from_meshes.size();
     588             :   else
     589       52372 :     return 1;
     590             : }
     591             : 
     592             : unsigned int
     593    14009368 : MultiAppGeneralFieldNearestLocationTransfer::getNumDivisions() const
     594             : {
     595             :   // This is not used currently, but conceptually it is better to only divide the domain with the
     596             :   // local of local applications rather than the global number of positions (# global applications
     597             :   // here)
     598    14009368 :   if (_use_nearest_app)
     599        8685 :     return _from_meshes.size();
     600             :   // Each nearest-position region is a division
     601    14000683 :   else if (_nearest_positions_obj && !_group_subapps)
     602       49356 :     return _nearest_positions_obj->getNumPositions(_fe_problem.getCurrentExecuteOnFlag() ==
     603       49356 :                                                    EXEC_INITIAL);
     604             :   // Assume all mesh divisions (on each sub-app) has the same number of divisions. This is checked
     605    13951327 :   else if (!_from_mesh_divisions.empty())
     606      834753 :     return _from_mesh_divisions[0]->getNumDivisions();
     607             :   // Grouping subapps or no special mode, we do not subdivide
     608             :   else
     609    13116574 :     return 1;
     610             : }
     611             : 
     612             : Point
     613       15089 : MultiAppGeneralFieldNearestLocationTransfer::getPointInLocalSourceFrame(unsigned int i_from,
     614             :                                                                         const Point & pt) const
     615             : {
     616             : 
     617       20141 :   if (!_nearest_positions_obj &&
     618        5052 :       (!_from_transforms[getGlobalSourceAppIndex(i_from)]->hasCoordinateSystemTypeChange() ||
     619           0 :        _skip_coordinate_collapsing))
     620        5052 :     return _from_transforms[getGlobalSourceAppIndex(i_from)]->mapBack(pt);
     621       10037 :   else if (!_nearest_positions_obj || !_group_subapps)
     622        4392 :     return pt - _from_positions[i_from];
     623             :   else
     624        5645 :     return pt;
     625             : }
     626             : 
     627             : bool
     628     6781632 : MultiAppGeneralFieldNearestLocationTransfer::checkRestrictionsForSource(
     629             :     const Point & pt, const unsigned int mesh_div, const unsigned int i_from) const
     630             : {
     631             :   // Only use the KDTree from the closest position if in "nearest-position" mode
     632     6781632 :   if (_nearest_positions_obj)
     633             :   {
     634             :     // See computeNumSources for the number of sources. i_from is the index in the source loop
     635             :     // i_from is local if looping on _from_problems as sources, positions are indexed globally
     636             :     // i_from is already indexing in positions if using group_subapps
     637       69207 :     auto position_index = i_from; // if _group_subapps
     638       69207 :     if (_use_nearest_app)
     639       11367 :       position_index = getGlobalSourceAppIndex(i_from);
     640       57840 :     else if (!_group_subapps)
     641       22596 :       position_index = i_from % getNumDivisions();
     642             : 
     643             :     // NOTE: if two positions are equi-distant to the point, this will chose one
     644             :     // This problem is detected if using search_value_conflicts in this call
     645       69207 :     if (!closestToPosition(position_index, pt))
     646       39819 :       return false;
     647             :   }
     648             : 
     649             :   // Application index depends on which source/grouping mode we are using
     650     6741813 :   const unsigned int app_index = getAppIndex(i_from, i_from / getNumDivisions());
     651             : 
     652             :   // Check mesh restriction before anything
     653     6741813 :   if (_source_app_must_contain_point)
     654             :   {
     655             :     // We have to be careful that getPointInLocalSourceFrame returns in the reference frame
     656           0 :     if (_nearest_positions_obj)
     657           0 :       mooseError("Nearest-positions + source_app_must_contain_point not implemented");
     658             :     // Transform the point to place it in the local coordinate system
     659           0 :     const auto local_pt = getPointInLocalSourceFrame(app_index, pt);
     660           0 :     if (!inMesh(_from_point_locators[app_index].get(), local_pt))
     661           0 :       return false;
     662             :   }
     663             : 
     664             :   // Check the mesh division. We have handled the restriction of the source locations when
     665             :   // building the nearest-neighbor trees. We only need to check that we meet the required
     666             :   // source division index.
     667     6741813 :   if (!_from_mesh_divisions.empty())
     668             :   {
     669             :     mooseAssert(mesh_div != MooseMeshDivision::INVALID_DIVISION_INDEX,
     670             :                 "We should not be receiving point requests with an invalid "
     671             :                 "source mesh division index");
     672      218280 :     const unsigned int kd_div_index = i_from % getNumDivisions();
     673             : 
     674             :     // If matching source mesh divisions to target apps, we check that the index of the target
     675             :     // application, which was passed in the point request, is equal to the current mesh division
     676      218280 :     if (_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
     677             :         mesh_div != kd_div_index)
     678       17568 :       return false;
     679             :     // If matching source mesh divisions to target mesh divisions, we check that the index of the
     680             :     // target mesh division, which was passed in the point request, is equal to the current mesh
     681             :     // division
     682      200712 :     else if ((_from_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
     683      200712 :               _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX) &&
     684             :              mesh_div != kd_div_index)
     685      148608 :       return false;
     686             :   }
     687             : 
     688             :   // If matching target apps to source mesh divisions, we check that the global index of the
     689             :   // application is equal to the target mesh division index, which was passed in the point request
     690     6577287 :   if (_to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_SUBAPP_INDEX &&
     691        1650 :       mesh_div != getGlobalSourceAppIndex(app_index))
     692        1200 :     return false;
     693             : 
     694     6574437 :   return true;
     695             : }

Generated by: LCOV version 1.14