https://mooseframework.inl.gov
Loading...
Searching...
No Matches
MooseMeshUtils.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
10// MOOSE includes
11#include "MooseMeshUtils.h"
12
13#include "libmesh/elem.h"
14#include "libmesh/boundary_info.h"
15#include "libmesh/id_types.h"
16#include "libmesh/int_range.h"
17#include "libmesh/parallel.h"
18#include "libmesh/parallel_algebra.h"
19#include "libmesh/utility.h"
20
21#include "libmesh/distributed_mesh.h"
22#include "libmesh/parallel_elem.h"
23#include "libmesh/parallel_node.h"
24#include "libmesh/compare_elems_by_level.h"
25#include "libmesh/mesh_communication.h"
26#include "libmesh/edge_edge3.h"
27#include "libmesh/enum_to_string.h"
28#include "libmesh/unstructured_mesh.h"
29
30#include "timpi/parallel_sync.h"
31
32using namespace libMesh;
33
34namespace MooseMeshUtils
35{
36
37void
39{
40 // We check if we have the same boundary name with different IDs. If we do, we assign the
41 // first ID to every occurrence.
42 const auto & side_bd_name_map = mesh.get_boundary_info().get_sideset_name_map();
43 const auto & node_bd_name_map = mesh.get_boundary_info().get_nodeset_name_map();
44 std::map<boundary_id_type, boundary_id_type> same_name_ids;
45
46 auto populate_map = [](const std::map<boundary_id_type, std::string> & map,
47 std::map<boundary_id_type, boundary_id_type> & same_ids)
48 {
49 for (const auto & pair_outer : map)
50 for (const auto & pair_inner : map)
51 // The last condition is needed to make sure we only store one combination
52 if (pair_outer.second == pair_inner.second && pair_outer.first != pair_inner.first &&
53 same_ids.find(pair_inner.first) == same_ids.end())
54 same_ids[pair_outer.first] = pair_inner.first;
55 };
56
57 populate_map(side_bd_name_map, same_name_ids);
58 populate_map(node_bd_name_map, same_name_ids);
59
60 for (const auto & [id1, id2] : same_name_ids)
62}
63
64void
66 const boundary_id_type old_id,
67 const boundary_id_type new_id,
68 bool delete_prev)
69{
70 // Get a reference to our BoundaryInfo object, we will use it several times below...
71 BoundaryInfo & boundary_info = mesh.get_boundary_info();
72
73 // Container to catch ids passed back from BoundaryInfo
74 std::vector<boundary_id_type> old_ids;
75
76 // Only level-0 elements store BCs. Loop over them.
77 for (auto & elem : as_range(mesh.level_elements_begin(0), mesh.level_elements_end(0)))
78 {
79 unsigned int n_sides = elem->n_sides();
80 for (const auto s : make_range(n_sides))
81 {
82 boundary_info.boundary_ids(elem, s, old_ids);
83 if (std::find(old_ids.begin(), old_ids.end(), old_id) != old_ids.end())
84 {
85 std::vector<boundary_id_type> new_ids(old_ids);
86 std::replace(new_ids.begin(), new_ids.end(), old_id, new_id);
87 if (delete_prev)
88 {
89 boundary_info.remove_side(elem, s);
90 boundary_info.add_side(elem, s, new_ids);
91 }
92 else
93 boundary_info.add_side(elem, s, new_ids);
94 }
95 }
96 }
97
98 // Remove any remaining references to the old ID from the
99 // BoundaryInfo object. This prevents things like empty sidesets
100 // from showing up when printing information, etc.
101 if (delete_prev)
102 boundary_info.remove_id(old_id);
103
104 // global information may now be out of sync
106}
107
108std::vector<boundary_id_type>
110 const std::vector<BoundaryName> & boundary_name,
111 bool generate_unknown)
112{
113 return getBoundaryIDs(
114 mesh, boundary_name, generate_unknown, mesh.get_boundary_info().get_boundary_ids());
115}
116
117std::vector<boundary_id_type>
119 const std::vector<BoundaryName> & boundary_name,
120 bool generate_unknown,
121 const std::set<BoundaryID> & mesh_boundary_ids)
122{
123 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
124 const std::map<BoundaryID, std::string> & sideset_map = boundary_info.get_sideset_name_map();
125 const std::map<BoundaryID, std::string> & nodeset_map = boundary_info.get_nodeset_name_map();
126
127 /* It is required to generate a new ID for a given name. It is used often in mesh modifiers such
128 * as SideSetsBetweenSubdomains. Then we need to check the current boundary ids since they are
129 * changing during "mesh modify()", and figure out the right max boundary ID. Most of mesh
130 * modifiers are running in serial, and we won't involve a global communication.
131 */
132 BoundaryID max_boundary_id = 0;
133 if (generate_unknown)
134 {
135 bool has_boundary_id_sets = mesh.preparation().has_boundary_id_sets;
136
137 // Ideally, this requirement should be able to be enforced earlier when we
138 // get the name maps. However, that preparedness check on sideset and
139 // nodeset maps doesn't exist yet.
140 if (!has_boundary_id_sets)
141 libmesh_parallel_only(mesh.comm());
142
143 const auto & bids = has_boundary_id_sets ? boundary_info.get_global_boundary_ids()
144 : boundary_info.get_boundary_ids();
145 if (bids.size())
146 max_boundary_id = *(bids.rbegin());
147 if (!has_boundary_id_sets)
148 mesh.comm().max(max_boundary_id);
149 }
150
151 std::vector<BoundaryID> ids(boundary_name.size());
152 for (const auto i : index_range(boundary_name))
153 {
154 if (boundary_name[i] == "ANY_BOUNDARY_ID")
155 {
156 ids.assign(mesh_boundary_ids.begin(), mesh_boundary_ids.end());
157 if (i)
158 mooseWarning("You passed \"ANY_BOUNDARY_ID\" in addition to other boundary_names. This "
159 "may be a logic error.");
160 break;
161 }
162
163 if (boundary_name[i].empty() && !generate_unknown)
164 mooseError("Incoming boundary name is empty and we are not generating unknown boundary IDs. "
165 "This is invalid.");
166
167 BoundaryID id;
168
169 if (boundary_name[i].empty() || !MooseUtils::isDigits(boundary_name[i]))
170 {
176 if (generate_unknown &&
177 !MooseUtils::doesMapContainValue(sideset_map, std::string(boundary_name[i])) &&
178 !MooseUtils::doesMapContainValue(nodeset_map, std::string(boundary_name[i])))
179 id = ++max_boundary_id;
180 else
181 id = boundary_info.get_id_by_name(boundary_name[i]);
182 }
183 else
184 id = getIDFromName<BoundaryName, BoundaryID>(boundary_name[i]);
185
186 ids[i] = id;
187 }
188
189 return ids;
190}
191
192std::set<BoundaryID>
194 const std::vector<BoundaryName> & boundary_name,
195 bool generate_unknown)
196{
197 auto boundaries = getBoundaryIDs(mesh, boundary_name, generate_unknown);
198 return std::set<BoundaryID>(boundaries.begin(), boundaries.end());
199}
200
201std::vector<subdomain_id_type>
202getSubdomainIDs(const MeshBase & mesh, const std::vector<SubdomainName> & subdomain_names)
203{
204 std::vector<subdomain_id_type> ids;
205
206 // shortcut for "ANY_BLOCK_ID"
207 if (subdomain_names.size() == 1 && subdomain_names[0] == "ANY_BLOCK_ID")
208 {
209 // since get_mesh_subdomains() requires a prepared mesh, we need to check that here
210 mooseAssert(mesh.is_prepared(),
211 "getSubdomainIDs() should only be called on a prepared mesh if ANY_BLOCK_ID is "
212 "used to query all block IDs");
213 ids.assign(mesh.get_mesh_subdomains().begin(), mesh.get_mesh_subdomains().end());
214 return ids;
215 }
216
217 // loop through subdomain names and get IDs (this preserves the order of subdomain_names)
218 ids.resize(subdomain_names.size());
219 for (auto i : index_range(subdomain_names))
220 {
221 if (subdomain_names[i] == "ANY_BLOCK_ID")
222 mooseError("getSubdomainIDs() accepts \"ANY_BLOCK_ID\" if and only if it is the only "
223 "subdomain name being queried.");
224 ids[i] = MooseMeshUtils::getSubdomainID(subdomain_names[i], mesh);
225 }
226
227 return ids;
228}
229
230std::set<subdomain_id_type>
231getSubdomainIDs(const MeshBase & mesh, const std::set<SubdomainName> & subdomain_names)
232{
233 const auto blk_ids = getSubdomainIDs(
234 mesh, std::vector<SubdomainName>(subdomain_names.begin(), subdomain_names.end()));
235 return {blk_ids.begin(), blk_ids.end()};
236}
237
239getBoundaryID(const BoundaryName & boundary_name, const MeshBase & mesh)
240{
242 if (boundary_name.empty())
243 return id;
244
245 if (!MooseUtils::isDigits(boundary_name))
246 id = mesh.get_boundary_info().get_id_by_name(boundary_name);
247 else
248 id = getIDFromName<BoundaryName, BoundaryID>(boundary_name);
249
250 return id;
251}
252
254getSubdomainID(const SubdomainName & subdomain_name, const MeshBase & mesh)
255{
256 if (subdomain_name == "ANY_BLOCK_ID")
257 mooseError("getSubdomainID() does not work with \"ANY_BLOCK_ID\"");
258
260 if (subdomain_name.empty())
261 return id;
262
263 if (!MooseUtils::isDigits(subdomain_name))
264 id = mesh.get_id_by_name(subdomain_name);
265 else
266 id = getIDFromName<SubdomainName, SubdomainID>(subdomain_name);
267
268 return id;
269}
270
271void
273{
274 for (const auto & elem : mesh.element_ptr_range())
275 if (elem->subdomain_id() == old_id)
276 elem->subdomain_id() = new_id;
277
278 // global cached information may now be out of sync
280}
281
282Point
284{
285 Point centroid_pt = Point(0.0, 0.0, 0.0);
286 Real vol_tmp = 0.0;
287 for (const auto & elem : mesh.active_local_element_ptr_range())
288 {
289 Real elem_vol = elem->volume();
290 centroid_pt += (elem->true_centroid()) * elem_vol;
291 vol_tmp += elem_vol;
292 }
293 mesh.comm().sum(centroid_pt);
294 mesh.comm().sum(vol_tmp);
295 centroid_pt /= vol_tmp;
296 return centroid_pt;
297}
298
299Point
300boundaryCentroidCalculator(const BoundaryName & boundary, MeshBase & mesh)
301{
302 // Need boundaries to be synchronized
303 if (!mesh.preparation().has_boundary_id_sets)
305 BoundaryInfo & mesh_boundary_info = mesh.get_boundary_info();
306 boundary_id_type boundary_id = mesh_boundary_info.get_id_by_name(boundary);
307 const auto side_list = mesh_boundary_info.build_side_list();
308
309 // Initialize sums
310 Real volume_sum = 0;
311 Point volume_weighted_centroid_sum(0, 0, 0);
312
313 for (const auto & [eid, side_i, bid] : side_list)
314 {
315 if (bid != boundary_id)
316 continue;
317
318 // Get the side
319 const auto elem = mesh.elem_ptr(eid);
320 const auto side = elem->side_ptr(side_i);
321
322 volume_sum += side->volume();
323 volume_weighted_centroid_sum += side->volume() * side->true_centroid();
324 }
325 // Sum across processes
326 mesh.comm().sum(volume_weighted_centroid_sum);
327 mesh.comm().sum(volume_sum);
328
329 return volume_weighted_centroid_sum / volume_sum;
330}
331
333boundaryWeightedNormal(const BoundaryName & boundary, MeshBase & mesh)
334{
335 // Need boundaries to be synchronized
336 if (!mesh.preparation().has_boundary_id_sets)
338 BoundaryInfo & mesh_boundary_info = mesh.get_boundary_info();
339 boundary_id_type boundary_id = mesh_boundary_info.get_id_by_name(boundary);
340 const auto side_list = mesh_boundary_info.build_side_list();
341
342 // Initialize sums
343 Real volume_sum = 0;
344 RealVectorValue volume_weighted_normal_sum(0, 0, 0);
345
346 for (const auto & [eid, side_i, bid] : side_list)
347 {
348 if (bid != boundary_id)
349 continue;
350
351 // Get the side
352 const auto elem = mesh.elem_ptr(eid);
353 const auto side = elem->side_ptr(side_i);
354
355 volume_sum += side->volume();
356 volume_weighted_normal_sum += side->volume() * elem->side_vertex_average_normal(side_i);
357 }
358 // Sum across processes
359 mesh.comm().sum(volume_weighted_normal_sum);
360 mesh.comm().sum(volume_sum);
361
362 return volume_weighted_normal_sum / volume_sum;
363}
364
365Real
367 const Point & origin,
368 const RealVectorValue & direction)
369{
370 Real distance = 0;
371 mooseAssert(MooseUtils::absoluteFuzzyEqual(direction.norm_sq(), 1),
372 "Direction should be normalized");
373 for (const auto & node : mesh.node_ptr_range())
374 if (const auto dist_node = (*node - origin).cross(direction).norm(); dist_node > distance)
375 distance = dist_node;
377 return distance;
378}
379
380std::unordered_map<dof_id_type, dof_id_type>
382 const std::set<SubdomainID> & block_ids,
383 std::vector<ExtraElementIDName> extra_ids)
384{
385 // check block restriction
386 const bool block_restricted = !block_ids.empty();
387 // get element id name of interest in recursive parsing algorithm
388 ExtraElementIDName id_name = extra_ids.back();
389 extra_ids.pop_back();
390 const auto id_index = mesh.get_elem_integer_index(id_name);
391
392 // create base parsed id set
393 if (extra_ids.empty())
394 {
395 // get set of extra id values;
396 std::vector<dof_id_type> ids;
397 {
398 std::set<dof_id_type> ids_set;
399 for (const auto & elem : mesh.active_element_ptr_range())
400 {
401 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
402 continue;
403 const auto id = elem->get_extra_integer(id_index);
404 ids_set.insert(id);
405 }
406 mesh.comm().set_union(ids_set);
407 ids.assign(ids_set.begin(), ids_set.end());
408 }
409
410 // determine new extra id values;
411 std::unordered_map<dof_id_type, dof_id_type> parsed_ids;
412 for (auto & elem : mesh.active_element_ptr_range())
413 {
414 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
415 continue;
416 parsed_ids[elem->id()] = std::distance(
417 ids.begin(), std::lower_bound(ids.begin(), ids.end(), elem->get_extra_integer(id_index)));
418 }
419 return parsed_ids;
420 }
421
422 // if extra_ids is not empty, recursively call getExtraIDUniqueCombinationMap
423 const auto base_parsed_ids =
425 // parsing extra ids based on ref_parsed_ids
426 std::vector<std::pair<dof_id_type, dof_id_type>> unique_ids;
427 {
428 std::set<std::pair<dof_id_type, dof_id_type>> unique_ids_set;
429 for (const auto & elem : mesh.active_element_ptr_range())
430 {
431 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
432 continue;
433 const dof_id_type id1 = libmesh_map_find(base_parsed_ids, elem->id());
434 const dof_id_type id2 = elem->get_extra_integer(id_index);
435 const std::pair<dof_id_type, dof_id_type> ids = std::make_pair(id1, id2);
436 unique_ids_set.insert(ids);
437 }
438 mesh.comm().set_union(unique_ids_set);
439 unique_ids.assign(unique_ids_set.begin(), unique_ids_set.end());
440 }
441
442 std::unordered_map<dof_id_type, dof_id_type> parsed_ids;
443
444 for (const auto & elem : mesh.active_element_ptr_range())
445 {
446 if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
447 continue;
448 const dof_id_type id1 = libmesh_map_find(base_parsed_ids, elem->id());
449 const dof_id_type id2 = elem->get_extra_integer(id_index);
450 const dof_id_type new_id = std::distance(
451 unique_ids.begin(),
452 std::lower_bound(unique_ids.begin(), unique_ids.end(), std::make_pair(id1, id2)));
453 parsed_ids[elem->id()] = new_id;
454 }
455
456 return parsed_ids;
457}
458
459bool
460isCoPlanar(const std::vector<Point> & vec_pts, const Point plane_nvec, const Point fixed_pt)
461{
462 for (const auto & pt : vec_pts)
463 if (!MooseUtils::absoluteFuzzyEqual((pt - fixed_pt) * plane_nvec, 0.0))
464 return false;
465 return true;
466}
467
468bool
469isCoPlanar(const std::vector<Point> & vec_pts, const Point plane_nvec)
470{
471 return isCoPlanar(vec_pts, plane_nvec, vec_pts.front());
472}
473
474bool
475isCoPlanar(const std::vector<Point> & vec_pts)
476{
477 // Assuming that overlapped Points are allowed, the Points that are overlapped with vec_pts[0] are
478 // removed before further calculation.
479 std::vector<Point> vec_pts_nonzero{vec_pts[0]};
480 for (const auto i : index_range(vec_pts))
481 if (!MooseUtils::absoluteFuzzyEqual((vec_pts[i] - vec_pts[0]).norm(), 0.0))
482 vec_pts_nonzero.push_back(vec_pts[i]);
483 // 3 or fewer points are always coplanar
484 if (vec_pts_nonzero.size() <= 3)
485 return true;
486 else
487 {
488 for (const auto i : make_range(vec_pts_nonzero.size() - 1))
489 {
490 const Point tmp_pt = (vec_pts_nonzero[i] - vec_pts_nonzero[0])
491 .cross(vec_pts_nonzero[i + 1] - vec_pts_nonzero[0]);
492 // if the three points are not collinear, use cross product as the normal vector of the plane
493 if (!MooseUtils::absoluteFuzzyEqual(tmp_pt.norm(), 0.0))
494 return isCoPlanar(vec_pts_nonzero, tmp_pt.unit());
495 }
496 }
497 // If all the points are collinear, they are also coplanar
498 return true;
499}
500
503{
504 // Call this to get most up to date block id information
505 input_mesh.cache_elem_data();
506
507 std::set<SubdomainID> preexisting_subdomain_ids;
508 input_mesh.subdomain_ids(preexisting_subdomain_ids);
509 if (preexisting_subdomain_ids.empty())
510 return 0;
511 else
512 {
513 const auto highest_subdomain_id =
514 *std::max_element(preexisting_subdomain_ids.begin(), preexisting_subdomain_ids.end());
515 mooseAssert(highest_subdomain_id < std::numeric_limits<SubdomainID>::max(),
516 "A SubdomainID with max possible value was found");
517 return highest_subdomain_id + 1;
518 }
519}
520
523{
524 if (!input_mesh.preparation().has_boundary_id_sets)
526
527 auto boundary_ids = input_mesh.get_boundary_info().get_boundary_ids();
528 if (boundary_ids.empty())
529 return 0;
530 return (*boundary_ids.rbegin() + 1);
531}
532
533bool
534hasSubdomainID(const MeshBase & input_mesh, const SubdomainID & id)
535{
536 std::set<SubdomainID> mesh_blocks;
537 input_mesh.subdomain_ids(mesh_blocks);
538
539 // On a distributed mesh we may have sideset IDs that only exist on
540 // other processors
541 if (!input_mesh.is_replicated())
542 input_mesh.comm().set_union(mesh_blocks);
543
544 return mesh_blocks.count(id) && (id != Moose::INVALID_BLOCK_ID);
545}
546
547bool
548hasSubdomainName(const MeshBase & input_mesh, const SubdomainName & name)
549{
550 const auto id = getSubdomainID(name, input_mesh);
551 return hasSubdomainID(input_mesh, id);
552}
553
554bool
555hasBoundaryID(const MeshBase & input_mesh, const BoundaryID id)
556{
557 const BoundaryInfo & boundary_info = input_mesh.get_boundary_info();
558 std::set<boundary_id_type> boundary_ids = boundary_info.get_boundary_ids();
559
560 // On a distributed mesh we may have boundary IDs that only exist on
561 // other processors
562 if (!input_mesh.is_replicated())
563 input_mesh.comm().set_union(boundary_ids);
564
565 return boundary_ids.count(id) && (id != Moose::INVALID_BOUNDARY_ID);
566}
567
568bool
569hasBoundaryName(const MeshBase & mesh, const BoundaryName & name)
570{
571 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
572 const BoundaryID id = boundary_info.get_id_by_name(name);
573 return id != Moose::INVALID_BOUNDARY_ID;
574}
575
576bool
577hasBoundaryNameOrID(const MeshBase & mesh, const BoundaryName & name_or_id)
578{
579 const auto id = getBoundaryID(name_or_id, mesh);
580 return hasBoundaryID(mesh, id);
581}
582
583void
584makeOrderedNodeList(std::vector<std::pair<dof_id_type, dof_id_type>> & node_assm,
585 std::vector<dof_id_type> & elem_id_list,
586 std::vector<dof_id_type> & midpoint_node_list,
587 std::vector<dof_id_type> & ordered_node_list,
588 std::vector<dof_id_type> & ordered_elem_id_list)
589{
590 // a flag to indicate if the ordered_node_list has been reversed
591 bool is_flipped = false;
592 // Start from the first element, try to find a chain of nodes
593 mooseAssert(node_assm.size(), "Node list must not be empty");
594 ordered_node_list.push_back(node_assm.front().first);
595 if (midpoint_node_list.front() != DofObject::invalid_id)
596 ordered_node_list.push_back(midpoint_node_list.front());
597 ordered_node_list.push_back(node_assm.front().second);
598 ordered_elem_id_list.push_back(elem_id_list.front());
599 // Remove the element that has just been added to ordered_node_list
600 node_assm.erase(node_assm.begin());
601 midpoint_node_list.erase(midpoint_node_list.begin());
602 elem_id_list.erase(elem_id_list.begin());
603 const unsigned int node_assm_size_0 = node_assm.size();
604 for (unsigned int i = 0; i < node_assm_size_0; i++)
605 {
606 // Find nodes to expand the chain
607 dof_id_type end_node_id = ordered_node_list.back();
608 auto isMatch1 = [end_node_id](std::pair<dof_id_type, dof_id_type> old_id_pair)
609 { return old_id_pair.first == end_node_id; };
610 auto isMatch2 = [end_node_id](std::pair<dof_id_type, dof_id_type> old_id_pair)
611 { return old_id_pair.second == end_node_id; };
612 auto result = std::find_if(node_assm.begin(), node_assm.end(), isMatch1);
613 bool match_first;
614 if (result == node_assm.end())
615 {
616 match_first = false;
617 result = std::find_if(node_assm.begin(), node_assm.end(), isMatch2);
618 }
619 else
620 {
621 match_first = true;
622 }
623 // If found, add the node to boundary_ordered_node_list
624 if (result != node_assm.end())
625 {
626 const auto elem_index = std::distance(node_assm.begin(), result);
627 if (midpoint_node_list[elem_index] != DofObject::invalid_id)
628 ordered_node_list.push_back(midpoint_node_list[elem_index]);
629 ordered_node_list.push_back(match_first ? (*result).second : (*result).first);
630 node_assm.erase(result);
631 midpoint_node_list.erase(midpoint_node_list.begin() + elem_index);
632 ordered_elem_id_list.push_back(elem_id_list[elem_index]);
633 elem_id_list.erase(elem_id_list.begin() + elem_index);
634 }
635 // If there are still elements in node_assm and result ==
636 // node_assm.end(), this means the curve is not a loop, the
637 // ordered_node_list is flipped and try the other direction that has not
638 // been examined yet.
639 else
640 {
641 if (is_flipped)
642 // Flipped twice; this means the node list has at least two segments.
643 throw MooseException("The node list provided has more than one segments.");
644
645 // mark the first flip event.
646 is_flipped = true;
647 std::reverse(ordered_node_list.begin(), ordered_node_list.end());
648 std::reverse(midpoint_node_list.begin(), midpoint_node_list.end());
649 std::reverse(ordered_elem_id_list.begin(), ordered_elem_id_list.end());
650 // As this iteration is wasted, set the iterator backward
651 i--;
652 }
653 }
654}
655
656void
657makeOrderedNodeList(std::vector<std::pair<dof_id_type, dof_id_type>> & node_assm,
658 std::vector<dof_id_type> & elem_id_list,
659 std::vector<dof_id_type> & ordered_node_list,
660 std::vector<dof_id_type> & ordered_elem_id_list)
661{
662 std::vector<dof_id_type> dummy_midpoint_node_list(node_assm.size(), DofObject::invalid_id);
664 node_assm, elem_id_list, dummy_midpoint_node_list, ordered_node_list, ordered_elem_id_list);
665}
666
667void
668swapNodesInElem(Elem & elem, const unsigned int nd1, const unsigned int nd2)
669{
670 Node * n_temp = elem.node_ptr(nd1);
671 elem.set_node(nd1, elem.node_ptr(nd2));
672 elem.set_node(nd2, n_temp);
673}
674
675void
677 const std::string & class_name,
678 const unsigned int num_sections,
679 const unsigned int num_integers,
680 const std::vector<std::vector<std::vector<dof_id_type>>> & elem_integers_swaps,
681 std::vector<std::unordered_map<dof_id_type, dof_id_type>> & elem_integers_swap_pairs)
682{
683 elem_integers_swap_pairs.reserve(num_sections * num_integers);
684 for (const auto i : make_range(num_integers))
685 {
686 const auto & elem_integer_swaps = elem_integers_swaps[i];
687 std::vector<std::unordered_map<dof_id_type, dof_id_type>> elem_integer_swap_pairs;
688 try
689 {
691 "elem_integers_swaps",
692 elem_integer_swaps,
693 elem_integer_swap_pairs,
694 i * num_sections);
695 }
696 catch (const MooseException & e)
697 {
698 throw MooseException(e.what());
699 }
700
701 elem_integers_swap_pairs.insert(elem_integers_swap_pairs.end(),
702 elem_integer_swap_pairs.begin(),
703 elem_integer_swap_pairs.end());
704 }
705}
706
707std::unique_ptr<ReplicatedMesh>
708buildBoundaryMesh(const MeshBase & input_mesh, const boundary_id_type boundary_id)
709{
710 if (!input_mesh.is_serial())
711 ::mooseError("Input mesh should be serialized for extracting the boundary mesh.\nInput mesh:" +
712 input_mesh.get_info());
713 auto poly_mesh = std::make_unique<ReplicatedMesh>(input_mesh.comm());
714
715 auto side_list = input_mesh.get_boundary_info().build_side_list();
716
717 std::unordered_map<dof_id_type, dof_id_type> old_new_node_map;
718 for (const auto & [eid, side_i, bid] : side_list)
719 {
720 if (bid != boundary_id)
721 continue;
722
723 // Get the side
724 const auto elem = input_mesh.elem_ptr(eid);
725 const auto side = elem->side_ptr(side_i);
726 auto side_elem = elem->build_side_ptr(side_i);
727 auto copy = side_elem->build(side_elem->type());
728
729 for (const auto i : side_elem->node_index_range())
730 {
731 auto & n = side_elem->node_ref(i);
732
733 if (old_new_node_map.count(n.id()))
734 copy->set_node(i, poly_mesh->node_ptr(old_new_node_map[n.id()]));
735 else
736 {
737 Node * node = poly_mesh->add_point(side_elem->point(i));
738 copy->set_node(i, node);
739 old_new_node_map[n.id()] = node->id();
740 }
741 }
742 poly_mesh->add_elem(copy.release());
743 }
744 poly_mesh->skip_partitioning(true);
745 poly_mesh->prepare_for_use();
746 if (poly_mesh->n_elem() == 0)
747 mooseError("The input mesh to extract the boundary from does not have a boundary with id ",
748 boundary_id,
749 ".\n",
750 input_mesh);
751
752 return poly_mesh;
753}
754
755std::unique_ptr<ReplicatedMesh>
756buildLoopBoundaryOf2DMesh(const MeshBase & input_mesh, const boundary_id_type boundary_id)
757{
758 if (!input_mesh.is_serial())
760 "Input 2D mesh should be serialized for extracting the loop boundary mesh.\nInput mesh:" +
761 input_mesh.get_info());
762 auto edge_mesh = std::make_unique<ReplicatedMesh>(input_mesh.comm());
763 auto side_list = input_mesh.get_boundary_info().build_side_list();
764 std::set<BoundaryInfo::BCTuple> visited;
765 bool already_seen_this_side_tuple = false;
766 BoundaryInfo::BCTuple first_side_visited = {libMesh::invalid_uint, 0, 0};
767
768 // Helps move elem to elem at a given node
769 const auto node_to_elem_map = buildBoundaryNodeToElemMap(input_mesh, boundary_id);
770 // Helps check if a node is part of a boundary
771 const auto & node_to_bids = input_mesh.get_boundary_info().get_nodeset_map();
772
773 // Traverse from the first side (edge) in the side_list that matches the boundary_id
774 for (const auto & bside : side_list)
775 {
776 if (std::get<2>(bside) != boundary_id)
777 continue;
778
779 // Check that we are not starting 'another' loop
780 if (bside != first_side_visited)
781 {
782 if (visited.size() && !visited.count(bside))
784 "Boundary " + std::to_string(boundary_id) +
785 " is not a (contiguous) loop. Boundary side: (" + Moose::stringify(bside) +
786 ") was not visited after a single pass around the boundary. Boundary sides visited: " +
787 Moose::stringify(visited));
788 else if (visited.empty())
789 first_side_visited = bside;
790 else
791 continue;
792 }
793
794 // Form the element to be able to find the side
795 // These three variables will be updated while traversing the loop boundary
796 const Elem * elem = input_mesh.elem_ptr(std::get<0>(bside));
797 auto current_side = std::get<1>(bside);
798 auto side_elem = elem->build_side_ptr(current_side);
799
800 // 3D elements should not be part of this boundary
801 if (elem->dim() != 2)
803 "Finding the loop boundary of a 2D mesh cannot be done with non-2D elements such as ",
804 *elem);
805
806 // Start from node 0 of the side (on the boundary), set the next node as the other node
807 // one that side, and keep going from tht next node
808 bool looped_back = false;
809 const Node * starting_node = side_elem->node_ptr(0);
810 const auto new_mesh_starting_node = edge_mesh->add_point(side_elem->point(0));
811 Node * new_first_node = new_mesh_starting_node;
812 [[maybe_unused]] dof_id_type first_node_index = starting_node->id();
813 dof_id_type second_node_index = input_mesh.node_ptr(side_elem->node_id(1))->id();
814
815 while (!looped_back && !already_seen_this_side_tuple)
816 {
817 if (MooseUtils::absoluteFuzzyEqual(input_mesh.point(second_node_index),
818 Point(*starting_node)))
819 looped_back = true;
820
821 // Get the opposite node (the next node) and add it to the edge mesh
822 Node * new_second_node = looped_back
823 ? new_mesh_starting_node
824 : edge_mesh->add_point(input_mesh.point(second_node_index));
825
826 // Add a copy of the edge side element to the mesh
827 side_elem = elem->build_side_ptr(current_side);
828 auto copy = side_elem->build(side_elem->type());
829 copy->set_node(0, new_first_node);
830 copy->set_node(1, new_second_node);
831 edge_mesh->add_elem(copy.release());
832
833 // Make this side as 'visited'
834 std::tuple<dof_id_type, unsigned short int, boundary_id_type> bc_tuple = {
835 elem->id(), current_side, boundary_id};
836 const auto & visit_iter = visited.insert(bc_tuple);
837 if (!looped_back && !visit_iter.second)
838 already_seen_this_side_tuple = true;
839
840 // Find the next element and side_elem
841 auto & connected_elems = libmesh_map_find(node_to_elem_map, second_node_index);
842 bool found_match = false;
843 const auto current_eid = elem->id();
844
845 for (const auto eid : connected_elems)
846 {
847 mooseAssert(!found_match,
848 "We should only find one node on a connected element on this boundary");
849 if (eid != current_eid)
850 {
851 // Update the element (on the input mesh)
852 elem = input_mesh.elem_ptr(eid);
853
854 // Find the side and the opposite node index in that side
855 for (const auto si : elem->side_index_range())
856 {
857 // Check that second node is on the side
858 const auto local_second_node_index =
859 elem->get_node_index(input_mesh.node_ptr(second_node_index));
860 // 2 sides should match this
861 if (elem->is_node_on_side(local_second_node_index, si))
862 {
863 // Only one side should be on the same boundary (node is connected to two elements)
864 // Form a bc_tuple and check the list of boundary sides
865 std::tuple<dof_id_type, unsigned short int, boundary_id_type> side_bc_tuple = {
866 elem->id(), si, boundary_id};
867
868 if (std::find(side_list.begin(), side_list.end(), side_bc_tuple) == side_list.end())
869 continue;
870
871 // We are on the right boundary, just need to get the other node
872 for (const auto local_side_node_id : elem->nodes_on_side(si))
873 {
874 const auto side_node_id = elem->node_id(local_side_node_id);
875
876 // Skip current node (use global index to compare)
877 if (side_node_id == second_node_index)
878 continue;
879 mooseAssert(side_node_id != first_node_index,
880 "Somehow looped back in a single element");
881
882 current_side = si;
883 second_node_index = side_node_id;
884 found_match = true;
885 break;
886 }
887 }
888 // No need to examine more sides
889 if (found_match)
890 break;
891 }
892
893 // No need to examine more elements
894 if (found_match)
895 break;
896 }
897 // next node could be on the same element, just moving on to the next side
898 else if (connected_elems.size() == 1)
899 {
900 elem = input_mesh.elem_ptr(eid);
901 const auto local_second_node_index =
902 elem->get_node_index(input_mesh.node_ptr(second_node_index));
903
904 // Move on to the next side
905 for (const auto si : elem->side_index_range())
906 if (si != current_side && elem->is_node_on_side(local_second_node_index, si))
907 {
908 // Check all nodes on that next side
909 for (const auto local_side_node_id : elem->nodes_on_side(si))
910 {
911 const auto side_node_id = elem->node_id(local_side_node_id);
912 // Skip current node
913 if (side_node_id == second_node_index)
914 continue;
915 mooseAssert((side_node_id != first_node_index) ||
916 (side_list.size() == elem->n_sides()),
917 "Somehow looped back in a single element");
918
919 // Check all the boundaries the other node (on the edge side) is part of
920 const auto bids_range = node_to_bids.equal_range(input_mesh.node_ptr(side_node_id));
921
922 for (auto iter = bids_range.first; iter != bids_range.second; iter++)
923 if (iter->second == boundary_id)
924 {
925 current_side = si;
926 second_node_index = side_node_id;
927 found_match = true;
928 }
929 }
930
931 // no need to examine other sides
932 if (found_match)
933 break;
934 }
935 }
936 }
937
938 // Set current node to opposite node of new element
939 // NOTE: do not use new_first_node or new_second_node to search in the input mesh!
940 new_first_node = new_second_node;
941 first_node_index = second_node_index;
942
943 // Handle loop ending criterion
944 if (!found_match)
945 {
946 mooseWarning("Search for next element in loop boundary failed. Is boundary '" +
947 std::to_string(boundary_id) + "' of mesh ",
948 input_mesh,
949 " a loop boundary?");
950 break;
951 }
952 }
953 }
954
955 if (already_seen_this_side_tuple)
956 mooseWarning("Boundary " + std::to_string(boundary_id) +
957 " seems to have cycles. A single-cycle loop should be used");
958
959 edge_mesh->skip_partitioning(true);
960 edge_mesh->prepare_for_use();
961 if (edge_mesh->n_elem() == 0)
962 mooseError("The input mesh to extract the boundary from does not have a boundary with id ",
963 boundary_id,
964 "\n",
965 input_mesh);
966
967 return edge_mesh;
968}
969
970std::unordered_map<dof_id_type, std::unordered_set<dof_id_type>>
971buildBoundaryNodeToElemMap(const MeshBase & input_mesh, const boundary_id_type boundary_id)
972{
973 if (!input_mesh.is_serial())
975 "Input 2D mesh should be serialized for extracting the loop boundary mesh.\nInput mesh:" +
976 input_mesh.get_info());
977
978 // Get all nodes on that boundary
979 // Boundary ID might be a sideset or a nodeset, get nodes regardless
980 const auto particular_node_ids = getBoundaryNodes(input_mesh, boundary_id);
981
982 std::unordered_map<dof_id_type, std::unordered_set<dof_id_type>> nid_to_eids_map;
983 // Fill the map from looping over elements
984 for (const auto & elem :
985 as_range(input_mesh.active_elements_begin(), input_mesh.active_elements_end()))
986 {
987 for (const auto & nd : elem->node_ref_range())
988 {
989 // Only add the element id if the node is on the boundary
990 if (!particular_node_ids.count(nd.id()))
991 continue;
992
993 auto & elem_ids = nid_to_eids_map[nd.id()];
994 elem_ids.insert(elem->id());
995 }
996 }
997 return nid_to_eids_map;
998}
999
1000std::set<dof_id_type>
1001getBoundaryNodes(const MeshBase & mesh, const BoundaryID boundary_id)
1002{
1003 std::set<dof_id_type> boundary_node_ids;
1004 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
1005
1006 // Get all nodes from the sideset with ID of boundary_id
1007 const auto & bc_sides =
1008 boundary_info.build_side_list(libMesh::BoundaryInfo::BCTupleSortBy::BOUNDARY_ID);
1009 for (const auto & [elem_id, side, bc_id] : bc_sides)
1010 {
1011 if (bc_id == boundary_id)
1012 {
1013 const auto elem = mesh.elem_ptr(elem_id);
1014 for (const auto ni : elem->nodes_on_side(side))
1015 boundary_node_ids.insert(elem->node_id(ni));
1016 }
1017 }
1018
1019 // Get all nodes from nodeset with ID of boundary_id
1020 const auto & bc_nodes = boundary_info.build_node_list();
1021 for (const auto & [n_id, bc_id] : bc_nodes)
1022 if (bc_id == boundary_id)
1023 boundary_node_ids.insert(n_id);
1024
1025 return boundary_node_ids;
1026}
1027
1028void
1030 std::vector<BoundaryName> boundary_names,
1031 const SubdomainID new_subdomain_id,
1032 const SubdomainName new_subdomain_name,
1033 const std::string type_name)
1034{
1035 // Generate a new block id if one isn't supplied.
1036 SubdomainID new_block_id = new_subdomain_id;
1037
1038 // Make sure our boundary info and parallel counts are setup
1039 if (!mesh.is_prepared())
1040 {
1041 const bool allow_remote_element_removal = mesh.allow_remote_element_removal();
1042 // We want all of our boundary elements available, so avoid removing them if they haven't
1043 // already been so
1046 mesh.allow_remote_element_removal(allow_remote_element_removal);
1047 }
1048
1049 // Check that the sidesets are present in the mesh
1050 for (const auto & sideset : boundary_names)
1052 mooseException("The sideset '", sideset, "' was not found within the mesh");
1053
1054 auto sideset_ids = MooseMeshUtils::getBoundaryIDs(mesh, boundary_names, true);
1055 std::set<boundary_id_type> sidesets(sideset_ids.begin(), sideset_ids.end());
1056 auto side_list = mesh.get_boundary_info().build_side_list();
1057 if (!mesh.is_serial() && mesh.comm().size() > 1)
1058 {
1059 std::vector<Elem *> elements_to_send;
1060 unsigned short i_need_boundary_elems = 0;
1061 for (const auto & [elem_id, side, bc_id] : side_list)
1062 {
1063 libmesh_ignore(side);
1064 if (sidesets.count(bc_id))
1065 {
1066 // Whether we have this boundary information through our locally owned element or a ghosted
1067 // element, we'll need the boundary elements for parallel consistent addition
1068 i_need_boundary_elems = 1;
1069 auto * elem = mesh.elem_ptr(elem_id);
1070 if (elem->processor_id() == mesh.processor_id())
1071 elements_to_send.push_back(elem);
1072 }
1073 }
1074
1075 std::set<const Elem *, libMesh::CompareElemIdsByLevel> connected_elements(
1076 elements_to_send.begin(), elements_to_send.end());
1077 std::set<const Node *> connected_nodes;
1078 reconnect_nodes(connected_elements, connected_nodes);
1079 std::set<dof_id_type> connected_node_ids;
1080 for (auto * nd : connected_nodes)
1081 connected_node_ids.insert(nd->id());
1082
1083 std::vector<unsigned short> need_boundary_elems(mesh.comm().size());
1084 mesh.comm().allgather(i_need_boundary_elems, need_boundary_elems);
1085 std::unordered_map<processor_id_type, decltype(elements_to_send)> push_element_data;
1086 std::unordered_map<processor_id_type, decltype(connected_nodes)> push_node_data;
1087
1088 for (const auto pid : index_range(mesh.comm()))
1089 // Don't need to send to self
1090 if (pid != mesh.processor_id() && need_boundary_elems[pid])
1091 {
1092 if (elements_to_send.size())
1093 push_element_data[pid] = elements_to_send;
1094 if (connected_nodes.size())
1095 push_node_data[pid] = connected_nodes;
1096 }
1097
1098 auto node_action_functor = [](processor_id_type, const auto &)
1099 {
1100 // Node packing specialization already has unpacked node into mesh, so nothing to do
1101 };
1102 Parallel::push_parallel_packed_range(mesh.comm(), push_node_data, &mesh, node_action_functor);
1103 auto elem_action_functor = [](processor_id_type, const auto &)
1104 {
1105 // Elem packing specialization already has unpacked elem into mesh, so nothing to do
1106 };
1107 TIMPI::push_parallel_packed_range(mesh.comm(), push_element_data, &mesh, elem_action_functor);
1108
1109 // now that we've gathered everything, we need to rebuild the side list
1110 side_list = mesh.get_boundary_info().build_side_list();
1111 }
1112
1113 std::vector<std::pair<dof_id_type, ElemSidePair>> element_sides_on_boundary;
1114 dof_id_type counter = 0;
1115 for (const auto & [eid, side, bid] : side_list)
1116 if (sidesets.count(bid))
1117 {
1118 if (auto elem = mesh.query_elem_ptr(eid))
1119 {
1120 if (!elem->active())
1121 mooseError(
1122 "Only active, level 0 elements can be made interior parents of new level 0 lower-d "
1123 "elements. Make sure that ",
1124 type_name,
1125 "s are run before any refinement generators");
1126 element_sides_on_boundary.push_back(std::make_pair(counter, ElemSidePair(elem, side)));
1127 }
1128 ++counter;
1129 }
1130
1131 dof_id_type max_elem_id = mesh.max_elem_id();
1132 unique_id_type max_unique_id = mesh.parallel_max_unique_id();
1133
1134 // Making an important assumption that at least our boundary elements are the same on all
1135 // processes even in distributed mesh mode (this is reliant on the correct ghosting functors
1136 // existing on the mesh)
1137 for (auto & [i, elem_side] : element_sides_on_boundary)
1138 {
1139 Elem * elem = elem_side.elem;
1140
1141 const auto side = elem_side.side;
1142
1143 // Build a non-proxy element from this side.
1144 std::unique_ptr<Elem> side_elem(elem->build_side_ptr(side));
1145
1146 // The side will be added with the same processor id as the parent.
1147 side_elem->processor_id() = elem->processor_id();
1148
1149 // Add subdomain ID
1150 side_elem->subdomain_id() = new_block_id;
1151
1152 // Also assign the side's interior parent, so it is always
1153 // easy to figure out the Elem we came from.
1154 side_elem->set_interior_parent(elem);
1155
1156 // Add id
1157 side_elem->set_id(max_elem_id + i);
1158 side_elem->set_unique_id(max_unique_id + i);
1159
1160 // Finally, add the lower-dimensional element to the mesh.
1161 mesh.add_elem(side_elem.release());
1162 };
1163
1164 // Assign block name, if provided
1165 if (new_subdomain_name.size())
1166 mesh.set_subdomain_name(new_block_id, new_subdomain_name);
1167
1168 const bool skip_partitioning_old = mesh.skip_partitioning();
1169 mesh.skip_partitioning(true);
1171 mesh.skip_partitioning(skip_partitioning_old);
1172}
1173
1174void
1176 MeshBase & target_mesh,
1177 const std::vector<SubdomainName> & target_blocks)
1178{
1179 if (!source_mesh.is_replicated())
1180 mooseError("This generator does not support distributed meshes.");
1181
1182 const auto target_block_ids = MooseMeshUtils::getSubdomainIDs(source_mesh, target_blocks);
1183
1184 // Check that the block ids/names exist in the mesh
1185 std::set<SubdomainID> mesh_blocks;
1186 source_mesh.subdomain_ids(mesh_blocks);
1187
1188 for (const auto i : index_range(target_block_ids))
1189 if (target_block_ids[i] == Moose::INVALID_BLOCK_ID || !mesh_blocks.count(target_block_ids[i]))
1190 {
1191 mooseException("The target_block '", target_blocks[i], "' was not found within the mesh.");
1192 }
1193
1194 // know which nodes have already been inserted, by tracking the old mesh's node's ids'
1195 std::unordered_map<dof_id_type, dof_id_type> old_new_node_map;
1196
1197 for (const auto target_block_id : target_block_ids)
1198 {
1199
1200 for (auto elem : source_mesh.active_subdomain_elements_ptr_range(target_block_id))
1201 {
1202 if (elem->level() != 0)
1203 mooseError("Refined blocks are not supported by this generator. "
1204 "Can you re-organize mesh generators to refine after converting the block?");
1205
1206 // make a deep copy so that mutiple meshes' destructors don't segfault at program termination
1207 auto copy = elem->build(elem->type());
1208
1209 // Keep the subdomain id
1210 copy->subdomain_id() = elem->subdomain_id();
1211
1212 // index of node in the copy element must be managed manually as there is no intelligent
1213 // insert method
1214 dof_id_type copy_n_index = 0;
1215
1216 // correctly assign new copies of nodes, loop over nodes
1217 for (dof_id_type i : elem->node_index_range())
1218 {
1219 auto & n = elem->node_ref(i);
1220
1221 if (old_new_node_map.count(n.id()))
1222 {
1223 // case where we have already inserted this particular point before
1224 // then we need to find the already-inserted one and hook it up right
1225 // to it's respective element
1226 copy->set_node(copy_n_index++, target_mesh.node_ptr(old_new_node_map[n.id()]));
1227 }
1228 else
1229 {
1230 // case where we've NEVER inserted this particular point before
1231 // add them both to the element and the mesh
1232
1233 // Nodes' IDs are their indexes in the nodes' respective mesh
1234 // If we set them as invalid they are automatically assigned
1235 // Add to mesh, auto-assigning a new id.
1236 Node * node = target_mesh.add_point(elem->point(i));
1237
1238 // Add to element copy (manually)
1239 copy->set_node(copy_n_index++, node);
1240
1241 // remember the (old) ID
1242 old_new_node_map[n.id()] = node->id();
1243 }
1244 }
1245
1246 // it is ok to release the copy element into the mesh because derived meshes class
1247 // (ReplicatedMesh, DistributedMesh) manage their own elements, will delete them
1248 target_mesh.add_elem(copy.release());
1249 }
1250 }
1251
1252 // Move subdomain names
1253 for (const auto sbd_id : target_block_ids)
1254 target_mesh.set_subdomain_name(sbd_id, source_mesh.subdomain_name(sbd_id));
1255}
1256
1257void
1259 UnstructuredMesh & destination,
1260 const UnstructuredMesh & source,
1261 const bool avoid_merging_subdomains,
1262 const bool avoid_merging_boundaries,
1263 const Parallel::Communicator & communicator)
1264{
1265 dof_id_type node_delta = destination.max_node_id();
1266 dof_id_type elem_delta = destination.max_elem_id();
1267
1268 unique_id_type unique_delta =
1269#ifdef LIBMESH_ENABLE_UNIQUE_ID
1270 destination.parallel_max_unique_id();
1271#else
1272 0;
1273#endif
1274
1275 // Prevent overlaps by offsetting the subdomains in
1276 std::unordered_map<subdomain_id_type, subdomain_id_type> id_remapping;
1277 unsigned int block_offset = 0;
1278 if (avoid_merging_subdomains)
1279 {
1280 // Note: if performance becomes an issue, this is overkill for just getting the max node id
1281 std::set<subdomain_id_type> source_ids;
1282 std::set<subdomain_id_type> dest_ids;
1283
1284 // We need source subdomain ids already cached; libMesh will
1285 // scream otherwise
1286 source.subdomain_ids(source_ids, true);
1287
1288 // Our destination is non-const, so we can fix any missing caches
1289 if (!destination.preparation().has_cached_elem_data)
1290 destination.cache_elem_data();
1291
1292 destination.subdomain_ids(dest_ids, true);
1293
1294 mooseAssert(source_ids.size(), "Should have a subdomain");
1295 mooseAssert(dest_ids.size(), "Should have a subdomain");
1296 unsigned int max_dest_bid = *dest_ids.rbegin();
1297 unsigned int min_source_bid = *source_ids.begin();
1298 communicator.max(max_dest_bid);
1299 communicator.min(min_source_bid);
1300 block_offset = 1 + max_dest_bid - min_source_bid;
1301 for (const auto bid : source_ids)
1302 id_remapping[bid] = block_offset + bid;
1303 }
1304
1305 // Copy mesh data over from the other mesh
1306 destination.copy_nodes_and_elements(source,
1307 // Skipping this should cause the neighbors
1308 // to simply be copied from the other mesh
1309 // (which makes sense and is way faster)
1310 /*skip_find_neighbors = */ true,
1311 elem_delta,
1312 node_delta,
1313 unique_delta,
1314 avoid_merging_subdomains ? &id_remapping : nullptr);
1315
1316 // Get an offset to prevent overlaps / wild merging between boundaries
1317 BoundaryInfo & boundary = destination.get_boundary_info();
1318 const BoundaryInfo & other_boundary = source.get_boundary_info();
1319
1320 unsigned int bid_offset = 0;
1321 if (avoid_merging_boundaries)
1322 {
1323 const auto boundary_ids = boundary.get_boundary_ids();
1324 const auto other_boundary_ids = other_boundary.get_boundary_ids();
1325 unsigned int max_dest_bid = boundary_ids.size() ? *boundary_ids.rbegin() : 0;
1326 unsigned int min_source_bid = other_boundary_ids.size() ? *other_boundary_ids.begin() : 0;
1327 communicator.max(max_dest_bid);
1328 communicator.min(min_source_bid);
1329 bid_offset = 1 + max_dest_bid - min_source_bid;
1330 }
1331
1332 // Note: the code below originally came from ReplicatedMesh::stitch_mesh_helper()
1333 // in libMesh replicated_mesh.C around line 1203
1334
1335 // Copy BoundaryInfo from other_mesh too. We do this via the
1336 // list APIs rather than element-by-element for speed.
1337 for (const auto & t : other_boundary.build_node_list())
1338 boundary.add_node(std::get<0>(t) + node_delta, bid_offset + std::get<1>(t));
1339
1340 for (const auto & t : other_boundary.build_side_list())
1341 boundary.add_side(std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
1342
1343 for (const auto & t : other_boundary.build_edge_list())
1344 boundary.add_edge(std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
1345
1346 for (const auto & t : other_boundary.build_shellface_list())
1347 boundary.add_shellface(
1348 std::get<0>(t) + elem_delta, std::get<1>(t), bid_offset + std::get<2>(t));
1349
1350 // Check for the case with two block ids sharing the same name
1351 if (avoid_merging_subdomains)
1352 {
1353 mooseAssert(mg.parameters().isParamDefined("avoid_merging_subdomains"),
1354 "Missing parameter in the mesh generator calling this function: "
1355 "avoid_merging_subdomains. Considering setting avoid_merging_subdomains to true.");
1356 for (const auto & [block_id, block_name] : destination.get_subdomain_name_map())
1357 for (const auto & [source_id, source_name] : source.get_subdomain_name_map())
1358 if (block_name == source_name)
1359 mg.paramWarning(
1360 "avoid_merging_subdomains",
1361 "Not merging subdomains is creating two subdomains with the same name '" +
1362 block_name + "' but different ids: " + std::to_string(source_id) + " & " +
1363 std::to_string(block_id + block_offset) +
1364 ".\n We recommend using a RenameBlockGenerator to prevent this as you "
1365 "will get errors reading the Exodus output later.");
1366 }
1367
1368 for (const auto & [block_id, block_name] : source.get_subdomain_name_map())
1369 destination.set_subdomain_name_map().insert(
1370 std::make_pair<SubdomainID, SubdomainName>(block_id + block_offset, block_name));
1371
1372 // Check for the case with two boundary ids sharing the same name
1373 if (avoid_merging_boundaries)
1374 {
1375 mooseAssert(mg.parameters().isParamDefined("avoid_merging_boundaries"),
1376 "Missing parameter in the mesh generator calling this function: "
1377 "avoid_merging_boundaries. Considering setting avoid_merging_boundaries to true.");
1378 for (const auto & [b_id, b_name] : other_boundary.get_sideset_name_map())
1379 for (const auto & [source_id, source_name] : boundary.get_sideset_name_map())
1380 if (b_name == source_name)
1381 mg.paramWarning(
1382 "avoid_merging_boundaries",
1383 "Not merging boundaries is creating two sidesets with the same name '" + b_name +
1384 "' but different ids: " + std::to_string(source_id) + " & " +
1385 std::to_string(b_id + bid_offset) +
1386 ".\n We recommend using a RenameBoundaryGenerator to prevent this as you "
1387 "will get errors reading the Exodus output later.");
1388 for (const auto & [b_id, b_name] : other_boundary.get_nodeset_name_map())
1389 for (const auto & [source_id, source_name] : boundary.get_nodeset_name_map())
1390 if (b_name == source_name)
1391 mg.paramWarning(
1392 "avoid_merging_boundaries",
1393 "Not merging boundaries is creating two nodesets with the same name '" + b_name +
1394 "' but different ids: " + std::to_string(source_id) + " & " +
1395 std::to_string(b_id + bid_offset) +
1396 ".\n We recommend using a RenameBoundaryGenerator to prevent this as you "
1397 "will get errors reading the Exodus output later.");
1398 }
1399
1400 for (const auto & [nodeset_id, nodeset_name] : other_boundary.get_nodeset_name_map())
1401 boundary.set_nodeset_name_map().insert(
1402 std::make_pair<BoundaryID, BoundaryName>(nodeset_id + bid_offset, nodeset_name));
1403
1404 for (const auto & [sideset_id, sideset_name] : other_boundary.get_sideset_name_map())
1405 boundary.set_sideset_name_map().insert(
1406 std::make_pair<BoundaryID, BoundaryName>(sideset_id + bid_offset, sideset_name));
1407
1408 for (const auto & [edgeset_id, edgeset_name] : other_boundary.get_edgeset_name_map())
1409 boundary.set_edgeset_name_map().insert(
1410 std::make_pair<BoundaryID, BoundaryName>(edgeset_id + bid_offset, edgeset_name));
1411}
1412
1413void
1415 const std::vector<Point> & points,
1416 const std::vector<Point> & mid_points,
1417 const bool loop,
1418 const BoundaryName & start_boundary,
1419 const BoundaryName & end_boundary,
1420 const std::vector<unsigned int> & nums_edges_between_points)
1421{
1422 mooseAssert(nums_edges_between_points.size() == 1 ||
1423 nums_edges_between_points.size() == points.size() - 1 + loop,
1424 "nums_edges_between_points must be either a single value or have the same number of "
1425 "entries as segments defined by the points.");
1426 mooseAssert(
1427 mid_points.size() == 0 || mid_points.size() == points.size() - (loop ? 0 : 1),
1428 "mid_points must be either empty or have the consistent number of entries as points.");
1429 mooseAssert(
1430 mid_points.size() == 0 ||
1431 (nums_edges_between_points.size() == 1 && nums_edges_between_points.front() == 1) ||
1432 (nums_edges_between_points.size() == points.size() - 1 + loop &&
1433 std::all_of(nums_edges_between_points.begin(),
1434 nums_edges_between_points.end(),
1435 [](unsigned int n) { return n == 1; })),
1436 "mid_points can only be provided if each segment has exactly one edge.");
1437
1438 const auto n_points = points.size();
1439 for (auto i : make_range(n_points))
1440 {
1441 const auto & num_edges_between_points =
1442 (nums_edges_between_points.size() == 1)
1443 ? nums_edges_between_points[0]
1444 : (i == nums_edges_between_points.size() ? 0 : nums_edges_between_points[i]);
1445
1446 Point p = points[i];
1447 const auto pt_counter = (nums_edges_between_points.size() == 1)
1448 ? i
1449 : std::accumulate(nums_edges_between_points.begin(),
1450 nums_edges_between_points.begin() + i,
1451 0);
1453 p, nums_edges_between_points.size() == 1 ? (i * num_edges_between_points) : pt_counter);
1454
1455 if (num_edges_between_points > 1)
1456 {
1457 if (!loop && (i + 1) == n_points)
1458 break;
1459
1460 const auto ip1 = (i + 1) % n_points;
1461 const Point pvec = (points[ip1] - p) / num_edges_between_points;
1462
1463 for (auto j : make_range(1u, num_edges_between_points))
1464 {
1465 p += pvec;
1467 p,
1468 (nums_edges_between_points.size() == 1 ? (i * num_edges_between_points) : pt_counter) +
1469 j);
1470 }
1471 }
1472 }
1473 // Add mid points if applicable. When mid points are provided, each segment has exactly one edge,
1474 // so the midpoint node ids follow the vertex node ids.
1475 for (const auto & i : make_range(mid_points.size()))
1476 mesh.add_point(mid_points[i], n_points + i);
1477
1478 const auto n_segments = loop ? n_points : (n_points - 1);
1479 const auto n_elem =
1480 nums_edges_between_points.size() == 1
1481 ? n_segments * nums_edges_between_points[0]
1482 : std::accumulate(nums_edges_between_points.begin(), nums_edges_between_points.end(), 0);
1483 const auto max_nodes =
1484 (nums_edges_between_points.size() == 1 ? n_segments * nums_edges_between_points[0]
1485 : std::accumulate(nums_edges_between_points.begin(),
1486 nums_edges_between_points.end(),
1487 0)) +
1488 (loop ? 0 : 1);
1489 for (auto i : make_range(n_elem))
1490 {
1491 std::unique_ptr<Elem> elem;
1492 if (mid_points.size())
1493 {
1494 elem = std::make_unique<Edge3>();
1495 elem->set_node(2, mesh.node_ptr(n_points + i));
1496 }
1497 else
1498 elem = Elem::build(EDGE2);
1499 const auto ip1 = (i + 1) % max_nodes;
1500 elem->set_node(0, mesh.node_ptr(i));
1501 elem->set_node(1, mesh.node_ptr(ip1));
1502 elem->set_id() = i;
1503 mesh.add_elem(std::move(elem));
1504 }
1505
1506 if (!loop)
1507 {
1509 std::vector<BoundaryName> bdy_names{start_boundary, end_boundary};
1510 std::vector<boundary_id_type> ids = MooseMeshUtils::getBoundaryIDs(mesh, bdy_names, true);
1511 bi.add_side(mesh.elem_ptr(0), 0, ids[0]);
1512 bi.add_side(mesh.elem_ptr(n_elem - 1), 1, ids[1]);
1513 }
1514 else
1515 mooseAssert(start_boundary.empty() && end_boundary.empty(),
1516 "Cannot assign start/end boundaries on a looped polyline.");
1517
1519}
1520
1521void
1523 const std::vector<Point> & points,
1524 const bool loop,
1525 const BoundaryName & start_boundary,
1526 const BoundaryName & end_boundary,
1527 const std::vector<unsigned int> & nums_edges_between_points)
1528{
1530 mesh, points, {}, loop, start_boundary, end_boundary, nums_edges_between_points);
1531}
1532
1533void
1535 const std::vector<Point> & points,
1536 const bool loop,
1537 const BoundaryName & start_boundary,
1538 const BoundaryName & end_boundary,
1539 const Real max_elem_size)
1540{
1541 std::vector<unsigned int> nums_edges_between_points;
1542 const auto n_points = points.size();
1543 for (auto i : make_range(n_points))
1544 {
1545 if (!loop && (i + 1) == n_points)
1546 break;
1547
1548 const auto ip1 = (i + 1) % n_points;
1549 const Real length = (points[ip1] - points[i]).norm();
1550 const unsigned int n_elems = std::max(
1551 static_cast<unsigned int>(std::ceil(length / max_elem_size)), static_cast<unsigned int>(1));
1552 nums_edges_between_points.push_back(n_elems);
1553 }
1554
1556 mesh, points, {}, loop, start_boundary, end_boundary, nums_edges_between_points);
1557}
1558
1559void
1560addExternalBoundary(MeshBase & mesh, const BoundaryID extern_bid, bool & has_external_bid)
1561{
1562 auto & binfo = mesh.get_boundary_info();
1563 for (const auto & elem : mesh.active_element_ptr_range())
1564 for (const auto & i_side : elem->side_index_range())
1565 if (elem->neighbor_ptr(i_side) == nullptr)
1566 {
1567 has_external_bid = true;
1568 binfo.add_side(elem, i_side, extern_bid);
1569 }
1570}
1571}
boundary_id_type BoundaryID
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
std::set< std::string > sideset
bool isParamDefined(const std::string &name) const
Method returns true if the parameter is defined for any type.
MeshGenerators are objects that can modify or add to an existing mesh.
const InputParameters & parameters() const
Get the parameters of the object.
Definition MooseBase.h:131
Provides a way for users to bail out of the current solve.
virtual const char * what() const
Get out the error message.
void paramWarning(const std::string &param, Args... args) const
void max(const T &r, T &o, Request &req) const
processor_id_type size() const
void set_union(T &data, const unsigned int root_id) const
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
void add_shellface(const dof_id_type elem, const unsigned short int shellface, const boundary_id_type id)
void synchronize_global_id_set()
void add_edge(const dof_id_type elem, const unsigned short int edge, const boundary_id_type id)
std::tuple< dof_id_type, unsigned short int, boundary_id_type > BCTuple
const std::set< boundary_id_type > & get_global_boundary_ids() const
std::vector< BCTuple > build_side_list(BCTupleSortBy sort_by=BCTupleSortBy::ELEM_ID) const
std::vector< NodeBCTuple > build_node_list(NodeBCTupleSortBy sort_by=NodeBCTupleSortBy::NODE_ID) const
boundary_id_type get_id_by_name(std::string_view name) const
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
void remove_id(boundary_id_type id, bool global=false)
std::map< boundary_id_type, std::string > & set_sideset_name_map()
std::vector< BCTuple > build_shellface_list() const
const std::multimap< const Node *, boundary_id_type > & get_nodeset_map() const
const std::set< boundary_id_type > & get_boundary_ids() const
void add_node(const Node *node, const boundary_id_type id)
std::map< boundary_id_type, std::string > & set_edgeset_name_map()
void renumber_id(boundary_id_type old_id, boundary_id_type new_id)
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
void remove_side(const Elem *elem, const unsigned short int side)
std::vector< BCTuple > build_edge_list() const
const std::map< boundary_id_type, std::string > & get_edgeset_name_map() const
const std::map< boundary_id_type, std::string > & get_nodeset_name_map() const
std::map< boundary_id_type, std::string > & set_nodeset_name_map()
const std::map< boundary_id_type, std::string > & get_sideset_name_map() const
processor_id_type processor_id() const
static constexpr dof_id_type invalid_id
dof_id_type id() const
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const=0
virtual Node *& set_node(const unsigned int i)
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
virtual std::vector< unsigned int > nodes_on_side(const unsigned int) const=0
virtual unsigned short dim() const=0
const Node * node_ptr(const unsigned int i) const
unsigned int get_node_index(const Node *node_ptr) const
virtual unsigned int n_sides() const=0
dof_id_type node_id(const unsigned int i) const
IntRange< unsigned short > side_index_range() const
virtual std::unique_ptr< Elem > build_side_ptr(const unsigned int i)=0
void set_subdomain_name(subdomain_id_type id, const std::string &name, bool synchronous=false)
void allow_remote_element_removal(bool allow)
virtual const Point & point(const dof_id_type i) const=0
virtual bool is_serial() const
const BoundaryInfo & get_boundary_info() const
Preparation preparation() const
bool is_prepared() const
virtual const Node * node_ptr(const dof_id_type i) const=0
virtual bool is_replicated() const
void subdomain_ids(std::set< subdomain_id_type > &ids, const bool global=true) const
unsigned int get_elem_integer_index(std::string_view name) const
const std::map< subdomain_id_type, std::string > & get_subdomain_name_map() const
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
virtual dof_id_type max_node_id() const=0
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
virtual const Elem * elem_ptr(const dof_id_type i) const=0
unsigned int level ElemType type std::set< subdomain_id_type > ss processor_id_type pid unsigned int level std::set< subdomain_id_type > virtual ss SimpleRange< element_iterator > active_subdomain_elements_ptr_range(subdomain_id_type sid)=0
virtual dof_id_type max_elem_id() const=0
const std::set< subdomain_id_type > & get_mesh_subdomains() const
virtual const Elem * query_elem_ptr(const dof_id_type i) const=0
virtual Elem * add_elem(Elem *e)=0
std::string & subdomain_name(subdomain_id_type id)
std::map< subdomain_id_type, std::string > & set_subdomain_name_map()
subdomain_id_type get_id_by_name(std::string_view name) const
void skip_partitioning(bool skip)
std::string get_info(const unsigned int verbosity=0, const bool global=true) const
void unset_is_prepared()
virtual unique_id_type parallel_max_unique_id() const=0
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
auto norm_sq() const
auto norm() const
TypeVector< Real > unit() const
virtual void copy_nodes_and_elements(const MeshBase &other_mesh, const bool skip_find_neighbors=false, dof_id_type element_id_offset=0, dof_id_type node_id_offset=0, unique_id_type unique_id_offset=0, std::unordered_map< subdomain_id_type, subdomain_id_type > *id_remapping=nullptr, const bool skip_preparation=false)
MeshBase & mesh
void copyIntoMesh(MeshGenerator &mg, UnstructuredMesh &destination, const UnstructuredMesh &source, const bool avoid_merging_subdomains, const bool avoid_merging_boundaries, const Parallel::Communicator &communicator)
Helper function for copying one mesh into another.
bool hasBoundaryID(const MeshBase &input_mesh, const BoundaryID id)
Whether a particular boundary ID exists in the mesh.
Point boundaryCentroidCalculator(const BoundaryName &boundary, MeshBase &mesh)
Calculates the centroid of a boundary on a mesh.
bool hasBoundaryName(const MeshBase &mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
void extraElemIntegerSwapParametersProcessor(const std::string &class_name, const unsigned int num_sections, const unsigned int num_integers, const std::vector< std::vector< std::vector< dof_id_type > > > &elem_integers_swaps, std::vector< std::unordered_map< dof_id_type, dof_id_type > > &elem_integers_swap_pairs)
Reprocess the elem_integers_swaps into maps so they are easier to use.
bool hasSubdomainName(const MeshBase &input_mesh, const SubdomainName &name)
Whether a particular subdomain name exists in the mesh.
void buildPolyLineMesh(MeshBase &mesh, const std::vector< Point > &points, const bool loop, const BoundaryName &start_boundary, const BoundaryName &end_boundary, const std::vector< unsigned int > &nums_edges_between_points)
Generates meshes from edges connecting a list of points.
void changeBoundaryId(MeshBase &mesh, const libMesh::boundary_id_type old_id, const libMesh::boundary_id_type new_id, bool delete_prev)
Changes the old boundary ID to a new ID in the mesh.
bool hasSubdomainID(const MeshBase &input_mesh, const SubdomainID &id)
Whether a particular subdomain ID exists in the mesh.
std::vector< subdomain_id_type > getSubdomainIDs(const libMesh::MeshBase &mesh, const std::vector< SubdomainName > &subdomain_name)
Get the associated subdomainIDs for the subdomain names that are passed in.
std::unique_ptr< ReplicatedMesh > buildBoundaryMesh(const MeshBase &input_mesh, const boundary_id_type boundary_id)
Build a lower-dimensional mesh from a boundary of an input mesh Note: The lower-dimensional mesh will...
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
Gets the boundary IDs with their names.
std::unordered_map< dof_id_type, std::unordered_set< dof_id_type > > buildBoundaryNodeToElemMap(const MeshBase &input_mesh, const boundary_id_type boundary_id)
Build a map from the node ids to all element ids they are part of for the nodes on a particular nodes...
void changeSubdomainId(MeshBase &mesh, const subdomain_id_type old_id, const subdomain_id_type new_id)
Changes the old subdomain ID to a new ID in the mesh.
std::set< dof_id_type > getBoundaryNodes(const MeshBase &mesh, const BoundaryID boundary_id)
Get all the nodes on that particular boundary, whether a nodeset or a sideset.
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
Checks input mesh and returns the largest boundary ID in the mesh plus one, which is a boundary ID in...
void addExternalBoundary(MeshBase &mesh, const BoundaryID extern_bid, bool &has_external_bid)
Adds a sideset for the external boundary of the mesh (e.g.
void swapNodesInElem(Elem &elem, const unsigned int nd1, const unsigned int nd2)
Swap two nodes within an element.
void convertBlockToMesh(MeshBase &source_mesh, MeshBase &target_mesh, const std::vector< SubdomainName > &target_blocks)
Convert a list of blocks in a given mesh to a standalone new mesh.
std::set< BoundaryID > getBoundaryIDSet(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown)
Gets the boundary IDs into a set with their names.
std::unordered_map< dof_id_type, dof_id_type > getExtraIDUniqueCombinationMap(const MeshBase &mesh, const std::set< SubdomainID > &block_ids, std::vector< ExtraElementIDName > extra_ids)
Create a new set of element-wise IDs by finding unique combinations of existing extra ID values.
void createSubdomainFromSidesets(MeshBase &mesh, std::vector< BoundaryName > boundary_names, const SubdomainID new_subdomain_id, const SubdomainName new_subdomain_name, const std::string type_name)
Create a new subdomain by generating new side elements from a list of sidesets in a given mesh.
void idSwapParametersProcessor(const std::string &class_name, const std::string &id_name, const std::vector< std::vector< T > > &id_swaps, std::vector< std::unordered_map< T, T > > &id_swap_pairs, const unsigned int row_index_shift=0)
Reprocess the swap related input parameters to make pairs out of them to ease further processing.
void mergeBoundaryIDsWithSameName(MeshBase &mesh)
Merges the boundary IDs of boundaries that have the same names but different IDs.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
Gets the boundary ID associated with the given BoundaryName.
bool isCoPlanar(const std::vector< Point > &vec_pts, const Point plane_nvec, const Point fixed_pt)
Decides whether all the Points of a vector of Points are in a plane that is defined by a normal vecto...
std::unique_ptr< ReplicatedMesh > buildLoopBoundaryOf2DMesh(const MeshBase &input_mesh, const boundary_id_type boundary_id)
Build a loop mesh of edges from the contiguous 2D boundary of 2D input mesh Note: The lower-dimension...
RealVectorValue boundaryWeightedNormal(const BoundaryName &boundary, MeshBase &mesh)
Calculates the side-volume weighted (side-vertex) average normal of a boundary on a mesh.
void makeOrderedNodeList(std::vector< std::pair< dof_id_type, dof_id_type > > &node_assm, std::vector< dof_id_type > &elem_id_list, std::vector< dof_id_type > &midpoint_node_list, std::vector< dof_id_type > &ordered_node_list, std::vector< dof_id_type > &ordered_elem_id_list)
Convert a list of sides in the form of a vector of pairs of node ids into a list of ordered nodes bas...
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
Point meshCentroidCalculator(const MeshBase &mesh)
Calculates the centroid of a MeshBase.
bool hasBoundaryNameOrID(const MeshBase &mesh, const BoundaryName &name_or_id)
Whether a particular boundary name or ID exists in the mesh.
Real computeMaxDistanceToAxis(const MeshBase &mesh, const Point &origin, const RealVectorValue &direction)
Computes the maximum distance from all nodes of a mesh to a general axis.
std::string stringify(const T &t)
conversion to string
Definition Conversion.h:64
const BoundaryID INVALID_BOUNDARY_ID
Definition MooseTypes.C:22
const SubdomainID INVALID_BLOCK_ID
Definition MooseTypes.C:20
void push_parallel_packed_range(const Communicator &comm, MapToContainers &&data, Context *context, const ActionFunctor &act_on_data)
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
uint8_t unique_id_type
void reconnect_nodes(connected_elem_set_type &connected_elements, connected_node_set_type &connected_nodes)
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
auto index_range(const T &sizable)
int8_t boundary_id_type
void libmesh_ignore(const Args &...)
const unsigned int invalid_uint
uint8_t dof_id_type
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
uint8_t processor_id_type
IntRange< T > make_range(T beg, T end)
Real distance(const Point &p)