https://mooseframework.inl.gov
MooseMeshElementConversionUtils.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
12 #include "MooseError.h"
13 #include "MathUtils.h"
14 #include "MooseMeshUtils.h"
15 
16 #include "libmesh/elem.h"
17 #include "libmesh/enum_order.h"
18 #include "libmesh/boundary_info.h"
19 #include "libmesh/mesh_base.h"
20 #include "libmesh/parallel.h"
21 #include "libmesh/parallel_algebra.h"
22 #include "libmesh/utility.h"
23 #include "libmesh/cell_tet4.h"
24 #include "libmesh/face_tri3.h"
25 #include "libmesh/cell_pyramid5.h"
26 #include "libmesh/cell_c0polyhedron.h"
27 
28 using namespace libMesh;
29 
31 {
32 void
34  const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
35  const dof_id_type elem_id,
36  std::vector<dof_id_type> & converted_elems_ids)
37 {
38  // Build boundary information of the mesh
39  BoundaryInfo & boundary_info = mesh.get_boundary_info();
40  // Create a list of sidesets involving the element to be split
41  std::vector<std::vector<boundary_id_type>> elem_side_list;
42  elem_side_list.resize(6);
43  elementBoundaryInfoCollector(bdry_side_list, elem_id, 6, elem_side_list);
44 
45  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
46  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
47  // Record all the element extra integers of the original quad element
48  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
49  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
50 
51  std::vector<std::vector<unsigned int>> opt_option;
52  std::vector<const Node *> elem_node_list = {mesh.elem_ptr(elem_id)->node_ptr(0),
53  mesh.elem_ptr(elem_id)->node_ptr(1),
54  mesh.elem_ptr(elem_id)->node_ptr(2),
55  mesh.elem_ptr(elem_id)->node_ptr(3),
56  mesh.elem_ptr(elem_id)->node_ptr(4),
57  mesh.elem_ptr(elem_id)->node_ptr(5),
58  mesh.elem_ptr(elem_id)->node_ptr(6),
59  mesh.elem_ptr(elem_id)->node_ptr(7)};
60  std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
61 
62  std::vector<std::vector<const Node *>> optimized_node_list;
63  hexNodesToTetNodesDeterminer(elem_node_list, rotated_tet_face_indices, optimized_node_list);
64 
65  std::vector<Elem *> elems_Tet4;
66  for (const auto i : index_range(optimized_node_list))
67  {
68  auto new_elem = std::make_unique<Tet4>();
69  new_elem->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
70  new_elem->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
71  new_elem->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
72  new_elem->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
73  new_elem->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
74  elems_Tet4.push_back(mesh.add_elem(std::move(new_elem)));
75  converted_elems_ids.push_back(elems_Tet4.back()->id());
76 
77  for (unsigned int j = 0; j < 4; j++)
78  {
79  // A hex element has 6 faces indexed from 0 to 5
80  // a <6 value indicates that the face of the tet element corresponds to the face of the
81  // original hex; a =6 value means the face of the tet is an interior face of the hex
82  if (rotated_tet_face_indices[i][j] < 6)
83  {
84  for (const auto & side_info : elem_side_list[rotated_tet_face_indices[i][j]])
85  boundary_info.add_side(elems_Tet4.back(), j, side_info);
86  }
87  }
88  }
89 
90  // Retain element extra integers
91  for (unsigned int i = 0; i < 6; i++)
92  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
93  {
94  elems_Tet4[i]->set_extra_integer(j, exist_extra_ids[j]);
95  }
96 }
97 
98 void
100  const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
101  const dof_id_type elem_id,
102  std::vector<dof_id_type> & converted_elems_ids)
103 {
104  // Build boundary information of the mesh
105  BoundaryInfo & boundary_info = mesh.get_boundary_info();
106  // Create a list of sidesets involving the element to be split
107  std::vector<std::vector<boundary_id_type>> elem_side_list;
108  elementBoundaryInfoCollector(bdry_side_list, elem_id, 5, elem_side_list);
109 
110  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
111  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
112 
113  // Record all the element extra integers of the original quad element
114  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
115  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
116 
117  std::vector<const Node *> elem_node_list = {mesh.elem_ptr(elem_id)->node_ptr(0),
118  mesh.elem_ptr(elem_id)->node_ptr(1),
119  mesh.elem_ptr(elem_id)->node_ptr(2),
120  mesh.elem_ptr(elem_id)->node_ptr(3),
121  mesh.elem_ptr(elem_id)->node_ptr(4),
122  mesh.elem_ptr(elem_id)->node_ptr(5)};
123  std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
124  std::vector<std::vector<const Node *>> optimized_node_list;
125  prismNodesToTetNodesDeterminer(elem_node_list, rotated_tet_face_indices, optimized_node_list);
126 
127  std::vector<Elem *> elems_Tet4;
128  for (const auto i : index_range(optimized_node_list))
129  {
130  auto new_elem = std::make_unique<Tet4>();
131  new_elem->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
132  new_elem->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
133  new_elem->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
134  new_elem->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
135  new_elem->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
136  elems_Tet4.push_back(mesh.add_elem(std::move(new_elem)));
137  converted_elems_ids.push_back(elems_Tet4.back()->id());
138 
139  for (unsigned int j = 0; j < 4; j++)
140  {
141  // A prism element has 5 faces indexed from 0 to 4
142  // a <4 value indicates that the face of the tet element corresponds to the face of the
143  // original prism; a =5 value means the face of the tet is an interior face of the prism
144  if (rotated_tet_face_indices[i][j] < 5)
145  {
146  for (const auto & side_info : elem_side_list[rotated_tet_face_indices[i][j]])
147  boundary_info.add_side(elems_Tet4.back(), j, side_info);
148  }
149  }
150  }
151 
152  // Retain element extra integers
153  for (unsigned int i = 0; i < 3; i++)
154  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
155  {
156  elems_Tet4[i]->set_extra_integer(j, exist_extra_ids[j]);
157  }
158 }
159 
160 void
162  const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
163  const dof_id_type elem_id,
164  std::vector<dof_id_type> & converted_elems_ids)
165 {
166  // Build boundary information of the mesh
167  BoundaryInfo & boundary_info = mesh.get_boundary_info();
168  // Create a list of sidesets involving the element to be split
169  std::vector<std::vector<boundary_id_type>> elem_side_list;
170  elementBoundaryInfoCollector(bdry_side_list, elem_id, 5, elem_side_list);
171 
172  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
173  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
174  // Record all the element extra integers of the original quad element
175  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
176  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
177 
178  std::vector<const Node *> elem_node_list = {mesh.elem_ptr(elem_id)->node_ptr(0),
179  mesh.elem_ptr(elem_id)->node_ptr(1),
180  mesh.elem_ptr(elem_id)->node_ptr(2),
181  mesh.elem_ptr(elem_id)->node_ptr(3),
182  mesh.elem_ptr(elem_id)->node_ptr(4)};
183  std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
184  std::vector<std::vector<const Node *>> optimized_node_list;
185  pyramidNodesToTetNodesDeterminer(elem_node_list, rotated_tet_face_indices, optimized_node_list);
186 
187  std::vector<Elem *> elems_Tet4;
188  for (const auto i : index_range(optimized_node_list))
189  {
190  auto new_elem = std::make_unique<Tet4>();
191  new_elem->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
192  new_elem->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
193  new_elem->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
194  new_elem->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
195  new_elem->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
196  elems_Tet4.push_back(mesh.add_elem(std::move(new_elem)));
197  converted_elems_ids.push_back(elems_Tet4.back()->id());
198 
199  for (unsigned int j = 0; j < 4; j++)
200  {
201  // A pyramid element has 5 faces indexed from 0 to 4
202  // a <4 value indicates that the face of the tet element corresponds to the face of the
203  // original pyramid; a =5 value means the face of the tet is an interior face of the pyramid
204  if (rotated_tet_face_indices[i][j] < 5)
205  {
206  for (const auto & side_info : elem_side_list[rotated_tet_face_indices[i][j]])
207  boundary_info.add_side(elems_Tet4.back(), j, side_info);
208  }
209  }
210  }
211 
212  // Retain element extra integers
213  for (unsigned int i = 0; i < 2; i++)
214  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
215  {
216  elems_Tet4[i]->set_extra_integer(j, exist_extra_ids[j]);
217  }
218 }
219 
220 void
222  const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
223  const dof_id_type elem_id,
224  std::vector<dof_id_type> & converted_elems_ids)
225 {
226  auto elem = mesh.elem_ptr(elem_id);
227  // Build boundary information of the mesh
228  BoundaryInfo & boundary_info = mesh.get_boundary_info();
229  // Create a list of sidesets involving the element to be split
230  std::vector<std::vector<boundary_id_type>> elem_side_list;
231  elementBoundaryInfoCollector(bdry_side_list, elem_id, elem->n_sides(), elem_side_list);
232 
233  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
234  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
235 
236  // Record all the element extra integers of the original quad element
237  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
238  exist_extra_ids[j] = elem->get_extra_integer(j);
239 
240  // Split the polygon using its tetrahedralization
241  const auto poly = dynamic_cast<C0Polyhedron *>(elem);
242  Node * v_avg_node = nullptr;
243  // Currently the only extra node ever use to tetrahedralize
244  if (dynamic_cast<C0Polyhedron *>(elem))
245  v_avg_node =
246  mesh.add_point(elem->vertex_average(), mesh.max_node_id() + elem_id, elem->processor_id());
247 
248  std::vector<Elem *> elems_Tet4;
249  for (const auto tri_i : make_range(poly->n_subelements()))
250  {
251  const auto tri_indices = poly->subelement(tri_i);
252 
253  auto new_elem = std::make_unique<Tet4>();
254  for (const auto i : make_range(4))
255  if (tri_indices[i] >= 0)
256  new_elem->set_node(i, const_cast<Node *>(elem->node_ptr(tri_indices[i])));
257  else
258  new_elem->set_node(i, v_avg_node);
259 
260  new_elem->subdomain_id() = elem->subdomain_id();
261  elems_Tet4.push_back(mesh.add_elem(std::move(new_elem)));
262  converted_elems_ids.push_back(elems_Tet4.back()->id());
263 
264  // Get subelement to side map
265  const auto tet_face_indices = poly->subelement_sides_to_poly_sides(tri_i);
266 
267  // Add the sides of the tets to the relevant boundaries
268  for (unsigned int j = 0; j < 4; j++)
269  if (tet_face_indices[j] < int(elem->n_sides()))
270  for (const auto & side_info : elem_side_list[tet_face_indices[j]])
271  boundary_info.add_side(elems_Tet4.back(), j, side_info);
272  }
273 
274  // Retain element extra integers
275  for (const auto i : make_range(poly->n_subelements()))
276  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
277  elems_Tet4[i]->set_extra_integer(j, exist_extra_ids[j]);
278 }
279 
280 std::vector<unsigned int>
281 neighborNodeIndicesHEX8(unsigned int min_id_index)
282 {
283  const std::vector<std::vector<unsigned int>> preset_indices = {
284  {1, 3, 4}, {0, 2, 5}, {3, 1, 6}, {2, 0, 7}, {5, 7, 0}, {4, 6, 1}, {7, 5, 2}, {6, 4, 3}};
285  if (min_id_index > 7)
286  mooseError("The input node index is out of range.");
287  else
288  return preset_indices[min_id_index];
289 }
290 
291 void
292 hexNodesToTetNodesDeterminer(std::vector<const Node *> & hex_nodes,
293  std::vector<std::vector<unsigned int>> & rotated_tet_face_indices,
294  std::vector<std::vector<const Node *>> & tet_nodes_list)
295 {
296  // Find the node with the minimum id
297  std::vector<dof_id_type> node_ids(8);
298  for (unsigned int i = 0; i < 8; i++)
299  node_ids[i] = hex_nodes[i]->id();
300 
301  const unsigned int min_node_id_index = std::distance(
302  std::begin(node_ids), std::min_element(std::begin(node_ids), std::end(node_ids)));
303  // Get the index of the three neighbor nodes of the minimum node
304  // The order is consistent with the description in nodeRotationHEX8()
305  // Then determine the index of the second minimum node
306  const auto neighbor_node_indices = neighborNodeIndicesHEX8(min_node_id_index);
307 
308  const auto neighbor_node_ids = {node_ids[neighbor_node_indices[0]],
309  node_ids[neighbor_node_indices[1]],
310  node_ids[neighbor_node_indices[2]]};
311  const unsigned int sec_min_pos =
312  std::distance(std::begin(neighbor_node_ids),
313  std::min_element(std::begin(neighbor_node_ids), std::end(neighbor_node_ids)));
314 
315  // Rotate the node and face indices based on the identified minimum and second minimum nodes
316  // After the rotation, we guarantee that the minimum node is the first node (Node 0)
317  // And the second node (Node 1) has the minium global id among the three neighbor nodes of Node 0
318  // This makes the splitting process simpler
319  std::vector<unsigned int> face_rotation;
320  std::vector<unsigned int> rotated_indices;
321  nodeRotationHEX8(min_node_id_index, sec_min_pos, face_rotation, rotated_indices);
322  std::vector<const Node *> rotated_hex_nodes;
323  for (unsigned int i = 0; i < 8; i++)
324  rotated_hex_nodes.push_back(hex_nodes[rotated_indices[i]]);
325 
326  // Find the selection of each face's cutting direction
327  const auto diagonal_directions = quadFaceDiagonalDirectionsHex(rotated_hex_nodes);
328 
329  // Based on the determined splitting directions of all the faces, determine the nodes of each
330  // resulting TET4 elements after the splitting.
331  std::vector<std::vector<unsigned int>> tet_face_indices;
332  const auto tet_nodes_set = tetNodesForHex(diagonal_directions, tet_face_indices);
333  for (const auto & tet_face_index : tet_face_indices)
334  {
335  rotated_tet_face_indices.push_back(std::vector<unsigned int>());
336  for (const auto & face_index : tet_face_index)
337  {
338  if (face_index < 6)
339  rotated_tet_face_indices.back().push_back(face_rotation[face_index]);
340  else
341  rotated_tet_face_indices.back().push_back(6);
342  }
343  }
344 
345  for (const auto & tet_nodes : tet_nodes_set)
346  {
347  tet_nodes_list.push_back(std::vector<const Node *>());
348  for (const auto & tet_node : tet_nodes)
349  tet_nodes_list.back().push_back(rotated_hex_nodes[tet_node]);
350  }
351 }
352 
353 std::vector<bool>
354 quadFaceDiagonalDirectionsHex(const std::vector<const Node *> & hex_nodes)
355 {
356  // Bottom/Top; Front/Back; Right/Left
357  const std::vector<std::vector<unsigned int>> face_indices = {
358  {0, 1, 2, 3}, {4, 5, 6, 7}, {0, 1, 5, 4}, {2, 3, 7, 6}, {1, 2, 6, 5}, {3, 0, 4, 7}};
359  std::vector<bool> diagonal_directions;
360  for (const auto & face_index : face_indices)
361  {
362  std::vector<const Node *> quad_nodes = {hex_nodes[face_index[0]],
363  hex_nodes[face_index[1]],
364  hex_nodes[face_index[2]],
365  hex_nodes[face_index[3]]};
366  diagonal_directions.push_back(quadFaceDiagonalDirection(quad_nodes));
367  }
368  return diagonal_directions;
369 }
370 
371 bool
372 quadFaceDiagonalDirection(const std::vector<const Node *> & quad_nodes)
373 {
374  const std::vector<dof_id_type> node_ids = {
375  quad_nodes[0]->id(), quad_nodes[1]->id(), quad_nodes[2]->id(), quad_nodes[3]->id()};
376  const unsigned int min_id_index = std::distance(
377  std::begin(node_ids), std::min_element(std::begin(node_ids), std::end(node_ids)));
378  if (min_id_index == 0 || min_id_index == 2)
379  return true;
380  else
381  return false;
382 }
383 
384 std::vector<std::vector<unsigned int>>
385 tetNodesForHex(const std::vector<bool> diagonal_directions,
386  std::vector<std::vector<unsigned int>> & tet_face_indices)
387 {
388  const std::vector<std::vector<bool>> possible_inputs = {{true, true, true, true, true, false},
389  {true, true, true, true, false, false},
390  {true, true, true, false, true, false},
391  {true, false, true, true, true, false},
392  {true, false, true, true, false, false},
393  {true, false, true, false, true, false},
394  {true, false, true, false, false, false}};
395 
396  const unsigned int input_index = std::distance(
397  std::begin(possible_inputs),
398  std::find(std::begin(possible_inputs), std::end(possible_inputs), diagonal_directions));
399 
400  switch (input_index)
401  {
402  case 0:
403  tet_face_indices = {
404  {0, 6, 2, 6}, {1, 6, 2, 6}, {1, 6, 5, 6}, {0, 6, 3, 4}, {6, 6, 3, 6}, {6, 4, 5, 6}};
405  return {{0, 1, 2, 6}, {0, 5, 1, 6}, {0, 4, 5, 6}, {0, 2, 3, 7}, {0, 6, 2, 7}, {0, 4, 6, 7}};
406  case 1:
407  tet_face_indices = {
408  {0, 1, 2, 6}, {6, 6, 2, 6}, {6, 6, 5, 1}, {0, 6, 3, 4}, {6, 6, 3, 6}, {6, 4, 5, 6}};
409  return {{0, 1, 2, 5}, {0, 2, 6, 5}, {0, 6, 4, 5}, {0, 2, 3, 7}, {0, 6, 2, 7}, {0, 4, 6, 7}};
410  case 2:
411  tet_face_indices = {
412  {0, 6, 2, 6}, {1, 6, 2, 6}, {1, 6, 5, 6}, {4, 6, 5, 6}, {4, 6, 3, 6}, {0, 6, 3, 6}};
413  return {{0, 1, 2, 6}, {0, 5, 1, 6}, {0, 4, 5, 6}, {0, 7, 4, 6}, {0, 3, 7, 6}, {0, 2, 3, 6}};
414  case 3:
415  tet_face_indices = {
416  {4, 6, 5, 1}, {6, 6, 5, 6}, {6, 1, 2, 6}, {4, 0, 3, 6}, {6, 6, 3, 6}, {6, 6, 2, 0}};
417  return {{0, 7, 4, 5}, {0, 6, 7, 5}, {0, 1, 6, 5}, {0, 3, 7, 2}, {0, 7, 6, 2}, {0, 6, 1, 2}};
418  case 4:
419  tet_face_indices = {{0, 1, 2, 6}, {0, 6, 3, 4}, {5, 4, 6, 1}, {5, 6, 3, 2}, {6, 6, 6, 6}};
420  return {{0, 1, 2, 5}, {0, 2, 3, 7}, {4, 7, 5, 0}, {5, 7, 6, 2}, {0, 2, 7, 5}};
421  case 5:
422  tet_face_indices = {
423  {4, 6, 5, 1}, {6, 6, 5, 6}, {6, 1, 2, 6}, {2, 6, 6, 0}, {3, 6, 6, 0}, {3, 6, 6, 4}};
424  return {{0, 7, 4, 5}, {0, 6, 7, 5}, {0, 1, 6, 5}, {1, 6, 2, 0}, {2, 6, 3, 0}, {3, 6, 7, 0}};
425  case 6:
426  tet_face_indices = {
427  {1, 4, 5, 6}, {6, 6, 5, 6}, {6, 6, 3, 4}, {1, 6, 2, 0}, {6, 6, 2, 6}, {6, 0, 3, 6}};
428  return {{0, 4, 5, 7}, {0, 5, 6, 7}, {0, 6, 3, 7}, {0, 5, 1, 2}, {0, 6, 5, 2}, {0, 3, 6, 2}};
429  default:
430  mooseError("Unexpected input.");
431  }
432 }
433 
434 void
435 nodeRotationHEX8(const unsigned int min_id_index,
436  const unsigned int sec_min_pos,
437  std::vector<unsigned int> & face_rotation,
438  std::vector<unsigned int> & node_rotation)
439 {
440  // Assuming the original hex element is a cube, the vectors formed by nodes 0-1, 0-3, and 0-4 are
441  // overlapped with the x, y, and z axes, respectively. sec_min_pos = 0 means the second minimum
442  // node is in the x direction, sec_min_pos = 1 means the second minimum node is in the y
443  // direction, and sec_min_pos = 2 means the second minimum node is in the z direction.
444  const std::vector<std::vector<std::vector<unsigned int>>> preset_indices = {
445  {{0, 1, 2, 3, 4, 5, 6, 7}, {0, 3, 7, 4, 1, 2, 6, 5}, {0, 4, 5, 1, 3, 7, 6, 2}},
446  {{1, 0, 4, 5, 2, 3, 7, 6}, {1, 2, 3, 0, 5, 6, 7, 4}, {1, 5, 6, 2, 0, 4, 7, 3}},
447  {{2, 3, 0, 1, 6, 7, 4, 5}, {2, 1, 5, 6, 3, 0, 4, 7}, {2, 6, 7, 3, 1, 5, 4, 0}},
448  {{3, 2, 6, 7, 0, 1, 5, 4}, {3, 0, 1, 2, 7, 4, 5, 6}, {3, 7, 4, 0, 2, 6, 5, 1}},
449  {{4, 5, 1, 0, 7, 6, 2, 3}, {4, 7, 6, 5, 0, 3, 2, 1}, {4, 0, 3, 7, 5, 1, 2, 6}},
450  {{5, 4, 7, 6, 1, 0, 3, 2}, {5, 6, 2, 1, 4, 7, 3, 0}, {5, 1, 0, 4, 6, 2, 3, 7}},
451  {{6, 7, 3, 2, 5, 4, 0, 1}, {6, 5, 4, 7, 2, 1, 0, 3}, {6, 2, 1, 5, 7, 3, 0, 4}},
452  {{7, 6, 5, 4, 3, 2, 1, 0}, {7, 4, 0, 3, 6, 5, 1, 2}, {7, 3, 2, 6, 4, 0, 1, 5}}};
453 
454  const std::vector<std::vector<std::vector<unsigned int>>> preset_face_indices = {
455  {{0, 1, 2, 3, 4, 5}, {4, 0, 3, 5, 1, 2}, {1, 4, 5, 2, 0, 3}},
456  {{1, 0, 4, 5, 2, 3}, {0, 2, 3, 4, 1, 5}, {2, 1, 5, 3, 0, 4}},
457  {{0, 3, 4, 1, 2, 5}, {2, 0, 1, 5, 3, 4}, {3, 2, 5, 4, 0, 1}},
458  {{3, 0, 2, 5, 4, 1}, {0, 4, 1, 2, 3, 5}, {4, 3, 5, 1, 0, 2}},
459  {{1, 5, 2, 0, 4, 3}, {5, 4, 3, 2, 1, 0}, {4, 1, 0, 3, 5, 2}},
460  {{5, 1, 4, 3, 2, 0}, {2, 5, 3, 0, 1, 4}, {1, 2, 0, 4, 5, 3}},
461  {{3, 5, 4, 0, 2, 1}, {5, 2, 1, 4, 3, 0}, {2, 3, 0, 1, 5, 4}},
462  {{5, 3, 2, 1, 4, 0}, {4, 5, 1, 0, 3, 2}, {3, 4, 0, 2, 5, 1}}};
463 
464  if (min_id_index > 7 || sec_min_pos > 2)
465  mooseError("The input node index is out of range.");
466  else
467  {
468  // index: new face index; value: old face index
469  face_rotation = preset_face_indices[min_id_index][sec_min_pos];
470  node_rotation = preset_indices[min_id_index][sec_min_pos];
471  }
472 }
473 
474 void
475 nodeRotationPRISM6(unsigned int min_id_index,
476  std::vector<unsigned int> & face_rotation,
477  std::vector<unsigned int> & node_rotation)
478 {
479  const std::vector<std::vector<unsigned int>> preset_indices = {{0, 1, 2, 3, 4, 5},
480  {1, 2, 0, 4, 5, 3},
481  {2, 0, 1, 5, 3, 4},
482  {3, 5, 4, 0, 2, 1},
483  {4, 3, 5, 1, 0, 2},
484  {5, 4, 3, 2, 1, 0}};
485 
486  const std::vector<std::vector<unsigned int>> preset_face_indices = {{0, 1, 2, 3, 4},
487  {0, 2, 3, 1, 4},
488  {0, 3, 1, 2, 4},
489  {4, 3, 2, 1, 0},
490  {4, 1, 3, 2, 0},
491  {4, 2, 1, 3, 0}};
492 
493  if (min_id_index > 5)
494  mooseError("The input node index is out of range.");
495  else
496  {
497  // index: new face index; value: old face index
498  face_rotation = preset_face_indices[min_id_index];
499  node_rotation = preset_indices[min_id_index];
500  }
501 }
502 
503 void
504 prismNodesToTetNodesDeterminer(std::vector<const Node *> & prism_nodes,
505  std::vector<std::vector<unsigned int>> & rotated_tet_face_indices,
506  std::vector<std::vector<const Node *>> & tet_nodes_list)
507 {
508  // Find the node with the minimum id
509  std::vector<dof_id_type> node_ids(6);
510  for (unsigned int i = 0; i < 6; i++)
511  node_ids[i] = prism_nodes[i]->id();
512 
513  const unsigned int min_node_id_index = std::distance(
514  std::begin(node_ids), std::min_element(std::begin(node_ids), std::end(node_ids)));
515 
516  // Rotate the node and face indices based on the identified minimum node
517  // After the rotation, we guarantee that the minimum node is the first node (Node 0)
518  // This makes the splitting process simpler
519  std::vector<unsigned int> face_rotation;
520  std::vector<unsigned int> rotated_indices;
521  nodeRotationPRISM6(min_node_id_index, face_rotation, rotated_indices);
522  std::vector<const Node *> rotated_prism_nodes;
523  for (unsigned int i = 0; i < 6; i++)
524  rotated_prism_nodes.push_back(prism_nodes[rotated_indices[i]]);
525 
526  std::vector<const Node *> key_quad_nodes = {rotated_prism_nodes[1],
527  rotated_prism_nodes[2],
528  rotated_prism_nodes[5],
529  rotated_prism_nodes[4]};
530 
531  // Find the selection of each face's cutting direction
532  const bool diagonal_direction = quadFaceDiagonalDirection(key_quad_nodes);
533 
534  // Based on the determined splitting directions of all the faces, determine the nodes of each
535  // resulting TET4 elements after the splitting.
536  std::vector<std::vector<unsigned int>> tet_face_indices;
537  const auto tet_nodes_set = tetNodesForPrism(diagonal_direction, tet_face_indices);
538  for (const auto & tet_face_index : tet_face_indices)
539  {
540  rotated_tet_face_indices.push_back(std::vector<unsigned int>());
541  for (const auto & face_index : tet_face_index)
542  {
543  if (face_index < 5)
544  rotated_tet_face_indices.back().push_back(face_rotation[face_index]);
545  else
546  rotated_tet_face_indices.back().push_back(5);
547  }
548  }
549 
550  for (const auto & tet_nodes : tet_nodes_set)
551  {
552  tet_nodes_list.push_back(std::vector<const Node *>());
553  for (const auto & tet_node : tet_nodes)
554  tet_nodes_list.back().push_back(rotated_prism_nodes[tet_node]);
555  }
556 }
557 
558 std::vector<std::vector<unsigned int>>
559 tetNodesForPrism(const bool diagonal_direction,
560  std::vector<std::vector<unsigned int>> & tet_face_indices)
561 {
562 
563  if (diagonal_direction)
564  {
565  tet_face_indices = {{4, 3, 5, 1}, {2, 1, 5, 5}, {2, 5, 3, 0}};
566  return {{3, 5, 4, 0}, {1, 4, 5, 0}, {1, 5, 2, 0}};
567  }
568  else
569  {
570  tet_face_indices = {{4, 3, 5, 1}, {2, 1, 5, 0}, {2, 5, 5, 3}};
571  return {{3, 5, 4, 0}, {1, 4, 2, 0}, {2, 4, 5, 0}};
572  }
573 }
574 
575 void
576 nodeRotationPYRAMID5(unsigned int min_id_index,
577  std::vector<unsigned int> & face_rotation,
578  std::vector<unsigned int> & node_rotation)
579 {
580  const std::vector<std::vector<unsigned int>> preset_indices = {
581  {0, 1, 2, 3, 4}, {1, 2, 3, 0, 4}, {2, 3, 0, 1, 4}, {3, 0, 1, 2, 4}};
582 
583  const std::vector<std::vector<unsigned int>> preset_face_indices = {
584  {0, 1, 2, 3, 4}, {1, 2, 3, 0, 4}, {2, 3, 0, 1, 4}, {3, 0, 1, 2, 4}};
585 
586  if (min_id_index > 3)
587  mooseError("The input node index is out of range.");
588  else
589  {
590  // index: new face index; value: old face index
591  face_rotation = preset_face_indices[min_id_index];
592  node_rotation = preset_indices[min_id_index];
593  }
594 }
595 
596 void
597 pyramidNodesToTetNodesDeterminer(std::vector<const Node *> & pyramid_nodes,
598  std::vector<std::vector<unsigned int>> & rotated_tet_face_indices,
599  std::vector<std::vector<const Node *>> & tet_nodes_list)
600 {
601  // Find the node with the minimum id, ignoring the top node
602  std::vector<dof_id_type> node_ids(4);
603  for (unsigned int i = 0; i < 4; i++)
604  node_ids[i] = pyramid_nodes[i]->id();
605 
606  const unsigned int min_node_id_index = std::distance(
607  std::begin(node_ids), std::min_element(std::begin(node_ids), std::end(node_ids)));
608 
609  // Rotate the node and face indices based on the identified minimum nodes
610  // After the rotation, we guarantee that the minimum node is the first node (Node 0)
611  // This makes the splitting process simpler
612  std::vector<unsigned int> face_rotation;
613  std::vector<unsigned int> rotated_indices;
614  nodeRotationPYRAMID5(min_node_id_index, face_rotation, rotated_indices);
615  std::vector<const Node *> rotated_pyramid_nodes;
616  for (unsigned int i = 0; i < 5; i++)
617  rotated_pyramid_nodes.push_back(pyramid_nodes[rotated_indices[i]]);
618 
619  // There is only one quad face in a pyramid element, so the splitting selection is binary
620  const std::vector<std::vector<unsigned int>> tet_nodes_set = {{0, 1, 2, 4}, {0, 2, 3, 4}};
621  const std::vector<std::vector<unsigned int>> tet_face_indices = {{4, 0, 1, 5}, {4, 5, 2, 3}};
622 
623  // Based on the determined splitting direction, determine the nodes of each resulting TET4
624  // elements after the splitting.
625  for (const auto & tet_face_index : tet_face_indices)
626  {
627  rotated_tet_face_indices.push_back(std::vector<unsigned int>());
628  for (const auto & face_index : tet_face_index)
629  {
630  if (face_index < 5)
631  rotated_tet_face_indices.back().push_back(face_rotation[face_index]);
632  else
633  rotated_tet_face_indices.back().push_back(5);
634  }
635  }
636 
637  for (const auto & tet_nodes : tet_nodes_set)
638  {
639  tet_nodes_list.push_back(std::vector<const Node *>());
640  for (const auto & tet_node : tet_nodes)
641  tet_nodes_list.back().push_back(rotated_pyramid_nodes[tet_node]);
642  }
643 }
644 
645 void
647  const std::vector<std::pair<dof_id_type, bool>> & elems_to_process,
648  std::vector<dof_id_type> & converted_elems_ids_to_track,
649  const subdomain_id_type block_id_to_remove,
650  const bool delete_block_to_remove)
651 {
652  mooseAssert(mesh.is_serial(),
653  "This method only supports serial meshes. If you are calling this method with a "
654  "distributed mesh, please serialize it first.");
655  std::vector<dof_id_type> converted_elems_ids_to_retain;
656  // Build boundary information of the mesh
657  BoundaryInfo & boundary_info = mesh.get_boundary_info();
658  const auto bdry_side_list = boundary_info.build_side_list();
659  for (const auto & elem_to_process : elems_to_process)
660  {
661  switch (mesh.elem_ptr(elem_to_process.first)->type())
662  {
663  case ElemType::HEX8:
665  bdry_side_list,
666  elem_to_process.first,
667  elem_to_process.second ? converted_elems_ids_to_track
668  : converted_elems_ids_to_retain);
669  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
670  break;
671  case ElemType::PYRAMID5:
673  bdry_side_list,
674  elem_to_process.first,
675  elem_to_process.second ? converted_elems_ids_to_track
676  : converted_elems_ids_to_retain);
677  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
678  break;
679  case ElemType::PRISM6:
681  bdry_side_list,
682  elem_to_process.first,
683  elem_to_process.second ? converted_elems_ids_to_track
684  : converted_elems_ids_to_retain);
685  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
686  break;
687  case ElemType::C0POLYHEDRON:
689  bdry_side_list,
690  elem_to_process.first,
691  elem_to_process.second ? converted_elems_ids_to_track
692  : converted_elems_ids_to_retain);
693  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
694  break;
695  case ElemType::TET4:
696  if (elem_to_process.second)
697  converted_elems_ids_to_track.push_back(elem_to_process.first);
698  else
699  converted_elems_ids_to_retain.push_back(elem_to_process.first);
700  break;
701  default:
702  mooseError("Unexpected element type.");
703  }
704  }
705 
706  if (delete_block_to_remove)
707  {
708  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
709  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
710  elem_it++)
711  mesh.delete_elem(*elem_it);
712 
713  mesh.contract();
715  }
716 }
717 
718 void
720 {
721  mooseAssert(mesh.is_serial(),
722  "This method only supports serial meshes. If you are calling this method with a "
723  "distributed mesh, please serialize it first.");
724  // Subdomain ID for new utility blocks must be new
725  std::set<subdomain_id_type> subdomain_ids_set;
726  mesh.subdomain_ids(subdomain_ids_set);
727  const subdomain_id_type max_subdomain_id = *subdomain_ids_set.rbegin();
728  const subdomain_id_type block_id_to_remove = max_subdomain_id + 1;
729  std::vector<std::pair<dof_id_type, bool>> original_elems;
730 
731  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
732  elem_it++)
733  {
734  if ((*elem_it)->default_order() != Order::FIRST)
735  mooseError("Only first order elements are supported for cutting.");
736  original_elems.push_back(std::make_pair((*elem_it)->id(), false));
737  }
738 
739  std::vector<dof_id_type> converted_elems_ids_to_track;
740 
742  mesh, original_elems, converted_elems_ids_to_track, block_id_to_remove, true);
743 }
744 
745 void
746 elementBoundaryInfoCollector(const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
747  const dof_id_type elem_id,
748  const unsigned short n_elem_sides,
749  std::vector<std::vector<boundary_id_type>> & elem_side_list)
750 {
751  elem_side_list.resize(n_elem_sides);
752  const auto selected_bdry_side_list =
753  std::equal_range(bdry_side_list.begin(), bdry_side_list.end(), elem_id, BCTupleKeyComp{});
754  for (auto selected_bdry_side = selected_bdry_side_list.first;
755  selected_bdry_side != selected_bdry_side_list.second;
756  ++selected_bdry_side)
757  {
758  elem_side_list[std::get<1>(*selected_bdry_side)].push_back(std::get<2>(*selected_bdry_side));
759  }
760 }
761 
762 void
764  const dof_id_type & elem_id,
765  const std::vector<unsigned int> & side_indices,
766  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
767  const SubdomainID & subdomain_id_shift_base)
768 {
769  const auto & elem_type = mesh.elem_ptr(elem_id)->type();
770  switch (elem_type)
771  {
772  case HEX8:
773  // HEX8 to PYRAMID5 (+2*subdomain_id_shift_base)
774  // HEX8 to TET4 (+subdomain_id_shift_base)
775  convertHex8Elem(mesh, elem_id, side_indices, elem_side_info, subdomain_id_shift_base);
776  break;
777  case PRISM6:
778  // PRISM6 to TET4 (+subdomain_id_shift_base)
779  // PRISM6 to PYRAMID5 (+2*subdomain_id_shift_base)
780  convertPrism6Elem(mesh, elem_id, side_indices, elem_side_info, subdomain_id_shift_base);
781  break;
782  case PYRAMID5:
783  // PYRAMID5 to TET4 (+subdomain_id_shift_base)
784  convertPyramid5Elem(mesh, elem_id, elem_side_info, subdomain_id_shift_base);
785  break;
786  default:
787  mooseAssert(false,
788  "The provided element type '" + std::to_string(elem_type) +
789  "' is not supported and is not supposed to be passed to this function. "
790  "Only HEX8, PRISM6 and PYRAMID5 are supported.");
791  }
792 }
793 
794 void
796  const dof_id_type & elem_id,
797  const std::vector<unsigned int> & side_indices,
798  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
799  const SubdomainID & subdomain_id_shift_base)
800 {
801  // We add a node at the centroid of the HEX8 element
802  // With this node, the HEX8 can be converted into 6 PYRAMID5 elements
803  // For the PYRAMID5 element at the 'side_indices', they can further be converted into 2 TET4
804  // elements
805  const Point elem_cent = mesh.elem_ptr(elem_id)->true_centroid();
806  auto new_node = mesh.add_point(elem_cent);
807  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
808  {
809  if (std::find(side_indices.begin(), side_indices.end(), i_side) != side_indices.end())
811  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
812  else
814  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
815  }
816 }
817 
818 void
820  const dof_id_type & elem_id,
821  const unsigned int & side_index,
822  const Node * new_node,
823  const std::vector<boundary_id_type> & side_info,
824  const SubdomainID & subdomain_id_shift_base)
825 {
826  auto new_elem = std::make_unique<Pyramid5>();
827  new_elem->set_node(0, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(3));
828  new_elem->set_node(1, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(2));
829  new_elem->set_node(2, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(1));
830  new_elem->set_node(3, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(0));
831  new_elem->set_node(4, const_cast<Node *>(new_node));
832  new_elem->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base * 2;
833  auto new_elem_ptr = mesh.add_elem(std::move(new_elem));
834  retainEEID(mesh, elem_id, new_elem_ptr);
835  for (const auto & bid : side_info)
836  mesh.get_boundary_info().add_side(new_elem_ptr, 4, bid);
837 }
838 
839 void
841  const dof_id_type & elem_id,
842  const unsigned int & side_index,
843  const Node * new_node,
844  const std::vector<boundary_id_type> & side_info,
845  const SubdomainID & subdomain_id_shift_base)
846 {
847  // We want to make sure that the QUAD4 is divided by the diagonal that involves the node with
848  // the lowest node id This may help maintain consistency for future applications
849  unsigned int lid_index = 0;
850  for (const auto & i : make_range(1, 4))
851  {
852  if (mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(i)->id() <
853  mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(lid_index)->id())
854  lid_index = i;
855  }
856 
857  auto new_elem_0 = std::make_unique<Tet4>();
858  new_elem_0->set_node(0,
859  mesh.elem_ptr(elem_id)
860  ->side_ptr(side_index)
861  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
862  new_elem_0->set_node(1,
863  mesh.elem_ptr(elem_id)
864  ->side_ptr(side_index)
865  ->node_ptr(MathUtils::euclideanMod(1 - lid_index % 2, 4)));
866  new_elem_0->set_node(2,
867  mesh.elem_ptr(elem_id)
868  ->side_ptr(side_index)
869  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
870  new_elem_0->set_node(3, const_cast<Node *>(new_node));
871  new_elem_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
872  auto new_elem_ptr_0 = mesh.add_elem(std::move(new_elem_0));
873  retainEEID(mesh, elem_id, new_elem_ptr_0);
874 
875  auto new_elem_1 = std::make_unique<Tet4>();
876  new_elem_1->set_node(0,
877  mesh.elem_ptr(elem_id)
878  ->side_ptr(side_index)
879  ->node_ptr(MathUtils::euclideanMod(3 - lid_index % 2, 4)));
880  new_elem_1->set_node(1,
881  mesh.elem_ptr(elem_id)
882  ->side_ptr(side_index)
883  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
884  new_elem_1->set_node(2,
885  mesh.elem_ptr(elem_id)
886  ->side_ptr(side_index)
887  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
888  new_elem_1->set_node(3, const_cast<Node *>(new_node));
889  new_elem_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
890  auto new_elem_ptr_1 = mesh.add_elem(std::move(new_elem_1));
891  retainEEID(mesh, elem_id, new_elem_ptr_1);
892 
893  for (const auto & bid : side_info)
894  {
895  mesh.get_boundary_info().add_side(new_elem_ptr_0, 0, bid);
896  mesh.get_boundary_info().add_side(new_elem_ptr_1, 0, bid);
897  }
898 }
899 
900 void
902  const dof_id_type & elem_id,
903  const std::vector<unsigned int> & side_indices,
904  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
905  const SubdomainID & subdomain_id_shift_base)
906 {
907  // We add a node at the centroid of the PRISM6 element
908  // With this node, the PRISM6 can be converted into 3 PYRAMID5 elements and 2 TET4 elements
909  // For the PYRAMID5 element, it can further be converted into 2 TET3 elements
910  const Point elem_cent = mesh.elem_ptr(elem_id)->true_centroid();
911  auto new_node = mesh.add_point(elem_cent);
912  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
913  {
914  if (i_side % 4 == 0 ||
915  std::find(side_indices.begin(), side_indices.end(), i_side) != side_indices.end())
917  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
918  else
920  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
921  }
922 }
923 
924 void
926  const dof_id_type & elem_id,
927  const unsigned int & side_index,
928  const Node * new_node,
929  const std::vector<boundary_id_type> & side_info,
930  const SubdomainID & subdomain_id_shift_base)
931 {
932  // For side 1 and side 4, they are already TRI3, so only one TET is created
933  // For side 0, 2, and 3, they are QUAD4, so we create 2 TETs
934  // We want to make sure that the QUAD4 is divided by the diagonal that involves
935  // the node with the lowest node id This may help maintain consistency for future applications
936  bool is_side_quad = (side_index % 4 != 0);
937  unsigned int lid_index = 0;
938  if (is_side_quad)
939  for (const auto & i : make_range(1, 4))
940  {
941  if (mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(i)->id() <
942  mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(lid_index)->id())
943  lid_index = i;
944  }
945  // For a TRI3 side, lid_index is always 0, so the indices are always 2,1,0 here
946  auto new_elem_0 = std::make_unique<Tet4>();
947  new_elem_0->set_node(0,
948  mesh.elem_ptr(elem_id)
949  ->side_ptr(side_index)
950  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
951  new_elem_0->set_node(1,
952  mesh.elem_ptr(elem_id)
953  ->side_ptr(side_index)
954  ->node_ptr(MathUtils::euclideanMod(1 - lid_index % 2, 4)));
955  new_elem_0->set_node(2,
956  mesh.elem_ptr(elem_id)
957  ->side_ptr(side_index)
958  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
959  new_elem_0->set_node(3, const_cast<Node *>(new_node));
960  new_elem_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
961  auto new_elem_ptr_0 = mesh.add_elem(std::move(new_elem_0));
962  retainEEID(mesh, elem_id, new_elem_ptr_0);
963 
964  Elem * new_elem_ptr_1 = nullptr;
965  if (is_side_quad)
966  {
967  auto new_elem_1 = std::make_unique<Tet4>();
968  new_elem_1->set_node(0,
969  mesh.elem_ptr(elem_id)
970  ->side_ptr(side_index)
971  ->node_ptr(MathUtils::euclideanMod(3 - lid_index % 2, 4)));
972  new_elem_1->set_node(1,
973  mesh.elem_ptr(elem_id)
974  ->side_ptr(side_index)
975  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
976  new_elem_1->set_node(2,
977  mesh.elem_ptr(elem_id)
978  ->side_ptr(side_index)
979  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
980  new_elem_1->set_node(3, const_cast<Node *>(new_node));
981  new_elem_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
982  new_elem_ptr_1 = mesh.add_elem(std::move(new_elem_1));
983  retainEEID(mesh, elem_id, new_elem_ptr_1);
984  }
985 
986  for (const auto & bid : side_info)
987  {
988  mesh.get_boundary_info().add_side(new_elem_ptr_0, 0, bid);
989  if (new_elem_ptr_1)
990  mesh.get_boundary_info().add_side(new_elem_ptr_1, 0, bid);
991  }
992 }
993 
994 void
996  const dof_id_type & elem_id,
997  const unsigned int & side_index,
998  const Node * new_node,
999  const std::vector<boundary_id_type> & side_info,
1000  const SubdomainID & subdomain_id_shift_base)
1001 {
1002  // Same as Hex8
1004  mesh, elem_id, side_index, new_node, side_info, subdomain_id_shift_base);
1005 }
1006 
1007 void
1009  const dof_id_type & elem_id,
1010  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
1011  const SubdomainID & subdomain_id_shift_base)
1012 {
1013  // A Pyramid5 element has only one QUAD4 face, so we can convert it to 2 TET4 elements
1014  unsigned int lid_index = 0;
1015  for (const auto & i : make_range(1, 4))
1016  {
1017  if (mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(i)->id() <
1018  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(lid_index)->id())
1019  lid_index = i;
1020  }
1021  auto new_elem_0 = std::make_unique<Tet4>();
1022  new_elem_0->set_node(
1023  0,
1024  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
1025  new_elem_0->set_node(
1026  1,
1027  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(1 - lid_index % 2, 4)));
1028  new_elem_0->set_node(
1029  2,
1030  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
1031  new_elem_0->set_node(3, mesh.elem_ptr(elem_id)->node_ptr(4));
1032  new_elem_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
1033  auto new_elem_ptr_0 = mesh.add_elem(std::move(new_elem_0));
1034  retainEEID(mesh, elem_id, new_elem_ptr_0);
1035 
1036  auto new_elem_1 = std::make_unique<Tet4>();
1037  new_elem_1->set_node(
1038  0,
1039  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(3 - lid_index % 2, 4)));
1040  new_elem_1->set_node(
1041  1,
1042  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
1043  new_elem_1->set_node(
1044  2,
1045  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
1046  new_elem_1->set_node(3, mesh.elem_ptr(elem_id)->node_ptr(4));
1047  new_elem_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
1048  auto new_elem_ptr_1 = mesh.add_elem(std::move(new_elem_1));
1049  retainEEID(mesh, elem_id, new_elem_ptr_1);
1050 
1051  for (const auto & bid : elem_side_info[0])
1052  mesh.get_boundary_info().add_side(new_elem_ptr_0, 2 - lid_index % 2, bid);
1053  for (const auto & bid : elem_side_info[1])
1054  if (lid_index % 2)
1055  mesh.get_boundary_info().add_side(new_elem_ptr_1, 2, bid);
1056  else
1057  mesh.get_boundary_info().add_side(new_elem_ptr_0, 1, bid);
1058  for (const auto & bid : elem_side_info[2])
1059  mesh.get_boundary_info().add_side(new_elem_ptr_1, 2 + lid_index % 2, bid);
1060  for (const auto & bid : elem_side_info[3])
1061  if (lid_index % 2)
1062  mesh.get_boundary_info().add_side(new_elem_ptr_0, 1, bid);
1063  else
1064  mesh.get_boundary_info().add_side(new_elem_ptr_1, 3, bid);
1065  for (const auto & bid : elem_side_info[4])
1066  {
1067  mesh.get_boundary_info().add_side(new_elem_ptr_0, 0, bid);
1068  mesh.get_boundary_info().add_side(new_elem_ptr_1, 0, bid);
1069  }
1070 }
1071 
1072 void
1073 retainEEID(MeshBase & mesh, const dof_id_type & elem_id, Elem * new_elem_ptr)
1074 {
1075  const unsigned int n_eeid = mesh.n_elem_integers();
1076  for (const auto & i : make_range(n_eeid))
1077  new_elem_ptr->set_extra_integer(i, mesh.elem_ptr(elem_id)->get_extra_integer(i));
1078 }
1079 
1080 void
1082  const std::vector<BoundaryName> & boundary_names,
1083  const unsigned int conversion_element_layer_number,
1084  const bool external_boundaries_checking)
1085 {
1086  mooseAssert(mesh.is_serial(),
1087  "This method only supports serial meshes. If you are calling this method with a "
1088  "distributed mesh, please serialize it first.");
1089  // The base subdomain ID to shift the original elements because of the element type change
1090  const auto sid_shift_base = MooseMeshUtils::getNextFreeSubdomainID(mesh);
1091  // The maximum subdomain ID that would be involved is sid_shift_base * 3, we would like to make
1092  // sure it would not overflow
1093  if (sid_shift_base * 3 > std::numeric_limits<subdomain_id_type>::max())
1094  throw MooseException("subdomain id overflow");
1095 
1096  // It would be convenient to have a single boundary id instead of a vector.
1097  const auto uniform_tmp_bid = MooseMeshUtils::getNextFreeBoundaryID(mesh);
1098 
1099  // Check the boundaries and merge them
1100  std::vector<boundary_id_type> boundary_ids;
1101  for (const auto & sideset : boundary_names)
1102  {
1104  throw MooseException("The provided boundary '", sideset, "' was not found within the mesh");
1105  boundary_ids.push_back(MooseMeshUtils::getBoundaryID(sideset, mesh));
1106  MooseMeshUtils::changeBoundaryId(mesh, boundary_ids.back(), uniform_tmp_bid, false);
1107  }
1108 
1109  auto & sideset_map = mesh.get_boundary_info().get_sideset_map();
1110  auto side_list = mesh.get_boundary_info().build_side_list();
1111 
1112  std::vector<std::pair<dof_id_type, std::vector<unsigned int>>> elems_list;
1113  std::vector<std::set<dof_id_type>> layered_elems_list;
1114  layered_elems_list.push_back(std::set<dof_id_type>());
1115  // Need to collect the list of elements that need to be converted
1116  if (!mesh.is_prepared())
1117  mesh.find_neighbors();
1118  for (const auto & side_info : side_list)
1119  {
1120  if (std::get<2>(side_info) == uniform_tmp_bid)
1121  {
1122  // Check if the involved side is TRI3 or QUAD4
1123  // We do not limit the element type in the input mesh
1124  // As long as the involved boundaries only consist of TRI3 and QUAD4 sides,
1125  // this generator will work
1126  // As the side element of a quadratic element is still a linear element in libMesh,
1127  // we need to check the element's default_side_order() first
1128  if (mesh.elem_ptr(std::get<0>(side_info))->default_side_order() != 1)
1129  throw MooseException(
1130  "The provided boundary set contains non-linear side elements, which is not supported.");
1131  const auto side_type =
1132  mesh.elem_ptr(std::get<0>(side_info))->side_ptr(std::get<1>(side_info))->type();
1133  layered_elems_list.back().emplace(std::get<0>(side_info));
1134  // If we enforce external boundary, then a non-null neighbor leads to an error
1135  // Otherwise, we need to convert both sides, that means the neighbor information also needs to
1136  // be added
1137  const auto neighbor_ptr =
1138  mesh.elem_ptr(std::get<0>(side_info))->neighbor_ptr(std::get<1>(side_info));
1139  if (neighbor_ptr)
1140  {
1141  if (external_boundaries_checking)
1142  throw MooseException(
1143  "The provided boundary contains non-external sides, which is required when "
1144  "external_boundaries_checking is enabled.");
1145  else
1146  layered_elems_list.back().emplace(neighbor_ptr->id());
1147  }
1148 
1149  if (conversion_element_layer_number == 1)
1150  {
1151  if (side_type == TRI3)
1152  continue; // Already TRI3, no need to convert
1153  else if (side_type == QUAD4)
1154  {
1155  auto pit = std::find_if(elems_list.begin(),
1156  elems_list.end(),
1157  [elem_id = std::get<0>(side_info)](const auto & p)
1158  { return p.first == elem_id; });
1159  if (elems_list.size() && pit != elems_list.end())
1160  {
1161  pit->second.push_back(std::get<1>(side_info));
1162  }
1163  else
1164  elems_list.push_back(std::make_pair(
1165  std::get<0>(side_info), std::vector<unsigned int>({std::get<1>(side_info)})));
1166  if (neighbor_ptr)
1167  {
1168  auto sit = std::find_if(elems_list.begin(),
1169  elems_list.end(),
1170  [elem_id = neighbor_ptr->id()](const auto & p)
1171  { return p.first == elem_id; });
1172  if (elems_list.size() && sit != elems_list.end())
1173  {
1174  sit->second.push_back(
1175  neighbor_ptr->which_neighbor_am_i(mesh.elem_ptr(std::get<0>(side_info))));
1176  }
1177  else
1178  {
1179  elems_list.push_back(std::make_pair(
1180  neighbor_ptr->id(),
1181  std::vector<unsigned int>(
1182  {neighbor_ptr->which_neighbor_am_i(mesh.elem_ptr(std::get<0>(side_info)))})));
1183  }
1184  }
1185  }
1186  else if (side_type == C0POLYGON)
1187  throw MooseException("The provided boundary set contains C0POLYGON side elements, which "
1188  "is not supported.");
1189  else
1190  mooseAssert(false,
1191  "Impossible scenario: a linear non-polygon side element that is neither TRI3 "
1192  "nor QUAD4.");
1193  }
1194  }
1195  }
1196 
1197  if (conversion_element_layer_number > 1)
1198  {
1199  std::set<dof_id_type> total_elems_set(layered_elems_list.back());
1200 
1201  while (layered_elems_list.back().size() &&
1202  layered_elems_list.size() < conversion_element_layer_number)
1203  {
1204  layered_elems_list.push_back(std::set<dof_id_type>());
1205  for (const auto & elem_id : *(layered_elems_list.end() - 2))
1206  {
1207  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
1208  {
1209  if (mesh.elem_ptr(elem_id)->neighbor_ptr(i_side) != nullptr)
1210  {
1211  const auto & neighbor_id = mesh.elem_ptr(elem_id)->neighbor_ptr(i_side)->id();
1212  if (total_elems_set.find(neighbor_id) == total_elems_set.end())
1213  {
1214  layered_elems_list.back().emplace(neighbor_id);
1215  total_elems_set.emplace(neighbor_id);
1216  }
1217  }
1218  }
1219  }
1220  }
1221  }
1222 
1223  // Remove the last empty layer
1224  if (layered_elems_list.back().empty())
1225  layered_elems_list.pop_back();
1226 
1227  if (conversion_element_layer_number > layered_elems_list.size())
1228  throw MooseException("There is fewer layers of elements in the input mesh than the requested "
1229  "number of layers to convert.");
1230 
1231  std::vector<std::pair<dof_id_type, bool>> original_elems;
1232  // construct a list of the element to convert to tet4
1233  // Convert at most n_layer_conversion layers of elements
1234  const unsigned int n_layer_conversion = layered_elems_list.size() - 1;
1235  for (unsigned int i = 0; i < n_layer_conversion; ++i)
1236  for (const auto & elem_id : layered_elems_list[i])
1237  {
1238  // As these elements will become TET4 elements, we need to shift the subdomain ID
1239  // But we do not need to convert original TET4 elements
1240  if (mesh.elem_ptr(elem_id)->type() != TET4)
1241  {
1242  original_elems.push_back(std::make_pair(elem_id, false));
1243  mesh.elem_ptr(elem_id)->subdomain_id() += sid_shift_base;
1244  }
1245  }
1246 
1247  const subdomain_id_type block_id_to_remove = sid_shift_base * 3;
1248 
1249  std::vector<dof_id_type> converted_elems_ids_to_track;
1251  mesh, original_elems, converted_elems_ids_to_track, block_id_to_remove, false);
1252 
1253  // Now we need to convert the elements on the transition layer
1254  // First, we need to identify that the sides that are on the interface with previous layer (all
1255  // TET layers)
1256  if (n_layer_conversion)
1257  {
1258  for (const auto & elem_id : layered_elems_list[n_layer_conversion])
1259  {
1260  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
1261  {
1262  if (mesh.elem_ptr(elem_id)->neighbor_ptr(i_side) != nullptr &&
1263  layered_elems_list[n_layer_conversion - 1].count(
1264  mesh.elem_ptr(elem_id)->neighbor_ptr(i_side)->id()))
1265  {
1266  if (elems_list.size() && elems_list.back().first == elem_id)
1267  elems_list.back().second.push_back(i_side);
1268  else
1269  elems_list.push_back(std::make_pair(elem_id, std::vector<unsigned int>({i_side})));
1270  }
1271  }
1272  }
1273  }
1274 
1275  // Now convert the elements
1276  for (const auto & elem_info : elems_list)
1277  {
1278  // Find the involved sidesets of the element so that we can retain them
1279  std::vector<std::vector<boundary_id_type>> elem_side_info(
1280  mesh.elem_ptr(elem_info.first)->n_sides());
1281  auto side_range = sideset_map.equal_range(mesh.elem_ptr(elem_info.first));
1282  for (auto i = side_range.first; i != side_range.second; ++i)
1283  elem_side_info[i->second.first].push_back(i->second.second);
1284 
1286  mesh, elem_info.first, elem_info.second, elem_side_info, sid_shift_base);
1287  }
1288 
1289  // delete the original elements that were converted
1290  for (const auto & elem_info : elems_list)
1291  mesh.elem_ptr(elem_info.first)->subdomain_id() = block_id_to_remove;
1292  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
1293  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
1294  elem_it++)
1295  mesh.delete_elem(*elem_it);
1296  // delete temporary boundary id
1297  mesh.get_boundary_info().remove_id(uniform_tmp_bid);
1298 
1299  mesh.contract();
1301 }
1302 
1303 void
1305  MeshBase & mesh,
1306  const std::set<subdomain_id_type> & original_subdomain_ids,
1307  const subdomain_id_type sid_shift_base,
1308  const SubdomainName & tet_suffix,
1309  const SubdomainName & pyramid_suffix)
1310 {
1311  // If we have an unprepared mesh, we at least need its element
1312  // caches prepared for subdomain_ids
1315 
1316  for (const auto & subdomain_id : original_subdomain_ids)
1317  {
1318  if (MooseMeshUtils::hasSubdomainID(mesh, subdomain_id + sid_shift_base))
1319  {
1320  const SubdomainName new_name =
1321  (mesh.subdomain_name(subdomain_id).empty() ? std::to_string(subdomain_id)
1322  : mesh.subdomain_name(subdomain_id)) +
1323  '_' + tet_suffix;
1324  if (MooseMeshUtils::hasSubdomainName(mesh, new_name))
1325  throw MooseException(
1326  "This suffix for converted TET4 elements results in a subdomain name, " + new_name +
1327  ", that already exists in the mesh. Please choose a different suffix.");
1328  mesh.subdomain_name(subdomain_id + sid_shift_base) = new_name;
1329  }
1330  if (MooseMeshUtils::hasSubdomainID(mesh, subdomain_id + 2 * sid_shift_base))
1331  {
1332  const SubdomainName new_name =
1333  (mesh.subdomain_name(subdomain_id).empty() ? std::to_string(subdomain_id)
1334  : mesh.subdomain_name(subdomain_id)) +
1335  '_' + pyramid_suffix;
1336  if (MooseMeshUtils::hasSubdomainName(mesh, new_name))
1337  throw MooseException(
1338  "This suffix for converted PYRAMID5 elements results in a subdomain name, " + new_name +
1339  ", that already exists in the mesh. Please choose a different suffix.");
1340  mesh.subdomain_name(subdomain_id + 2 * sid_shift_base) = new_name;
1341  }
1342  }
1343 }
1344 }
virtual Point true_centroid() const
void assignConvertedElementsSubdomainNameSuffix(MeshBase &mesh, const std::set< subdomain_id_type > &original_subdomain_ids, const subdomain_id_type sid_shift_base, const SubdomainName &tet_suffix, const SubdomainName &pyramid_suffix)
void remove_id(boundary_id_type id, bool global=false)
void createUnitPyramid5FromHex8(MeshBase &mesh, const dof_id_type &elem_id, const unsigned int &side_index, const Node *new_node, const std::vector< boundary_id_type > &side_info, const SubdomainID &subdomain_id_shift_base)
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 createUnitTet4FromHex8(MeshBase &mesh, const dof_id_type &elem_id, const unsigned int &side_index, const Node *new_node, const std::vector< boundary_id_type > &side_info, const SubdomainID &subdomain_id_shift_base)
virtual Node *& set_node(const unsigned int i)
std::vector< bool > quadFaceDiagonalDirectionsHex(const std::vector< const Node *> &hex_nodes)
virtual Order default_side_order() const
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:242
bool hasBoundaryName(const MeshBase &input_mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
void nodeRotationHEX8(const unsigned int min_id_index, const unsigned int sec_min_pos, std::vector< unsigned int > &face_rotation, std::vector< unsigned int > &node_rotation)
Rotate a HEX8 element&#39;s nodes to ensure that the node with the minimum id is the first node; and the ...
std::set< std::string > sideset
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
virtual void find_neighbors(const bool reset_remote_elements=false, const bool reset_current_list=true, const bool assert_valid=true)=0
void createUnitPyramid5FromPrism6(MeshBase &mesh, const dof_id_type &elem_id, const unsigned int &side_index, const Node *new_node, const std::vector< boundary_id_type > &side_info, const SubdomainID &subdomain_id_shift_base)
unsigned int n_elem_integers() const
MeshBase & mesh
bool quadFaceDiagonalDirection(const std::vector< const Node *> &quad_nodes)
void polyhedronElemSplitter(MeshBase &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, std::vector< dof_id_type > &converted_elems_ids)
void hexNodesToTetNodesDeterminer(std::vector< const Node *> &hex_nodes, std::vector< std::vector< unsigned int >> &rotated_tet_face_indices, std::vector< std::vector< const Node *>> &tet_nodes_list)
void elementBoundaryInfoCollector(const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, const unsigned short n_elem_sides, std::vector< std::vector< boundary_id_type >> &elem_side_list)
Collect the boundary information of the given element in a mesh.
std::vector< unsigned int > neighborNodeIndicesHEX8(unsigned int min_id_index)
Calculate the indices (within the element nodes) of the three neighboring nodes of a node in a HEX8 e...
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
void convertHex8Elem(MeshBase &mesh, const dof_id_type &elem_id, const std::vector< unsigned int > &side_indices, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
const BoundaryInfo & get_boundary_info() const
std::vector< BCTuple > build_side_list(BCTupleSortBy sort_by=BCTupleSortBy::ELEM_ID) const
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
auto max(const L &left, const R &right)
void prismElemSplitter(MeshBase &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, std::vector< dof_id_type > &converted_elems_ids)
bool hasSubdomainID(const MeshBase &input_mesh, const SubdomainID &id)
Whether a particular subdomain ID exists in the mesh.
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
Gets the boundary ID associated with the given BoundaryName.
std::size_t euclideanMod(T1 dividend, T2 divisor)
perform modulo operator for Euclidean division that ensures a non-negative result ...
Definition: MathUtils.h:439
virtual bool is_serial() const
std::vector< std::vector< unsigned int > > tetNodesForPrism(const bool diagonal_direction, std::vector< std::vector< unsigned int >> &tet_face_indices)
Creates sets of four nodes indices that can form TET4 elements to replace the original PRISM6 element...
void convert3DMeshToAllTet4(MeshBase &mesh, const std::vector< std::pair< dof_id_type, bool >> &elems_to_process, std::vector< dof_id_type > &converted_elems_ids_to_track, const subdomain_id_type block_id_to_remove, const bool delete_block_to_remove)
Convert all the elements in a 3D mesh, consisting of only linear elements, into TET4 elements...
virtual void delete_elem(Elem *e)=0
dof_id_type id() const
void retainEEID(MeshBase &mesh, const dof_id_type &elem_id, Elem *new_elem_ptr)
virtual Elem * add_elem(Elem *e)=0
void nodeRotationPRISM6(unsigned int min_id_index, std::vector< unsigned int > &face_rotation, std::vector< unsigned int > &node_rotation)
Rotate a PRISM6 element nodes to ensure that the node with the minimum id is the first node...
void subdomain_ids(std::set< subdomain_id_type > &ids, const bool global=true) const
std::string & subdomain_name(subdomain_id_type id)
void createUnitTet4FromPrism6(MeshBase &mesh, const dof_id_type &elem_id, const unsigned int &side_index, const Node *new_node, const std::vector< boundary_id_type > &side_info, const SubdomainID &subdomain_id_shift_base)
void nodeRotationPYRAMID5(unsigned int min_id_index, std::vector< unsigned int > &face_rotation, std::vector< unsigned int > &node_rotation)
Rotate a PYRAMID5 element nodes to ensure that the node with the minimum id is the first node for the...
void convertPyramid5Elem(MeshBase &mesh, const dof_id_type &elem_id, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
void hexElemSplitter(MeshBase &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, std::vector< dof_id_type > &converted_elems_ids)
const std::multimap< const Elem *, std::pair< unsigned short int, boundary_id_type > > & get_sideset_map() const
bool hasSubdomainName(const MeshBase &input_mesh, const SubdomainName &name)
Whether a particular subdomain name exists in the mesh.
virtual const Elem * elem_ptr(const dof_id_type i) const=0
void convertElem(MeshBase &mesh, const dof_id_type &elem_id, const std::vector< unsigned int > &side_indices, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
virtual unsigned int n_sides() const=0
const Elem * neighbor_ptr(unsigned int i) const
Provides a way for users to bail out of the current solve.
virtual bool contract()=0
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
void pyramidElemSplitter(MeshBase &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, std::vector< dof_id_type > &converted_elems_ids)
subdomain_id_type subdomain_id() const
const Node * node_ptr(const unsigned int i) const
void pyramidNodesToTetNodesDeterminer(std::vector< const Node *> &pyramid_nodes, std::vector< std::vector< unsigned int >> &rotated_tet_face_indices, std::vector< std::vector< const Node *>> &tet_nodes_list)
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
IntRange< T > make_range(T beg, T end)
void convertPrism6Elem(MeshBase &mesh, const dof_id_type &elem_id, const std::vector< unsigned int > &side_indices, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
std::vector< std::vector< unsigned int > > tetNodesForHex(const std::vector< bool > diagonal_directions, std::vector< std::vector< unsigned int >> &tet_face_indices)
Creates sets of four nodes indices that can form TET4 elements to replace the original HEX8 element...
void changeBoundaryId(MeshBase &mesh, const libMesh::boundary_id_type old_id, const libMesh::boundary_id_type new_id, bool delete_prev)
Changes the old boundary ID to a new ID in the mesh.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
virtual dof_id_type max_node_id() const=0
void prismNodesToTetNodesDeterminer(std::vector< const Node *> &prism_nodes, std::vector< std::vector< unsigned int >> &rotated_tet_face_indices, std::vector< std::vector< const Node *>> &tet_nodes_list)
BoundaryID getNextFreeBoundaryID(MeshBase &input_mesh)
Checks input mesh and returns the largest boundary ID in the mesh plus one, which is a boundary ID in...
virtual ElemType type() const=0
void transitionLayerGenerator(MeshBase &mesh, const std::vector< BoundaryName > &boundary_names, const unsigned int conversion_element_layer_number, const bool external_boundaries_checking)
void unset_is_prepared()
auto index_range(const T &sizable)
void set_extra_integer(const unsigned int index, const dof_id_type value)
dof_id_type get_extra_integer(const unsigned int index) const
uint8_t dof_id_type