https://mooseframework.inl.gov
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 
57  params.addClassDescription(
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 
80 std::unique_ptr<MeshBase>
82 {
83  auto replicated_mesh_ptr = dynamic_cast<ReplicatedMesh *>(_input.get());
84  if (!replicated_mesh_ptr)
85  paramError("input", "Input is not a replicated mesh, which is required");
86  if (*(replicated_mesh_ptr->elem_dimensions().begin()) != 3 ||
87  *(replicated_mesh_ptr->elem_dimensions().rbegin()) != 3)
88  paramError(
89  "input",
90  "Only 3D meshes are supported. Use XYMeshLineCutter for 2D meshes if applicable. Mixed "
91  "dimensional meshes are not supported at the moment.");
92 
93  ReplicatedMesh & mesh = *replicated_mesh_ptr;
94 
95  if (!_cut_face_name.empty())
96  {
98  {
99  const boundary_id_type exist_cut_face_id =
101  if (_cut_face_id != -1 && _cut_face_id != exist_cut_face_id)
102  paramError("cut_face_id",
103  "The cut face boundary name and id are both provided, but they are inconsistent "
104  "with an existing boundary name which has a different id.");
105  else
106  _cut_face_id = exist_cut_face_id;
107  }
108  else
109  {
110  if (_cut_face_id == -1)
112  mesh.get_boundary_info().sideset_name(_cut_face_id) = _cut_face_name;
113  }
114  }
115  else if (_cut_face_id == -1)
116  {
118  }
119 
120  // Subdomain ID for new utility blocks must be new
121  // Find sid shifts
122  const auto sid_shift_base = MooseMeshUtils::getNextFreeSubdomainID(mesh);
123  const auto block_id_to_remove = sid_shift_base * 3;
124 
125  // For the boolean value in the pair, true means the element is crossed by the cutting plane
126  // false means the element is on the remaining side
127  std::vector<std::pair<dof_id_type, bool>> cross_and_remained_elems_pre_convert;
128 
129  // collect the subdomain ids of the original mesh
130  std::set<subdomain_id_type> original_subdomain_ids;
131  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
132  elem_it++)
133  {
134  original_subdomain_ids.emplace((*elem_it)->subdomain_id());
135  const unsigned int & n_vertices = (*elem_it)->n_vertices();
136  unsigned short elem_vertices_counter(0);
137  for (unsigned int i = 0; i < n_vertices; i++)
138  {
139  // We define elem_vertices_counter in this way so that those elements with one face on the
140  // plane are also processed to have the cut face boundary assigned.
141  if (pointLevelSetRelation((*(*elem_it)->node_ptr(i))) !=
143  elem_vertices_counter++;
144  }
145  if (elem_vertices_counter == n_vertices)
146  (*elem_it)->subdomain_id() = block_id_to_remove;
147  else
148  {
149  // Check if any elements to be processed are not first order
150  if ((*elem_it)->default_order() != Order::FIRST)
151  mooseError("Only first order elements are supported for cutting.");
152  // If we are generating a transition layer, we only need to record the crossed elements
153  if (_generate_transition_layer && elem_vertices_counter > 0)
154  {
155  cross_and_remained_elems_pre_convert.push_back(std::make_pair((*elem_it)->id(), true));
156  // Since we are converting these elements to TET4, and we would keep the original type of
157  // some elements in these blocks
158  if ((*elem_it)->type() != TET4)
159  (*elem_it)->subdomain_id() += sid_shift_base;
160  }
161  else if (!_generate_transition_layer)
162  cross_and_remained_elems_pre_convert.push_back(
163  std::make_pair((*elem_it)->id(), elem_vertices_counter > 0));
164  }
165  }
166 
167  // Then we need to identify and make the transition layer
168  std::vector<dof_id_type> transition_elems_list;
169  std::vector<std::vector<unsigned int>> transition_elems_sides_list;
171  {
172  if (!mesh.is_prepared())
173  mesh.find_neighbors();
174  // First, we need to identify the retained elements that share the boundary with the crossed
175  // elements.
176  for (const auto & elem_info : cross_and_remained_elems_pre_convert)
177  {
178  for (const auto & i_side : make_range(mesh.elem_ptr(elem_info.first)->n_sides()))
179  {
180  // No need to work on a TRI3 side
181  if (mesh.elem_ptr(elem_info.first)->side_ptr(i_side)->type() == QUAD4)
182  {
183  if (mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side) != nullptr &&
184  mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->subdomain_id() !=
185  block_id_to_remove &&
186  std::find(cross_and_remained_elems_pre_convert.begin(),
187  cross_and_remained_elems_pre_convert.end(),
188  std::make_pair(mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->id(),
189  true)) == cross_and_remained_elems_pre_convert.end())
190  {
191  const auto & neighbor_elem_id =
192  mesh.elem_ptr(elem_info.first)->neighbor_ptr(i_side)->id();
193  const auto & neighbor_elem_side_index =
194  mesh.elem_ptr(elem_info.first)
195  ->neighbor_ptr(i_side)
196  ->which_neighbor_am_i(mesh.elem_ptr(elem_info.first));
197  const auto & id_found = std::find(
198  transition_elems_list.begin(), transition_elems_list.end(), neighbor_elem_id);
199  if (id_found == transition_elems_list.end())
200  {
201  transition_elems_list.push_back(neighbor_elem_id);
202  transition_elems_sides_list.push_back(
203  std::vector<unsigned int>({neighbor_elem_side_index}));
204  }
205  else
206  {
207  // If the element is already in the list, we just add the side index
208  const auto index = std::distance(transition_elems_list.begin(), id_found);
209  transition_elems_sides_list[index].push_back(neighbor_elem_side_index);
210  }
211  }
212  }
213  }
214  }
215  }
216 
217  std::vector<dof_id_type> converted_elems_ids_to_cut;
218  // Then convert these non-TET4 elements into TET4 elements
220  cross_and_remained_elems_pre_convert,
221  converted_elems_ids_to_cut,
222  block_id_to_remove,
223  false);
224 
225  // Make the transition layer if applicable
226  auto & sideset_map = mesh.get_boundary_info().get_sideset_map();
227  for (const auto & i_elem : index_range(transition_elems_list))
228  {
229  const auto & elem_id = transition_elems_list[i_elem];
230  const auto & side_indices = transition_elems_sides_list[i_elem];
231  // Find the involved sidesets of the element so that we can retain them
232  std::vector<std::vector<boundary_id_type>> elem_side_info(mesh.elem_ptr(elem_id)->n_sides());
233  auto side_range = sideset_map.equal_range(mesh.elem_ptr(elem_id));
234  for (auto i = side_range.first; i != side_range.second; ++i)
235  elem_side_info[i->second.first].push_back(i->second.second);
236 
238  mesh, elem_id, side_indices, elem_side_info, sid_shift_base);
239  }
240 
241  std::vector<const Node *> new_on_plane_nodes;
242  // We build the sideset information now, as we only need the information of the elements before
243  // cutting
244  BoundaryInfo & boundary_info = mesh.get_boundary_info();
245  const auto bdry_side_list = boundary_info.build_side_list();
246  // Cut the TET4 Elements
247  for (const auto & converted_elems_id_to_cut : converted_elems_ids_to_cut)
248  {
250  mesh, bdry_side_list, converted_elems_id_to_cut, block_id_to_remove, new_on_plane_nodes);
251  }
252 
253  // If a transition layer is generated, we need to add some subdomain names
255  {
256  try
257  {
259  mesh,
260  original_subdomain_ids,
261  sid_shift_base,
264  }
265  catch (const std::exception & e)
266  {
267  if (((std::string)e.what()).compare(26, 4, "TET4") == 0)
268  paramError("converted_tet_element_subdomain_name_suffix", e.what());
269  else // if (((std::string)e.what()).compare(26, 7, "PYRAMID5") == 0)
270  paramError("converted_pyramid_element_subdomain_name_suffix", e.what());
271  }
272  }
273 
274  // delete the original elements that were converted
275  for (const auto & elem_id : transition_elems_list)
276  mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
277  // Delete the block to remove
278  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
279  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
280  elem_it++)
281  mesh.delete_elem(*elem_it);
282 
283  mesh.contract();
284  mesh.set_isnt_prepared();
285  return std::move(_input);
286 }
287 
290 {
291  const Real level_set_value = levelSetEvaluator(point);
292  if (MooseUtils::absoluteFuzzyLessThan(level_set_value, 0.0))
294  else if (MooseUtils::absoluteFuzzyGreaterThan(level_set_value, 0.0))
296  else
298 }
299 
300 Point
302  const Point & point2)
303 {
304  Real dist1 = levelSetEvaluator(point1);
305  Real dist2 = levelSetEvaluator(point2);
306 
307  if (MooseUtils::absoluteFuzzyEqual(dist1, 0.0) || MooseUtils::absoluteFuzzyEqual(dist2, 0.0))
308  mooseError("At least one of the two points are on the plane.");
309  if (MooseUtils::absoluteFuzzyGreaterThan(dist1 * dist2, 0.0))
310  mooseError("The two points are on the same side of the plane.");
311 
312  Point p1(point1);
313  Point p2(point2);
314  Real dist = abs(dist1) + abs(dist2);
315  Point mid_point;
316 
317  // Bisection method to find midpoint
318  while (MooseUtils::absoluteFuzzyGreaterThan(dist, 0.0))
319  {
320  mid_point = 0.5 * (p1 + p2);
321  const Real dist_mid = levelSetEvaluator(mid_point);
322  // We do not need Fuzzy here as it will be covered by the while loop
323  if (dist_mid == 0.0)
324  dist = 0.0;
325  else if (dist_mid * dist1 < 0.0)
326  {
327  p2 = mid_point;
328  dist2 = levelSetEvaluator(p2);
329  dist = abs(dist1) + abs(dist2);
330  }
331  else
332  {
333  p1 = mid_point;
334  dist1 = levelSetEvaluator(p1);
335  dist = abs(dist1) + abs(dist2);
336  }
337  }
338  return mid_point;
339 }
340 
341 const Node *
343  ReplicatedMesh & mesh,
344  std::vector<const Node *> & new_on_plane_nodes,
345  const Point & new_point) const
346 {
347  for (const auto & new_on_plane_node : new_on_plane_nodes)
348  {
349  if (MooseUtils::absoluteFuzzyEqual((*new_on_plane_node - new_point).norm(), 0.0))
350  return new_on_plane_node;
351  }
352  new_on_plane_nodes.push_back(mesh.add_point(new_point));
353  return new_on_plane_nodes.back();
354 }
355 
356 void
358  ReplicatedMesh & mesh,
359  const std::vector<libMesh::BoundaryInfo::BCTuple> & bdry_side_list,
360  const dof_id_type elem_id,
361  const subdomain_id_type & block_id_to_remove,
362  std::vector<const Node *> & new_on_plane_nodes)
363 {
364  // Retrieve boundary information for the mesh
365  BoundaryInfo & boundary_info = mesh.get_boundary_info();
366  // Create a list of sidesets involving the element to be split
367  // It might be complex to assign the boundary id to the new elements
368  // In TET4, none of the four faces have the same normal vector
369  // So we are using the normal vector to identify the faces that
370  // need to be assigned the same boundary id
371  std::vector<Point> elem_side_normal_list;
372  elem_side_normal_list.resize(4);
373  for (const auto i : make_range(4))
374  {
375  auto elem_side = mesh.elem_ptr(elem_id)->side_ptr(i);
376  elem_side_normal_list[i] = (*elem_side->node_ptr(1) - *elem_side->node_ptr(0))
377  .cross(*elem_side->node_ptr(2) - *elem_side->node_ptr(1))
378  .unit();
379  }
380 
381  std::vector<std::vector<boundary_id_type>> elem_side_list;
383  bdry_side_list, elem_id, 4, elem_side_list);
384 
385  std::vector<PointLevelSetRelationIndex> node_plane_relation(4);
386  std::vector<const Node *> tet4_nodes(4);
387  std::vector<const Node *> tet4_nodes_on_plane;
388  std::vector<const Node *> tet4_nodes_outside_plane;
389  std::vector<const Node *> tet4_nodes_inside_plane;
390  // Sort tetrahedral nodes based on their positioning wrt the plane
391  for (unsigned int i = 0; i < 4; i++)
392  {
393  tet4_nodes[i] = mesh.elem_ptr(elem_id)->node_ptr(i);
394  node_plane_relation[i] = pointLevelSetRelation(*tet4_nodes[i]);
395  if (node_plane_relation[i] == PointLevelSetRelationIndex::on_level_set)
396  tet4_nodes_on_plane.push_back(tet4_nodes[i]);
397  else if (node_plane_relation[i] == PointLevelSetRelationIndex::level_set_out_side)
398  tet4_nodes_outside_plane.push_back(tet4_nodes[i]);
399  else
400  tet4_nodes_inside_plane.push_back(tet4_nodes[i]);
401  }
402  std::vector<Elem *> elems_tet4;
403  bool new_elements_created(false);
404  // No cutting needed if no nodes are outside the plane
405  if (tet4_nodes_outside_plane.size() == 0)
406  {
407  if (tet4_nodes_on_plane.size() == 3)
408  {
409  // Record the element for future cross section boundary assignment
410  elems_tet4.push_back(mesh.elem_ptr(elem_id));
411  }
412  }
413  // Remove the element if all the nodes are outside the plane
414  else if (tet4_nodes_inside_plane.size() == 0)
415  {
416  mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
417  if (tet4_nodes_on_plane.size() == 3)
418  {
419  // I think the neighboring element will be handled,
420  // So we do not need to assign the cross section boundary here
421  }
422  }
423  // As we have nodes on both sides, six different scenarios are possible
424  else
425  {
426  new_elements_created = true;
427  if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 3)
428  {
429  std::vector<const Node *> new_plane_nodes;
430  // A smaller TET4 element is created, this solution is unique
431  for (const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
432  {
433  new_plane_nodes.push_back(nonDuplicateNodeCreator(
434  mesh,
435  new_on_plane_nodes,
436  pointPairLevelSetInterception(*tet4_node_outside_plane, *tet4_nodes_inside_plane[0])));
437  }
438  auto new_elem_tet4 = std::make_unique<Tet4>();
439  new_elem_tet4->set_node(0, const_cast<Node *>(tet4_nodes_inside_plane[0]));
440  new_elem_tet4->set_node(1, const_cast<Node *>(new_plane_nodes[0]));
441  new_elem_tet4->set_node(2, const_cast<Node *>(new_plane_nodes[1]));
442  new_elem_tet4->set_node(3, const_cast<Node *>(new_plane_nodes[2]));
443  new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
444  elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
445  }
446  else if (tet4_nodes_inside_plane.size() == 2 && tet4_nodes_outside_plane.size() == 2)
447  {
448  std::vector<const Node *> new_plane_nodes;
449  // 3 smaller TET3 elements are created
450  for (const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
451  {
452  for (const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
453  {
454  new_plane_nodes.push_back(nonDuplicateNodeCreator(
455  mesh,
456  new_on_plane_nodes,
457  pointPairLevelSetInterception(*tet4_node_outside_plane, *tet4_node_inside_plane)));
458  }
459  }
460  std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[1],
461  new_plane_nodes[3],
462  new_plane_nodes[1],
463  tet4_nodes_inside_plane[0],
464  new_plane_nodes[2],
465  new_plane_nodes[0]};
466  std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
467  std::vector<std::vector<const Node *>> optimized_node_list;
469  new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
470 
471  for (unsigned int i = 0; i < optimized_node_list.size(); i++)
472  {
473  auto new_elem_tet4 = std::make_unique<Tet4>();
474  new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
475  new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
476  new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
477  new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
478  new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
479  elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
480  }
481  }
482  else if (tet4_nodes_inside_plane.size() == 3 && tet4_nodes_outside_plane.size() == 1)
483  {
484  std::vector<const Node *> new_plane_nodes;
485  // 3 smaller Tet4 elements are created
486  for (const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
487  {
488  new_plane_nodes.push_back(nonDuplicateNodeCreator(
489  mesh,
490  new_on_plane_nodes,
491  pointPairLevelSetInterception(*tet4_node_inside_plane, *tet4_nodes_outside_plane[0])));
492  }
493  std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[0],
494  tet4_nodes_inside_plane[1],
495  tet4_nodes_inside_plane[2],
496  new_plane_nodes[0],
497  new_plane_nodes[1],
498  new_plane_nodes[2]};
499  std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
500  std::vector<std::vector<const Node *>> optimized_node_list;
502  new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
503 
504  for (unsigned int i = 0; i < optimized_node_list.size(); i++)
505  {
506  auto new_elem_tet4 = std::make_unique<Tet4>();
507  new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
508  new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
509  new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
510  new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
511  new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
512  elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
513  }
514  }
515  else if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 1)
516  {
517  auto new_plane_node = nonDuplicateNodeCreator(
518  mesh,
519  new_on_plane_nodes,
520  pointPairLevelSetInterception(*tet4_nodes_inside_plane[0], *tet4_nodes_outside_plane[0]));
521  // A smaller Tet4 is created, this solution is unique
522  auto new_elem_tet4 = std::make_unique<Tet4>();
523  new_elem_tet4->set_node(0, const_cast<Node *>(new_plane_node));
524  new_elem_tet4->set_node(1, const_cast<Node *>(tet4_nodes_on_plane[0]));
525  new_elem_tet4->set_node(2, const_cast<Node *>(tet4_nodes_on_plane[1]));
526  new_elem_tet4->set_node(3, const_cast<Node *>(tet4_nodes_inside_plane[0]));
527  new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
528  elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
529  }
530  else if (tet4_nodes_inside_plane.size() == 1 && tet4_nodes_outside_plane.size() == 2)
531  {
532  std::vector<const Node *> new_plane_nodes;
533  // A smaller Tet4 element is created, this solution is unique
534  for (const auto & tet4_node_outside_plane : tet4_nodes_outside_plane)
535  {
536  new_plane_nodes.push_back(nonDuplicateNodeCreator(
537  mesh,
538  new_on_plane_nodes,
539  pointPairLevelSetInterception(*tet4_node_outside_plane, *tet4_nodes_inside_plane[0])));
540  }
541  auto new_elem_tet4 = std::make_unique<Tet4>();
542  new_elem_tet4->set_node(0, const_cast<Node *>(new_plane_nodes[0]));
543  new_elem_tet4->set_node(1, const_cast<Node *>(new_plane_nodes[1]));
544  new_elem_tet4->set_node(2, const_cast<Node *>(tet4_nodes_on_plane[0]));
545  new_elem_tet4->set_node(3, const_cast<Node *>(tet4_nodes_inside_plane[0]));
546  new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
547  elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
548  }
549  else if (tet4_nodes_inside_plane.size() == 2 && tet4_nodes_outside_plane.size() == 1)
550  {
551  std::vector<const Node *> new_plane_nodes;
552  // 2 smaller TET4 elements are created
553  for (const auto & tet4_node_inside_plane : tet4_nodes_inside_plane)
554  {
555  new_plane_nodes.push_back(nonDuplicateNodeCreator(
556  mesh,
557  new_on_plane_nodes,
558  pointPairLevelSetInterception(*tet4_node_inside_plane, *tet4_nodes_outside_plane[0])));
559  }
560  std::vector<const Node *> new_elems_nodes = {tet4_nodes_inside_plane[0],
561  tet4_nodes_inside_plane[1],
562  new_plane_nodes[1],
563  new_plane_nodes[0],
564  tet4_nodes_on_plane[0]};
565  std::vector<std::vector<unsigned int>> rotated_tet_face_indices;
566  std::vector<std::vector<const Node *>> optimized_node_list;
568  new_elems_nodes, rotated_tet_face_indices, optimized_node_list);
569 
570  for (unsigned int i = 0; i < optimized_node_list.size(); i++)
571  {
572  auto new_elem_tet4 = std::make_unique<Tet4>();
573  new_elem_tet4->set_node(0, const_cast<Node *>(optimized_node_list[i][0]));
574  new_elem_tet4->set_node(1, const_cast<Node *>(optimized_node_list[i][1]));
575  new_elem_tet4->set_node(2, const_cast<Node *>(optimized_node_list[i][2]));
576  new_elem_tet4->set_node(3, const_cast<Node *>(optimized_node_list[i][3]));
577  new_elem_tet4->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id();
578  elems_tet4.push_back(mesh.add_elem(std::move(new_elem_tet4)));
579  }
580  }
581  else
582  mooseError("Unexpected scenario.");
583 
584  mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
585  }
586 
587  for (auto & elem_tet4 : elems_tet4)
588  {
589  if (new_elements_created)
590  {
591  if (elem_tet4->volume() < 0.0)
592  {
593  Node * temp = elem_tet4->node_ptr(0);
594  elem_tet4->set_node(0, elem_tet4->node_ptr(1));
595  elem_tet4->set_node(1, temp);
596  }
597  }
598  // Find the boundary id of the new element
599  for (unsigned int i = 0; i < 4; i++)
600  {
601  const Point & side_pt_0 = *elem_tet4->side_ptr(i)->node_ptr(0);
602  const Point & side_pt_1 = *elem_tet4->side_ptr(i)->node_ptr(1);
603  const Point & side_pt_2 = *elem_tet4->side_ptr(i)->node_ptr(2);
604 
605  const Point side_normal = (side_pt_1 - side_pt_0).cross(side_pt_2 - side_pt_1).unit();
606  for (unsigned int j = 0; j < 4; j++)
607  {
608  if (new_elements_created)
609  {
610  if (MooseUtils::absoluteFuzzyEqual(side_normal * elem_side_normal_list[j], 1.0))
611  {
612  for (const auto & side_info : elem_side_list[j])
613  {
614  boundary_info.add_side(elem_tet4, i, side_info);
615  }
616  }
617  }
618  }
619  if (MooseUtils::absoluteFuzzyEqual(levelSetEvaluator(side_pt_0), 0.0) &&
622  {
623 
624  boundary_info.add_side(elem_tet4, i, _cut_face_id);
625  }
626  }
627  }
628 }
629 
630 Real
632 {
633  _func_params[0] = point(0);
634  _func_params[1] = point(1);
635  _func_params[2] = point(2);
636  return evaluate(_func_level_set);
637 }
std::unique_ptr< MeshBase > & _input
Reference to input mesh pointer.
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...
GenericReal< is_ad > evaluate(SymFunctionPtr &, const std::string &object_name="")
Evaluate FParser object and check EvalError.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
Find a value in an array.
Definition: KokkosUtils.h:30
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
Definition: MooseUtils.h:380
bool hasBoundaryName(const MeshBase &input_mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
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:439
SymFunctionPtr _func_level_set
function parser object describing the level set
MeshBase & mesh
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
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.
PointLevelSetRelationIndex pointLevelSetRelation(const Point &point)
Evaluate whether a point is on the level set, inside or outside the level set.
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...
const bool _generate_transition_layer
Whether to generate a transition layer for the cut mesh.
const BoundaryName _cut_face_name
The boundary name of the surface generated by the cut.
Real levelSetEvaluator(const Point &point)
Evaluate the level set function at a given point.
QUAD4
BoundaryID getBoundaryID(const BoundaryName &boundary_name, const MeshBase &mesh)
Gets the boundary ID associated with the given BoundaryName.
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...
int8_t boundary_id_type
PointLevelSetRelationIndex
An enum class for style of input polygon size.
TET4
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
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...
bool absoluteFuzzyLessThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is less than another variable within an absolute tolerance...
Definition: MooseUtils.h:475
auto norm(const T &a) -> decltype(std::abs(a))
static InputParameters validParams()
Definition: MeshGenerator.C:23
void convert3DMeshToAllTet4(ReplicatedMesh &mesh, const std::vector< std::pair< dof_id_type, bool >> &elems_to_process, std::vector< dof_id_type > &converted_elems_ids_to_track, const subdomain_id_type block_id_to_remove, const bool delete_block_to_remove)
Convert all the elements in a 3D mesh, consisting of only linear elements, into TET4 elements...
const 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.
CutMeshByLevelSetGeneratorBase(const InputParameters &parameters)
void convertElem(ReplicatedMesh &mesh, const dof_id_type &elem_id, const std::vector< unsigned int > &side_indices, const std::vector< std::vector< boundary_id_type >> &elem_side_info, const SubdomainID &subdomain_id_shift_base)
Convert the element to an element with TRI3 side-elements on the user-specified sides by modifying th...
boundary_id_type _cut_face_id
The boundary id of the surface generated by the cut.
void assignConvertedElementsSubdomainNameSuffix(ReplicatedMesh &mesh, const std::set< subdomain_id_type > &original_subdomain_ids, const subdomain_id_type sid_shift_base, const SubdomainName &tet_suffix, const SubdomainName &pyramid_suffix)
Assign a subdomain name suffix to the converted elements created during transition layer generation...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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 ...
std::vector< GenericReal< is_ad > > _func_params
Array to stage the parameters passed to the functions when calling Eval.
IntRange< T > make_range(T beg, T end)
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
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...
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...
static InputParameters validParams()
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...
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
Definition: MooseBase.h:199
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
void prismNodesToTetNodesDeterminer(std::vector< const Node *> &prism_nodes, std::vector< std::vector< unsigned int >> &rotated_tet_face_indices, std::vector< std::vector< const Node *>> &tet_nodes_list)
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...
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:32
auto index_range(const T &sizable)
bool absoluteFuzzyGreaterThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
Function to check whether a variable is greater than another variable within an absolute tolerance...
Definition: MooseUtils.h:428
uint8_t dof_id_type