https://mooseframework.inl.gov
Loading...
Searching...
No Matches
CutMeshByLevelSetGeneratorBase.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
12#include "MooseMeshUtils.h"
13
14#include "libmesh/elem.h"
15#include "libmesh/boundary_info.h"
16#include "libmesh/mesh_base.h"
17#include "libmesh/parallel.h"
18#include "libmesh/parallel_algebra.h"
19#include "libmesh/cell_tet4.h"
20
21// C++ includes
22#include <cmath>
23
26{
29
30 params.addRequiredParam<MeshGeneratorName>("input", "The input mesh that needs to be trimmed.");
31
32 params.addParam<bool>(
33 "generate_transition_layer",
34 false,
35 "Whether to generate a transition layer for the cut mesh. "
36 "If false, the entire input mesh will be converted to TET4 elements to facilitate the "
37 "cutting; if true, only the elements near the cut face will be converted with a transition "
38 "layer to maintain compatibility with the original mesh.");
39
40 params.addParam<boundary_id_type>("cut_face_id",
41 "The boundary id of the face generated by the cut. An "
42 "id will be automatically assigned if not provided.");
43 params.addParam<BoundaryName>(
44 "cut_face_name", BoundaryName(), "The boundary name of the face generated by the cut.");
45 params.addParam<SubdomainName>("converted_tet_element_subdomain_name_suffix",
46 "to_tet",
47 "The suffix to be added to the original subdomain name for the "
48 "subdomains containing the elements converted to TET4. This is "
49 "only applicable when transition layer is generated.");
50 params.addParam<SubdomainName>(
51 "converted_pyramid_element_subdomain_name_suffix",
52 "to_pyramid",
53 "The suffix to be added to the original subdomain name for the subdomains containing the "
54 "elements converted to PYRAMID5. This is only applicable when transition layer is "
55 "generated.");
56
58 "This CutMeshByLevelSetGeneratorBase object is designed to be the base class of mesh "
59 "generator that cuts a 3D mesh based on an analytic level set function. The level set "
60 "function could be provided explicitly or indirectly.");
61
62 return params;
63}
64
66 : MeshGenerator(parameters),
67 FunctionParserUtils<false>(parameters),
68 _input_name(getParam<MeshGeneratorName>("input")),
69 _generate_transition_layer(getParam<bool>("generate_transition_layer")),
70 _cut_face_name(getParam<BoundaryName>("cut_face_name")),
71 _converted_tet_element_subdomain_name_suffix(
72 getParam<SubdomainName>("converted_tet_element_subdomain_name_suffix")),
73 _converted_pyramid_element_subdomain_name_suffix(
74 getParam<SubdomainName>("converted_pyramid_element_subdomain_name_suffix")),
75 _input(getMeshByName(_input_name))
76{
77 _cut_face_id = isParamValid("cut_face_id") ? getParam<boundary_id_type>("cut_face_id") : -1;
78}
79
80std::unique_ptr<MeshBase>
82{
83 // We're querying elem dim caches from our input mesh
84 if (!_input->preparation().has_cached_elem_data)
85 _input->cache_elem_data();
86
87 auto replicated_mesh_ptr = dynamic_cast<ReplicatedMesh *>(_input.get());
88 if (!replicated_mesh_ptr)
89 paramError("input", "Input is not a replicated mesh, which is required");
90 if (*(replicated_mesh_ptr->elem_dimensions().begin()) != 3 ||
91 *(replicated_mesh_ptr->elem_dimensions().rbegin()) != 3)
93 "input",
94 "Only 3D meshes are supported. Use XYMeshLineCutter for 2D meshes if applicable. Mixed "
95 "dimensional meshes are not supported at the moment.");
96
97 ReplicatedMesh & mesh = *replicated_mesh_ptr;
98
99 if (!_cut_face_name.empty())
100 {
102 {
103 const boundary_id_type exist_cut_face_id =
105 if (_cut_face_id != -1 && _cut_face_id != exist_cut_face_id)
106 paramError("cut_face_id",
107 "The cut face boundary name and id are both provided, but they are inconsistent "
108 "with an existing boundary name which has a different id.");
109 else
110 _cut_face_id = exist_cut_face_id;
111 }
112 else
113 {
114 if (_cut_face_id == -1)
116 mesh.get_boundary_info().sideset_name(_cut_face_id) = _cut_face_name;
117 }
118 }
119 else if (_cut_face_id == -1)
120 {
122 }
123
124 // Subdomain ID for new utility blocks must be new
125 // Find sid shifts
126 const auto sid_shift_base = MooseMeshUtils::getNextFreeSubdomainID(mesh);
127 const auto block_id_to_remove = sid_shift_base * 3;
128
129 // For the boolean value in the pair, true means the element is crossed by the cutting plane
130 // false means the element is on the remaining side
131 std::vector<std::pair<dof_id_type, bool>> cross_and_remained_elems_pre_convert;
132
133 // collect the subdomain ids of the original mesh
134 std::set<subdomain_id_type> original_subdomain_ids;
135 for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
136 elem_it++)
137 {
138 original_subdomain_ids.emplace((*elem_it)->subdomain_id());
139 const unsigned int & n_vertices = (*elem_it)->n_vertices();
140 unsigned short elem_vertices_counter(0);
141 for (unsigned int i = 0; i < n_vertices; i++)
142 {
143 // We define elem_vertices_counter in this way so that those elements with one face on the
144 // plane are also processed to have the cut face boundary assigned.
145 if (pointLevelSetRelation((*(*elem_it)->node_ptr(i))) !=
147 elem_vertices_counter++;
148 }
149 if (elem_vertices_counter == n_vertices)
150 (*elem_it)->subdomain_id() = block_id_to_remove;
151 else
152 {
153 // Check if any elements to be processed are not first order
154 if ((*elem_it)->default_order() != Order::FIRST)
155 mooseError("Only first order elements are supported for cutting.");
156 // If we are generating a transition layer, we only need to record the crossed elements
157 if (_generate_transition_layer && elem_vertices_counter > 0)
158 {
159 cross_and_remained_elems_pre_convert.push_back(std::make_pair((*elem_it)->id(), true));
160 // Since we are converting these elements to TET4, and we would keep the original type of
161 // some elements in these blocks
162 if ((*elem_it)->type() != TET4)
163 (*elem_it)->subdomain_id() += sid_shift_base;
164 }
166 cross_and_remained_elems_pre_convert.push_back(
167 std::make_pair((*elem_it)->id(), elem_vertices_counter > 0));
168 }
169 }
170
171 // Then we need to identify and make the transition layer
172 std::vector<dof_id_type> transition_elems_list;
173 std::vector<std::vector<unsigned int>> transition_elems_sides_list;
175 {
176 if (!mesh.is_prepared())
177 mesh.find_neighbors();
178 // First, we need to identify the retained elements that share the boundary with the crossed
179 // elements.
180 for (const auto & elem_info : cross_and_remained_elems_pre_convert)
181 {
182 for (const auto & i_side : make_range(mesh.elem_ptr(elem_info.first)->n_sides()))
183 {
184 // No need to work on a TRI3 side
185 if (mesh.elem_ptr(elem_info.first)->side_ptr(i_side)->type() == QUAD4)
186 {
187 if (mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side) != nullptr &&
188 mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->subdomain_id() !=
189 block_id_to_remove &&
190 std::find(cross_and_remained_elems_pre_convert.begin(),
191 cross_and_remained_elems_pre_convert.end(),
192 std::make_pair(mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->id(),
193 true)) == cross_and_remained_elems_pre_convert.end())
194 {
195 const auto & neighbor_elem_id =
196 mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->id();
197 const auto & neighbor_elem_side_index =
198 mesh.elem_ptr(elem_info.first)
199 ->neighbor_ptr(i_side)
200 ->which_neighbor_am_i(mesh.elem_ptr(elem_info.first));
201 const auto & id_found = std::find(
202 transition_elems_list.begin(), transition_elems_list.end(), neighbor_elem_id);
203 if (id_found == transition_elems_list.end())
204 {
205 transition_elems_list.push_back(neighbor_elem_id);
206 transition_elems_sides_list.push_back(
207 std::vector<unsigned int>({neighbor_elem_side_index}));
208 }
209 else
210 {
211 // If the element is already in the list, we just add the side index
212 const auto index = std::distance(transition_elems_list.begin(), id_found);
213 transition_elems_sides_list[index].push_back(neighbor_elem_side_index);
214 }
215 }
216 }
217 }
218 }
219 }
220
221 std::vector<dof_id_type> converted_elems_ids_to_cut;
222 // Then convert these non-TET4 elements into TET4 elements
224 cross_and_remained_elems_pre_convert,
225 converted_elems_ids_to_cut,
226 block_id_to_remove,
227 false);
228
229 // Make the transition layer if applicable
230 auto & sideset_map = mesh.get_boundary_info().get_sideset_map();
231 for (const auto & i_elem : index_range(transition_elems_list))
232 {
233 const auto & elem_id = transition_elems_list[i_elem];
234 const auto & side_indices = transition_elems_sides_list[i_elem];
235 // Find the involved sidesets of the element so that we can retain them
236 std::vector<std::vector<boundary_id_type>> elem_side_info(mesh.elem_ptr(elem_id)->n_sides());
237 auto side_range = sideset_map.equal_range(mesh.elem_ptr(elem_id));
238 for (auto i = side_range.first; i != side_range.second; ++i)
239 elem_side_info[i->second.first].push_back(i->second.second);
240
242 mesh, elem_id, side_indices, elem_side_info, sid_shift_base);
243 }
244
245 std::vector<const Node *> new_on_plane_nodes;
246 // We build the sideset information now, as we only need the information of the elements before
247 // cutting
248 BoundaryInfo & boundary_info = mesh.get_boundary_info();
249 const auto bdry_side_list = boundary_info.build_side_list();
250 // Cut the TET4 Elements
251 for (const auto & converted_elems_id_to_cut : converted_elems_ids_to_cut)
252 {
254 mesh, bdry_side_list, converted_elems_id_to_cut, block_id_to_remove, new_on_plane_nodes);
255 }
256
257 // If a transition layer is generated, we need to add some subdomain names
259 {
260 try
261 {
263 mesh,
264 original_subdomain_ids,
265 sid_shift_base,
268 }
269 catch (const std::exception & e)
270 {
271 if (((std::string)e.what()).compare(26, 4, "TET4") == 0)
272 paramError("converted_tet_element_subdomain_name_suffix", e.what());
273 else // if (((std::string)e.what()).compare(26, 7, "PYRAMID5") == 0)
274 paramError("converted_pyramid_element_subdomain_name_suffix", e.what());
275 }
276 }
277
278 // delete the original elements that were converted
279 for (const auto & elem_id : transition_elems_list)
280 mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
281 // Delete the block to remove
282 for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
283 elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
284 elem_it++)
285 mesh.delete_elem(*elem_it);
286
287 mesh.contract();
288 mesh.unset_is_prepared();
289 return std::move(_input);
290}
291
294{
295 const Real level_set_value = levelSetEvaluator(point);
296 if (MooseUtils::absoluteFuzzyLessThan(level_set_value, 0.0))
298 else if (MooseUtils::absoluteFuzzyGreaterThan(level_set_value, 0.0))
300 else
302}
303
304Point
306 const Point & point2)
307{
308 Real dist1 = levelSetEvaluator(point1);
309 Real dist2 = levelSetEvaluator(point2);
310
311 if (MooseUtils::absoluteFuzzyEqual(dist1, 0.0) || MooseUtils::absoluteFuzzyEqual(dist2, 0.0))
312 mooseError("At least one of the two points are on the plane.");
313 if (MooseUtils::absoluteFuzzyGreaterThan(dist1 * dist2, 0.0))
314 mooseError("The two points are on the same side of the plane.");
315
316 Point p1(point1);
317 Point p2(point2);
318 Real dist = abs(dist1) + abs(dist2);
319 Point mid_point;
320
321 // Bisection method to find midpoint
322 while (MooseUtils::absoluteFuzzyGreaterThan(dist, 0.0))
323 {
324 mid_point = 0.5 * (p1 + p2);
325 const Real dist_mid = levelSetEvaluator(mid_point);
326 // We do not need Fuzzy here as it will be covered by the while loop
327 if (dist_mid == 0.0)
328 dist = 0.0;
329 else if (dist_mid * dist1 < 0.0)
330 {
331 p2 = mid_point;
332 dist2 = levelSetEvaluator(p2);
333 dist = abs(dist1) + abs(dist2);
334 }
335 else
336 {
337 p1 = mid_point;
338 dist1 = levelSetEvaluator(p1);
339 dist = abs(dist1) + abs(dist2);
340 }
341 }
342 return mid_point;
343}
344
345const Node *
347 ReplicatedMesh & mesh,
348 std::vector<const Node *> & new_on_plane_nodes,
349 const Point & new_point) const
350{
351 for (const auto & new_on_plane_node : new_on_plane_nodes)
352 {
353 if (MooseUtils::absoluteFuzzyEqual((*new_on_plane_node - new_point).norm(), 0.0))
354 return new_on_plane_node;
355 }
356 new_on_plane_nodes.push_back(mesh.add_point(new_point));
357 return new_on_plane_nodes.back();
358}
359
360void
362 ReplicatedMesh & mesh,
363 const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
364 const dof_id_type elem_id,
365 const subdomain_id_type & block_id_to_remove,
366 std::vector<const Node *> & new_on_plane_nodes)
367{
368 // Retrieve boundary information for the mesh
369 BoundaryInfo & boundary_info = mesh.get_boundary_info();
370 // Create a list of sidesets involving the element to be split
371 // It might be complex to assign the boundary id to the new elements
372 // In TET4, none of the four faces have the same normal vector
373 // So we are using the normal vector to identify the faces that
374 // need to be assigned the same boundary id
375 std::vector<Point> elem_side_normal_list;
376 elem_side_normal_list.resize(4);
377 for (const auto i : make_range(4))
378 {
379 auto elem_side = mesh.elem_ptr(elem_id)->side_ptr(i);
380 elem_side_normal_list[i] = (*elem_side->node_ptr(1) - *elem_side->node_ptr(0))
381 .cross(*elem_side->node_ptr(2) - *elem_side->node_ptr(1))
382 .unit();
383 }
384
385 std::vector<std::vector<boundary_id_type>> elem_side_list;
387 bdry_side_list, elem_id, 4, elem_side_list);
388
389 std::vector<PointLevelSetRelationIndex> node_plane_relation(4);
390 std::vector<const Node *> tet4_nodes(4);
391 std::vector<const Node *> tet4_nodes_on_plane;
392 std::vector<const Node *> tet4_nodes_outside_plane;
393 std::vector<const Node *> tet4_nodes_inside_plane;
394 // Sort tetrahedral nodes based on their positioning wrt the plane
395 for (unsigned int i = 0; i < 4; i++)
396 {
397 tet4_nodes[i] = mesh.elem_ptr(elem_id)->node_ptr(i);
398 node_plane_relation[i] = pointLevelSetRelation(*tet4_nodes[i]);
399 if (node_plane_relation[i] == PointLevelSetRelationIndex::on_level_set)
400 tet4_nodes_on_plane.push_back(tet4_nodes[i]);
401 else if (node_plane_relation[i] == PointLevelSetRelationIndex::level_set_out_side)
402 tet4_nodes_outside_plane.push_back(tet4_nodes[i]);
403 else
404 tet4_nodes_inside_plane.push_back(tet4_nodes[i]);
405 }
406 std::vector<Elem *> elems_tet4;
407 bool new_elements_created(false);
408 // No cutting needed if no nodes are outside the plane
409 if (tet4_nodes_outside_plane.size() == 0)
410 {
411 if (tet4_nodes_on_plane.size() == 3)
412 {
413 // Record the element for future cross section boundary assignment
414 elems_tet4.push_back(mesh.elem_ptr(elem_id));
415 }
416 }
417 // Remove the element if all the nodes are outside the plane
418 else if (tet4_nodes_inside_plane.size() == 0)
419 {
420 mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
421 if (tet4_nodes_on_plane.size() == 3)
422 {
423 // I think the neighboring element will be handled,
424 // So we do not need to assign the cross section boundary here
425 }
426 }
427 // As we have nodes on both sides, six different scenarios are possible
428 else
429 {
430 new_elements_created = true;
431 if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 3)
432 {
433 std::vector<const Node *> new_plane_nodes;
434 // A smaller TET4 element is created, this solution is unique
435 for (const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
436 {
437 new_plane_nodes.push_back(nonDuplicateNodeCreator(
438 mesh,
439 new_on_plane_nodes,
440 pointPairLevelSetInterception(*tet4_node_outside_plane, *tet4_nodes_inside_plane[0])));
441 }
442 auto new_elem_tet4 = std::make_unique<Tet4>();
443 new_elem_tet4->set_node(0, const_cast<Node *>(tet4_nodes_inside_plane[0]));
444 new_elem_tet4->set_node(1, const_cast<Node *>(new_plane_nodes[0]));
445 new_elem_tet4->set_node(2, const_cast<Node *>(new_plane_nodes[1]));
446 new_elem_tet4->set_node(3, const_cast<Node *>(new_plane_nodes[2]));
447 new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
448 elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
449 }
450 else if (tet4_nodes_inside_plane.size() == 2 && tet4_nodes_outside_plane.size() == 2)
451 {
452 std::vector<const Node *> new_plane_nodes;
453 // 3 smaller TET3 elements are created
454 for (const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
455 {
456 for (const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
457 {
458 new_plane_nodes.push_back(nonDuplicateNodeCreator(
459 mesh,
460 new_on_plane_nodes,
461 pointPairLevelSetInterception(*tet4_node_outside_plane, *tet4_node_inside_plane)));
462 }
463 }
464 std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[1],
465 new_plane_nodes[3],
466 new_plane_nodes[1],
467 tet4_nodes_inside_plane[0],
468 new_plane_nodes[2],
469 new_plane_nodes[0]};
470 std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
471 std::vector<std::vector<const Node *>> optimized_node_list;
473 new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
474
475 for (unsigned int i = 0; i < optimized_node_list.size(); i++)
476 {
477 auto new_elem_tet4 = std::make_unique<Tet4>();
478 new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
479 new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
480 new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
481 new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
482 new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
483 elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
484 }
485 }
486 else if (tet4_nodes_inside_plane.size() == 3 && tet4_nodes_outside_plane.size() == 1)
487 {
488 std::vector<const Node *> new_plane_nodes;
489 // 3 smaller Tet4 elements are created
490 for (const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
491 {
492 new_plane_nodes.push_back(nonDuplicateNodeCreator(
493 mesh,
494 new_on_plane_nodes,
495 pointPairLevelSetInterception(*tet4_node_inside_plane, *tet4_nodes_outside_plane[0])));
496 }
497 std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[0],
498 tet4_nodes_inside_plane[1],
499 tet4_nodes_inside_plane[2],
500 new_plane_nodes[0],
501 new_plane_nodes[1],
502 new_plane_nodes[2]};
503 std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
504 std::vector<std::vector<const Node *>> optimized_node_list;
506 new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
507
508 for (unsigned int i = 0; i < optimized_node_list.size(); i++)
509 {
510 auto new_elem_tet4 = std::make_unique<Tet4>();
511 new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
512 new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
513 new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
514 new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
515 new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
516 elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
517 }
518 }
519 else if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 1)
520 {
521 auto new_plane_node = nonDuplicateNodeCreator(
522 mesh,
523 new_on_plane_nodes,
524 pointPairLevelSetInterception(*tet4_nodes_inside_plane[0], *tet4_nodes_outside_plane[0]));
525 // A smaller Tet4 is created, this solution is unique
526 auto new_elem_tet4 = std::make_unique<Tet4>();
527 new_elem_tet4->set_node(0, const_cast<Node *>(new_plane_node));
528 new_elem_tet4->set_node(1, const_cast<Node *>(tet4_nodes_on_plane[0]));
529 new_elem_tet4->set_node(2, const_cast<Node *>(tet4_nodes_on_plane[1]));
530 new_elem_tet4->set_node(3, const_cast<Node *>(tet4_nodes_inside_plane[0]));
531 new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
532 elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
533 }
534 else if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 2)
535 {
536 std::vector<const Node *> new_plane_nodes;
537 // A smaller Tet4 element is created, this solution is unique
538 for (const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
539 {
540 new_plane_nodes.push_back(nonDuplicateNodeCreator(
541 mesh,
542 new_on_plane_nodes,
543 pointPairLevelSetInterception(*tet4_node_outside_plane, *tet4_nodes_inside_plane[0])));
544 }
545 auto new_elem_tet4 = std::make_unique<Tet4>();
546 new_elem_tet4->set_node(0, const_cast<Node *>(new_plane_nodes[0]));
547 new_elem_tet4->set_node(1, const_cast<Node *>(new_plane_nodes[1]));
548 new_elem_tet4->set_node(2, const_cast<Node *>(tet4_nodes_on_plane[0]));
549 new_elem_tet4->set_node(3, const_cast<Node *>(tet4_nodes_inside_plane[0]));
550 new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
551 elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
552 }
553 else if (tet4_nodes_inside_plane.size() == 2 && tet4_nodes_outside_plane.size() == 1)
554 {
555 std::vector<const Node *> new_plane_nodes;
556 // 2 smaller TET4 elements are created
557 for (const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
558 {
559 new_plane_nodes.push_back(nonDuplicateNodeCreator(
560 mesh,
561 new_on_plane_nodes,
562 pointPairLevelSetInterception(*tet4_node_inside_plane, *tet4_nodes_outside_plane[0])));
563 }
564 std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[0],
565 tet4_nodes_inside_plane[1],
566 new_plane_nodes[1],
567 new_plane_nodes[0],
568 tet4_nodes_on_plane[0]};
569 std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
570 std::vector<std::vector<const Node *>> optimized_node_list;
572 new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
573
574 for (unsigned int i = 0; i < optimized_node_list.size(); i++)
575 {
576 auto new_elem_tet4 = std::make_unique<Tet4>();
577 new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
578 new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
579 new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
580 new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
581 new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
582 elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
583 }
584 }
585 else
586 mooseError("Unexpected scenario.");
587
588 mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
589 }
590
591 for (auto & elem_tet4 : elems_tet4)
592 {
593 if (new_elements_created)
594 {
595 if (elem_tet4->volume() < 0.0)
596 {
597 Node * temp = elem_tet4->node_ptr(0);
598 elem_tet4->set_node(0, elem_tet4->node_ptr(1));
599 elem_tet4->set_node(1, temp);
600 }
601 }
602 // Find the boundary id of the new element
603 for (unsigned int i = 0; i < 4; i++)
604 {
605 const Point & side_pt_0 = *elem_tet4->side_ptr(i)->node_ptr(0);
606 const Point & side_pt_1 = *elem_tet4->side_ptr(i)->node_ptr(1);
607 const Point & side_pt_2 = *elem_tet4->side_ptr(i)->node_ptr(2);
608
609 const Point side_normal = (side_pt_1 - side_pt_0).cross(side_pt_2 - side_pt_1).unit();
610 for (unsigned int j = 0; j < 4; j++)
611 {
612 if (new_elements_created)
613 {
614 if (MooseUtils::absoluteFuzzyEqual(side_normal * elem_side_normal_list[j], 1.0))
615 {
616 for (const auto & side_info : elem_side_list[j])
617 {
618 boundary_info.add_side(elem_tet4, i, side_info);
619 }
620 }
621 }
622 }
623 if (MooseUtils::absoluteFuzzyEqual(levelSetEvaluator(side_pt_0), 0.0) &&
624 MooseUtils::absoluteFuzzyEqual(levelSetEvaluator(side_pt_1), 0.0) &&
625 MooseUtils::absoluteFuzzyEqual(levelSetEvaluator(side_pt_2), 0.0))
626 {
627
628 boundary_info.add_side(elem_tet4, i, _cut_face_id);
629 }
630 }
631 }
632}
633
634Real
636{
637 _func_params[0] = point(0);
638 _func_params[1] = point(1);
639 _func_params[2] = point(2);
641}
SymFunctionPtr _func_level_set
function parser object describing the level set
Real levelSetEvaluator(const Point &point)
Evaluate the level set function at a given point.
CutMeshByLevelSetGeneratorBase(const InputParameters &parameters)
const BoundaryName _cut_face_name
The boundary name of the surface generated by the cut.
Point pointPairLevelSetInterception(const Point &point1, const Point &point2)
Calculate the intersection point of a level set and a line segment defined by two points separated by...
std::unique_ptr< MeshBase > & _input
Reference to input mesh pointer.
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
const bool _generate_transition_layer
Whether to generate a transition layer for the cut mesh.
const SubdomainName _converted_tet_element_subdomain_name_suffix
The suffix to be added to the original subdomain name for the subdomains containing the elements conv...
boundary_id_type _cut_face_id
The boundary id of the surface generated by the cut.
const SubdomainName _converted_pyramid_element_subdomain_name_suffix
The suffix to be added to the original subdomain name for the subdomains containing the elements conv...
PointLevelSetRelationIndex pointLevelSetRelation(const Point &point)
Evaluate whether a point is on the level set, inside or outside the level set.
PointLevelSetRelationIndex
An enum class for style of input polygon size.
void tet4ElemCutter(ReplicatedMesh &mesh, const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, const subdomain_id_type &block_id_to_remove, std::vector< const Node * > &new_on_plane_nodes)
For a TET4 elements crossed by the level set, keep the part of the element on the retaining side of t...
const Node * nonDuplicateNodeCreator(ReplicatedMesh &mesh, std::vector< const Node * > &new_on_plane_nodes, const Point &new_point) const
Check if a position on a plane has already been used as a node in the mesh.
std::vector< GenericReal< is_ad > > _func_params
Array to stage the parameters passed to the functions when calling Eval.
GenericReal< is_ad > evaluate(SymFunctionPtr &, const std::string &object_name="")
Evaluate FParser object and check EvalError.
static InputParameters validParams()
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object.
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
MeshGenerators are objects that can modify or add to an existing mesh.
static InputParameters validParams()
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition MooseBase.h:457
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition MooseBase.h:271
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition MooseBase.h:199
MeshBase & mesh
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 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 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 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...
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.