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