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