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 "ElementSubdomainModifierBase.h"
11 : #include "DisplacedProblem.h"
12 : #include "MaterialWarehouse.h"
13 :
14 : #include "libmesh/dof_map.h"
15 : #include "libmesh/remote_elem.h"
16 : #include "libmesh/parallel_ghost_sync.h"
17 : #include "libmesh/numeric_vector.h"
18 : #include "libmesh/parameters.h"
19 : #include <iterator>
20 : #include <unordered_set>
21 :
22 : InputParameters
23 44297 : ElementSubdomainModifierBase::validParams()
24 : {
25 44297 : InputParameters params = ElementUserObject::validParams();
26 :
27 : // ESMs only operate on the undisplaced mesh to gather subdomain and sideset information. This
28 : // information is used to modify both the undisplaced and the displaced meshes. It is the
29 : // developer's responsibility to make sure the element IDs and sideset info are consistent across
30 : // both meshes.
31 88594 : params.set<bool>("use_displaced_mesh") = false;
32 88594 : params.suppressParameter<bool>("use_displaced_mesh");
33 :
34 177188 : params.addParam<std::vector<BoundaryName>>(
35 : "moving_boundaries",
36 : {},
37 : "Moving boundaries between subdomains. These boundaries (both sidesets and nodesets) will be "
38 : "updated for elements that change subdomain. The subdomains that each moving "
39 : "boundary lies between shall be specified using the parameter "
40 : "'moving_boundary_subdomain_pairs'. If one boundary and multiple subdomain pairs are "
41 : "specified, then it is assumed that the pairs all apply to the boundary. A boundary will be "
42 : "created on the mesh if it does not already exist.");
43 177188 : params.addParam<std::vector<std::vector<SubdomainName>>>(
44 : "moving_boundary_subdomain_pairs",
45 : {},
46 : "The subdomain pairs associated with each moving boundary. For each pair of subdomains, only "
47 : "the element side from the first subdomain will be added to the moving boundary, i.e., the "
48 : "side normal is pointing from the first subdomain to the second subdomain. The pairs shall "
49 : "be delimited by ';'. If a pair only has one subdomain, the moving boundary is associated "
50 : "with the subdomain's external boundary, i.e., when the elements have no neighboring "
51 : "elements.");
52 :
53 221485 : params.addParam<std::vector<SubdomainName>>(
54 : "reinitialize_subdomains",
55 : {"ANY_BLOCK_ID"},
56 : "By default, any element which changes subdomain is reinitialized. If a list of subdomains "
57 : "(IDs or names) is provided, then only elements whose new subdomain is in the list will be "
58 : "reinitialized. If an empty list is set, then no elements will be reinitialized.");
59 :
60 132891 : params.addParam<bool>(
61 : "old_subdomain_reinitialized",
62 88594 : true,
63 : "This parameter must be set with a non-empty list in 'reinitialize_subdomains'. When set to "
64 : "the default true, the element's old subdomain is not considered when determining if an "
65 : "element should be reinitialized. If set to false, only elements whose old subdomain was not "
66 : "in 'reinitialize_subdomains' are reinitialized. ");
67 :
68 177188 : params.addParam<std::vector<VariableName>>(
69 : "reinitialize_variables", {}, "Which variables to reinitialize when subdomain changes.");
70 177188 : MooseEnum reinit_strategy("IC POLYNOMIAL_NEIGHBOR POLYNOMIAL_WHOLE POLYNOMIAL_NEARBY", "IC");
71 221485 : params.addParam<std::vector<MooseEnum>>(
72 : "reinitialization_strategy",
73 : {reinit_strategy},
74 : "The strategy used to reinitialize the solution when elements change subdomain. If multiple "
75 : "strategies are provided, each strategy will be applied to the corresponding variable. If "
76 : "only one strategy is provided, it will be applied to all variables.");
77 177188 : params.addParam<std::vector<UserObjectName>>(
78 : "polynomial_fitters",
79 : {},
80 : "List of NodalPatchRecovery UserObjects used for polynomial fitting during variable "
81 : "reinitialization. Required only if 'reinitialization_strategy' includes "
82 : "POLYNOMIAL_NEIGHBOR, POLYNOMIAL_WHOLE, or POLYNOMIAL_NEARBY.");
83 132891 : params.addParam<int>(
84 : "nearby_kd_tree_leaf_max_size",
85 88594 : 10,
86 : "Maximum number of elements in a leaf node of the K-D tree used to search for nearby "
87 : "elements. Only needed if 'reinitialization_strategy' is set to POLYNOMIAL_NEARBY.");
88 177188 : params.addParam<double>(
89 : "nearby_distance_threshold",
90 88594 : -1.0,
91 : "Threshold for considering elements as 'nearby' in the K-D tree search. Only elements within "
92 : "this distance will be considered for polynomial fitting.");
93 177188 : params.addParam<std::vector<bool>>(
94 : "restore_overridden_dofs",
95 : {},
96 : "A list of boolean flags, one for each variable in 'reinitialize_variables', specifying "
97 : "whether overridden DOF values should be restored after reinitialization for each variable. "
98 : "This is useful when the solved values on these DOFs should be preserved. If the list is "
99 : "empty, overridden DOF values will NOT be restored for any variable by default.");
100 :
101 132891 : params.addParam<bool>("skip_restore_subdomain_changes",
102 88594 : false,
103 : "Skip restoring the subdomain changes if the timestep is not advanced.");
104 :
105 44297 : params.registerBase("MeshModifier");
106 :
107 88594 : return params;
108 132891 : }
109 :
110 779 : ElementSubdomainModifierBase::ElementSubdomainModifierBase(const InputParameters & parameters)
111 : : ElementUserObject(parameters),
112 779 : _displaced_problem(_fe_problem.getDisplacedProblem().get()),
113 779 : _displaced_mesh(_displaced_problem ? &_displaced_problem->mesh() : nullptr),
114 779 : _nl_sys(_fe_problem.getNonlinearSystemBase(systemNumber())),
115 779 : _aux_sys(_fe_problem.getAuxiliarySystem()),
116 1558 : _t_step_old(declareRestartableData<int>("t_step_old", 0)),
117 779 : _restep(false),
118 1558 : _skip_restore_subdomain_changes(getParam<bool>("skip_restore_subdomain_changes")),
119 1558 : _old_subdomain_reinitialized(getParam<bool>("old_subdomain_reinitialized")),
120 1558 : _pr_names(getParam<std::vector<UserObjectName>>("polynomial_fitters")),
121 1558 : _reinit_vars(getParam<std::vector<VariableName>>("reinitialize_variables")),
122 1558 : _leaf_max_size(getParam<int>("nearby_kd_tree_leaf_max_size")),
123 7790 : _nearby_distance_threshold(getParam<double>("nearby_distance_threshold"))
124 : {
125 779 : }
126 :
127 : void
128 770 : ElementSubdomainModifierBase::initialSetup()
129 : {
130 : const std::vector<SubdomainName> subdomain_names_to_reinitialize =
131 1540 : getParam<std::vector<SubdomainName>>("reinitialize_subdomains");
132 :
133 770 : if (std::find(subdomain_names_to_reinitialize.begin(),
134 : subdomain_names_to_reinitialize.end(),
135 1540 : "ANY_BLOCK_ID") != subdomain_names_to_reinitialize.end())
136 465 : _subdomain_ids_to_reinitialize.push_back(Moose::ANY_BLOCK_ID);
137 : else
138 305 : _subdomain_ids_to_reinitialize = _mesh.getSubdomainIDs(subdomain_names_to_reinitialize);
139 :
140 : std::set<SubdomainID> set_subdomain_ids_to_reinitialize(_subdomain_ids_to_reinitialize.begin(),
141 770 : _subdomain_ids_to_reinitialize.end());
142 :
143 1242 : if (_old_subdomain_reinitialized == false &&
144 236 : (std::find(_subdomain_ids_to_reinitialize.begin(),
145 : _subdomain_ids_to_reinitialize.end(),
146 1242 : Moose::ANY_BLOCK_ID) != _subdomain_ids_to_reinitialize.end() ||
147 236 : set_subdomain_ids_to_reinitialize == _mesh.meshSubdomains()))
148 0 : paramError("old_subdomain_reinitialized",
149 : "'old_subdomain_reinitialized' can only be set to false if "
150 : "reinitialize_subdomains does "
151 : "not cover the whole model, otherwise no elements will be reinitialized as it is "
152 : "impossible for an element's old subdomain to not be in the list.");
153 770 : else if (_old_subdomain_reinitialized == false && _subdomain_ids_to_reinitialize.empty())
154 0 : paramError("old_subdomain_reinitialized",
155 : "'old_subdomain_reinitialized' can only be set to false if "
156 : "reinitialize_subdomains is set to a non-empty list of subdomains, otherwise no "
157 : "elements will be reinitialized, as it is impossible for an element's new subdomain "
158 : "to be in the list.");
159 :
160 1540 : auto bnd_names = getParam<std::vector<BoundaryName>>("moving_boundaries");
161 770 : auto bnd_ids = _mesh.getBoundaryIDs(bnd_names, true);
162 : const auto bnd_subdomains =
163 1540 : getParam<std::vector<std::vector<SubdomainName>>>("moving_boundary_subdomain_pairs");
164 :
165 770 : if (bnd_names.size() == 1 && bnd_subdomains.size() > 1)
166 : {
167 226 : bnd_names.insert(bnd_names.end(), bnd_subdomains.size() - 1, bnd_names[0]);
168 226 : bnd_ids.insert(bnd_ids.end(), bnd_subdomains.size() - 1, bnd_ids[0]);
169 : }
170 544 : else if (bnd_names.size() != bnd_subdomains.size())
171 0 : paramError("moving_boundary_subdomain_pairs",
172 : "Each moving boundary must correspond to a pair of subdomains. ",
173 : bnd_names.size(),
174 : " boundaries are specified by the parameter 'moving_boundaries', while ",
175 : bnd_subdomains.size(),
176 : " subdomain pairs are provided. Alternatively, if one boundary and multiple "
177 : "subdomain pairs are provided, then the subdomain pairs all apply to one boundary.");
178 :
179 1478 : for (auto i : index_range(bnd_names))
180 : {
181 708 : _moving_boundary_names[bnd_ids[i]] = bnd_names[i];
182 :
183 708 : if (bnd_subdomains[i].size() == 2)
184 426 : _moving_boundaries[{_mesh.getSubdomainID(bnd_subdomains[i][0]),
185 852 : _mesh.getSubdomainID(bnd_subdomains[i][1])}] = bnd_ids[i];
186 282 : else if (bnd_subdomains[i].size() == 1)
187 282 : _moving_boundaries[{_mesh.getSubdomainID(bnd_subdomains[i][0]), Moose::INVALID_BLOCK_ID}] =
188 282 : bnd_ids[i];
189 : else
190 0 : paramError("moving_boundary_subdomain_pairs",
191 : "Each subdomain pair must contain 1 or 2 subdomain names, but ",
192 0 : bnd_subdomains[i].size(),
193 : " are given.");
194 : }
195 :
196 : // Check if the variables to reinitialize exist in the system
197 1180 : for (const auto & var_name : _reinit_vars)
198 410 : if (!_nl_sys.hasVariable(var_name) && !_aux_sys.hasVariable(var_name))
199 0 : paramError("reinitialize_variables", "Variable ", var_name, " does not exist.");
200 :
201 : // Determine the reinitialization strategy for each variable.
202 : // (1) If they are of the same size, we perform a 1-to-1 mapping.
203 : // (2) If only one strategy or restore flag is provided, it applies to all variables.
204 1540 : const auto reinit_strategy_in = getParam<std::vector<MooseEnum>>("reinitialization_strategy");
205 1540 : const auto restore_overridden_dofs_in = getParam<std::vector<bool>>("restore_overridden_dofs");
206 :
207 770 : if (std::any_of(reinit_strategy_in.begin(),
208 : reinit_strategy_in.end(),
209 2498 : [](const MooseEnum & val) { return val == "POLYNOMIAL_NEARBY"; }) &&
210 854 : !isParamSetByUser("nearby_distance_threshold"))
211 0 : mooseError("The 'nearby_distance_threshold' parameter must be set when using the "
212 : "POLYNOMIAL_NEARBY reinitialization strategy.");
213 :
214 770 : if (reinit_strategy_in.size() == 1)
215 692 : _reinit_strategy.resize(_reinit_vars.size(), reinit_strategy_in[0].getEnum<ReinitStrategy>());
216 78 : else if (reinit_strategy_in.size() == _reinit_vars.size())
217 300 : for (const auto & e : reinit_strategy_in)
218 226 : _reinit_strategy.push_back(e.getEnum<ReinitStrategy>());
219 : else
220 8 : paramError(
221 : "reinitialization_strategy",
222 : "The 'reinitialization_strategy' parameter must have either a single value or a number "
223 : "of values equal to the number of 'reinitialize_variables'. "
224 : "Got ",
225 : reinit_strategy_in.size(),
226 : " strategies for ",
227 : _reinit_vars.size(),
228 : " variables.");
229 :
230 766 : if (restore_overridden_dofs_in.size() == 1)
231 : {
232 190 : if (restore_overridden_dofs_in[0])
233 : _vars_to_restore_overridden_dofs =
234 190 : _reinit_vars; // Restore overridden DOFs for all reinitialized variables
235 : }
236 576 : else if (restore_overridden_dofs_in.size() == _reinit_vars.size())
237 : {
238 614 : for (auto i : index_range(_reinit_vars))
239 56 : if (restore_overridden_dofs_in[i])
240 28 : _vars_to_restore_overridden_dofs.push_back(_reinit_vars[i]);
241 : }
242 : else
243 : {
244 18 : if (!restore_overridden_dofs_in.empty())
245 8 : paramError(
246 : "restore_overridden_dofs",
247 : "The 'restore_overridden_dofs' parameter must have either a single value or a number "
248 : "of values equal to the number of 'reinitialize_variables'. "
249 : "Got ",
250 : restore_overridden_dofs_in.size(),
251 : " restore_overridden_dofs for ",
252 : _reinit_vars.size(),
253 : " variables.");
254 : }
255 :
256 : // For all the other variables, we set the reinitialization strategy to IC
257 2795 : for (const auto & var_name : _fe_problem.getVariableNames())
258 2033 : if (std::find(_reinit_vars.begin(), _reinit_vars.end(), var_name) == _reinit_vars.end())
259 : {
260 1647 : _reinit_vars.push_back(var_name);
261 1647 : _reinit_strategy.push_back(ReinitStrategy::IC);
262 762 : }
263 :
264 : // Map variable names to the index of the nodal patch recovery user object
265 762 : _pr.resize(_pr_names.size());
266 762 : std::size_t pr_count = 0;
267 2787 : for (auto i : index_range(_reinit_vars))
268 2029 : if (_reinit_strategy[i] == ReinitStrategy::POLYNOMIAL_NEIGHBOR ||
269 3788 : _reinit_strategy[i] == ReinitStrategy::POLYNOMIAL_WHOLE ||
270 1759 : _reinit_strategy[i] == ReinitStrategy::POLYNOMIAL_NEARBY)
271 : {
272 298 : _var_name_to_pr_idx[_reinit_vars[i]] = pr_count;
273 298 : if (pr_count >= _pr_names.size())
274 8 : paramError("polynomial_fitters",
275 : "The number of polynomial fitters (",
276 : _pr_names.size(),
277 : ") is less than the number of variables to reinitialize with polynomial "
278 : "extrapolation.");
279 588 : _pr[pr_count] =
280 294 : &_fe_problem.getUserObject<NodalPatchRecoveryVariable>(_pr_names[pr_count], /*tid=*/0);
281 : mooseAssert(_pr[pr_count]->variableName() == _reinit_vars[i],
282 : "The patch recovery UserObject's variable name must match the variable being "
283 : "reinitialized in ElementSubdomainModifierBase.");
284 294 : _depend_uo.insert(_pr_names[pr_count]);
285 294 : pr_count++;
286 : }
287 758 : if (_pr_names.size() != pr_count)
288 8 : paramError("polynomial_fitters",
289 : "Mismatch between number of reinitialization strategies using polynomial "
290 : "extrapolation and polynomial fitters (expected: ",
291 : pr_count,
292 : ", given: ",
293 : _pr_names.size(),
294 : ").");
295 754 : }
296 :
297 : void
298 3497 : ElementSubdomainModifierBase::timestepSetup()
299 : {
300 3497 : if (_t_step == _t_step_old && !_skip_restore_subdomain_changes)
301 : {
302 29 : mooseInfoRepeated(name(), ": Restoring element subdomain changes.");
303 :
304 : // Reverse the subdomain changes
305 29 : auto moved_elem_reversed = _moved_elems;
306 617 : for (auto & [elem_id, subdomain] : moved_elem_reversed)
307 588 : std::swap(subdomain.first, subdomain.second);
308 :
309 29 : _restep = true;
310 29 : modify(moved_elem_reversed);
311 29 : _restep = false;
312 29 : }
313 :
314 3497 : _t_step_old = _t_step;
315 3497 : }
316 :
317 : void
318 3842 : ElementSubdomainModifierBase::modify(
319 : const std::unordered_map<dof_id_type, std::pair<SubdomainID, SubdomainID>> & moved_elems)
320 : {
321 3842 : if (!_restep)
322 : // Cache the moved elements for potential restore
323 3813 : _moved_elems = moved_elems;
324 :
325 : // If nothing need to change, just return.
326 : // This will skip all mesh changes, and so no adaptivity mesh files will be written.
327 3842 : auto n_moved_elem = moved_elems.size();
328 3842 : gatherSum(n_moved_elem);
329 3842 : if (n_moved_elem == 0)
330 1623 : return;
331 :
332 : // Create moving boundaries on the undisplaced and displaced meshes
333 : //
334 : // Note: We do this _everytime_ because previous execution might have removed the sidesets and
335 : // nodesets. Most of the moving boundary algorithms below assume that the moving sidesets and
336 : // nodesets already exist on the mesh.
337 2219 : createMovingBoundaries(_mesh);
338 2219 : if (_displaced_mesh)
339 124 : createMovingBoundaries(*_displaced_mesh);
340 :
341 : // This has to be done *before* subdomain changes are applied
342 2219 : findReinitializedElemsAndNodes(moved_elems);
343 :
344 : // Apply cached subdomain changes
345 2219 : applySubdomainChanges(moved_elems, _mesh);
346 2219 : if (_displaced_mesh)
347 124 : applySubdomainChanges(moved_elems, *_displaced_mesh);
348 :
349 : // Update moving boundaries
350 2219 : gatherMovingBoundaryChanges(moved_elems);
351 2219 : applyMovingBoundaryChanges(_mesh);
352 2219 : if (_displaced_mesh)
353 124 : applyMovingBoundaryChanges(*_displaced_mesh);
354 :
355 : // Some variable reinitialization strategies require patch elements to be gathered
356 : // This has to be done *before* reinitializing the equation systems because we need to find
357 : // currently evaluable elements
358 2219 : if (!_restep)
359 : {
360 2191 : _evaluable_elems.clear();
361 2191 : _patch_elem_ids.clear();
362 7989 : for (auto i : index_range(_reinit_vars))
363 5798 : prepareVariableForReinitialization(_reinit_vars[i], _reinit_strategy[i]);
364 : }
365 :
366 : // Reinit equation systems
367 2219 : _fe_problem.meshChanged(
368 : /*intermediate_change=*/false, /*contract_mesh=*/false, /*clean_refinement_flags=*/false);
369 :
370 : // Initialize solution and stateful material properties
371 2219 : if (!_restep)
372 : {
373 2191 : applyIC();
374 2191 : if (_fe_problem.getMaterialWarehouse().hasActiveObjects(0))
375 595 : initElementStatefulProps();
376 : }
377 : }
378 :
379 : void
380 2343 : ElementSubdomainModifierBase::createMovingBoundaries(MooseMesh & mesh)
381 : {
382 2343 : auto & bnd_info = mesh.getMesh().get_boundary_info();
383 4085 : for (const auto & [bnd_id, bnd_name] : _moving_boundary_names)
384 : {
385 1742 : bnd_info.sideset_name(bnd_id) = bnd_name;
386 1742 : bnd_info.nodeset_name(bnd_id) = bnd_name;
387 : }
388 2343 : }
389 :
390 : void
391 2343 : ElementSubdomainModifierBase::applySubdomainChanges(
392 : const std::unordered_map<dof_id_type, std::pair<SubdomainID, SubdomainID>> & moved_elems,
393 : MooseMesh & mesh)
394 : {
395 37028 : for (const auto & [elem_id, subdomain] : moved_elems)
396 : {
397 : // Change the element's subdomain ID
398 34685 : auto elem = mesh.elemPtr(elem_id);
399 34685 : const auto & [from, to] = subdomain;
400 : mooseAssert(elem->subdomain_id() == from, "Inconsistent element subdomain ID.");
401 34685 : elem->subdomain_id() = to;
402 :
403 : // Change the ancestors' (if any) subdomain ID
404 34685 : setAncestorsSubdomainIDs(elem, to);
405 : }
406 :
407 : // Synchronize ghost element subdomain changes
408 2343 : libMesh::SyncSubdomainIds sync(mesh.getMesh());
409 2343 : Parallel::sync_dofobject_data_by_id(
410 4686 : mesh.getMesh().comm(), mesh.getMesh().elements_begin(), mesh.getMesh().elements_end(), sync);
411 2343 : }
412 :
413 : void
414 34685 : ElementSubdomainModifierBase::setAncestorsSubdomainIDs(Elem * elem, const SubdomainID subdomain_id)
415 : {
416 34685 : auto curr_elem = elem;
417 :
418 43733 : for (unsigned int i = curr_elem->level(); i > 0; --i)
419 : {
420 : // Change the parent's subdomain
421 9048 : curr_elem = curr_elem->parent();
422 9048 : curr_elem->subdomain_id() = subdomain_id;
423 : }
424 34685 : }
425 :
426 : void
427 2219 : ElementSubdomainModifierBase::gatherMovingBoundaryChanges(
428 : const std::unordered_map<dof_id_type, std::pair<SubdomainID, SubdomainID>> & moved_elems)
429 : {
430 : // Clear moving boundary changes from last execution
431 2219 : _add_element_sides.clear();
432 2219 : _add_neighbor_sides.clear();
433 2219 : _remove_element_sides.clear();
434 2219 : _remove_neighbor_sides.clear();
435 :
436 2219 : const auto & sidesets = _mesh.getMesh().get_boundary_info().get_sideset_map();
437 :
438 36226 : for (const auto & [elem_id, subdomain_assignment] : moved_elems)
439 : {
440 34007 : auto elem = _mesh.elemPtr(elem_id);
441 :
442 : // The existing moving boundaries on the element side should be removed
443 46537 : for (auto itr = sidesets.lower_bound(elem); itr != sidesets.upper_bound(elem); itr++)
444 12530 : if (_moving_boundary_names.count(itr->second.second))
445 3792 : _remove_element_sides[elem->id()].emplace(itr->second.first, itr->second.second);
446 :
447 177483 : for (auto side : elem->side_index_range())
448 : {
449 143476 : auto neigh = elem->neighbor_ptr(side);
450 :
451 : // Don't mess with remote element neighbor
452 143476 : if (neigh && neigh == libMesh::remote_elem)
453 0 : continue;
454 : // If neighbor doesn't exist
455 143476 : else if (!neigh)
456 9863 : gatherMovingBoundaryChangesHelper(elem, side, nullptr, 0);
457 : // If neighbor exists
458 : else
459 : {
460 133613 : auto neigh_side = neigh->which_neighbor_am_i(elem);
461 :
462 133613 : if (neigh->active())
463 130513 : gatherMovingBoundaryChangesHelper(elem, side, neigh, neigh_side);
464 : else
465 : {
466 : // Find the active neighbors of the element
467 3100 : std::vector<const Elem *> active_neighs;
468 : // Neighbor has active children, they are neighbors of the element along that side
469 : mooseAssert(!neigh->subactive(),
470 : "The case where the active neighbor is an ancestor of this neighbor is not "
471 : "handled at this time.");
472 3100 : neigh->active_family_tree_by_neighbor(active_neighs, elem);
473 :
474 9300 : for (auto active_neigh : active_neighs)
475 6200 : gatherMovingBoundaryChangesHelper(elem, side, active_neigh, neigh_side);
476 3100 : }
477 : }
478 : }
479 : }
480 2219 : }
481 :
482 : void
483 146576 : ElementSubdomainModifierBase::gatherMovingBoundaryChangesHelper(const Elem * elem,
484 : unsigned short side,
485 : const Elem * neigh,
486 : unsigned short neigh_side)
487 : {
488 146576 : const auto & sidesets = _mesh.getMesh().get_boundary_info().get_sideset_map();
489 :
490 : // Detect element side change
491 146576 : SubdomainPair subdomain_pair = {elem->subdomain_id(),
492 146576 : neigh ? neigh->subdomain_id() : Moose::INVALID_BLOCK_ID};
493 146576 : if (_moving_boundaries.count(subdomain_pair))
494 11787 : _add_element_sides[elem->id()].emplace(side, _moving_boundaries.at(subdomain_pair));
495 :
496 146576 : if (neigh)
497 : {
498 : // The existing moving boundaries on the neighbor side should be removed
499 184957 : for (auto itr = sidesets.lower_bound(neigh); itr != sidesets.upper_bound(neigh); itr++)
500 48244 : if (itr->second.first == neigh_side && _moving_boundary_names.count(itr->second.second))
501 11633 : _remove_neighbor_sides[neigh->id()].emplace(itr->second.first, itr->second.second);
502 :
503 : // Detect neighbor side change (by reversing the subdomain pair)
504 136713 : subdomain_pair = {subdomain_pair.second, subdomain_pair.first};
505 136713 : if (_moving_boundaries.count(subdomain_pair))
506 3152 : _add_neighbor_sides[neigh->id()].emplace(neigh_side, _moving_boundaries.at(subdomain_pair));
507 : }
508 146576 : }
509 :
510 : void
511 2343 : ElementSubdomainModifierBase::applyMovingBoundaryChanges(MooseMesh & mesh)
512 : {
513 2343 : auto & bnd_info = mesh.getMesh().get_boundary_info();
514 :
515 : // Remove all boundary nodes from the previous moving boundaries
516 2343 : auto nodesets = bnd_info.get_nodeset_map();
517 214551 : for (const auto & [node_id, bnd] : nodesets)
518 212208 : if (_moving_boundary_names.count(bnd))
519 36302 : bnd_info.remove_node(node_id, bnd);
520 :
521 : // Keep track of ghost element changes
522 : std::unordered_map<processor_id_type,
523 : std::vector<std::tuple<dof_id_type, unsigned short, BoundaryID>>>
524 2343 : add_ghost_sides, remove_ghost_sides;
525 :
526 : // Remove element sides from moving boundaries
527 6057 : for (const auto & [elem_id, sides] : _remove_element_sides)
528 7654 : for (const auto & [side, bnd] : sides)
529 3940 : bnd_info.remove_side(mesh.elemPtr(elem_id), side, bnd);
530 :
531 : // Remove neighbor sides from moving boundaries
532 12573 : for (const auto & [elem_id, sides] : _remove_neighbor_sides)
533 : {
534 10230 : auto elem = mesh.elemPtr(elem_id);
535 21863 : for (const auto & [side, bnd] : sides)
536 : {
537 11633 : bnd_info.remove_side(elem, side, bnd);
538 : // Keep track of changes to ghosted elements
539 11633 : if (elem->processor_id() != processor_id())
540 125 : remove_ghost_sides[elem->processor_id()].push_back({elem_id, side, bnd});
541 : }
542 : }
543 :
544 2343 : Parallel::push_parallel_vector_data(
545 2343 : bnd_info.comm(),
546 : remove_ghost_sides,
547 2343 : [&mesh,
548 : &bnd_info](processor_id_type,
549 : const std::vector<std::tuple<dof_id_type, unsigned short, BoundaryID>> & received)
550 : {
551 198 : for (const auto & [elem_id, side, bnd] : received)
552 125 : bnd_info.remove_side(mesh.elemPtr(elem_id), side, bnd);
553 73 : });
554 :
555 : // Add element sides to moving boundaries
556 10843 : for (const auto & [elem_id, sides] : _add_element_sides)
557 20247 : for (const auto & [side, bnd] : sides)
558 11747 : bnd_info.add_side(mesh.elemPtr(elem_id), side, bnd);
559 :
560 : // Add neighbor sides to moving boundaries
561 4767 : for (const auto & [elem_id, sides] : _add_neighbor_sides)
562 : {
563 2424 : auto elem = mesh.elemPtr(elem_id);
564 4950 : for (const auto & [side, bnd] : sides)
565 : {
566 2526 : bnd_info.add_side(elem, side, bnd);
567 : // Keep track of changes to ghosted elements
568 2526 : if (elem->processor_id() != processor_id())
569 13 : add_ghost_sides[elem->processor_id()].push_back({elem_id, side, bnd});
570 : }
571 : }
572 :
573 2343 : Parallel::push_parallel_vector_data(
574 2343 : bnd_info.comm(),
575 : add_ghost_sides,
576 2343 : [&mesh,
577 : &bnd_info](processor_id_type,
578 : const std::vector<std::tuple<dof_id_type, unsigned short, BoundaryID>> & received)
579 : {
580 22 : for (const auto & [elem_id, side, bnd] : received)
581 13 : bnd_info.add_side(mesh.elemPtr(elem_id), side, bnd);
582 9 : });
583 :
584 2343 : bnd_info.parallel_sync_side_ids();
585 2343 : bnd_info.parallel_sync_node_ids();
586 2343 : mesh.update();
587 2343 : }
588 :
589 : void
590 5798 : ElementSubdomainModifierBase::prepareVariableForReinitialization(const VariableName & var_name,
591 : ReinitStrategy reinit_strategy)
592 : {
593 5798 : switch (reinit_strategy)
594 : {
595 5033 : case ReinitStrategy::IC:
596 : // No additional preparation needed for IC
597 5033 : break;
598 765 : case ReinitStrategy::POLYNOMIAL_NEIGHBOR:
599 : case ReinitStrategy::POLYNOMIAL_WHOLE:
600 : case ReinitStrategy::POLYNOMIAL_NEARBY:
601 : {
602 765 : if (_var_name_to_pr_idx.find(var_name) == _var_name_to_pr_idx.end())
603 0 : return;
604 765 : const int pr_idx = _var_name_to_pr_idx[var_name];
605 : // The patch elements might be different for each variable
606 765 : gatherPatchElements(var_name, reinit_strategy);
607 :
608 : // Notify the patch recovery user object about the patch elements
609 765 : _pr[pr_idx]->sync(_patch_elem_ids[var_name]);
610 :
611 765 : break;
612 : }
613 0 : default:
614 0 : mooseError("Unknown reinitialization strategy");
615 : break;
616 : }
617 : }
618 :
619 : void
620 3839 : ElementSubdomainModifierBase::meshChanged()
621 : {
622 : // Clear cached ranges
623 3839 : _reinitialized_elem_range.reset();
624 3839 : _reinitialized_bnd_node_range.reset();
625 3839 : _reinitialized_node_range.reset();
626 :
627 3839 : updateAMRMovingBoundary(_mesh);
628 3839 : if (_displaced_mesh)
629 216 : updateAMRMovingBoundary(*_displaced_mesh);
630 3839 : }
631 :
632 : void
633 4055 : ElementSubdomainModifierBase::updateAMRMovingBoundary(MooseMesh & mesh)
634 : {
635 4055 : auto & bnd_info = mesh.getMesh().get_boundary_info();
636 4055 : auto sidesets = bnd_info.get_sideset_map();
637 290494 : for (const auto & i : sidesets)
638 : {
639 286439 : auto elem = i.first;
640 286439 : auto side = i.second.first;
641 286439 : auto bnd = i.second.second;
642 286439 : if (_moving_boundary_names.count(bnd) && !elem->active())
643 : {
644 5834 : bnd_info.remove_side(elem, side, bnd);
645 :
646 5834 : std::vector<const Elem *> elem_family;
647 5834 : elem->active_family_tree_by_side(elem_family, side);
648 18649 : for (auto felem : elem_family)
649 12815 : bnd_info.add_side(felem, side, bnd);
650 5834 : }
651 : }
652 :
653 4055 : bnd_info.parallel_sync_side_ids();
654 4055 : bnd_info.parallel_sync_node_ids();
655 4055 : }
656 :
657 : void
658 2219 : ElementSubdomainModifierBase::findReinitializedElemsAndNodes(
659 : const std::unordered_map<dof_id_type, std::pair<SubdomainID, SubdomainID>> & moved_elems)
660 : {
661 : // Clear cached element reinitialization data
662 2219 : _reinitialized_elems.clear();
663 2219 : _reinitialized_nodes.clear();
664 :
665 : // One more algorithm:
666 : // (1) Loop over moved elements
667 : // (2) If neighbor element processor ID is not the same as current processor ID (ghost element),
668 : // push the moved element ID to the neighbor processor
669 :
670 2219 : std::unordered_map<processor_id_type, std::unordered_set<dof_id_type>> push_data_set;
671 2219 : std::unordered_map<processor_id_type, std::vector<dof_id_type>> push_data;
672 :
673 36226 : for (const auto & [elem_id, subdomain] : moved_elems)
674 : {
675 : mooseAssert(_mesh.elemPtr(elem_id)->active(), "Moved elements should be active");
676 : // Default: any element that changes subdomain is reinitialized
677 34007 : if (std::find(_subdomain_ids_to_reinitialize.begin(),
678 : _subdomain_ids_to_reinitialize.end(),
679 68014 : Moose::ANY_BLOCK_ID) != _subdomain_ids_to_reinitialize.end())
680 24691 : _reinitialized_elems.insert(elem_id);
681 : else // Reinitialize if new subdomain is in list of subdomains to be reinitialized
682 : {
683 9316 : const auto & [from, to] = subdomain;
684 9316 : if (subdomainIsReinitialized(to) && _old_subdomain_reinitialized)
685 88 : _reinitialized_elems.insert(elem_id);
686 : // Only reinitialize if original subdomain is not in list of subdomains
687 14631 : else if (subdomainIsReinitialized(to) && !_old_subdomain_reinitialized &&
688 5403 : !subdomainIsReinitialized(from))
689 4440 : _reinitialized_elems.insert(elem_id);
690 : else // New subdomain is not in list of subdomains
691 4788 : continue;
692 : }
693 29219 : const auto & elem = _mesh.elemPtr(elem_id);
694 :
695 : // (1) Loop over nodes of moved elements
696 : // (2) node to element map is used to find neighbor elements
697 : // (3) If neighbor element processor ID is not the same as current processor ID (means that the
698 : // current element is ghosted element to the neighbor processor), push the moved element (or
699 : // reinitialized, or newly-activated) ID to the neighbor processor
700 162825 : for (const auto & node : elem->node_ref_range())
701 854651 : for (const auto & neigh_id : _mesh.nodeToElemMap().at(node.id()))
702 721045 : if (neigh_id != elem_id) // Don't check the element itself
703 : {
704 587439 : const auto neigh_elem = _mesh.elemPtr(neigh_id);
705 587439 : if (neigh_elem->processor_id() != processor_id())
706 18295 : push_data_set[neigh_elem->processor_id()].insert(elem_id);
707 : }
708 :
709 162825 : for (unsigned int i = 0; i < elem->n_nodes(); ++i)
710 133606 : if (nodeIsNewlyReinitialized(elem->node_id(i)))
711 16572 : _reinitialized_nodes.insert(elem->node_id(i));
712 : }
713 :
714 2826 : for (auto & [pid, s] : push_data_set)
715 1821 : push_data[pid] = {s.begin(), s.end()};
716 :
717 2219 : _semi_local_reinitialized_elems = _reinitialized_elems;
718 :
719 : auto push_receiver =
720 607 : [this](const processor_id_type, const std::vector<dof_id_type> & received_data)
721 : {
722 2659 : for (const auto & id : received_data)
723 2052 : _semi_local_reinitialized_elems.insert(id);
724 607 : };
725 :
726 2219 : Parallel::push_parallel_vector_data(_mesh.comm(), push_data, push_receiver);
727 2219 : }
728 :
729 : bool
730 220368 : ElementSubdomainModifierBase::subdomainIsReinitialized(SubdomainID id) const
731 : {
732 : // Default: any element that changes subdomain is reinitialized
733 220368 : if (std::find(_subdomain_ids_to_reinitialize.begin(),
734 : _subdomain_ids_to_reinitialize.end(),
735 440736 : Moose::ANY_BLOCK_ID) != _subdomain_ids_to_reinitialize.end())
736 109792 : return true;
737 :
738 : // Is subdomain in list of subdomains to be reinitialized
739 110576 : return std::find(_subdomain_ids_to_reinitialize.begin(),
740 : _subdomain_ids_to_reinitialize.end(),
741 221152 : id) != _subdomain_ids_to_reinitialize.end();
742 : }
743 :
744 : bool
745 133606 : ElementSubdomainModifierBase::nodeIsNewlyReinitialized(dof_id_type node_id) const
746 : {
747 : // If any of the node neighbor elements has reinitialized, then the node is NOT newly
748 : // reinitialized.
749 212993 : for (auto neighbor_elem_id : _mesh.nodeToElemMap().at(node_id))
750 196421 : if (subdomainIsReinitialized(_mesh.elemPtr(neighbor_elem_id)->subdomain_id()))
751 117034 : return false;
752 16572 : return true;
753 : }
754 :
755 : void
756 2191 : ElementSubdomainModifierBase::applyIC()
757 : {
758 : // Before reinitializing variables, some DOFs may be overwritten.
759 : // By default, these overwritten DOF values are NOT restored.
760 : // If the user sets `restore_overridden_dofs` to true, we first save the current
761 : // values of these DOFs, then restore them after reinitialization.
762 3067 : for (const auto & var_name : _vars_to_restore_overridden_dofs)
763 876 : storeOverriddenDofValues(var_name);
764 :
765 : // ReinitStrategy::IC
766 2191 : std::set<VariableName> ic_vars;
767 7989 : for (auto i : index_range(_reinit_vars))
768 5798 : if (_reinit_strategy[i] == ReinitStrategy::IC)
769 5033 : ic_vars.insert(_reinit_vars[i]);
770 2191 : if (!ic_vars.empty())
771 2191 : _fe_problem.projectInitialConditionOnCustomRange(
772 : reinitializedElemRange(), reinitializedBndNodeRange(), ic_vars);
773 :
774 : // ReinitStrategy::POLYNOMIAL_NEIGHBOR, POLYNOMIAL_WHOLE, POLYNOMIAL_NEARBY
775 2956 : for (const auto & [var, patch] : _patch_elem_ids)
776 765 : extrapolatePolynomial(var);
777 :
778 : // See the comment above, now we restore the values of the dofs that were overridden
779 3067 : for (const auto & var_name : _vars_to_restore_overridden_dofs)
780 876 : restoreOverriddenDofValues(var_name);
781 :
782 : mooseAssert(_fe_problem.numSolverSystems() <= 1,
783 : "This code was written for a single nonlinear system");
784 : // Set old and older solutions on the reinitialized dofs to the reinitialized values
785 : // note: from current -> old -> older
786 2191 : setOldAndOlderSolutions(_fe_problem.getNonlinearSystemBase(_sys.number()),
787 : reinitializedElemRange(),
788 : reinitializedBndNodeRange());
789 4382 : setOldAndOlderSolutions(
790 2191 : _fe_problem.getAuxiliarySystem(), reinitializedElemRange(), reinitializedBndNodeRange());
791 :
792 : // Note: Need method to handle solve failures at timesteps where subdomain changes. The old
793 : // solutions are now set to the reinitialized values. Does this impact restoring solutions
794 2191 : }
795 :
796 : void
797 876 : ElementSubdomainModifierBase::storeOverriddenDofValues(const VariableName & var_name)
798 : {
799 876 : const auto & sys = _fe_problem.getSystem(var_name);
800 876 : const auto & current_solution = *sys.current_local_solution;
801 876 : const auto & dof_map = sys.get_dof_map();
802 876 : const auto & var = _fe_problem.getStandardVariable(0, var_name);
803 876 : const auto var_num = var.number();
804 :
805 : // Get the DOFs on the reinitialized elements
806 : // Here we should loop over both ghosted and local reinitialized elements.
807 : // The ghosted elements here can take care of DoFs that is belong to the reinitialized
808 : // elements but are not on the current processor.
809 876 : std::set<dof_id_type> reinitialized_dofs;
810 5964 : for (const auto & elem_id : _semi_local_reinitialized_elems)
811 : {
812 5088 : const auto & elem = _mesh.elemPtr(elem_id);
813 5088 : std::vector<dof_id_type> elem_dofs;
814 5088 : dof_map.dof_indices(elem, elem_dofs, var_num);
815 5088 : reinitialized_dofs.insert(elem_dofs.begin(), elem_dofs.end());
816 5088 : }
817 :
818 : // Get existing DOFs on the active elements excluding reinitialized elements
819 876 : std::set<dof_id_type> existing_dofs;
820 47328 : for (const auto * elem : *_mesh.getActiveLocalElementRange())
821 : {
822 46452 : if (_reinitialized_elems.count(elem->id()))
823 4514 : continue; // Skip reinitialized elements
824 41938 : std::vector<dof_id_type> elem_dofs;
825 41938 : dof_map.dof_indices(elem, elem_dofs, var_num);
826 41938 : existing_dofs.insert(elem_dofs.begin(), elem_dofs.end());
827 41938 : }
828 :
829 : // Get the DOFs on the nodes that are overridden on reinitialized elements
830 876 : std::vector<dof_id_type> overridden_dofs;
831 876 : std::set_intersection(reinitialized_dofs.begin(),
832 : reinitialized_dofs.end(),
833 : existing_dofs.begin(),
834 : existing_dofs.end(),
835 : std::back_inserter(overridden_dofs));
836 :
837 : // Values before overriding (to be restored later)
838 876 : std::vector<Number> values;
839 5180 : for (auto dof : overridden_dofs)
840 4304 : values.push_back(current_solution(dof));
841 :
842 876 : _overridden_values_on_reinit_elems[var_name] = {overridden_dofs, values};
843 876 : }
844 :
845 : void
846 876 : ElementSubdomainModifierBase::restoreOverriddenDofValues(const VariableName & var_name)
847 : {
848 876 : const auto sn = _fe_problem.systemNumForVariable(var_name);
849 876 : auto & sys = _fe_problem.getSystemBase(sn);
850 876 : auto & sol = sys.solution();
851 876 : const auto & dof_map = sys.dofMap();
852 876 : const auto & [dof_ids, values] = _overridden_values_on_reinit_elems[var_name];
853 :
854 876 : std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, Number>>> push_data;
855 :
856 5180 : for (const int i : index_range(dof_ids))
857 : {
858 4304 : if (dof_map.dof_owner(dof_ids[i]) == processor_id())
859 4207 : sol.set(dof_ids[i], values[i]);
860 : else
861 97 : push_data[dof_map.dof_owner(dof_ids[i])].emplace_back(dof_ids[i], values[i]);
862 : }
863 :
864 74 : auto push_receiver = [&](const processor_id_type,
865 : const std::vector<std::pair<dof_id_type, Number>> & received_data)
866 : {
867 171 : for (const auto & [id, value] : received_data)
868 97 : sol.set(id, value);
869 74 : };
870 :
871 876 : Parallel::push_parallel_vector_data(_mesh.comm(), push_data, push_receiver);
872 :
873 876 : sol.close();
874 876 : sol.localize(*sys.system().current_local_solution, sys.dofMap().get_send_list());
875 876 : }
876 :
877 : void
878 595 : ElementSubdomainModifierBase::initElementStatefulProps()
879 : {
880 595 : _fe_problem.initElementStatefulProps(reinitializedElemRange(), /*threaded=*/true);
881 595 : }
882 :
883 : ConstElemRange &
884 10124 : ElementSubdomainModifierBase::reinitializedElemRange()
885 : {
886 10124 : if (_reinitialized_elem_range)
887 7933 : return *_reinitialized_elem_range.get();
888 :
889 : // Create a vector of the newly reinitialized elements
890 2191 : std::vector<Elem *> elems;
891 31242 : for (auto elem_id : _reinitialized_elems)
892 29051 : elems.push_back(_mesh.elemPtr(elem_id));
893 :
894 : // Make some fake element iterators defining this vector of elements
895 2191 : Elem * const * elem_itr_begin = const_cast<Elem * const *>(elems.data());
896 2191 : Elem * const * elem_itr_end = elem_itr_begin + elems.size();
897 :
898 : const auto elems_begin = MeshBase::const_element_iterator(
899 2191 : elem_itr_begin, elem_itr_end, Predicates::NotNull<Elem * const *>());
900 : const auto elems_end = MeshBase::const_element_iterator(
901 2191 : elem_itr_end, elem_itr_end, Predicates::NotNull<Elem * const *>());
902 :
903 2191 : _reinitialized_elem_range = std::make_unique<ConstElemRange>(elems_begin, elems_end);
904 :
905 2191 : return reinitializedElemRange();
906 2191 : }
907 :
908 : ConstBndNodeRange &
909 8764 : ElementSubdomainModifierBase::reinitializedBndNodeRange()
910 : {
911 8764 : if (_reinitialized_bnd_node_range)
912 6573 : return *_reinitialized_bnd_node_range.get();
913 :
914 : // Create a vector of the newly reinitialized boundary nodes
915 2191 : std::vector<const BndNode *> nodes;
916 2191 : auto bnd_nodes = _mesh.getBoundaryNodeRange();
917 204758 : for (auto bnd_node : *bnd_nodes)
918 202567 : if (bnd_node->_node)
919 202567 : if (_reinitialized_nodes.count(bnd_node->_node->id()))
920 4657 : nodes.push_back(bnd_node);
921 :
922 2191 : BndNode * const * bnd_node_itr_begin = const_cast<BndNode * const *>(nodes.data());
923 2191 : BndNode * const * bnd_node_itr_end = bnd_node_itr_begin + nodes.size();
924 :
925 : const auto bnd_nodes_begin = MooseMesh::const_bnd_node_iterator(
926 2191 : bnd_node_itr_begin, bnd_node_itr_end, Predicates::NotNull<const BndNode * const *>());
927 : const auto bnd_nodes_end = MooseMesh::const_bnd_node_iterator(
928 2191 : bnd_node_itr_end, bnd_node_itr_end, Predicates::NotNull<const BndNode * const *>());
929 :
930 : _reinitialized_bnd_node_range =
931 2191 : std::make_unique<ConstBndNodeRange>(bnd_nodes_begin, bnd_nodes_end);
932 :
933 2191 : return reinitializedBndNodeRange();
934 2191 : }
935 :
936 : ConstNodeRange &
937 0 : ElementSubdomainModifierBase::reinitializedNodeRange()
938 : {
939 0 : if (_reinitialized_node_range)
940 0 : return *_reinitialized_node_range.get();
941 :
942 : // Create a vector of the newly reinitialized nodes
943 0 : std::vector<const Node *> nodes;
944 :
945 0 : for (auto node_id : _reinitialized_nodes)
946 0 : nodes.push_back(_mesh.nodePtr(node_id)); // displaced mesh shares the same node object
947 :
948 0 : Node * const * node_itr_begin = const_cast<Node * const *>(nodes.data());
949 0 : Node * const * node_itr_end = node_itr_begin + nodes.size();
950 :
951 : const auto nodes_begin = MeshBase::const_node_iterator(
952 0 : node_itr_begin, node_itr_end, Predicates::NotNull<const Node * const *>());
953 : const auto nodes_end = MeshBase::const_node_iterator(
954 0 : node_itr_end, node_itr_end, Predicates::NotNull<const Node * const *>());
955 :
956 0 : _reinitialized_node_range = std::make_unique<ConstNodeRange>(nodes_begin, nodes_end);
957 :
958 0 : return *_reinitialized_node_range.get();
959 0 : }
960 :
961 : void
962 4382 : ElementSubdomainModifierBase::setOldAndOlderSolutions(SystemBase & sys,
963 : ConstElemRange & elem_range,
964 : ConstBndNodeRange & bnd_node_range)
965 : {
966 13696 : for (auto bnd : bnd_node_range)
967 : {
968 9314 : const Node * bnode = bnd->_node;
969 9314 : if (!bnode)
970 0 : continue;
971 : }
972 :
973 : // Don't do anything if this is a steady simulation
974 4382 : if (!sys.hasSolutionState(1))
975 24 : return;
976 :
977 4358 : NumericVector<Number> & current_solution = *sys.system().current_local_solution;
978 4358 : NumericVector<Number> & old_solution = sys.solutionOld();
979 4358 : NumericVector<Number> * older_solution = sys.hasSolutionState(2) ? &sys.solutionOlder() : nullptr;
980 :
981 : // Get dofs for the reinitialized elements and nodes
982 4358 : DofMap & dof_map = sys.dofMap();
983 4358 : std::vector<dof_id_type> dofs;
984 :
985 62082 : for (auto & elem : elem_range)
986 : {
987 57724 : std::vector<dof_id_type> elem_dofs;
988 57724 : dof_map.dof_indices(elem, elem_dofs);
989 57724 : dofs.insert(dofs.end(), elem_dofs.begin(), elem_dofs.end());
990 57724 : }
991 :
992 13672 : for (auto & bnd_node : bnd_node_range)
993 : {
994 9314 : std::vector<dof_id_type> bnd_node_dofs;
995 9314 : dof_map.dof_indices(bnd_node->_node, bnd_node_dofs);
996 9314 : dofs.insert(dofs.end(), bnd_node_dofs.begin(), bnd_node_dofs.end());
997 9314 : }
998 :
999 : // Set the old and older solutions to match the reinitialization
1000 253213 : for (auto dof : dofs)
1001 : {
1002 248855 : old_solution.set(dof, current_solution(dof));
1003 248855 : if (older_solution)
1004 824 : older_solution->set(dof, current_solution(dof));
1005 : }
1006 :
1007 4358 : old_solution.close();
1008 4358 : if (older_solution)
1009 37 : older_solution->close();
1010 4358 : }
1011 :
1012 : void
1013 765 : ElementSubdomainModifierBase::gatherPatchElements(const VariableName & var_name,
1014 : ReinitStrategy reinit_strategy)
1015 : {
1016 765 : _patch_elem_ids[var_name].clear();
1017 :
1018 : // First collect all elements who own dofs in the current dofmap
1019 765 : auto & sys = _fe_problem.getSystem(var_name);
1020 :
1021 : // Cache evaluable elements for the system if not already done
1022 765 : if (!_evaluable_elems.count(sys.number()))
1023 : {
1024 469 : auto & [candidate_elems, candidate_elem_ids] = _evaluable_elems[sys.number()];
1025 469 : const auto & dof_map = sys.get_dof_map();
1026 469 : std::vector<dof_id_type> elem_dofs;
1027 469 : auto vn = sys.variable_number(static_cast<std::string>(var_name));
1028 31829 : for (const auto elem : *_mesh.getActiveLocalElementRange())
1029 : {
1030 31360 : if (std::find(_reinitialized_elems.begin(), _reinitialized_elems.end(), elem->id()) !=
1031 62720 : _reinitialized_elems.end())
1032 2952 : continue; // Skip elements that were reinitialized
1033 :
1034 28408 : dof_map.dof_indices(elem, elem_dofs, vn);
1035 28408 : if (!elem_dofs.empty())
1036 : {
1037 10456 : candidate_elems.insert(elem);
1038 10456 : candidate_elem_ids.push_back(elem->id());
1039 : }
1040 : }
1041 469 : }
1042 765 : auto & [candidate_elems, candidate_elem_ids] = _evaluable_elems[sys.number()];
1043 :
1044 : // Now we gather patch elements based on the reinit strategy
1045 765 : auto & patch_elems = _patch_elem_ids[var_name];
1046 :
1047 765 : switch (reinit_strategy)
1048 : {
1049 580 : case ReinitStrategy::POLYNOMIAL_NEIGHBOR:
1050 : {
1051 11926 : auto has_neighbor_in_reinit_elems = [&](const Elem * elem) -> bool
1052 : {
1053 66605 : for (const auto & node : elem->node_ref_range())
1054 314560 : for (const auto & neigh_id : _mesh.nodeToElemMap().at(node.id()))
1055 : // here we need to use _global_reinitialized_elems gathering from all processors
1056 259881 : if (_semi_local_reinitialized_elems.count(neigh_id))
1057 2912 : return true;
1058 9014 : return false;
1059 580 : };
1060 : // Loop over all candidate elements, for each element, if any of its point neighbor belongs
1061 : // to the reinitialized elements, we will include that element in the patch element set.
1062 12506 : for (const auto * elem : candidate_elems)
1063 11926 : if (has_neighbor_in_reinit_elems(elem))
1064 2912 : patch_elems.push_back(elem->id());
1065 580 : break;
1066 : }
1067 111 : case ReinitStrategy::POLYNOMIAL_WHOLE:
1068 : {
1069 : // This is simple: all candidate elements are patch elements
1070 111 : patch_elems = candidate_elem_ids;
1071 111 : break;
1072 : }
1073 74 : case ReinitStrategy::POLYNOMIAL_NEARBY:
1074 : {
1075 74 : std::vector<Point> kd_points;
1076 74 : std::vector<dof_id_type> global_candidate_elem_ids;
1077 :
1078 74 : if (_mesh.isDistributedMesh())
1079 : {
1080 12 : std::vector<std::pair<Point, dof_id_type>> pts_ids(candidate_elem_ids.size());
1081 116 : for (std::size_t i = 0; i < candidate_elem_ids.size(); ++i)
1082 208 : pts_ids[i] = {_mesh.elemPtr(candidate_elem_ids[i])->vertex_average(),
1083 208 : candidate_elem_ids[i]};
1084 12 : _mesh.comm().allgather(pts_ids);
1085 220 : for (const auto & [pt, id] : pts_ids)
1086 : {
1087 208 : kd_points.push_back(pt);
1088 208 : global_candidate_elem_ids.push_back(id);
1089 : }
1090 12 : }
1091 : else
1092 : {
1093 62 : _mesh.comm().allgather(candidate_elem_ids);
1094 62 : global_candidate_elem_ids = candidate_elem_ids;
1095 1146 : for (const auto & id : candidate_elem_ids)
1096 1084 : kd_points.push_back(_mesh.elemPtr(id)->vertex_average());
1097 : }
1098 :
1099 74 : const auto kd_tree = std::make_unique<KDTree>(kd_points, _leaf_max_size);
1100 :
1101 74 : std::vector<nanoflann::ResultItem<std::size_t, Real>> query_result;
1102 358 : for (const auto & elem_id : _reinitialized_elems)
1103 : {
1104 284 : const Point & centroid = _mesh.elemPtr(elem_id)->vertex_average();
1105 284 : kd_tree->radiusSearch(centroid, _nearby_distance_threshold, query_result);
1106 1504 : for (const auto & [qid, dist] : query_result)
1107 1220 : patch_elems.push_back(global_candidate_elem_ids[qid]);
1108 : }
1109 74 : break;
1110 74 : }
1111 0 : default:
1112 0 : mooseError("Unknown reinitialization strategy");
1113 : break;
1114 : }
1115 :
1116 : // every processor should have the same patch elements to do the polynomial extrapolation,
1117 : // so we gather them across all processors
1118 765 : _mesh.comm().allgather(patch_elems);
1119 :
1120 : // Remove duplicates from the patch elements (espcially important for POLYNOMIAL_NEARBY)
1121 765 : std::sort(patch_elems.begin(), patch_elems.end());
1122 765 : patch_elems.erase(std::unique(patch_elems.begin(), patch_elems.end()), patch_elems.end());
1123 765 : }
1124 :
1125 : void
1126 765 : ElementSubdomainModifierBase::extrapolatePolynomial(const VariableName & var_name)
1127 : {
1128 : const auto & coef =
1129 765 : _pr[_var_name_to_pr_idx[var_name]]->getCachedCoefficients(_patch_elem_ids[var_name]);
1130 :
1131 765 : const unsigned dim = _mesh.dimension();
1132 :
1133 765 : libMesh::Parameters function_parameters;
1134 :
1135 765 : const auto & multi_index = _pr[_var_name_to_pr_idx[var_name]]->multiIndex();
1136 :
1137 1530 : function_parameters.set<std::vector<std::vector<unsigned int>>>("multi_index") = multi_index;
1138 :
1139 765 : std::vector<Real> coef_vec(coef.size());
1140 3235 : for (auto i = 0; i < coef.size(); ++i)
1141 2470 : coef_vec[i] = coef(i);
1142 :
1143 765 : function_parameters.set<std::vector<Real>>("multi_index_coefficients") = coef_vec;
1144 1530 : function_parameters.set<unsigned int>("dimension_for_projection") = dim;
1145 :
1146 : // Define projection function
1147 66566 : auto poly_func = [](const Point & p,
1148 : const libMesh::Parameters & parameters,
1149 : const std::string &,
1150 : const std::string &) -> libMesh::Number
1151 : {
1152 : const auto & multi_index =
1153 66566 : parameters.get<std::vector<std::vector<unsigned int>>>("multi_index");
1154 66566 : const auto & coeffs = parameters.get<std::vector<Real>>("multi_index_coefficients");
1155 :
1156 66566 : Real val = 0.0;
1157 :
1158 338357 : for (unsigned int r = 0; r < multi_index.size(); r++)
1159 : {
1160 271791 : Real monomial = 1.0;
1161 918363 : for (unsigned int d = 0; d < multi_index[r].size(); d++)
1162 : {
1163 646572 : const auto power = multi_index[r][d];
1164 646572 : if (power == 0)
1165 410450 : continue;
1166 :
1167 236122 : monomial *= std::pow(p(d), power);
1168 : }
1169 271791 : val += coeffs[r] * monomial;
1170 : }
1171 :
1172 66566 : return val;
1173 : };
1174 :
1175 : // Define gradient
1176 0 : auto poly_func_grad = [](const Point & p,
1177 : const libMesh::Parameters & parameters,
1178 : const std::string &,
1179 : const std::string &) -> libMesh::Gradient
1180 : {
1181 0 : const unsigned int dim = parameters.get<unsigned int>("dimension_for_projection");
1182 :
1183 : const auto & multi_index =
1184 0 : parameters.get<std::vector<std::vector<unsigned int>>>("multi_index");
1185 0 : const auto & coeffs = parameters.get<std::vector<Real>>("multi_index_coefficients");
1186 :
1187 0 : libMesh::Gradient grad; // Zero-initialized
1188 :
1189 0 : for (unsigned int r = 0; r < multi_index.size(); ++r)
1190 : {
1191 0 : const auto & powers = multi_index[r];
1192 0 : const Real coef = coeffs[r];
1193 :
1194 0 : for (unsigned int d = 0; d < dim; ++d) // Loop over dimension
1195 : {
1196 0 : const auto power_d = powers[d];
1197 0 : if (power_d == 0)
1198 0 : continue;
1199 :
1200 : // Compute partial derivative in direction d
1201 0 : Real partial = coef * power_d;
1202 :
1203 0 : for (unsigned int i = 0; i < powers.size(); ++i)
1204 : {
1205 0 : if (i == d)
1206 : {
1207 0 : if (powers[i] > 1)
1208 0 : partial *= std::pow(p(i), powers[i] - 1); // reduce power by 1
1209 : }
1210 : else
1211 : {
1212 0 : if (powers[i] > 0)
1213 0 : partial *= std::pow(p(i), powers[i]); // full power
1214 : }
1215 : }
1216 :
1217 0 : grad(d) += partial;
1218 : }
1219 : }
1220 :
1221 0 : return grad;
1222 : };
1223 :
1224 765 : _fe_problem.projectFunctionOnCustomRange(
1225 : reinitializedElemRange(), poly_func, poly_func_grad, function_parameters, var_name);
1226 765 : }
|