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