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 
27 #include "timpi/parallel_sync.h"
28 
29 using namespace libMesh;
30 
31 namespace MooseMeshUtils
32 {
33 
34 void
36 {
37  // We check if we have the same boundary name with different IDs. If we do, we assign the
38  // first ID to every occurrence.
39  const auto & side_bd_name_map = mesh.get_boundary_info().get_sideset_name_map();
40  const auto & node_bd_name_map = mesh.get_boundary_info().get_nodeset_name_map();
41  std::map<boundary_id_type, boundary_id_type> same_name_ids;
42 
43  auto populate_map = [](const std::map<boundary_id_type, std::string> & map,
44  std::map<boundary_id_type, boundary_id_type> & same_ids)
45  {
46  for (const auto & pair_outer : map)
47  for (const auto & pair_inner : map)
48  // The last condition is needed to make sure we only store one combination
49  if (pair_outer.second == pair_inner.second && pair_outer.first != pair_inner.first &&
50  same_ids.find(pair_inner.first) == same_ids.end())
51  same_ids[pair_outer.first] = pair_inner.first;
52  };
53 
54  populate_map(side_bd_name_map, same_name_ids);
55  populate_map(node_bd_name_map, same_name_ids);
56 
57  for (const auto & [id1, id2] : same_name_ids)
59 }
60 
61 void
63  const boundary_id_type old_id,
64  const boundary_id_type new_id,
65  bool delete_prev)
66 {
67  // Get a reference to our BoundaryInfo object, we will use it several times below...
68  BoundaryInfo & boundary_info = mesh.get_boundary_info();
69 
70  // Container to catch ids passed back from BoundaryInfo
71  std::vector<boundary_id_type> old_ids;
72 
73  // Only level-0 elements store BCs. Loop over them.
74  for (auto & elem : as_range(mesh.level_elements_begin(0), mesh.level_elements_end(0)))
75  {
76  unsigned int n_sides = elem->n_sides();
77  for (const auto s : make_range(n_sides))
78  {
79  boundary_info.boundary_ids(elem, s, old_ids);
80  if (std::find(old_ids.begin(), old_ids.end(), old_id) != old_ids.end())
81  {
82  std::vector<boundary_id_type> new_ids(old_ids);
83  std::replace(new_ids.begin(), new_ids.end(), old_id, new_id);
84  if (delete_prev)
85  {
86  boundary_info.remove_side(elem, s);
87  boundary_info.add_side(elem, s, new_ids);
88  }
89  else
90  boundary_info.add_side(elem, s, new_ids);
91  }
92  }
93  }
94 
95  // Remove any remaining references to the old ID from the
96  // BoundaryInfo object. This prevents things like empty sidesets
97  // from showing up when printing information, etc.
98  if (delete_prev)
99  boundary_info.remove_id(old_id);
100 
101  // global information may now be out of sync
103 }
104 
105 std::vector<boundary_id_type>
107  const std::vector<BoundaryName> & boundary_name,
108  bool generate_unknown)
109 {
110  return getBoundaryIDs(
111  mesh, boundary_name, generate_unknown, mesh.get_boundary_info().get_boundary_ids());
112 }
113 
114 std::vector<boundary_id_type>
116  const std::vector<BoundaryName> & boundary_name,
117  bool generate_unknown,
118  const std::set<BoundaryID> & mesh_boundary_ids)
119 {
120  const BoundaryInfo & boundary_info = mesh.get_boundary_info();
121  const std::map<BoundaryID, std::string> & sideset_map = boundary_info.get_sideset_name_map();
122  const std::map<BoundaryID, std::string> & nodeset_map = boundary_info.get_nodeset_name_map();
123 
124  BoundaryID max_boundary_local_id = 0;
125  /* It is required to generate a new ID for a given name. It is used often in mesh modifiers such
126  * as SideSetsBetweenSubdomains. Then we need to check the current boundary ids since they are
127  * changing during "mesh modify()", and figure out the right max boundary ID. Most of mesh
128  * modifiers are running in serial, and we won't involve a global communication.
129  */
130  if (generate_unknown)
131  {
134  max_boundary_local_id = bids.empty() ? 0 : *(bids.rbegin());
135  /* We should not hit this often */
136  if (!mesh.is_prepared() && !mesh.is_serial())
137  mesh.comm().max(max_boundary_local_id);
138  }
139 
140  BoundaryID max_boundary_id = mesh_boundary_ids.empty() ? 0 : *(mesh_boundary_ids.rbegin());
141 
142  max_boundary_id =
143  max_boundary_id > max_boundary_local_id ? max_boundary_id : max_boundary_local_id;
144 
145  std::vector<BoundaryID> ids(boundary_name.size());
146  for (const auto i : index_range(boundary_name))
147  {
148  if (boundary_name[i] == "ANY_BOUNDARY_ID")
149  {
150  ids.assign(mesh_boundary_ids.begin(), mesh_boundary_ids.end());
151  if (i)
152  mooseWarning("You passed \"ANY_BOUNDARY_ID\" in addition to other boundary_names. This "
153  "may be a logic error.");
154  break;
155  }
156 
157  if (boundary_name[i].empty() && !generate_unknown)
158  mooseError("Incoming boundary name is empty and we are not generating unknown boundary IDs. "
159  "This is invalid.");
160 
161  BoundaryID id;
162 
163  if (boundary_name[i].empty() || !MooseUtils::isDigits(boundary_name[i]))
164  {
170  if (generate_unknown &&
171  !MooseUtils::doesMapContainValue(sideset_map, std::string(boundary_name[i])) &&
172  !MooseUtils::doesMapContainValue(nodeset_map, std::string(boundary_name[i])))
173  id = ++max_boundary_id;
174  else
175  id = boundary_info.get_id_by_name(boundary_name[i]);
176  }
177  else
178  id = getIDFromName<BoundaryName, BoundaryID>(boundary_name[i]);
179 
180  ids[i] = id;
181  }
182 
183  return ids;
184 }
185 
186 std::set<BoundaryID>
188  const std::vector<BoundaryName> & boundary_name,
189  bool generate_unknown)
190 {
191  auto boundaries = getBoundaryIDs(mesh, boundary_name, generate_unknown);
192  return std::set<BoundaryID>(boundaries.begin(), boundaries.end());
193 }
194 
195 std::vector<subdomain_id_type>
196 getSubdomainIDs(const MeshBase & mesh, const std::vector<SubdomainName> & subdomain_names)
197 {
198  std::vector<subdomain_id_type> ids;
199 
200  // shortcut for "ANY_BLOCK_ID"
201  if (subdomain_names.size() == 1 && subdomain_names[0] == "ANY_BLOCK_ID")
202  {
203  // since get_mesh_subdomains() requires a prepared mesh, we need to check that here
204  mooseAssert(mesh.is_prepared(),
205  "getSubdomainIDs() should only be called on a prepared mesh if ANY_BLOCK_ID is "
206  "used to query all block IDs");
207  ids.assign(mesh.get_mesh_subdomains().begin(), mesh.get_mesh_subdomains().end());
208  return ids;
209  }
210 
211  // loop through subdomain names and get IDs (this preserves the order of subdomain_names)
212  ids.resize(subdomain_names.size());
213  for (auto i : index_range(subdomain_names))
214  {
215  if (subdomain_names[i] == "ANY_BLOCK_ID")
216  mooseError("getSubdomainIDs() accepts \"ANY_BLOCK_ID\" if and only if it is the only "
217  "subdomain name being queried.");
218  ids[i] = MooseMeshUtils::getSubdomainID(subdomain_names[i], mesh);
219  }
220 
221  return ids;
222 }
223 
224 std::set<subdomain_id_type>
225 getSubdomainIDs(const MeshBase & mesh, const std::set<SubdomainName> & subdomain_names)
226 {
227  const auto blk_ids = getSubdomainIDs(
228  mesh, std::vector<SubdomainName>(subdomain_names.begin(), subdomain_names.end()));
229  return {blk_ids.begin(), blk_ids.end()};
230 }
231 
233 getBoundaryID(const BoundaryName & boundary_name, const MeshBase & mesh)
234 {
236  if (boundary_name.empty())
237  return id;
238 
239  if (!MooseUtils::isDigits(boundary_name))
240  id = mesh.get_boundary_info().get_id_by_name(boundary_name);
241  else
242  id = getIDFromName<BoundaryName, BoundaryID>(boundary_name);
243 
244  return id;
245 }
246 
248 getSubdomainID(const SubdomainName & subdomain_name, const MeshBase & mesh)
249 {
250  if (subdomain_name == "ANY_BLOCK_ID")
251  mooseError("getSubdomainID() does not work with \"ANY_BLOCK_ID\"");
252 
254  if (subdomain_name.empty())
255  return id;
256 
257  if (!MooseUtils::isDigits(subdomain_name))
258  id = mesh.get_id_by_name(subdomain_name);
259  else
260  id = getIDFromName<SubdomainName, SubdomainID>(subdomain_name);
261 
262  return id;
263 }
264 
265 void
267 {
268  for (const auto & elem : mesh.element_ptr_range())
269  if (elem->subdomain_id() == old_id)
270  elem->subdomain_id() = new_id;
271 
272  // global cached information may now be out of sync
274 }
275 
276 Point
278 {
279  Point centroid_pt = Point(0.0, 0.0, 0.0);
280  Real vol_tmp = 0.0;
281  for (const auto & elem :
282  as_range(mesh.active_local_elements_begin(), mesh.active_local_elements_end()))
283  {
284  Real elem_vol = elem->volume();
285  centroid_pt += (elem->true_centroid()) * elem_vol;
286  vol_tmp += elem_vol;
287  }
288  mesh.comm().sum(centroid_pt);
289  mesh.comm().sum(vol_tmp);
290  centroid_pt /= vol_tmp;
291  return centroid_pt;
292 }
293 
294 std::unordered_map<dof_id_type, dof_id_type>
296  const std::set<SubdomainID> & block_ids,
297  std::vector<ExtraElementIDName> extra_ids)
298 {
299  // check block restriction
300  const bool block_restricted = !block_ids.empty();
301  // get element id name of interest in recursive parsing algorithm
302  ExtraElementIDName id_name = extra_ids.back();
303  extra_ids.pop_back();
304  const auto id_index = mesh.get_elem_integer_index(id_name);
305 
306  // create base parsed id set
307  if (extra_ids.empty())
308  {
309  // get set of extra id values;
310  std::vector<dof_id_type> ids;
311  {
312  std::set<dof_id_type> ids_set;
313  for (const auto & elem : mesh.active_element_ptr_range())
314  {
315  if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
316  continue;
317  const auto id = elem->get_extra_integer(id_index);
318  ids_set.insert(id);
319  }
320  mesh.comm().set_union(ids_set);
321  ids.assign(ids_set.begin(), ids_set.end());
322  }
323 
324  // determine new extra id values;
325  std::unordered_map<dof_id_type, dof_id_type> parsed_ids;
326  for (auto & elem : mesh.active_element_ptr_range())
327  {
328  if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
329  continue;
330  parsed_ids[elem->id()] = std::distance(
331  ids.begin(), std::lower_bound(ids.begin(), ids.end(), elem->get_extra_integer(id_index)));
332  }
333  return parsed_ids;
334  }
335 
336  // if extra_ids is not empty, recursively call getExtraIDUniqueCombinationMap
337  const auto base_parsed_ids =
339  // parsing extra ids based on ref_parsed_ids
340  std::vector<std::pair<dof_id_type, dof_id_type>> unique_ids;
341  {
342  std::set<std::pair<dof_id_type, dof_id_type>> unique_ids_set;
343  for (const auto & elem : mesh.active_element_ptr_range())
344  {
345  if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
346  continue;
347  const dof_id_type id1 = libmesh_map_find(base_parsed_ids, elem->id());
348  const dof_id_type id2 = elem->get_extra_integer(id_index);
349  const std::pair<dof_id_type, dof_id_type> ids = std::make_pair(id1, id2);
350  unique_ids_set.insert(ids);
351  }
352  mesh.comm().set_union(unique_ids_set);
353  unique_ids.assign(unique_ids_set.begin(), unique_ids_set.end());
354  }
355 
356  std::unordered_map<dof_id_type, dof_id_type> parsed_ids;
357 
358  for (const auto & elem : mesh.active_element_ptr_range())
359  {
360  if (block_restricted && block_ids.find(elem->subdomain_id()) == block_ids.end())
361  continue;
362  const dof_id_type id1 = libmesh_map_find(base_parsed_ids, elem->id());
363  const dof_id_type id2 = elem->get_extra_integer(id_index);
364  const dof_id_type new_id = std::distance(
365  unique_ids.begin(),
366  std::lower_bound(unique_ids.begin(), unique_ids.end(), std::make_pair(id1, id2)));
367  parsed_ids[elem->id()] = new_id;
368  }
369 
370  return parsed_ids;
371 }
372 
373 bool
374 isCoPlanar(const std::vector<Point> vec_pts, const Point plane_nvec, const Point fixed_pt)
375 {
376  for (const auto & pt : vec_pts)
377  if (!MooseUtils::absoluteFuzzyEqual((pt - fixed_pt) * plane_nvec, 0.0))
378  return false;
379  return true;
380 }
381 
382 bool
383 isCoPlanar(const std::vector<Point> vec_pts, const Point plane_nvec)
384 {
385  return isCoPlanar(vec_pts, plane_nvec, vec_pts.front());
386 }
387 
388 bool
389 isCoPlanar(const std::vector<Point> vec_pts)
390 {
391  // Assuming that overlapped Points are allowed, the Points that are overlapped with vec_pts[0] are
392  // removed before further calculation.
393  std::vector<Point> vec_pts_nonzero{vec_pts[0]};
394  for (const auto i : index_range(vec_pts))
395  if (!MooseUtils::absoluteFuzzyEqual((vec_pts[i] - vec_pts[0]).norm(), 0.0))
396  vec_pts_nonzero.push_back(vec_pts[i]);
397  // 3 or fewer points are always coplanar
398  if (vec_pts_nonzero.size() <= 3)
399  return true;
400  else
401  {
402  for (const auto i : make_range(vec_pts_nonzero.size() - 1))
403  {
404  const Point tmp_pt = (vec_pts_nonzero[i] - vec_pts_nonzero[0])
405  .cross(vec_pts_nonzero[i + 1] - vec_pts_nonzero[0]);
406  // if the three points are not collinear, use cross product as the normal vector of the plane
407  if (!MooseUtils::absoluteFuzzyEqual(tmp_pt.norm(), 0.0))
408  return isCoPlanar(vec_pts_nonzero, tmp_pt.unit());
409  }
410  }
411  // If all the points are collinear, they are also coplanar
412  return true;
413 }
414 
417 {
418  // Call this to get most up to date block id information
419  input_mesh.cache_elem_data();
420 
421  std::set<SubdomainID> preexisting_subdomain_ids;
422  input_mesh.subdomain_ids(preexisting_subdomain_ids);
423  if (preexisting_subdomain_ids.empty())
424  return 0;
425  else
426  {
427  const auto highest_subdomain_id =
428  *std::max_element(preexisting_subdomain_ids.begin(), preexisting_subdomain_ids.end());
429  mooseAssert(highest_subdomain_id < std::numeric_limits<SubdomainID>::max(),
430  "A SubdomainID with max possible value was found");
431  return highest_subdomain_id + 1;
432  }
433 }
434 
437 {
438  auto boundary_ids = input_mesh.get_boundary_info().get_boundary_ids();
439  if (boundary_ids.empty())
440  return 0;
441  return (*boundary_ids.rbegin() + 1);
442 }
443 
444 bool
445 hasSubdomainID(const MeshBase & input_mesh, const SubdomainID & id)
446 {
447  std::set<SubdomainID> mesh_blocks;
448  input_mesh.subdomain_ids(mesh_blocks);
449 
450  // On a distributed mesh we may have sideset IDs that only exist on
451  // other processors
452  if (!input_mesh.is_replicated())
453  input_mesh.comm().set_union(mesh_blocks);
454 
455  return mesh_blocks.count(id) && (id != Moose::INVALID_BLOCK_ID);
456 }
457 
458 bool
459 hasSubdomainName(const MeshBase & input_mesh, const SubdomainName & name)
460 {
461  const auto id = getSubdomainID(name, input_mesh);
462  return hasSubdomainID(input_mesh, id);
463 }
464 
465 bool
466 hasBoundaryID(const MeshBase & input_mesh, const BoundaryID id)
467 {
468  const BoundaryInfo & boundary_info = input_mesh.get_boundary_info();
469  std::set<boundary_id_type> boundary_ids = boundary_info.get_boundary_ids();
470 
471  // On a distributed mesh we may have boundary IDs that only exist on
472  // other processors
473  if (!input_mesh.is_replicated())
474  input_mesh.comm().set_union(boundary_ids);
475 
476  return boundary_ids.count(id) && (id != Moose::INVALID_BOUNDARY_ID);
477 }
478 
479 bool
480 hasBoundaryName(const MeshBase & input_mesh, const BoundaryName & name)
481 {
482  const auto id = getBoundaryID(name, input_mesh);
483  return hasBoundaryID(input_mesh, id);
484 }
485 
486 void
487 makeOrderedNodeList(std::vector<std::pair<dof_id_type, dof_id_type>> & node_assm,
488  std::vector<dof_id_type> & elem_id_list,
489  std::vector<dof_id_type> & midpoint_node_list,
490  std::vector<dof_id_type> & ordered_node_list,
491  std::vector<dof_id_type> & ordered_elem_id_list)
492 {
493  // a flag to indicate if the ordered_node_list has been reversed
494  bool is_flipped = false;
495  // Start from the first element, try to find a chain of nodes
496  mooseAssert(node_assm.size(), "Node list must not be empty");
497  ordered_node_list.push_back(node_assm.front().first);
498  if (midpoint_node_list.front() != DofObject::invalid_id)
499  ordered_node_list.push_back(midpoint_node_list.front());
500  ordered_node_list.push_back(node_assm.front().second);
501  ordered_elem_id_list.push_back(elem_id_list.front());
502  // Remove the element that has just been added to ordered_node_list
503  node_assm.erase(node_assm.begin());
504  midpoint_node_list.erase(midpoint_node_list.begin());
505  elem_id_list.erase(elem_id_list.begin());
506  const unsigned int node_assm_size_0 = node_assm.size();
507  for (unsigned int i = 0; i < node_assm_size_0; i++)
508  {
509  // Find nodes to expand the chain
510  dof_id_type end_node_id = ordered_node_list.back();
511  auto isMatch1 = [end_node_id](std::pair<dof_id_type, dof_id_type> old_id_pair)
512  { return old_id_pair.first == end_node_id; };
513  auto isMatch2 = [end_node_id](std::pair<dof_id_type, dof_id_type> old_id_pair)
514  { return old_id_pair.second == end_node_id; };
515  auto result = std::find_if(node_assm.begin(), node_assm.end(), isMatch1);
516  bool match_first;
517  if (result == node_assm.end())
518  {
519  match_first = false;
520  result = std::find_if(node_assm.begin(), node_assm.end(), isMatch2);
521  }
522  else
523  {
524  match_first = true;
525  }
526  // If found, add the node to boundary_ordered_node_list
527  if (result != node_assm.end())
528  {
529  const auto elem_index = std::distance(node_assm.begin(), result);
530  if (midpoint_node_list[elem_index] != DofObject::invalid_id)
531  ordered_node_list.push_back(midpoint_node_list[elem_index]);
532  ordered_node_list.push_back(match_first ? (*result).second : (*result).first);
533  node_assm.erase(result);
534  midpoint_node_list.erase(midpoint_node_list.begin() + elem_index);
535  ordered_elem_id_list.push_back(elem_id_list[elem_index]);
536  elem_id_list.erase(elem_id_list.begin() + elem_index);
537  }
538  // If there are still elements in node_assm and result ==
539  // node_assm.end(), this means the curve is not a loop, the
540  // ordered_node_list is flipped and try the other direction that has not
541  // been examined yet.
542  else
543  {
544  if (is_flipped)
545  // Flipped twice; this means the node list has at least two segments.
546  throw MooseException("The node list provided has more than one segments.");
547 
548  // mark the first flip event.
549  is_flipped = true;
550  std::reverse(ordered_node_list.begin(), ordered_node_list.end());
551  std::reverse(midpoint_node_list.begin(), midpoint_node_list.end());
552  std::reverse(ordered_elem_id_list.begin(), ordered_elem_id_list.end());
553  // As this iteration is wasted, set the iterator backward
554  i--;
555  }
556  }
557 }
558 
559 void
560 makeOrderedNodeList(std::vector<std::pair<dof_id_type, dof_id_type>> & node_assm,
561  std::vector<dof_id_type> & elem_id_list,
562  std::vector<dof_id_type> & ordered_node_list,
563  std::vector<dof_id_type> & ordered_elem_id_list)
564 {
565  std::vector<dof_id_type> dummy_midpoint_node_list(node_assm.size(), DofObject::invalid_id);
567  node_assm, elem_id_list, dummy_midpoint_node_list, ordered_node_list, ordered_elem_id_list);
568 }
569 
570 void
571 swapNodesInElem(Elem & elem, const unsigned int nd1, const unsigned int nd2)
572 {
573  Node * n_temp = elem.node_ptr(nd1);
574  elem.set_node(nd1, elem.node_ptr(nd2));
575  elem.set_node(nd2, n_temp);
576 }
577 
578 void
580  const std::string & class_name,
581  const unsigned int num_sections,
582  const unsigned int num_integers,
583  const std::vector<std::vector<std::vector<dof_id_type>>> & elem_integers_swaps,
584  std::vector<std::unordered_map<dof_id_type, dof_id_type>> & elem_integers_swap_pairs)
585 {
586  elem_integers_swap_pairs.reserve(num_sections * num_integers);
587  for (const auto i : make_range(num_integers))
588  {
589  const auto & elem_integer_swaps = elem_integers_swaps[i];
590  std::vector<std::unordered_map<dof_id_type, dof_id_type>> elem_integer_swap_pairs;
591  try
592  {
594  "elem_integers_swaps",
595  elem_integer_swaps,
596  elem_integer_swap_pairs,
597  i * num_sections);
598  }
599  catch (const MooseException & e)
600  {
601  throw MooseException(e.what());
602  }
603 
604  elem_integers_swap_pairs.insert(elem_integers_swap_pairs.end(),
605  elem_integer_swap_pairs.begin(),
606  elem_integer_swap_pairs.end());
607  }
608 }
609 
610 std::unique_ptr<ReplicatedMesh>
611 buildBoundaryMesh(const ReplicatedMesh & input_mesh, const boundary_id_type boundary_id)
612 {
613  auto poly_mesh = std::make_unique<ReplicatedMesh>(input_mesh.comm());
614 
615  auto side_list = input_mesh.get_boundary_info().build_side_list();
616 
617  std::unordered_map<dof_id_type, dof_id_type> old_new_node_map;
618  for (const auto & bside : side_list)
619  {
620  if (std::get<2>(bside) != boundary_id)
621  continue;
622 
623  const Elem * elem = input_mesh.elem_ptr(std::get<0>(bside));
624  const auto side = std::get<1>(bside);
625  auto side_elem = elem->build_side_ptr(side);
626  auto copy = side_elem->build(side_elem->type());
627 
628  for (const auto i : side_elem->node_index_range())
629  {
630  auto & n = side_elem->node_ref(i);
631 
632  if (old_new_node_map.count(n.id()))
633  copy->set_node(i, poly_mesh->node_ptr(old_new_node_map[n.id()]));
634  else
635  {
636  Node * node = poly_mesh->add_point(side_elem->point(i));
637  copy->set_node(i, node);
638  old_new_node_map[n.id()] = node->id();
639  }
640  }
641  poly_mesh->add_elem(copy.release());
642  }
643  poly_mesh->skip_partitioning(true);
644  poly_mesh->prepare_for_use();
645  if (poly_mesh->n_elem() == 0)
646  mooseError("The input mesh does not have a boundary with id ", boundary_id);
647 
648  return poly_mesh;
649 }
650 
651 void
652 createSubdomainFromSidesets(std::unique_ptr<MeshBase> & mesh,
653  std::vector<BoundaryName> boundary_names,
654  const SubdomainID new_subdomain_id,
655  const SubdomainName new_subdomain_name,
656  const std::string type_name)
657 {
658  // Generate a new block id if one isn't supplied.
659  SubdomainID new_block_id = new_subdomain_id;
660 
661  // Make sure our boundary info and parallel counts are setup
662  if (!mesh->is_prepared())
663  {
664  const bool allow_remote_element_removal = mesh->allow_remote_element_removal();
665  // We want all of our boundary elements available, so avoid removing them if they haven't
666  // already been so
669  mesh->allow_remote_element_removal(allow_remote_element_removal);
670  }
671 
672  // Check that the sidesets are present in the mesh
673  for (const auto & sideset : boundary_names)
675  mooseException("The sideset '", sideset, "' was not found within the mesh");
676 
677  auto sideset_ids = MooseMeshUtils::getBoundaryIDs(*mesh, boundary_names, true);
678  std::set<boundary_id_type> sidesets(sideset_ids.begin(), sideset_ids.end());
679  auto side_list = mesh->get_boundary_info().build_side_list();
680  if (!mesh->is_serial() && mesh->comm().size() > 1)
681  {
682  std::vector<Elem *> elements_to_send;
683  unsigned short i_need_boundary_elems = 0;
684  for (const auto & [elem_id, side, bc_id] : side_list)
685  {
686  libmesh_ignore(side);
687  if (sidesets.count(bc_id))
688  {
689  // Whether we have this boundary information through our locally owned element or a ghosted
690  // element, we'll need the boundary elements for parallel consistent addition
691  i_need_boundary_elems = 1;
692  auto * elem = mesh->elem_ptr(elem_id);
693  if (elem->processor_id() == mesh->processor_id())
694  elements_to_send.push_back(elem);
695  }
696  }
697 
698  std::set<const Elem *, libMesh::CompareElemIdsByLevel> connected_elements(
699  elements_to_send.begin(), elements_to_send.end());
700  std::set<const Node *> connected_nodes;
701  reconnect_nodes(connected_elements, connected_nodes);
702  std::set<dof_id_type> connected_node_ids;
703  for (auto * nd : connected_nodes)
704  connected_node_ids.insert(nd->id());
705 
706  std::vector<unsigned short> need_boundary_elems(mesh->comm().size());
707  mesh->comm().allgather(i_need_boundary_elems, need_boundary_elems);
708  std::unordered_map<processor_id_type, decltype(elements_to_send)> push_element_data;
709  std::unordered_map<processor_id_type, decltype(connected_nodes)> push_node_data;
710 
711  for (const auto pid : index_range(mesh->comm()))
712  // Don't need to send to self
713  if (pid != mesh->processor_id() && need_boundary_elems[pid])
714  {
715  if (elements_to_send.size())
716  push_element_data[pid] = elements_to_send;
717  if (connected_nodes.size())
718  push_node_data[pid] = connected_nodes;
719  }
720 
721  auto node_action_functor = [](processor_id_type, const auto &)
722  {
723  // Node packing specialization already has unpacked node into mesh, so nothing to do
724  };
725  Parallel::push_parallel_packed_range(
726  mesh->comm(), push_node_data, mesh.get(), node_action_functor);
727  auto elem_action_functor = [](processor_id_type, const auto &)
728  {
729  // Elem packing specialization already has unpacked elem into mesh, so nothing to do
730  };
732  mesh->comm(), push_element_data, mesh.get(), elem_action_functor);
733 
734  // now that we've gathered everything, we need to rebuild the side list
735  side_list = mesh->get_boundary_info().build_side_list();
736  }
737 
738  std::vector<std::pair<dof_id_type, ElemSidePair>> element_sides_on_boundary;
739  dof_id_type counter = 0;
740  for (const auto & triple : side_list)
741  if (sidesets.count(std::get<2>(triple)))
742  {
743  if (auto elem = mesh->query_elem_ptr(std::get<0>(triple)))
744  {
745  if (!elem->active())
746  mooseError(
747  "Only active, level 0 elements can be made interior parents of new level 0 lower-d "
748  "elements. Make sure that ",
749  type_name,
750  "s are run before any refinement generators");
751  element_sides_on_boundary.push_back(
752  std::make_pair(counter, ElemSidePair(elem, std::get<1>(triple))));
753  }
754  ++counter;
755  }
756 
757  dof_id_type max_elem_id = mesh->max_elem_id();
758  unique_id_type max_unique_id = mesh->parallel_max_unique_id();
759 
760  // Making an important assumption that at least our boundary elements are the same on all
761  // processes even in distributed mesh mode (this is reliant on the correct ghosting functors
762  // existing on the mesh)
763  for (auto & [i, elem_side] : element_sides_on_boundary)
764  {
765  Elem * elem = elem_side.elem;
766 
767  const auto side = elem_side.side;
768 
769  // Build a non-proxy element from this side.
770  std::unique_ptr<Elem> side_elem(elem->build_side_ptr(side));
771 
772  // The side will be added with the same processor id as the parent.
773  side_elem->processor_id() = elem->processor_id();
774 
775  // Add subdomain ID
776  side_elem->subdomain_id() = new_block_id;
777 
778  // Also assign the side's interior parent, so it is always
779  // easy to figure out the Elem we came from.
780  side_elem->set_interior_parent(elem);
781 
782  // Add id
783  side_elem->set_id(max_elem_id + i);
784  side_elem->set_unique_id(max_unique_id + i);
785 
786  // Finally, add the lower-dimensional element to the mesh->
787  mesh->add_elem(side_elem.release());
788  };
789 
790  // Assign block name, if provided
791  if (new_subdomain_name.size())
792  mesh->subdomain_name(new_block_id) = new_subdomain_name;
793 
794  const bool skip_partitioning_old = mesh->skip_partitioning();
795  mesh->skip_partitioning(true);
797  mesh->skip_partitioning(skip_partitioning_old);
798 }
799 
800 void
801 convertBlockToMesh(std::unique_ptr<MeshBase> & source_mesh,
802  std::unique_ptr<MeshBase> & target_mesh,
803  const std::vector<SubdomainName> & target_blocks)
804 {
805  if (!source_mesh->is_replicated())
806  mooseError("This generator does not support distributed meshes.");
807 
808  const auto target_block_ids = MooseMeshUtils::getSubdomainIDs(*source_mesh, target_blocks);
809 
810  // Check that the block ids/names exist in the mesh
811  std::set<SubdomainID> mesh_blocks;
812  source_mesh->subdomain_ids(mesh_blocks);
813 
814  for (const auto i : index_range(target_block_ids))
815  if (target_block_ids[i] == Moose::INVALID_BLOCK_ID || !mesh_blocks.count(target_block_ids[i]))
816  {
817  mooseException("The target_block '", target_blocks[i], "' was not found within the mesh.");
818  }
819 
820  // know which nodes have already been inserted, by tracking the old mesh's node's ids'
821  std::unordered_map<dof_id_type, dof_id_type> old_new_node_map;
822 
823  for (const auto target_block_id : target_block_ids)
824  {
825 
826  for (auto elem : source_mesh->active_subdomain_elements_ptr_range(target_block_id))
827  {
828  if (elem->level() != 0)
829  mooseError("Refined blocks are not supported by this generator. "
830  "Can you re-organize mesh generators to refine after converting the block?");
831 
832  // make a deep copy so that mutiple meshes' destructors don't segfault at program termination
833  auto copy = elem->build(elem->type());
834 
835  // index of node in the copy element must be managed manually as there is no intelligent
836  // insert method
837  dof_id_type copy_n_index = 0;
838 
839  // correctly assign new copies of nodes, loop over nodes
840  for (dof_id_type i : elem->node_index_range())
841  {
842  auto & n = elem->node_ref(i);
843 
844  if (old_new_node_map.count(n.id()))
845  {
846  // case where we have already inserted this particular point before
847  // then we need to find the already-inserted one and hook it up right
848  // to it's respective element
849  copy->set_node(copy_n_index++, target_mesh->node_ptr(old_new_node_map[n.id()]));
850  }
851  else
852  {
853  // case where we've NEVER inserted this particular point before
854  // add them both to the element and the mesh
855 
856  // Nodes' IDs are their indexes in the nodes' respective mesh
857  // If we set them as invalid they are automatically assigned
858  // Add to mesh, auto-assigning a new id.
859  Node * node = target_mesh->add_point(elem->point(i));
860 
861  // Add to element copy (manually)
862  copy->set_node(copy_n_index++, node);
863 
864  // remember the (old) ID
865  old_new_node_map[n.id()] = node->id();
866  }
867  }
868 
869  // it is ok to release the copy element into the mesh because derived meshes class
870  // (ReplicatedMesh, DistributedMesh) manage their own elements, will delete them
871  target_mesh->add_elem(copy.release());
872  }
873  }
874 }
875 }
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)
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
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)
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.
std::unique_ptr< ReplicatedMesh > buildBoundaryMesh(const ReplicatedMesh &input_mesh, const boundary_id_type boundary_id)
virtual unique_id_type parallel_max_unique_id() const=0
auto norm() const -> decltype(std::norm(Real()))
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: MooseUtils.h:380
bool hasBoundaryName(const MeshBase &input_mesh, const BoundaryName &name)
void createSubdomainFromSidesets(std::unique_ptr< MeshBase > &mesh, std::vector< BoundaryName > boundary_names, const SubdomainID new_subdomain_id, const SubdomainName new_subdomain_name, const std::string type_name)
void reconnect_nodes(connected_elem_set_type &connected_elements, connected_node_set_type &connected_nodes)
std::set< std::string > sideset
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:323
const BoundaryID INVALID_BOUNDARY_ID
Definition: MooseTypes.C:22
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition: MooseError.h:357
bool doesMapContainValue(const std::map< T1, T2 > &the_map, const T2 &value)
This routine is a simple helper function for searching a map by values instead of keys...
Definition: MooseUtils.h:356
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
void set_isnt_prepared()
const std::map< boundary_id_type, std::string > & get_sideset_name_map() const
MeshBase & mesh
const Parallel::Communicator & comm() const
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...
const BoundaryInfo & get_boundary_info() const
void renumber_id(boundary_id_type old_id, boundary_id_type new_id)
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
void build_side_list(std::vector< dof_id_type > &element_id_list, std::vector< unsigned short int > &side_list, std::vector< boundary_id_type > &bc_id_list) const
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
virtual const Elem * elem_ptr(const dof_id_type i) const override final
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 &...)
const std::map< boundary_id_type, std::string > & get_nodeset_name_map() const
int8_t boundary_id_type
dof_id_type id() const
void push_parallel_packed_range(const Communicator &comm, MapToContainers &&data, Context *context, const ActionFunctor &act_on_data)
virtual Elem * add_elem(Elem *e)=0
boundary_id_type BoundaryID
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
virtual dof_id_type max_elem_id() const=0
void subdomain_ids(std::set< subdomain_id_type > &ids, const bool global=true) const
static const dof_id_type invalid_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)
std::string & subdomain_name(subdomain_id_type id)
auto norm(const T &a) -> decltype(std::abs(a))
bool hasSubdomainName(const MeshBase &input_mesh, const SubdomainName &name)
bool isCoPlanar(const std::vector< Point > vec_pts)
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)
void remove_side(const Elem *elem, const unsigned short int side)
Provides a way for users to bail out of the current solve.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual const Elem * query_elem_ptr(const dof_id_type i) const=0
void max(const T &r, T &o, Request &req) const
const Node * node_ptr(const unsigned int i) const
virtual bool is_replicated() const
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
const std::set< boundary_id_type > & get_global_boundary_ids() const
IntRange< T > make_range(T beg, T end)
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.
Point meshCentroidCalculator(const MeshBase &mesh)
void mergeBoundaryIDsWithSameName(MeshBase &mesh)
void convertBlockToMesh(std::unique_ptr< MeshBase > &source_mesh, std::unique_ptr< MeshBase > &target_mesh, const std::vector< SubdomainName > &target_blocks)
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
bool isDigits(const std::string &str)
Courtesy https://stackoverflow.com/a/8889045 and https://en.cppreference.com/w/cpp/string/byte/isdigi...
Definition: MooseUtils.h:1208
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
processor_id_type processor_id() const
processor_id_type processor_id() const
uint8_t unique_id_type
auto index_range(const T &sizable)
uint8_t dof_id_type
void set_union(T &data, const unsigned int root_id) const