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  // 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)
92  paramError(
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  }
165  else if (!_generate_transition_layer)
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 
304 Point
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 
345 const 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 
360 void
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 
634 Real
636 {
637  _func_params[0] = point(0);
638  _func_params[1] = point(1);
639  _func_params[2] = point(2);
640  return evaluate(_func_level_set);
641 }
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...
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:50
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:40
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:467
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...
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...
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...
static InputParameters validParams()
Definition: MeshGenerator.C:23
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)
boundary_id_type _cut_face_id
The boundary id of the surface generated by the cut.
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...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto norm(const T &a)
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:281
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:209
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:33
auto index_range(const T &sizable)
uint8_t dof_id_type