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  std::vector<dof_id_type> converted_elems_ids_to_retain;
592  // Build boundary information of the mesh
593  BoundaryInfo & boundary_info = mesh.get_boundary_info();
594  const auto bdry_side_list = boundary_info.build_side_list();
595  for (const auto & elem_to_process : elems_to_process)
596  {
597  switch (mesh.elem_ptr(elem_to_process.first)->type())
598  {
599  case ElemType::HEX8:
601  bdry_side_list,
602  elem_to_process.first,
603  elem_to_process.second ? converted_elems_ids_to_track
604  : converted_elems_ids_to_retain);
605  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
606  break;
607  case ElemType::PYRAMID5:
609  bdry_side_list,
610  elem_to_process.first,
611  elem_to_process.second ? converted_elems_ids_to_track
612  : converted_elems_ids_to_retain);
613  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
614  break;
615  case ElemType::PRISM6:
617  bdry_side_list,
618  elem_to_process.first,
619  elem_to_process.second ? converted_elems_ids_to_track
620  : converted_elems_ids_to_retain);
621  mesh.elem_ptr(elem_to_process.first)->subdomain_id() = block_id_to_remove;
622  break;
623  case ElemType::TET4:
624  if (elem_to_process.second)
625  converted_elems_ids_to_track.push_back(elem_to_process.first);
626  else
627  converted_elems_ids_to_retain.push_back(elem_to_process.first);
628  break;
629  default:
630  mooseError("Unexpected element type.");
631  }
632  }
633 
634  if (delete_block_to_remove)
635  {
636  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
637  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
638  elem_it++)
639  mesh.delete_elem(*elem_it);
640 
641  mesh.contract();
643  }
644 }
645 
646 void
648 {
649  // Subdomain ID for new utility blocks must be new
650  std::set<subdomain_id_type> subdomain_ids_set;
651  mesh.subdomain_ids(subdomain_ids_set);
652  const subdomain_id_type max_subdomain_id = *subdomain_ids_set.rbegin();
653  const subdomain_id_type block_id_to_remove = max_subdomain_id + 1;
654  std::vector<std::pair<dof_id_type, bool>> original_elems;
655 
656  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
657  elem_it++)
658  {
659  if ((*elem_it)->default_order() != Order::FIRST)
660  mooseError("Only first order elements are supported for cutting.");
661  original_elems.push_back(std::make_pair((*elem_it)->id(), false));
662  }
663 
664  std::vector<dof_id_type> converted_elems_ids_to_track;
665 
667  mesh, original_elems, converted_elems_ids_to_track, block_id_to_remove, true);
668 }
669 
670 void
671 elementBoundaryInfoCollector(const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
672  const dof_id_type elem_id,
673  const unsigned short n_elem_sides,
674  std::vector<std::vector<boundary_id_type>> & elem_side_list)
675 {
676  elem_side_list.resize(n_elem_sides);
677  const auto selected_bdry_side_list =
678  std::equal_range(bdry_side_list.begin(), bdry_side_list.end(), elem_id, BCTupleKeyComp{});
679  for (auto selected_bdry_side = selected_bdry_side_list.first;
680  selected_bdry_side != selected_bdry_side_list.second;
681  ++selected_bdry_side)
682  {
683  elem_side_list[std::get<1>(*selected_bdry_side)].push_back(std::get<2>(*selected_bdry_side));
684  }
685 }
686 
687 void
689  const dof_id_type & elem_id,
690  const std::vector<unsigned int> & side_indices,
691  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
692  const SubdomainID & subdomain_id_shift_base)
693 {
694  const auto & elem_type = mesh.elem_ptr(elem_id)->type();
695  switch (elem_type)
696  {
697  case HEX8:
698  // HEX8 to PYRAMID5 (+2*subdomain_id_shift_base)
699  // HEX8 to TET4 (+subdomain_id_shift_base)
700  convertHex8Elem(mesh, elem_id, side_indices, elem_side_info, subdomain_id_shift_base);
701  break;
702  case PRISM6:
703  // PRISM6 to TET4 (+subdomain_id_shift_base)
704  // PRISM6 to PYRAMID5 (+2*subdomain_id_shift_base)
705  convertPrism6Elem(mesh, elem_id, side_indices, elem_side_info, subdomain_id_shift_base);
706  break;
707  case PYRAMID5:
708  // PYRAMID5 to TET4 (+subdomain_id_shift_base)
709  convertPyramid5Elem(mesh, elem_id, elem_side_info, subdomain_id_shift_base);
710  break;
711  default:
712  mooseAssert(false,
713  "The provided element type '" + std::to_string(elem_type) +
714  "' is not supported and is not supposed to be passed to this function. "
715  "Only HEX8, PRISM6 and PYRAMID5 are supported.");
716  }
717 }
718 
719 void
721  const dof_id_type & elem_id,
722  const std::vector<unsigned int> & side_indices,
723  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
724  const SubdomainID & subdomain_id_shift_base)
725 {
726  // We add a node at the centroid of the HEX8 element
727  // With this node, the HEX8 can be converted into 6 PYRAMID5 elements
728  // For the PYRAMID5 element at the 'side_indices', they can further be converted into 2 TET4
729  // elements
730  const Point elem_cent = mesh.elem_ptr(elem_id)->true_centroid();
731  auto new_node = mesh.add_point(elem_cent);
732  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
733  {
734  if (std::find(side_indices.begin(), side_indices.end(), i_side) != side_indices.end())
736  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
737  else
739  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
740  }
741 }
742 
743 void
745  const dof_id_type & elem_id,
746  const unsigned int & side_index,
747  const Node * new_node,
748  const std::vector<boundary_id_type> & side_info,
749  const SubdomainID & subdomain_id_shift_base)
750 {
751  auto new_elem = std::make_unique<Pyramid5>();
752  new_elem->set_node(0, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(3));
753  new_elem->set_node(1, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(2));
754  new_elem->set_node(2, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(1));
755  new_elem->set_node(3, mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(0));
756  new_elem->set_node(4, const_cast<Node *>(new_node));
757  new_elem->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base * 2;
758  auto new_elem_ptr = mesh.add_elem(std::move(new_elem));
759  retainEEID(mesh, elem_id, new_elem_ptr);
760  for (const auto & bid : side_info)
761  mesh.get_boundary_info().add_side(new_elem_ptr, 4, bid);
762 }
763 
764 void
766  const dof_id_type & elem_id,
767  const unsigned int & side_index,
768  const Node * new_node,
769  const std::vector<boundary_id_type> & side_info,
770  const SubdomainID & subdomain_id_shift_base)
771 {
772  // We want to make sure that the QUAD4 is divided by the diagonal that involves the node with
773  // the lowest node id This may help maintain consistency for future applications
774  unsigned int lid_index = 0;
775  for (const auto & i : make_range(1, 4))
776  {
777  if (mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(i)->id() <
778  mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(lid_index)->id())
779  lid_index = i;
780  }
781 
782  auto new_elem_0 = std::make_unique<Tet4>();
783  new_elem_0->set_node(0,
784  mesh.elem_ptr(elem_id)
785  ->side_ptr(side_index)
786  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
787  new_elem_0->set_node(1,
788  mesh.elem_ptr(elem_id)
789  ->side_ptr(side_index)
790  ->node_ptr(MathUtils::euclideanMod(1 - lid_index % 2, 4)));
791  new_elem_0->set_node(2,
792  mesh.elem_ptr(elem_id)
793  ->side_ptr(side_index)
794  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
795  new_elem_0->set_node(3, const_cast<Node *>(new_node));
796  new_elem_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
797  auto new_elem_ptr_0 = mesh.add_elem(std::move(new_elem_0));
798  retainEEID(mesh, elem_id, new_elem_ptr_0);
799 
800  auto new_elem_1 = std::make_unique<Tet4>();
801  new_elem_1->set_node(0,
802  mesh.elem_ptr(elem_id)
803  ->side_ptr(side_index)
804  ->node_ptr(MathUtils::euclideanMod(3 - lid_index % 2, 4)));
805  new_elem_1->set_node(1,
806  mesh.elem_ptr(elem_id)
807  ->side_ptr(side_index)
808  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
809  new_elem_1->set_node(2,
810  mesh.elem_ptr(elem_id)
811  ->side_ptr(side_index)
812  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
813  new_elem_1->set_node(3, const_cast<Node *>(new_node));
814  new_elem_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
815  auto new_elem_ptr_1 = mesh.add_elem(std::move(new_elem_1));
816  retainEEID(mesh, elem_id, new_elem_ptr_1);
817 
818  for (const auto & bid : side_info)
819  {
820  mesh.get_boundary_info().add_side(new_elem_ptr_0, 0, bid);
821  mesh.get_boundary_info().add_side(new_elem_ptr_1, 0, bid);
822  }
823 }
824 
825 void
827  const dof_id_type & elem_id,
828  const std::vector<unsigned int> & side_indices,
829  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
830  const SubdomainID & subdomain_id_shift_base)
831 {
832  // We add a node at the centroid of the PRISM6 element
833  // With this node, the PRISM6 can be converted into 3 PYRAMID5 elements and 2 TET4 elements
834  // For the PYRAMID5 element, it can further be converted into 2 TET3 elements
835  const Point elem_cent = mesh.elem_ptr(elem_id)->true_centroid();
836  auto new_node = mesh.add_point(elem_cent);
837  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
838  {
839  if (i_side % 4 == 0 ||
840  std::find(side_indices.begin(), side_indices.end(), i_side) != side_indices.end())
842  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
843  else
845  mesh, elem_id, i_side, new_node, elem_side_info[i_side], subdomain_id_shift_base);
846  }
847 }
848 
849 void
851  const dof_id_type & elem_id,
852  const unsigned int & side_index,
853  const Node * new_node,
854  const std::vector<boundary_id_type> & side_info,
855  const SubdomainID & subdomain_id_shift_base)
856 {
857  // For side 1 and side 4, they are already TRI3, so only one TET is created
858  // For side 0, 2, and 3, they are QUAD4, so we create 2 TETs
859  // We want to make sure that the QUAD4 is divided by the diagonal that involves
860  // the node with the lowest node id This may help maintain consistency for future applications
861  bool is_side_quad = (side_index % 4 != 0);
862  unsigned int lid_index = 0;
863  if (is_side_quad)
864  for (const auto & i : make_range(1, 4))
865  {
866  if (mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(i)->id() <
867  mesh.elem_ptr(elem_id)->side_ptr(side_index)->node_ptr(lid_index)->id())
868  lid_index = i;
869  }
870  // For a TRI3 side, lid_index is always 0, so the indices are always 2,1,0 here
871  auto new_elem_0 = std::make_unique<Tet4>();
872  new_elem_0->set_node(0,
873  mesh.elem_ptr(elem_id)
874  ->side_ptr(side_index)
875  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
876  new_elem_0->set_node(1,
877  mesh.elem_ptr(elem_id)
878  ->side_ptr(side_index)
879  ->node_ptr(MathUtils::euclideanMod(1 - lid_index % 2, 4)));
880  new_elem_0->set_node(2,
881  mesh.elem_ptr(elem_id)
882  ->side_ptr(side_index)
883  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
884  new_elem_0->set_node(3, const_cast<Node *>(new_node));
885  new_elem_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
886  auto new_elem_ptr_0 = mesh.add_elem(std::move(new_elem_0));
887  retainEEID(mesh, elem_id, new_elem_ptr_0);
888 
889  Elem * new_elem_ptr_1 = nullptr;
890  if (is_side_quad)
891  {
892  auto new_elem_1 = std::make_unique<Tet4>();
893  new_elem_1->set_node(0,
894  mesh.elem_ptr(elem_id)
895  ->side_ptr(side_index)
896  ->node_ptr(MathUtils::euclideanMod(3 - lid_index % 2, 4)));
897  new_elem_1->set_node(1,
898  mesh.elem_ptr(elem_id)
899  ->side_ptr(side_index)
900  ->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
901  new_elem_1->set_node(2,
902  mesh.elem_ptr(elem_id)
903  ->side_ptr(side_index)
904  ->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
905  new_elem_1->set_node(3, const_cast<Node *>(new_node));
906  new_elem_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
907  new_elem_ptr_1 = mesh.add_elem(std::move(new_elem_1));
908  retainEEID(mesh, elem_id, new_elem_ptr_1);
909  }
910 
911  for (const auto & bid : side_info)
912  {
913  mesh.get_boundary_info().add_side(new_elem_ptr_0, 0, bid);
914  if (new_elem_ptr_1)
915  mesh.get_boundary_info().add_side(new_elem_ptr_1, 0, bid);
916  }
917 }
918 
919 void
921  const dof_id_type & elem_id,
922  const unsigned int & side_index,
923  const Node * new_node,
924  const std::vector<boundary_id_type> & side_info,
925  const SubdomainID & subdomain_id_shift_base)
926 {
927  // Same as Hex8
929  mesh, elem_id, side_index, new_node, side_info, subdomain_id_shift_base);
930 }
931 
932 void
934  const dof_id_type & elem_id,
935  const std::vector<std::vector<boundary_id_type>> & elem_side_info,
936  const SubdomainID & subdomain_id_shift_base)
937 {
938  // A Pyramid5 element has only one QUAD4 face, so we can convert it to 2 TET4 elements
939  unsigned int lid_index = 0;
940  for (const auto & i : make_range(1, 4))
941  {
942  if (mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(i)->id() <
943  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(lid_index)->id())
944  lid_index = i;
945  }
946  auto new_elem_0 = std::make_unique<Tet4>();
947  new_elem_0->set_node(
948  0,
949  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
950  new_elem_0->set_node(
951  1,
952  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(1 - lid_index % 2, 4)));
953  new_elem_0->set_node(
954  2,
955  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
956  new_elem_0->set_node(3, mesh.elem_ptr(elem_id)->node_ptr(4));
957  new_elem_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
958  auto new_elem_ptr_0 = mesh.add_elem(std::move(new_elem_0));
959  retainEEID(mesh, elem_id, new_elem_ptr_0);
960 
961  auto new_elem_1 = std::make_unique<Tet4>();
962  new_elem_1->set_node(
963  0,
964  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(3 - lid_index % 2, 4)));
965  new_elem_1->set_node(
966  1,
967  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(2 - lid_index % 2, 4)));
968  new_elem_1->set_node(
969  2,
970  mesh.elem_ptr(elem_id)->side_ptr(4)->node_ptr(MathUtils::euclideanMod(0 - lid_index % 2, 4)));
971  new_elem_1->set_node(3, mesh.elem_ptr(elem_id)->node_ptr(4));
972  new_elem_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + subdomain_id_shift_base;
973  auto new_elem_ptr_1 = mesh.add_elem(std::move(new_elem_1));
974  retainEEID(mesh, elem_id, new_elem_ptr_1);
975 
976  for (const auto & bid : elem_side_info[0])
977  mesh.get_boundary_info().add_side(new_elem_ptr_0, 2 - lid_index % 2, bid);
978  for (const auto & bid : elem_side_info[1])
979  if (lid_index % 2)
980  mesh.get_boundary_info().add_side(new_elem_ptr_1, 2, bid);
981  else
982  mesh.get_boundary_info().add_side(new_elem_ptr_0, 1, bid);
983  for (const auto & bid : elem_side_info[2])
984  mesh.get_boundary_info().add_side(new_elem_ptr_1, 2 + lid_index % 2, bid);
985  for (const auto & bid : elem_side_info[3])
986  if (lid_index % 2)
987  mesh.get_boundary_info().add_side(new_elem_ptr_0, 1, bid);
988  else
989  mesh.get_boundary_info().add_side(new_elem_ptr_1, 3, bid);
990  for (const auto & bid : elem_side_info[4])
991  {
992  mesh.get_boundary_info().add_side(new_elem_ptr_0, 0, bid);
993  mesh.get_boundary_info().add_side(new_elem_ptr_1, 0, bid);
994  }
995 }
996 
997 void
998 retainEEID(ReplicatedMesh & mesh, const dof_id_type & elem_id, Elem * new_elem_ptr)
999 {
1000  const unsigned int n_eeid = mesh.n_elem_integers();
1001  for (const auto & i : make_range(n_eeid))
1002  new_elem_ptr->set_extra_integer(i, mesh.elem_ptr(elem_id)->get_extra_integer(i));
1003 }
1004 
1005 void
1007  const std::vector<BoundaryName> & boundary_names,
1008  const unsigned int conversion_element_layer_number,
1009  const bool external_boundaries_checking)
1010 {
1011  // The base subdomain ID to shift the original elements because of the element type change
1012  const auto sid_shift_base = MooseMeshUtils::getNextFreeSubdomainID(mesh);
1013  // The maximum subdomain ID that would be involved is sid_shift_base * 3, we would like to make
1014  // sure it would not overflow
1015  if (sid_shift_base * 3 > std::numeric_limits<subdomain_id_type>::max())
1016  throw MooseException("subdomain id overflow");
1017 
1018  // It would be convenient to have a single boundary id instead of a vector.
1019  const auto uniform_tmp_bid = MooseMeshUtils::getNextFreeBoundaryID(mesh);
1020 
1021  // Check the boundaries and merge them
1022  std::vector<boundary_id_type> boundary_ids;
1023  for (const auto & sideset : boundary_names)
1024  {
1026  throw MooseException("The provided boundary '", sideset, "' was not found within the mesh");
1027  boundary_ids.push_back(MooseMeshUtils::getBoundaryID(sideset, mesh));
1028  MooseMeshUtils::changeBoundaryId(mesh, boundary_ids.back(), uniform_tmp_bid, false);
1029  }
1030 
1031  auto & sideset_map = mesh.get_boundary_info().get_sideset_map();
1032  auto side_list = mesh.get_boundary_info().build_side_list();
1033 
1034  std::vector<std::pair<dof_id_type, std::vector<unsigned int>>> elems_list;
1035  std::vector<std::set<dof_id_type>> layered_elems_list;
1036  layered_elems_list.push_back(std::set<dof_id_type>());
1037  // Need to collect the list of elements that need to be converted
1038  if (!mesh.is_prepared())
1039  mesh.find_neighbors();
1040  for (const auto & side_info : side_list)
1041  {
1042  if (std::get<2>(side_info) == uniform_tmp_bid)
1043  {
1044  // Check if the involved side is TRI3 or QUAD4
1045  // We do not limit the element type in the input mesh
1046  // As long as the involved boundaries only consist of TRI3 and QUAD4 sides,
1047  // this generator will work
1048  // As the side element of a quadratic element is still a linear element in libMesh,
1049  // we need to check the element's default_side_order() first
1050  if (mesh.elem_ptr(std::get<0>(side_info))->default_side_order() != 1)
1051  throw MooseException(
1052  "The provided boundary set contains non-linear side elements, which is not supported.");
1053  const auto side_type =
1054  mesh.elem_ptr(std::get<0>(side_info))->side_ptr(std::get<1>(side_info))->type();
1055  layered_elems_list.back().emplace(std::get<0>(side_info));
1056  // If we enforce external boundary, then a non-null neighbor leads to an error
1057  // Otherwise, we need to convert both sides, that means the neighbor information also needs to
1058  // be added
1059  const auto neighbor_ptr =
1060  mesh.elem_ptr(std::get<0>(side_info))->neighbor_ptr(std::get<1>(side_info));
1061  if (neighbor_ptr)
1062  {
1063  if (external_boundaries_checking)
1064  throw MooseException(
1065  "The provided boundary contains non-external sides, which is required when "
1066  "external_boundaries_checking is enabled.");
1067  else
1068  layered_elems_list.back().emplace(neighbor_ptr->id());
1069  }
1070 
1071  if (conversion_element_layer_number == 1)
1072  {
1073  if (side_type == TRI3)
1074  continue; // Already TRI3, no need to convert
1075  else if (side_type == QUAD4)
1076  {
1077  auto pit = std::find_if(elems_list.begin(),
1078  elems_list.end(),
1079  [elem_id = std::get<0>(side_info)](const auto & p)
1080  { return p.first == elem_id; });
1081  if (elems_list.size() && pit != elems_list.end())
1082  {
1083  pit->second.push_back(std::get<1>(side_info));
1084  }
1085  else
1086  elems_list.push_back(std::make_pair(
1087  std::get<0>(side_info), std::vector<unsigned int>({std::get<1>(side_info)})));
1088  if (neighbor_ptr)
1089  {
1090  auto sit = std::find_if(elems_list.begin(),
1091  elems_list.end(),
1092  [elem_id = neighbor_ptr->id()](const auto & p)
1093  { return p.first == elem_id; });
1094  if (elems_list.size() && sit != elems_list.end())
1095  {
1096  sit->second.push_back(
1097  neighbor_ptr->which_neighbor_am_i(mesh.elem_ptr(std::get<0>(side_info))));
1098  }
1099  else
1100  {
1101  elems_list.push_back(std::make_pair(
1102  neighbor_ptr->id(),
1103  std::vector<unsigned int>(
1104  {neighbor_ptr->which_neighbor_am_i(mesh.elem_ptr(std::get<0>(side_info)))})));
1105  }
1106  }
1107  }
1108  else if (side_type == C0POLYGON)
1109  throw MooseException("The provided boundary set contains C0POLYGON side elements, which "
1110  "is not supported.");
1111  else
1112  mooseAssert(false,
1113  "Impossible scenario: a linear non-polygon side element that is neither TRI3 "
1114  "nor QUAD4.");
1115  }
1116  }
1117  }
1118 
1119  if (conversion_element_layer_number > 1)
1120  {
1121  std::set<dof_id_type> total_elems_set(layered_elems_list.back());
1122 
1123  while (layered_elems_list.back().size() &&
1124  layered_elems_list.size() < conversion_element_layer_number)
1125  {
1126  layered_elems_list.push_back(std::set<dof_id_type>());
1127  for (const auto & elem_id : *(layered_elems_list.end() - 2))
1128  {
1129  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
1130  {
1131  if (mesh.elem_ptr(elem_id)->neighbor_ptr(i_side) != nullptr)
1132  {
1133  const auto & neighbor_id = mesh.elem_ptr(elem_id)->neighbor_ptr(i_side)->id();
1134  if (total_elems_set.find(neighbor_id) == total_elems_set.end())
1135  {
1136  layered_elems_list.back().emplace(neighbor_id);
1137  total_elems_set.emplace(neighbor_id);
1138  }
1139  }
1140  }
1141  }
1142  }
1143  }
1144 
1145  // Remove the last empty layer
1146  if (layered_elems_list.back().empty())
1147  layered_elems_list.pop_back();
1148 
1149  if (conversion_element_layer_number > layered_elems_list.size())
1150  throw MooseException("There is fewer layers of elements in the input mesh than the requested "
1151  "number of layers to convert.");
1152 
1153  std::vector<std::pair<dof_id_type, bool>> original_elems;
1154  // construct a list of the element to convert to tet4
1155  // Convert at most n_layer_conversion layers of elements
1156  const unsigned int n_layer_conversion = layered_elems_list.size() - 1;
1157  for (unsigned int i = 0; i < n_layer_conversion; ++i)
1158  for (const auto & elem_id : layered_elems_list[i])
1159  {
1160  // As these elements will become TET4 elements, we need to shift the subdomain ID
1161  // But we do not need to convert original TET4 elements
1162  if (mesh.elem_ptr(elem_id)->type() != TET4)
1163  {
1164  original_elems.push_back(std::make_pair(elem_id, false));
1165  mesh.elem_ptr(elem_id)->subdomain_id() += sid_shift_base;
1166  }
1167  }
1168 
1169  const subdomain_id_type block_id_to_remove = sid_shift_base * 3;
1170 
1171  std::vector<dof_id_type> converted_elems_ids_to_track;
1173  mesh, original_elems, converted_elems_ids_to_track, block_id_to_remove, false);
1174 
1175  // Now we need to convert the elements on the transition layer
1176  // First, we need to identify that the sides that are on the interface with previous layer (all
1177  // TET layers)
1178  if (n_layer_conversion)
1179  {
1180  for (const auto & elem_id : layered_elems_list[n_layer_conversion])
1181  {
1182  for (const auto & i_side : make_range(mesh.elem_ptr(elem_id)->n_sides()))
1183  {
1184  if (mesh.elem_ptr(elem_id)->neighbor_ptr(i_side) != nullptr &&
1185  layered_elems_list[n_layer_conversion - 1].count(
1186  mesh.elem_ptr(elem_id)->neighbor_ptr(i_side)->id()))
1187  {
1188  if (elems_list.size() && elems_list.back().first == elem_id)
1189  elems_list.back().second.push_back(i_side);
1190  else
1191  elems_list.push_back(std::make_pair(elem_id, std::vector<unsigned int>({i_side})));
1192  }
1193  }
1194  }
1195  }
1196 
1197  // Now convert the elements
1198  for (const auto & elem_info : elems_list)
1199  {
1200  // Find the involved sidesets of the element so that we can retain them
1201  std::vector<std::vector<boundary_id_type>> elem_side_info(
1202  mesh.elem_ptr(elem_info.first)->n_sides());
1203  auto side_range = sideset_map.equal_range(mesh.elem_ptr(elem_info.first));
1204  for (auto i = side_range.first; i != side_range.second; ++i)
1205  elem_side_info[i->second.first].push_back(i->second.second);
1206 
1208  mesh, elem_info.first, elem_info.second, elem_side_info, sid_shift_base);
1209  }
1210 
1211  // delete the original elements that were converted
1212  for (const auto & elem_info : elems_list)
1213  mesh.elem_ptr(elem_info.first)->subdomain_id() = block_id_to_remove;
1214  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
1215  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
1216  elem_it++)
1217  mesh.delete_elem(*elem_it);
1218  // delete temporary boundary id
1219  mesh.get_boundary_info().remove_id(uniform_tmp_bid);
1220 
1221  mesh.contract();
1223 }
1224 
1225 void
1227  ReplicatedMesh & mesh,
1228  const std::set<subdomain_id_type> & original_subdomain_ids,
1229  const subdomain_id_type sid_shift_base,
1230  const SubdomainName & tet_suffix,
1231  const SubdomainName & pyramid_suffix)
1232 {
1233  for (const auto & subdomain_id : original_subdomain_ids)
1234  {
1235  if (MooseMeshUtils::hasSubdomainID(mesh, subdomain_id + sid_shift_base))
1236  {
1237  const SubdomainName new_name =
1238  (mesh.subdomain_name(subdomain_id).empty() ? std::to_string(subdomain_id)
1239  : mesh.subdomain_name(subdomain_id)) +
1240  '_' + tet_suffix;
1241  if (MooseMeshUtils::hasSubdomainName(mesh, new_name))
1242  throw MooseException(
1243  "This suffix for converted TET4 elements results in a subdomain name, " + new_name +
1244  ", that already exists in the mesh. Please choose a different suffix.");
1245  mesh.subdomain_name(subdomain_id + sid_shift_base) = new_name;
1246  }
1247  if (MooseMeshUtils::hasSubdomainID(mesh, subdomain_id + 2 * sid_shift_base))
1248  {
1249  const SubdomainName new_name =
1250  (mesh.subdomain_name(subdomain_id).empty() ? std::to_string(subdomain_id)
1251  : mesh.subdomain_name(subdomain_id)) +
1252  '_' + pyramid_suffix;
1253  if (MooseMeshUtils::hasSubdomainName(mesh, new_name))
1254  throw MooseException(
1255  "This suffix for converted PYRAMID5 elements results in a subdomain name, " + new_name +
1256  ", that already exists in the mesh. Please choose a different suffix.");
1257  mesh.subdomain_name(subdomain_id + 2 * sid_shift_base) = new_name;
1258  }
1259  }
1260 }
1261 }
virtual Point true_centroid() const
void remove_id(boundary_id_type id, bool global=false)
void convertPyramid5Elem(ReplicatedMesh &mesh, const dof_id_type &elem_id, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
bool is_prepared() const
void pyramidElemSplitter(ReplicatedMesh &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, std::vector< dof_id_type > &converted_elems_ids)
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:333
void createUnitTet4FromHex8(ReplicatedMesh &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 createUnitPyramid5FromPrism6(ReplicatedMesh &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 prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
void set_isnt_prepared()
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...
const BoundaryInfo & get_boundary_info() const
void retainEEID(ReplicatedMesh &mesh, const dof_id_type &elem_id, Elem *new_elem_ptr)
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
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:434
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...
virtual void delete_elem(Elem *e)=0
dof_id_type id() const
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
void createUnitTet4FromPrism6(ReplicatedMesh &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)
std::string & subdomain_name(subdomain_id_type id)
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 convert3DMeshToAllTet4(ReplicatedMesh &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...
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.
void convertElem(ReplicatedMesh &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 const Elem * elem_ptr(const dof_id_type i) const=0
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.
void prismElemSplitter(ReplicatedMesh &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 assignConvertedElementsSubdomainNameSuffix(ReplicatedMesh &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)
virtual bool contract()=0
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
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 hexElemSplitter(ReplicatedMesh &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 convertPrism6Elem(ReplicatedMesh &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)
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 convertHex8Elem(ReplicatedMesh &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...
void createUnitPyramid5FromHex8(ReplicatedMesh &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 ElemType type() const=0
auto index_range(const T &sizable)
void set_extra_integer(const unsigned int index, const dof_id_type value)
void transitionLayerGenerator(ReplicatedMesh &mesh, const std::vector< BoundaryName > &boundary_names, const unsigned int conversion_element_layer_number, const bool external_boundaries_checking)
dof_id_type get_extra_integer(const unsigned int index) const
uint8_t dof_id_type