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 "MultiAppGeneralFieldFunctorTransfer.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 : #include "MooseFunctorArguments.h"
21 :
22 : using namespace libMesh;
23 :
24 : registerMooseObject("MooseApp", MultiAppGeneralFieldFunctorTransfer);
25 :
26 : InputParameters
27 4819 : MultiAppGeneralFieldFunctorTransfer::validParams()
28 : {
29 4819 : InputParameters params = MultiAppGeneralFieldKDTreeTransferBase::validParams();
30 4819 : params += NonADFunctorInterface::validParams();
31 9638 : params.addClassDescription(
32 : "Transfers functor data at the MultiApp position by evaluating the functor inside its domain "
33 : "of definition and extrapolating with a user-selected behavior outside");
34 :
35 : // Input variables as functors instead
36 9638 : params.suppressParameter<std::vector<VariableName>>("source_variable");
37 : // NOTE: could rename this instead once we support array or vector functor component transfer
38 9638 : params.suppressParameter<std::vector<unsigned int>>("source_variable_components");
39 :
40 19276 : params.addRequiredParam<std::vector<MooseFunctorName>>(
41 : "source_functors", "Functors providing the values to transfer to the target variables");
42 :
43 : // Potential additional parameters:
44 : // - functor evaluation spatial argument type
45 : // - functor evaluation time argument type
46 : // - number of points to use when creating extrapolation 'patches'
47 : // - other 'nearest' locations: node, element, side or a general 'location'
48 : // - options for a radius based search and build patches instead of 'nearest'
49 :
50 19276 : MooseEnum extrapolation("flat evaluate_oob nearest-node nearest-elem", "nearest-node");
51 19276 : params.addParam<MooseEnum>("extrapolation_behavior",
52 : extrapolation,
53 : "How to extrapolate the functors when a target point for the transfer "
54 : "is outside the domain of evaluation");
55 :
56 : // This is a convenient heuristic to limit communication
57 24095 : params.renameParam("use_nearest_app", "assume_nearest_app_holds_evaluation_location", "");
58 :
59 9638 : return params;
60 4819 : }
61 :
62 879 : MultiAppGeneralFieldFunctorTransfer::MultiAppGeneralFieldFunctorTransfer(
63 879 : const InputParameters & parameters)
64 : : MultiAppGeneralFieldKDTreeTransferBase(parameters),
65 : NonADFunctorInterface(this),
66 1758 : _functor_names(getParam<std::vector<MooseFunctorName>>("source_functors")),
67 2637 : _extrapolation_behavior(getParam<MooseEnum>("extrapolation_behavior"))
68 : {
69 : // Check extrapolation options
70 2637 : if (isParamSetByUser("extrapolation_constant") && _extrapolation_behavior != "flat")
71 0 : paramError("extrapolation_behavior",
72 : "Flat (single-constant) extrapolation must be selected if an extrapolation constant "
73 : "is specified");
74 879 : if (_post_transfer_extrapolation != "none" && _extrapolation_behavior != "flat")
75 0 : paramError("extrapolation_behavior",
76 : "Flat (single-constant) extrapolation must be selected if an extrapolation post-"
77 : "treatment is specified");
78 :
79 : // Check size
80 879 : if (_functor_names.size() != _to_var_names.size())
81 0 : paramError("source_functors", "Should be the same size as target 'variable'");
82 879 : }
83 :
84 : void
85 879 : MultiAppGeneralFieldFunctorTransfer::initialSetup()
86 : {
87 879 : MultiAppGeneralFieldKDTreeTransferBase::initialSetup();
88 :
89 : // Retrieve the functors
90 879 : _functors.resize(_from_problems.size());
91 879 : _functor_is_variable.resize(_functor_names.size());
92 1758 : for (const auto i_functor : index_range(_functor_names))
93 : {
94 879 : const auto & fname = _functor_names[i_functor];
95 :
96 : // Different functors for every source
97 2178 : for (const auto i_from : index_range(_from_problems))
98 1299 : _functors[i_from].push_back(
99 1299 : &_from_problems[i_from]->getFunctor<Real>(fname, /*thread*/ 0, name(), false));
100 :
101 : // Need to keep track of variables because of ghosting needs
102 : // NOTE: we don't really expect the functor type to vary between problems
103 2178 : for (const auto i_from : index_range(_from_problems))
104 0 : _functor_is_variable[i_functor] =
105 1299 : _functor_is_variable[i_functor] || _from_problems[i_from]->hasVariable(fname);
106 : }
107 879 : }
108 :
109 : void
110 804 : MultiAppGeneralFieldFunctorTransfer::execute()
111 : {
112 : // Execute the user object if it was specified to execute on TRANSFER
113 1608 : for (const auto & fname : _functor_names)
114 804 : switch (_current_direction)
115 : {
116 270 : case TO_MULTIAPP:
117 : {
118 270 : if (!_fe_problem.hasUserObject(fname))
119 28 : continue;
120 242 : checkParentAppUserObjectExecuteOn(fname);
121 242 : _fe_problem.computeUserObjectByName(EXEC_TRANSFER, Moose::PRE_AUX, fname);
122 242 : _fe_problem.computeUserObjectByName(EXEC_TRANSFER, Moose::POST_AUX, fname);
123 242 : break;
124 : }
125 270 : case FROM_MULTIAPP:
126 270 : errorIfObjectExecutesOnTransferInSourceApp(fname);
127 : }
128 :
129 : // Perfom the actual transfer
130 804 : MultiAppGeneralFieldKDTreeTransferBase::execute();
131 798 : }
132 :
133 : void
134 804 : MultiAppGeneralFieldFunctorTransfer::prepareEvaluationOfInterpValues(const unsigned int var_index)
135 : {
136 804 : MultiAppGeneralFieldKDTreeTransferBase::prepareEvaluationOfInterpValues(var_index);
137 :
138 : // Get the point locators
139 804 : _point_locators.resize(_from_problems.size());
140 1965 : for (const auto app_index : index_range(_from_problems))
141 1161 : _point_locators[app_index] =
142 2322 : _from_problems[app_index]->mesh(_displaced_source_mesh).getPointLocator();
143 804 : }
144 :
145 : void
146 804 : MultiAppGeneralFieldFunctorTransfer::buildKDTrees(const unsigned int var_index)
147 : {
148 804 : computeNumSources();
149 804 : const auto num_apps_per_tree = getNumAppsPerTree();
150 804 : _local_kdtrees.resize(_num_sources);
151 804 : _local_points.resize(_num_sources);
152 804 : _local_values.resize(_num_sources);
153 804 : unsigned int max_leaf_size = 0;
154 :
155 : // Construct a local KDTree for each source. A source can be a single app or multiple apps
156 : // combined (option for nearest-position / mesh-divisions)
157 2073 : for (const auto i_source : make_range(_num_sources))
158 : {
159 : // Nest a loop on apps in case multiple apps contribute to the same KD-Tree source
160 2578 : for (const auto app_i : make_range(num_apps_per_tree))
161 : {
162 : // Get the current app index
163 1309 : const auto i_from = getAppIndex(i_source, app_i);
164 : // Current position index, if using nearest positions (not used for use_nearest_app)
165 1309 : const auto i_pos = _group_subapps ? i_source : (i_source % getNumDivisions());
166 :
167 : // Get access to the variable and some variable information
168 1309 : FEProblemBase & from_problem = *_from_problems[i_from];
169 1309 : auto & from_mesh = from_problem.mesh(_displaced_source_mesh);
170 : // No need for displaced mesh for checking domain of definition
171 1309 : const auto & node_to_elem_map = from_problem.mesh().nodeToActiveSemilocalElemMap();
172 :
173 : // Get functor for that app
174 1309 : const auto & functor = _functors[i_from][var_index];
175 :
176 : // Form the block restriction for evaluation. We need to prevent evaluation outside the
177 : // domain of evaluation of the functor (could crash) or the transfer (disobeys user)
178 : // Note: the functor subdomains of evaluation are checked below as well, so we
179 : // should be fairly safe
180 1309 : std::set<SubdomainID> from_blocks;
181 1309 : if (_from_blocks.size())
182 : {
183 770 : for (const auto bl : _from_blocks)
184 420 : if (functor->hasBlocks(bl))
185 420 : from_blocks.insert(bl);
186 : }
187 : else
188 959 : from_blocks = Moose::NodeArg::undefined_subdomain_connection;
189 :
190 : // We need to loop over the nodes at the edge of the domain of definition of the
191 : // current functor
192 1309 : if (_extrapolation_behavior == 2) // nearest-node
193 11001 : for (const auto & node : from_mesh.getMesh().local_node_ptr_range())
194 : {
195 : // No way to check number of dofs for a functor
196 : // Functor should be defined on at least one block by the block to have a value
197 : // Node should be either on a functor or a mesh boundary to be relevant for extrapolation
198 5376 : bool on_at_least_one_block = false;
199 5376 : bool on_boundary = false;
200 19296 : for (const auto eid : libmesh_map_find(node_to_elem_map, node->id()))
201 : {
202 13920 : bool has_block = functor->hasBlocks(from_mesh.elemPtr(eid)->subdomain_id());
203 13920 : if (has_block)
204 12576 : on_at_least_one_block = true;
205 : else
206 1344 : on_boundary = true;
207 : // Detect a mesh boundary
208 13920 : const auto elem = from_mesh.elemPtr(eid);
209 69600 : for (const auto side : elem->side_index_range())
210 69120 : if (!elem->neighbor_ptr(side) &&
211 13440 : elem->is_node_on_side(elem->get_node_index(node), side))
212 6720 : on_boundary = true;
213 : }
214 :
215 : // Not on a boundary
216 5376 : if (!on_at_least_one_block || !on_boundary)
217 3244 : continue;
218 :
219 3168 : if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, node))
220 0 : continue;
221 :
222 3168 : if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, node))
223 0 : continue;
224 :
225 : // Handle the various source mesh divisions behaviors
226 : // NOTE: This could be more efficient, as instead of rejecting points in the
227 : // wrong division, we could just be adding them to the tree for the right division
228 3168 : if (!_from_mesh_divisions.empty())
229 : {
230 528 : const auto tree_division_index = i_source % getNumDivisions();
231 528 : const auto node_div_index = _from_mesh_divisions[i_from]->divisionIndex(*node);
232 :
233 : // Spatial restriction is always active
234 528 : if (node_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
235 0 : continue;
236 : // We fill one tree per division index for matching subapp index or division index. We
237 : // only accept source data from the division index
238 528 : else if ((_from_mesh_division_behavior ==
239 528 : MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
240 528 : _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
241 528 : _from_mesh_division_behavior ==
242 1056 : MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
243 : tree_division_index != node_div_index)
244 396 : continue;
245 : }
246 :
247 : // Transformed node is in the reference space, as is the _nearest_positions_obj
248 2772 : const auto transformed_node = (*_from_transforms[getGlobalSourceAppIndex(i_from)])(*node);
249 :
250 : // Only add to the KDTree nodes that are closest to the 'position'
251 : // When querying values at a target point, the KDTree associated to the closest
252 : // position to the target point is queried
253 : // We do not need to check the positions when using nearest app as we will assume
254 : // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
255 : // than to any other
256 4052 : if (!_use_nearest_app && _nearest_positions_obj &&
257 1280 : !closestToPosition(i_pos, transformed_node))
258 640 : continue;
259 :
260 2132 : _local_points[i_source].push_back(transformed_node);
261 :
262 : // Evaluate the functor on the boundary node
263 2132 : Moose::NodeArg node_arg = {node, &from_blocks};
264 2132 : Moose::StateArg time_arg(0, Moose::SolutionIterationType::Time);
265 2132 : _local_values[i_source].push_back((*functor)(node_arg, time_arg));
266 249 : }
267 : // Nearest-element option
268 : // We also use this for 'evaluate_oob', which will influence the distance found and used to
269 : // select the nearest value from out of bounds evaluations. It will not influence the result
270 : // of the evaluation
271 : else
272 128492 : for (const auto & elem : from_mesh.getMesh().local_element_ptr_range())
273 : {
274 : // No way to check number of dofs for a functor
275 63716 : if (!functor->hasBlocks(elem->subdomain_id()))
276 24148 : continue;
277 :
278 : // Make sure sure it is at a boundary, for either the mesh or a functor
279 63380 : bool at_a_boundary = false;
280 239842 : for (const auto side : elem->side_index_range())
281 : {
282 : // Boundary of the mesh
283 219334 : if (!elem->neighbor_ptr(side))
284 : {
285 42776 : at_a_boundary = true;
286 42776 : break;
287 : }
288 : // Boundary of the domain of definition of the functor
289 176558 : else if (!functor->hasBlocks(elem->neighbor_ptr(side)->subdomain_id()))
290 : {
291 96 : at_a_boundary = true;
292 96 : break;
293 : }
294 : }
295 : // Non boundary elements are not relevant as we can just evaluate the functor
296 63380 : if (!at_a_boundary)
297 20508 : continue;
298 :
299 42872 : if (!_from_blocks.empty() && !inBlocks(_from_blocks, from_mesh, elem))
300 2528 : continue;
301 :
302 40344 : if (!_from_boundaries.empty() && !onBoundaries(_from_boundaries, from_mesh, elem))
303 0 : continue;
304 :
305 : // Handle the various source mesh divisions behaviors
306 : // NOTE: This could be more efficient, as instead of rejecting points in the
307 : // wrong division, we could just be adding them to the tree for the right division
308 40344 : if (!_from_mesh_divisions.empty())
309 : {
310 352 : const auto tree_division_index = i_source % getNumDivisions();
311 : const auto node_div_index =
312 352 : _from_mesh_divisions[i_from]->divisionIndex(elem->vertex_average());
313 :
314 : // Spatial restriction is always active
315 352 : if (node_div_index == MooseMeshDivision::INVALID_DIVISION_INDEX)
316 0 : continue;
317 : // We fill one tree per division index for matching subapp index or division index. We
318 : // only accept source data from the division index
319 352 : else if ((_from_mesh_division_behavior ==
320 352 : MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
321 352 : _to_mesh_division_behavior == MeshDivisionTransferUse::MATCH_DIVISION_INDEX ||
322 352 : _from_mesh_division_behavior ==
323 704 : MeshDivisionTransferUse::MATCH_SUBAPP_INDEX) &&
324 : tree_division_index != node_div_index)
325 264 : continue;
326 : }
327 :
328 : // Transformed centroid is in the reference space, as is the _nearest_positions_obj
329 : const auto transformed_centroid =
330 40080 : (*_from_transforms[getGlobalSourceAppIndex(i_from)])(elem->vertex_average());
331 :
332 : // Only add to the KDTree nodes that are closest to the 'position'
333 : // When querying values at a target point, the KDTree associated to the closest
334 : // position to the target point is queried
335 : // We do not need to check the positions when using nearest app as we will assume
336 : // (somewhat incorrectly) that all the points in each subapp are closer to that subapp
337 : // than to any other
338 41104 : if (!_use_nearest_app && _nearest_positions_obj &&
339 1024 : !closestToPosition(i_pos, transformed_centroid))
340 512 : continue;
341 :
342 39568 : _local_points[i_source].push_back(transformed_centroid);
343 :
344 : // Evaluate the functor on the boundary node
345 39568 : Moose::ElemArg elem_arg = {elem, /*sknewness*/ false};
346 39568 : Moose::StateArg time_arg(0, Moose::SolutionIterationType::Time);
347 39568 : _local_values[i_source].push_back((*functor)(elem_arg, time_arg));
348 1060 : }
349 :
350 1309 : max_leaf_size = std::max(max_leaf_size, from_mesh.getMaxLeafSize());
351 1309 : }
352 :
353 : // Make a KDTree from the accumulated points data
354 : std::shared_ptr<KDTree> _kd_tree =
355 1269 : std::make_shared<KDTree>(_local_points[i_source], max_leaf_size);
356 1269 : _local_kdtrees[i_source] = _kd_tree;
357 1269 : }
358 804 : }
359 :
360 : void
361 1194 : MultiAppGeneralFieldFunctorTransfer::evaluateInterpValues(
362 : const unsigned int var_index,
363 : const std::vector<std::pair<Point, unsigned int>> & incoming_points,
364 : std::vector<std::pair<Real, Real>> & outgoing_vals)
365 : {
366 1194 : evaluateValues(var_index, incoming_points, outgoing_vals);
367 1194 : }
368 :
369 : void
370 1194 : MultiAppGeneralFieldFunctorTransfer::evaluateValues(
371 : const unsigned int var_index,
372 : const std::vector<std::pair<Point, unsigned int>> & incoming_points,
373 : std::vector<std::pair<Real, Real>> & outgoing_vals)
374 : {
375 1194 : dof_id_type i_pt = 0;
376 1194 : std::set<const Elem *> elem_candidates;
377 1194 : const auto num_apps_per_tree = getNumAppsPerTree();
378 :
379 110484 : for (const auto & [pt, mesh_div] : incoming_points)
380 : {
381 : // Reset distance
382 109290 : outgoing_vals[i_pt].second = std::numeric_limits<Real>::max();
383 109290 : bool point_found = false;
384 :
385 : // Loop on all sources: locate the point and evaluate the functor if it is in-domain.
386 : // Extrapolation (all modes) is handled after this loop, only when no in-domain hit is found.
387 261661 : for (const auto i_source : make_range(_num_sources))
388 : {
389 : // Examine all restrictions for the point. This source (KDTree+values) could be ruled out
390 152371 : if (!checkRestrictionsForSource(pt, mesh_div, i_source))
391 7568 : continue;
392 : // Note: because this transfer is intended for extrapolation,
393 : // this will usually not restrict the source. The distance comparisons will be crucial
394 :
395 : // Inner loop: when group_subapps is true, multiple apps contribute to the same source
396 : // (one KD-tree per position). Mirror the structure of buildKDTrees.
397 290826 : for (const auto app_i : make_range(num_apps_per_tree))
398 : {
399 146023 : const auto app_index = getAppIndex(i_source, app_i);
400 :
401 : // Point locators and functor evaluation work in each app's local frame
402 : const Point app_local_pt =
403 292046 : getPointInSourceAppFrame(pt, app_index, "Functor value evaluation");
404 :
405 : // Retrieve the functor
406 146023 : const auto & functor = *_functors[app_index][var_index];
407 :
408 : // Get the intersection of the functor and transfer source block restrictions
409 146023 : std::set<SubdomainID> from_blocks;
410 146023 : for (const auto bl :
411 146023 : _from_blocks.size()
412 146023 : ? _from_blocks
413 495186 : : _from_problems[app_index]->mesh().getMesh().get_mesh_subdomains())
414 203140 : if (functor.hasBlocks(bl))
415 202304 : from_blocks.insert(bl);
416 :
417 : // If in domain, use the functor evaluation at the point
418 : // Locate the point and an element
419 : // Clear the nearest candidates
420 146023 : elem_candidates.clear();
421 146023 : if (from_blocks.size())
422 146023 : (*_point_locators[app_index])(app_local_pt, elem_candidates, &from_blocks);
423 : else
424 0 : (*_point_locators[app_index])(app_local_pt, elem_candidates);
425 146023 : if (elem_candidates.size())
426 : {
427 : // Register conflict if any
428 68084 : if (point_found && _search_value_conflicts)
429 : {
430 : // In the nearest-position/app mode, we save conflicts in the reference frame
431 18 : if (_nearest_positions_obj)
432 0 : registerConflict(i_source, /*dof*/ 0, pt, 0, true);
433 : else
434 18 : registerConflict(i_source, 0, app_local_pt, 0, true);
435 : }
436 :
437 : // Average the result for now
438 : // TODO: if we knew the functor were continuous, we could return earlier
439 68084 : Real value = 0;
440 68084 : unsigned int num_values = 0;
441 143897 : for (const auto elem : elem_candidates)
442 : {
443 : // Variables would hit a ghosting error; compare against the sub-app communicator rank,
444 : // not the parent communicator rank - each sub-app runs in its own sub-communicator
445 : // where ranks start at 0, regardless of the global rank of the owning process
446 156462 : if (_functor_is_variable[var_index] &&
447 80649 : elem->processor_id() != _from_problems[app_index]->processor_id())
448 1816 : continue;
449 : // Avoid evaluating outside of element
450 75325 : if (!elem->contains_point(app_local_pt, libMesh::TOLERANCE * libMesh::TOLERANCE))
451 1328 : continue;
452 73997 : Moose::ElemPointArg elem_pt_arg = {elem, app_local_pt, /*correct skewness*/ false};
453 73997 : Moose::StateArg time_arg(0, Moose::SolutionIterationType::Time);
454 73997 : value += functor(elem_pt_arg, time_arg);
455 73997 : num_values++;
456 : }
457 68084 : if (num_values == 0)
458 728 : continue;
459 :
460 67356 : value /= num_values;
461 67356 : point_found = true;
462 67356 : outgoing_vals[i_pt] = {value, 0};
463 : }
464 146023 : }
465 : }
466 :
467 : // Extrapolation: only reached when no in-domain functor evaluation was found.
468 : // flat: return OutOfMeshValue; the base class post-transfer step handles
469 : // any user-specified constant or nearest-node fill on the target mesh
470 : // evaluate_oob: find the nearest boundary point via KD-tree, then evaluate the functor
471 : // there (out-of-bounds evaluation with a nullptr element)
472 : // nearest-node / nearest-elem: delegate to the shared KD-tree method on the base class,
473 : // which also handles search_value_conflicts detection
474 109290 : if (!point_found)
475 : {
476 41952 : if (_extrapolation_behavior == 0) /*flat*/
477 : // The base class will take care of replacing the value
478 37387 : outgoing_vals[i_pt] = {GeneralFieldTransfer::OutOfMeshValue,
479 74774 : GeneralFieldTransfer::OutOfMeshValue};
480 :
481 4565 : else if (_extrapolation_behavior == 1) /*evaluate_oob*/
482 0 : for (const auto i_source : make_range(_num_sources))
483 : {
484 0 : if (!checkRestrictionsForSource(pt, mesh_div, i_source))
485 0 : continue;
486 :
487 : // TODO: Pre-allocate these two work arrays. They will be regularly resized by the
488 : // searches
489 0 : std::vector<std::size_t> return_index(_num_nearest_points);
490 0 : std::vector<Real> return_dist_sqr(_num_nearest_points);
491 :
492 : // KD-tree neighbor search uses global pt (KD-trees store global coords);
493 : // functor evaluation needs the per-app local coordinate
494 0 : const auto first_app = getAppIndex(i_source, 0);
495 : const Point oob_local_pt =
496 0 : getPointInSourceAppFrame(pt, first_app, "Out-of-bounds functor extrapolation");
497 0 : const auto & functor = *_functors[first_app][var_index];
498 :
499 0 : if (_local_kdtrees[i_source]->numberCandidatePoints())
500 : {
501 0 : point_found = true;
502 0 : _local_kdtrees[i_source]->neighborSearch(
503 : pt, _num_nearest_points, return_index, return_dist_sqr);
504 0 : Real dist_sum = 0;
505 0 : for (const auto index : return_index)
506 0 : dist_sum += (_local_points[i_source][index] - pt).norm();
507 :
508 0 : const auto new_distance = dist_sum / return_dist_sqr.size();
509 0 : if (new_distance < outgoing_vals[i_pt].second)
510 : {
511 0 : Moose::ElemPointArg elem_pt_arg = {nullptr, oob_local_pt, /*correct skewness*/ false};
512 0 : Moose::StateArg time_arg(0, Moose::SolutionIterationType::Time);
513 0 : outgoing_vals[i_pt] = {functor(elem_pt_arg, time_arg), new_distance};
514 : }
515 : }
516 0 : }
517 6340 : else if (_extrapolation_behavior == 2 /*nearest-node*/ ||
518 1775 : _extrapolation_behavior == 3 /*nearest-elem*/)
519 4565 : evaluateNearestNodeFromKDTrees(pt, mesh_div, outgoing_vals[i_pt], point_found);
520 : else
521 : mooseAssert(false,
522 : "Unexpected extrapolation behavior '" << std::to_string(_extrapolation_behavior)
523 : << "'");
524 : }
525 :
526 : // Move to next point
527 109290 : i_pt++;
528 : }
529 1194 : }
|