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