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
23using namespace libMesh;
24
27 MultiAppGeneralFieldNearestNodeTransfer,
28 "12/31/2024 24:00",
30
33{
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
64
65void
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)
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())
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
168void
170{
172 const auto num_apps_per_tree = getNumAppsPerTree();
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
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
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
328void
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
337void
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}
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.
Number point_value(unsigned int var, const Point &p, const bool insist_on_success=true, const NumericVector< Number > *sol=nullptr) const
const FEType & variable_type(const unsigned int i) const
std::unique_ptr< NumericVector< Number > > solution
unsigned int variable_number(std::string_view var) const
unsigned int number() 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
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
SIDE_DISCONTINUOUS
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
auto index_range(const T &sizable)
uint8_t dof_id_type
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)