https://mooseframework.inl.gov
MultiAppGeneralFieldNearestLocationTransfer.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
11 
12 // MOOSE includes
13 #include "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 
27  MultiAppGeneralFieldNearestNodeTransfer,
28  "12/31/2024 24:00",
30 
33 {
35  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 
39  // Nearest node is historically more an extrapolation transfer
40  params.set<Real>("extrapolation_constant") = GeneralFieldTransfer::OutOfMeshValue;
41  params.suppressParameter<Real>("extrapolation_constant");
42  // We dont keep track of both point distance to app and to nearest node, so we cannot guarantee
43  // that the nearest app (among the local apps, not globally) will hold the nearest location.
44  // However, if the user knows this is true, we can use this heuristic to reduce the number of apps
45  // that are requested to provide a candidate value. If the user is wrong, then the nearest
46  // location is used, which can be from the non-nearest app.
47  params.renameParam("use_nearest_app", "assume_nearest_app_holds_nearest_location", "");
48 
49  // the default of node/centroid switching based on the variable is causing lots of mistakes and
50  // bad results
51  std::vector<MooseEnum> source_types = {
52  MooseEnum("nodes centroids variable_default", "variable_default")};
53  params.addParam<std::vector<MooseEnum>>(
54  "source_type", source_types, "Where to get the source values from for each source variable");
55 
56  return params;
57 }
58 
60  const InputParameters & parameters)
62 {
63 }
64 
65 void
67 {
69 
70  // Handle the source types ahead of time
71  const auto & source_types = getParam<std::vector<MooseEnum>>("source_type");
72  _source_is_nodes.resize(_from_var_names.size());
74  if (source_types.size() != _from_var_names.size())
75  mooseError("Not enough source types specified for this number of variables. Source types must "
76  "be specified for transfers with multiple variables");
77  for (const auto var_index : index_range(_from_var_names))
78  {
79  // Local app does not own any of the source problems
80  if (_from_problems.empty())
81  break;
82 
83  // Get some info on the source variable
84  FEProblemBase & from_problem = *_from_problems[0];
85  MooseVariableFieldBase & from_var =
86  from_problem.getVariable(0,
87  _from_var_names[var_index],
90  System & from_sys = from_var.sys().system();
91  const auto & fe_type = from_sys.variable_type(from_var.number());
92 
93  // Select where to get the origin values
94  if (source_types[var_index] == "nodes")
95  _source_is_nodes[var_index] = true;
96  else if (source_types[var_index] == "centroids")
97  _source_is_nodes[var_index] = false;
98  else
99  {
100  // "Nodal" variables are continuous for sure at nodes
101  if (from_var.isNodal())
102  _source_is_nodes[var_index] = true;
103  // for everyone else, we know they are continuous at centroids
104  else
105  _source_is_nodes[var_index] = false;
106  }
107 
108  // Some variables can be sampled directly at their 0 dofs
109  // - lagrange at nodes on a first order mesh
110  // Higher order mesh is also fine incidentally because on the 'other' (mid-face for example)
111  // nodes, the 1st order lagrange variable has 0 dofs, and the second order lagrange
112  // use the mid-edge nodes as lagrange points (and 0 dofs on extra ones).
113  // Third order is different (except on edge4, but not used much)
114  // - anything constant and elemental obviously has the 0-dof value at the centroid (or
115  // vertex-average). However, higher order elemental, even monomial, do not hold the centroid
116  // value at dof index 0. For example: pyramid has dof 0 at the center of the base, prism has dof
117  // 0 on an edge etc
118  if ((_source_is_nodes[var_index] && fe_type.family == LAGRANGE && fe_type.order <= SECOND) ||
119  (!_source_is_nodes[var_index] && fe_type.order == CONSTANT))
120  _use_zero_dof_for_value[var_index] = true;
121  else
122  _use_zero_dof_for_value[var_index] = false;
123 
124  // Check with the source variable that it is not discontinuous at the source
125  if (_source_is_nodes[var_index])
126  if (from_var.getContinuity() == DISCONTINUOUS ||
127  from_var.getContinuity() == SIDE_DISCONTINUOUS)
128  mooseError("Source variable cannot be sampled at nodes as it is discontinuous");
129 
130  // Local app does not own any of the target problems
131  if (_to_problems.empty())
132  break;
133 
134  // Check with the target variable that we are not doing awful projections
135  MooseVariableFieldBase & to_var =
136  _to_problems[0]->getVariable(0,
137  _to_var_names[var_index],
140  System & to_sys = to_var.sys().system();
141  const auto & to_fe_type = to_sys.variable_type(to_var.number());
142  if (_source_is_nodes[var_index])
143  {
144  if (to_fe_type.order == CONSTANT)
145  mooseWarning(
146  "Transfer is projecting from nearest-nodes to centroids. This is likely causing "
147  "floating point indetermination in the results because multiple nodes are 'nearest' to "
148  "a centroid. Please consider using a ProjectionAux to build an elemental source "
149  "variable (for example constant monomial) before transferring");
150  }
151  else if (to_var.isNodal())
152  mooseWarning(
153  "Transfer is projecting from nearest-centroids to nodes. This is likely causing "
154  "floating point indetermination in the results because multiple centroids are "
155  "'nearest' to a node. Please consider using a ProjectionAux to build a nodal source "
156  "variable (for example linear Lagrange) before transferring");
157  }
158 
159  // We need to improve the indexing if we are to allow this
160  if (!_from_mesh_divisions.empty())
161  for (const auto mesh_div : _from_mesh_divisions)
162  if (mesh_div->getNumDivisions() != _from_mesh_divisions[0]->getNumDivisions())
163  paramError("from_mesh_division",
164  "This transfer has only been implemented with a uniform number of source mesh "
165  "divisions across all source applications");
166 }
167 
168 void
170 {
172  const auto num_apps_per_tree = getNumAppsPerTree();
174  _local_points.resize(_num_sources);
175  _local_values.resize(_num_sources);
176  unsigned int max_leaf_size = 0;
177 
178  // Construct a local KDTree for each source. A source can be a single app or multiple apps
179  // combined (option for nearest-position / mesh-divisions)
180  for (const auto i_source : make_range(_num_sources))
181  {
182  // Nest a loop on apps in case multiple apps contribute to the same KD-Tree source
183  for (const auto app_i : make_range(num_apps_per_tree))
184  {
185  // Get the current app index
186  const auto i_from = getAppIndex(i_source, app_i);
187  // Current position index, if using nearest positions (not used for use_nearest_app)
188  const auto i_pos = _group_subapps ? i_source : (i_source % getNumDivisions());
189 
190  // Get access to the variable and some variable information
191  FEProblemBase & from_problem = *_from_problems[i_from];
192  auto & from_mesh = from_problem.mesh(_displaced_source_mesh);
193  MooseVariableFieldBase & from_var =
194  from_problem.getVariable(0,
195  _from_var_names[var_index],
198  System & from_sys = from_var.sys().system();
199  const unsigned int from_var_num = from_sys.variable_number(getFromVarName(var_index));
200 
201  // Build KDTree from nodes and nodal values
202  if (_source_is_nodes[var_index])
203  {
204  for (const auto & node : from_mesh.getMesh().local_node_ptr_range())
205  {
206  // This notably excludes first order Lagrange variables on the mid-nodes
207  // e.g. the mid-nodes are not added to the KD tree
208  if (node->n_dofs(from_sys.number(), from_var_num) < 1)
209  continue;
210 
211  if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, node))
212  continue;
213 
214  if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, node))
215  continue;
216 
217  // Handle the various source mesh divisions behaviors
218  // NOTE: This could be more efficient, as instead of rejecting points in the
219  // wrong division, we could just be adding them to the tree for the right division
220  if (!_from_mesh_divisions.empty())
221  {
222  const auto tree_division_index = i_source % getNumDivisions();
223  const auto node_div_index = _from_mesh_divisions[i_from]->divisionIndex(*node);
224 
225  // Spatial restriction is always active
226  if (node_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
227  continue;
228  // We fill one tree per division index for matching subapp index or division index. We
229  // only accept source data from the division index
230  else if ((_from_mesh_division_behavior ==
231  MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
232  _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
234  MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
235  tree_division_index != node_div_index)
236  continue;
237  }
238 
239  // Transformed node is in the reference space, as is the _nearest_positions_obj
240  const auto transformed_node = (*_from_transforms[getGlobalSourceAppIndex(i_from)])(*node);
241 
242  // Only add to the KDTree nodes that are closest to the 'position'
243  // When querying values at a target point, the KDTree associated to the closest
244  // position to the target point is queried
245  // We do not need to check the positions when using nearest app as we will assume
246  // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
247  // than to any other
249  !closestToPosition(i_pos, transformed_node))
250  continue;
251 
252  _local_points[i_source].push_back(transformed_node);
253  const auto dof = node->dof_number(from_sys.number(), from_var_num, 0);
254  _local_values[i_source].push_back((*from_sys.solution)(dof));
255  if (!_use_zero_dof_for_value[var_index])
256  flagSolutionWarning(
257  "Nearest-location is not implemented for this source variable type on "
258  "this mesh. Returning value at dof 0");
259  }
260  }
261  // Build KDTree from centroids and value at centroids
262  else
263  {
264  for (auto & elem : as_range(from_mesh.getMesh().local_elements_begin(),
265  from_mesh.getMesh().local_elements_end()))
266  {
267  if (elem->n_dofs(from_sys.number(), from_var_num) < 1)
268  continue;
269 
270  if (!_from_blocks.empty() && !inBlocks(_from_blocks, elem))
271  continue;
272 
273  if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, elem))
274  continue;
275 
276  // Handle the various source mesh divisions behaviors
277  if (!_from_mesh_divisions.empty())
278  {
279  const auto tree_division_index = i_source % getNumDivisions();
280  const auto elem_div_index = _from_mesh_divisions[i_from]->divisionIndex(*elem);
281 
282  // Spatial restriction is always active
283  if (elem_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
284  continue;
285  // We fill one tree per division index for matching subapp index or division index. We
286  // only accept source data from the division index
287  else if ((_from_mesh_division_behavior ==
288  MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
290  MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
291  tree_division_index != elem_div_index)
292  continue;
293  }
294 
295  const auto vertex_average = elem->vertex_average();
296  // Transformed element vertex average is in the reference space, as is the
297  // _nearest_positions_obj
298  const auto transformed_vertex_average =
299  (*_from_transforms[getGlobalSourceAppIndex(i_from)])(vertex_average);
300 
301  // We do not need to check the positions when using nearest app as we will assume
302  // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
304  !closestToPosition(i_pos, transformed_vertex_average))
305  continue;
306 
307  _local_points[i_source].push_back(transformed_vertex_average);
308  if (_use_zero_dof_for_value[var_index])
309  {
310  auto dof = elem->dof_number(from_sys.number(), from_var_num, 0);
311  _local_values[i_source].push_back((*from_sys.solution)(dof));
312  }
313  else
314  _local_values[i_source].push_back(
315  from_sys.point_value(from_var_num, vertex_average, elem));
316  }
317  }
318  max_leaf_size = std::max(max_leaf_size, from_mesh.getMaxLeafSize());
319  }
320 
321  // Make a KDTree from the accumulated points data
322  std::shared_ptr<KDTree> _kd_tree =
323  std::make_shared<KDTree>(_local_points[i_source], max_leaf_size);
324  _local_kdtrees[i_source] = _kd_tree;
325  }
326 }
327 
328 void
330  const unsigned int /*var_index*/,
331  const std::vector<std::pair<Point, unsigned int>> & incoming_points,
332  std::vector<std::pair<Real, Real>> & outgoing_vals)
333 {
334  evaluateInterpValuesNearestNode(incoming_points, outgoing_vals);
335 }
336 
337 void
339  const std::vector<std::pair<Point, unsigned int>> & incoming_points,
340  std::vector<std::pair<Real, Real>> & outgoing_vals)
341 {
342  dof_id_type i_pt = 0;
343  for (const auto & [pt, mesh_div] : incoming_points)
344  {
345  outgoing_vals[i_pt].second = std::numeric_limits<Real>::max();
346  bool point_found = false;
347  evaluateNearestNodeFromKDTrees(pt, mesh_div, outgoing_vals[i_pt], point_found);
348  if (!point_found)
349  outgoing_vals[i_pt] = {GeneralFieldTransfer::OutOfMeshValue,
351  i_pt++;
352  }
353 }
LAGRANGE
void renameParam(const std::string &old_name, const std::string &new_name, const std::string &new_docstring)
Rename a parameter and provide a new documentation string.
virtual bool isNodal() const
Is this variable nodal.
registerMooseObjectRenamed("MooseApp", MultiAppGeneralFieldNearestNodeTransfer, "12/31/2024 24:00", MultiAppGeneralFieldNearestLocationTransfer)
void initialSetup() override
Method called at the beginning of the simulation for checking integrity or doing one-time setup...
registerMooseObject("MooseApp", MultiAppGeneralFieldNearestLocationTransfer)
unsigned int getNumDivisions() const
Number of divisions (nearest-positions or source mesh divisions) used when building KD-Trees...
const bool _group_subapps
Whether to group data when creating the nearest-point regions.
Base class for working with KDTrees in transfers, whether for interpolation or extrapolation.
void computeNumSources()
Pre-compute the number of sources Number of KDTrees used to hold the locations and variable value dat...
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:467
std::vector< bool > _use_zero_dof_for_value
Whether we can just use the local zero-indexed dof to get the value from the solution.
unsigned int number() const
Get variable number coming from libMesh.
virtual libMesh::FEContinuity getContinuity() const
Return the continuity of this variable.
unsigned int getAppIndex(unsigned int kdtree_index, unsigned int app_index_in_tree) const
Get the index of the app when inside of a KD-Tree source loop, where multiple applications could be l...
T & set(const std::string &name, bool quiet_mode=false)
Returns a writable reference to the named parameters.
virtual libMesh::System & system()=0
Get the reference to the libMesh system.
const std::vector< VariableName > _from_var_names
Name of variables transferring from.
std::vector< bool > _source_is_nodes
Whether the source of the values is at nodes (true) or centroids (false) for each variable...
std::vector< FEProblemBase * > _to_problems
Number point_value(unsigned int var, const Point &p, const bool insist_on_success=true, const NumericVector< Number > *sol=nullptr) const
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
bool inBlocks(const std::set< SubdomainID > &blocks, const MooseMesh &mesh, const Elem *elem) const override
This class provides an interface for common operations on field variables of both FE and FV types wit...
std::vector< const MeshDivision * > _from_mesh_divisions
Division of the origin mesh.
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
virtual void evaluateInterpValues(const unsigned int, const std::vector< std::pair< Point, unsigned int >> &incoming_points, std::vector< std::pair< Real, Real >> &outgoing_vals) override
SECOND
bool closestToPosition(unsigned int pos_index, const Point &pt) const
Whether a point is closest to a position at the index specified than any other position.
unsigned int _num_sources
Number of KD-Trees to create.
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
std::set< BoundaryID > _from_boundaries
Origin boundary(ies) restriction.
virtual const MooseVariableFieldBase & getVariable(const THREAD_ID tid, const std::string &var_name, Moose::VarKindType expected_var_type=Moose::VarKindType::VAR_ANY, Moose::VarFieldType expected_var_field_type=Moose::VarFieldType::VAR_FIELD_ANY) const override
Returns the variable reference for requested variable which must be of the expected_var_type (Nonline...
DISCONTINUOUS
auto max(const L &left, const R &right)
unsigned int variable_number(std::string_view var) const
void suppressParameter(const std::string &name)
This method suppresses an inherited parameter so that it isn&#39;t required or valid in the derived class...
void evaluateNearestNodeFromKDTrees(const Point &pt, unsigned int source_index, std::pair< Real, Real > &outgoing_val, bool &point_found)
Search all local KD-trees for the nearest node/element and update outgoing_val.
unsigned int getNumAppsPerTree() const
Number of applications which contributed nearest-locations to each KD-tree.
SIDE_DISCONTINUOUS
void mooseWarning(Args &&... args) const
CONSTANT
unsigned int number() const
VariableName getFromVarName(unsigned int var_index) const
Get the source variable name, with the suffix for array/vector variables.
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
const std::vector< AuxVariableName > _to_var_names
Name of variables transferring to.
std::unique_ptr< NumericVector< Number > > solution
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
unsigned int getGlobalSourceAppIndex(unsigned int i_from) const
Return the global app index from the local index in the "from-multiapp" transfer direction.
unsigned int INVALID_DIVISION_INDEX
Invalid subdomain id to return when outside the mesh division.
Definition: MeshDivision.h:28
void evaluateInterpValuesNearestNode(const std::vector< std::pair< Point, unsigned int >> &incoming_points, std::vector< std::pair< Real, Real >> &outgoing_vals)
const FEType & variable_type(const unsigned int i) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const MooseEnum & _to_mesh_division_behavior
How to use the target mesh divisions to restrict the transfer.
std::vector< std::unique_ptr< MultiAppCoordTransform > > _from_transforms
IntRange< T > make_range(T beg, T end)
virtual MooseMesh & mesh() override
bool _displaced_source_mesh
True if displaced mesh is used for the source mesh, otherwise false.
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:281
std::set< SubdomainID > _from_blocks
Origin block(s) restriction.
Performs a geometric interpolation based on the values at the nearest nodes to a target location in t...
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
SystemBase & sys()
Get the system this variable is part of.
void initialSetup() override
Method called at the beginning of the simulation for checking integrity or doing one-time setup...
std::vector< std::vector< Real > > _local_values
Values of the variable being transferred at all the points in _local_points.
bool onBoundaries(const std::set< BoundaryID > &boundaries, const MooseMesh &mesh, const Node *node) const
std::vector< std::vector< Point > > _local_points
All the nodes that meet the spatial restrictions in all the local source apps.
std::vector< FEProblemBase * > _from_problems
auto index_range(const T &sizable)
std::vector< std::shared_ptr< KDTree > > _local_kdtrees
KD-Trees for all the local source apps.
uint8_t dof_id_type
const MooseEnum & _from_mesh_division_behavior
How to use the origin mesh divisions to restrict the transfer.
const bool _use_nearest_app
Whether to keep track of the distance from the requested point to the app position.