https://mooseframework.inl.gov
MooseMeshXYCuttingUtils.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 // MOOSE includes
12 #include "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/face_tri3.h"
20 
21 using namespace libMesh;
22 
24 {
25 
26 void
28  const std::vector<Real> & bdry_pars,
29  const subdomain_id_type block_id_to_remove,
30  const std::set<subdomain_id_type> & subdomain_ids_set,
31  const boundary_id_type trimming_section_boundary_id,
32  const boundary_id_type external_boundary_id,
33  const std::vector<boundary_id_type> & other_boundaries_to_conform,
34  const bool assign_ext_to_new,
35  const bool side_to_remove)
36 {
37  // Build boundary information of the mesh
38  BoundaryInfo & boundary_info = mesh.get_boundary_info();
39  auto bdry_side_list = boundary_info.build_side_list();
40  // Only select the boundaries_to_conform
41  std::vector<std::tuple<dof_id_type, unsigned short int, boundary_id_type>> slc_bdry_side_list;
42  for (unsigned int i = 0; i < bdry_side_list.size(); i++)
43  if (std::get<2>(bdry_side_list[i]) == external_boundary_id ||
44  std::find(other_boundaries_to_conform.begin(),
45  other_boundaries_to_conform.end(),
46  std::get<2>(bdry_side_list[i])) != other_boundaries_to_conform.end())
47  slc_bdry_side_list.push_back(bdry_side_list[i]);
48 
49  // Assign block id for elements to be removed
50  // Also record the elements crossed by the line and with its average vertices on the removal side
51  std::vector<dof_id_type> crossed_elems_to_remove;
52  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
53  elem_it++)
54  {
55  // Check all the vertices of the element
56  unsigned short removal_side_count = 0;
57  for (unsigned int i = 0; i < (*elem_it)->n_vertices(); i++)
58  {
59  // First check if the vertex is on the XY-Plane
60  if (!MooseUtils::absoluteFuzzyEqual((*elem_it)->point(i)(2), 0.0))
61  mooseError(
62  "MooseMeshXYCuttingUtils::lineRemoverMoveNode() only works for 2D meshes in XY plane.");
63  if (lineSideDeterminator((*elem_it)->point(i)(0),
64  (*elem_it)->point(i)(1),
65  bdry_pars[0],
66  bdry_pars[1],
67  bdry_pars[2],
68  side_to_remove))
69  removal_side_count++;
70  }
71  if (removal_side_count == (*elem_it)->n_vertices())
72  {
73  (*elem_it)->subdomain_id() = block_id_to_remove;
74  continue;
75  }
76  // Check the average of the vertices of the element
77  if (lineSideDeterminator((*elem_it)->vertex_average()(0),
78  (*elem_it)->vertex_average()(1),
79  bdry_pars[0],
80  bdry_pars[1],
81  bdry_pars[2],
82  side_to_remove))
83  crossed_elems_to_remove.push_back((*elem_it)->id());
84  }
85  // Check each crossed element to see if removing it would lead to boundary moving
86  for (const auto & elem_id : crossed_elems_to_remove)
87  {
88  bool remove_flag = true;
89  for (unsigned int i = 0; i < mesh.elem_ptr(elem_id)->n_sides(); i++)
90  {
91  if (mesh.elem_ptr(elem_id)->neighbor_ptr(i) != nullptr)
92  if (mesh.elem_ptr(elem_id)->neighbor_ptr(i)->subdomain_id() != block_id_to_remove &&
93  std::find(crossed_elems_to_remove.begin(),
94  crossed_elems_to_remove.end(),
95  mesh.elem_ptr(elem_id)->neighbor_ptr(i)->id()) ==
96  crossed_elems_to_remove.end())
97  {
98  if (mesh.elem_ptr(elem_id)->subdomain_id() !=
99  mesh.elem_ptr(elem_id)->neighbor_ptr(i)->subdomain_id())
100  {
101  remove_flag = false;
102  break;
103  }
104  }
105  }
106  if (remove_flag)
107  mesh.elem_ptr(elem_id)->subdomain_id() = block_id_to_remove;
108  }
109 
110  // Identify all the nodes that are on the interface between block_id_to_remove and other blocks
111  // !!! We need a check here: if a node is on the retaining side, the removed element has a
112  // different subdomain id
113  std::vector<dof_id_type> node_list;
114  for (auto elem_it = mesh.active_subdomain_set_elements_begin(subdomain_ids_set);
115  elem_it != mesh.active_subdomain_set_elements_end(subdomain_ids_set);
116  elem_it++)
117  {
118  for (unsigned int i = 0; i < (*elem_it)->n_sides(); i++)
119  {
120  if ((*elem_it)->neighbor_ptr(i) != nullptr)
121  if ((*elem_it)->neighbor_ptr(i)->subdomain_id() == block_id_to_remove)
122  {
123  node_list.push_back((*elem_it)->side_ptr(i)->node_ptr(0)->id());
124  node_list.push_back((*elem_it)->side_ptr(i)->node_ptr(1)->id());
125  boundary_info.add_side(*elem_it, i, trimming_section_boundary_id);
126  if (assign_ext_to_new && trimming_section_boundary_id != external_boundary_id)
127  boundary_info.add_side(*elem_it, i, external_boundary_id);
128  }
129  }
130  }
131  // Remove duplicate nodes
132  const auto unique_it = std::unique(node_list.begin(), node_list.end());
133  node_list.resize(std::distance(node_list.begin(), unique_it));
134  // Mark those nodes that are on a boundary that requires conformality
135  // If both nodes of a side are involved, we should only move one node
136  std::vector<bool> node_list_flag(node_list.size(), false);
137  std::vector<Point> node_list_point(node_list.size(), Point(0.0, 0.0, 0.0));
138  // Loop over all the selected sides
139  for (unsigned int i = 0; i < slc_bdry_side_list.size(); i++)
140  {
141  // Get the two node ids of the side
142  dof_id_type side_id_0 = mesh.elem_ptr(std::get<0>(slc_bdry_side_list[i]))
143  ->side_ptr(std::get<1>(slc_bdry_side_list[i]))
144  ->node_ptr(0)
145  ->id();
146  dof_id_type side_id_1 = mesh.elem_ptr(std::get<0>(slc_bdry_side_list[i]))
147  ->side_ptr(std::get<1>(slc_bdry_side_list[i]))
148  ->node_ptr(1)
149  ->id();
150  // True means the selected bdry node is in the node list of the trimming interface
151  bool side_id_0_in =
152  !(std::find(node_list.begin(), node_list.end(), side_id_0) == node_list.end());
153  bool side_id_1_in =
154  !(std::find(node_list.begin(), node_list.end(), side_id_1) == node_list.end());
155 
156  // True means the selected bdry node is on the removal side of the trimming interface
157  bool side_node_0_remove = lineSideDeterminator((*mesh.node_ptr(side_id_0))(0),
158  (*mesh.node_ptr(side_id_0))(1),
159  bdry_pars[0],
160  bdry_pars[1],
161  bdry_pars[2],
162  side_to_remove);
163  bool side_node_1_remove = lineSideDeterminator((*mesh.node_ptr(side_id_1))(0),
164  (*mesh.node_ptr(side_id_1))(1),
165  bdry_pars[0],
166  bdry_pars[1],
167  bdry_pars[2],
168  side_to_remove);
169  // If both nodes of that side are involved in the trimming interface
170  if (side_id_0_in && side_id_1_in)
171  // The side needs to be removed from the sideset because it is not longer an interface
172  // The other node will be handled by other element's side
173  boundary_info.remove_side(mesh.elem_ptr(std::get<0>(slc_bdry_side_list[i])),
174  std::get<1>(slc_bdry_side_list[i]),
175  std::get<2>(slc_bdry_side_list[i]));
176  // If node 0 is on the trimming interface, and the side is cut by the trimming line
177  else if (side_id_0_in && (side_node_0_remove != side_node_1_remove))
178  {
179  // Use the intersection point as the destination of the node after moving
180  node_list_flag[std::distance(
181  node_list.begin(), std::find(node_list.begin(), node_list.end(), side_id_0))] = true;
182  const Point p0 = *mesh.node_ptr(side_id_0);
183  const Point p1 = *mesh.node_ptr(side_id_1);
184 
185  node_list_point[std::distance(node_list.begin(),
186  std::find(node_list.begin(), node_list.end(), side_id_0))] =
187  twoPointandLineIntersection(p0, p1, bdry_pars[0], bdry_pars[1], bdry_pars[2]);
188  }
189  // If node 1 is on the trimming interface, and the side is cut by the trimming line
190  else if (side_id_1_in && (side_node_0_remove != side_node_1_remove))
191  {
192  // Use the intersection point as the destination of the node after moving
193  node_list_flag[std::distance(
194  node_list.begin(), std::find(node_list.begin(), node_list.end(), side_id_1))] = true;
195  const Point p0 = *mesh.node_ptr(side_id_0);
196  const Point p1 = *mesh.node_ptr(side_id_1);
197 
198  node_list_point[std::distance(node_list.begin(),
199  std::find(node_list.begin(), node_list.end(), side_id_1))] =
200  twoPointandLineIntersection(p0, p1, bdry_pars[0], bdry_pars[1], bdry_pars[2]);
201  }
202  }
203 
204  // move nodes
205  for (unsigned int i = 0; i < node_list.size(); i++)
206  {
207  // This means the node is on both the trimming boundary and the original external
208  // boundary/selected interface boundaries. In order to keep the shape of the original external
209  // boundary, the node is moved along the original external boundary.
210  if (node_list_flag[i])
211  *(mesh.node_ptr(node_list[i])) = node_list_point[i];
212  // This means the node does not need to conform to any boundaries.
213  // Just move it along the normal direction of the trimming line.
214  else
215  {
216  const Real x0 = (*(mesh.node_ptr(node_list[i])))(0);
217  const Real y0 = (*(mesh.node_ptr(node_list[i])))(1);
218  (*(mesh.node_ptr(node_list[i])))(0) =
219  (bdry_pars[1] * (bdry_pars[1] * x0 - bdry_pars[0] * y0) - bdry_pars[0] * bdry_pars[2]) /
220  (bdry_pars[0] * bdry_pars[0] + bdry_pars[1] * bdry_pars[1]);
221  (*(mesh.node_ptr(node_list[i])))(1) =
222  (bdry_pars[0] * (-bdry_pars[1] * x0 + bdry_pars[0] * y0) - bdry_pars[1] * bdry_pars[2]) /
223  (bdry_pars[0] * bdry_pars[0] + bdry_pars[1] * bdry_pars[1]);
224  }
225  }
226 
227  // Delete the block
228  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
229  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
230  elem_it++)
231  mesh.delete_elem(*elem_it);
232  mesh.contract();
234  // Delete zero volume elements
235  std::vector<dof_id_type> zero_elems;
236  for (auto elem_it = mesh.elements_begin(); elem_it != mesh.elements_end(); elem_it++)
237  {
238  if (MooseUtils::absoluteFuzzyEqual((*elem_it)->volume(), 0.0))
239  {
240  for (unsigned int i = 0; i < (*elem_it)->n_sides(); i++)
241  {
242  if ((*elem_it)->neighbor_ptr(i) != nullptr)
243  {
244  boundary_info.add_side((*elem_it)->neighbor_ptr(i),
245  ((*elem_it)->neighbor_ptr(i))->which_neighbor_am_i(*elem_it),
246  external_boundary_id);
247  boundary_info.add_side((*elem_it)->neighbor_ptr(i),
248  ((*elem_it)->neighbor_ptr(i))->which_neighbor_am_i(*elem_it),
249  trimming_section_boundary_id);
250  }
251  }
252  zero_elems.push_back((*elem_it)->id());
253  }
254  }
255  for (const auto & zero_elem : zero_elems)
256  mesh.delete_elem(mesh.elem_ptr(zero_elem));
257  mesh.contract();
258  // As we modified the side_list, it is safer to clear the node_list
259  boundary_info.clear_boundary_node_ids();
261 }
262 
263 bool
265  const Real py,
266  const Real param_1,
267  const Real param_2,
268  const Real param_3,
269  const bool direction_param,
270  const Real dis_tol)
271 {
272  const Real tmp = px * param_1 + py * param_2 + param_3;
273  return direction_param ? tmp >= dis_tol : tmp <= dis_tol;
274 }
275 
276 Point
277 twoLineIntersection(const Real param_11,
278  const Real param_12,
279  const Real param_13,
280  const Real param_21,
281  const Real param_22,
282  const Real param_23)
283 {
284  return Point(
285  (param_12 * param_23 - param_22 * param_13) / (param_11 * param_22 - param_21 * param_12),
286  (param_13 * param_21 - param_23 * param_11) / (param_11 * param_22 - param_21 * param_12),
287  0.0);
288 }
289 
290 Point
292  const Point & pt2,
293  const Real param_1,
294  const Real param_2,
295  const Real param_3)
296 {
297  return twoLineIntersection(param_1,
298  param_2,
299  param_3,
300  pt2(1) - pt1(1),
301  pt1(0) - pt2(0),
302  pt2(0) * pt1(1) - pt1(0) * pt2(1));
303 }
304 
305 bool
307  const std::set<subdomain_id_type> & subdomain_ids_set,
308  const subdomain_id_type tri_elem_subdomain_shift,
309  const SubdomainName tri_elem_subdomain_name_suffix)
310 {
311  BoundaryInfo & boundary_info = mesh.get_boundary_info();
312  // Define the subdomain id shift for the new TRI3 element subdomain(s)
313  const subdomain_id_type max_subdomain_id(*subdomain_ids_set.rbegin());
314  const subdomain_id_type tri_subdomain_id_shift =
315  tri_elem_subdomain_shift == Moose::INVALID_BLOCK_ID ? max_subdomain_id
316  : tri_elem_subdomain_shift;
317  mooseAssert(std::numeric_limits<subdomain_id_type>::max() - max_subdomain_id >
318  tri_subdomain_id_shift,
319  "The TRI elements subdomain id to be assigned may exceed the numeric limit.");
320  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
321  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
322  std::vector<std::tuple<Elem *, unsigned int, bool, bool>> bad_elems_rec;
323  // Loop over all the active elements to find any degenerate QUAD elements
324  for (auto & elem : as_range(mesh.active_elements_begin(), mesh.active_elements_end()))
325  {
326  // Two types of degenerate QUAD elements are identified:
327  // (1) QUAD elements with three collinear vertices
328  // (2) QUAD elements with two overlapped vertices
329  const auto elem_angles = vertex_angles(*elem);
330  const auto elem_distances = vertex_distances(*elem);
331  // Type 1
332  if (MooseUtils::absoluteFuzzyEqual(elem_angles.front().first, M_PI, 0.001))
333  {
334  bad_elems_rec.push_back(std::make_tuple(elem, elem_angles.front().second, false, true));
335  continue;
336  }
337  // Type 2
338  if (MooseUtils::absoluteFuzzyEqual(elem_distances.front().first, 0.0))
339  {
340  bad_elems_rec.push_back(std::make_tuple(elem, elem_distances.front().second, false, false));
341  }
342  }
343  std::set<subdomain_id_type> new_subdomain_ids;
344  // Loop over all the identified degenerate QUAD elements
345  for (const auto & bad_elem : bad_elems_rec)
346  {
347  std::vector<boundary_id_type> elem_bdry_container_0;
348  std::vector<boundary_id_type> elem_bdry_container_1;
349  std::vector<boundary_id_type> elem_bdry_container_2;
350 
351  Elem * elem_0 = std::get<0>(bad_elem);
352  if (std::get<3>(bad_elem))
353  {
354  // elems 1 and 2 are the neighboring elements of the degenerate element corresponding to the
355  // two collinear sides.
356  // For the degenerated element with three colinear vertices, if the elems 1 and 2 do not
357  // exist, the two sides are on the external boundary formed by trimming.
358  Elem * elem_1 = elem_0->neighbor_ptr(std::get<1>(bad_elem));
359  Elem * elem_2 = elem_0->neighbor_ptr((std::get<1>(bad_elem) - 1) % elem_0->n_vertices());
360  if ((elem_1 != nullptr || elem_2 != nullptr))
361  throw MooseException("The input mesh has degenerate quad element before trimming.");
362  }
363  mesh.get_boundary_info().boundary_ids(elem_0, std::get<1>(bad_elem), elem_bdry_container_0);
365  elem_0, (std::get<1>(bad_elem) + 1) % elem_0->n_vertices(), elem_bdry_container_1);
367  elem_0, (std::get<1>(bad_elem) + 2) % elem_0->n_vertices(), elem_bdry_container_2);
369  elem_0, (std::get<1>(bad_elem) + 3) % elem_0->n_vertices(), elem_bdry_container_0);
370 
371  // Record subdomain id of the degenerate element
372  auto elem_block_id = elem_0->subdomain_id();
373  // Define the three of four nodes that will be used to generate the TRI element
374  auto pt0 = elem_0->node_ptr((std::get<1>(bad_elem) + 1) % elem_0->n_vertices());
375  auto pt1 = elem_0->node_ptr((std::get<1>(bad_elem) + 2) % elem_0->n_vertices());
376  auto pt2 = elem_0->node_ptr((std::get<1>(bad_elem) + 3) % elem_0->n_vertices());
377  // Record all the element extra integers of the degenerate element
378  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
379  exist_extra_ids[j] = elem_0->get_extra_integer(j);
380  // Delete the degenerate QUAD element
381  mesh.delete_elem(elem_0);
382  // Create the new TRI element
383  Elem * elem_Tri3 = mesh.add_elem(new Tri3);
384  elem_Tri3->set_node(0, pt0);
385  elem_Tri3->set_node(1, pt1);
386  elem_Tri3->set_node(2, pt2);
387  // Retain the boundary information
388  for (auto bdry_id : elem_bdry_container_0)
389  boundary_info.add_side(elem_Tri3, 2, bdry_id);
390  for (auto bdry_id : elem_bdry_container_1)
391  boundary_info.add_side(elem_Tri3, 0, bdry_id);
392  for (auto bdry_id : elem_bdry_container_2)
393  boundary_info.add_side(elem_Tri3, 1, bdry_id);
394  // Assign subdomain id for the TRI element by shifting its original subdomain id
395  elem_Tri3->subdomain_id() = elem_block_id + tri_subdomain_id_shift;
396  new_subdomain_ids.emplace(elem_block_id + tri_subdomain_id_shift);
397  // Retain element extra integers
398  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
399  elem_Tri3->set_extra_integer(j, exist_extra_ids[j]);
400  }
401  // Assign subdomain names for the new TRI elements
402  for (auto & nid : new_subdomain_ids)
403  {
404  const SubdomainName old_name = mesh.subdomain_name(nid - tri_subdomain_id_shift);
406  (old_name.empty() ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
407  : old_name) +
408  "_" + tri_elem_subdomain_name_suffix,
410  throw MooseException("The new subdomain name already exists in the mesh.");
411  mesh.subdomain_name(nid) =
412  (old_name.empty() ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
413  : old_name) +
414  "_" + tri_elem_subdomain_name_suffix;
415  mooseWarning("Degenerate QUAD elements have been converted into TRI elements with a new "
416  "subdomain name: " +
417  mesh.subdomain_name(nid) + ".");
418  }
419  return bad_elems_rec.size();
420 }
421 
422 std::vector<std::pair<Real, unsigned int>>
423 vertex_angles(const Elem & elem)
424 {
425  std::vector<std::pair<Real, unsigned int>> angles;
426  const unsigned int n_vertices = elem.n_vertices();
427 
428  for (unsigned int i = 0; i < n_vertices; i++)
429  {
430  Point v1 = (*elem.node_ptr((i - 1) % n_vertices) - *elem.node_ptr(i % n_vertices));
431  Point v2 = (*elem.node_ptr((i + 1) % n_vertices) - *elem.node_ptr(i % n_vertices));
432  Real tmp = v1 * v2 / v1.norm() / v2.norm();
433  if (tmp > 1.0)
434  tmp = 1.0;
435  else if (tmp < -1.0)
436  tmp = -1.0;
437  angles.push_back(std::make_pair(acos(tmp), i));
438  }
439  std::sort(angles.begin(), angles.end(), std::greater<>());
440  return angles;
441 }
442 
443 std::vector<std::pair<Real, unsigned int>>
444 vertex_distances(const Elem & elem)
445 {
446  std::vector<std::pair<Real, unsigned int>> distances;
447  const unsigned int n_vertices = elem.n_vertices();
448 
449  for (unsigned int i = 0; i < n_vertices; i++)
450  {
451  Point v1 = (*elem.node_ptr((i + 1) % n_vertices) - *elem.node_ptr(i % n_vertices));
452  distances.push_back(std::make_pair(v1.norm(), i));
453  }
454  std::sort(distances.begin(), distances.end());
455  return distances;
456 }
457 
458 void
460  const dof_id_type elem_id,
461  const unsigned short node_shift,
462  const dof_id_type nid_3,
463  const dof_id_type nid_4,
464  const subdomain_id_type single_elem_side_id,
465  const subdomain_id_type double_elem_side_id)
466 {
467  const auto elem_old = mesh.elem_ptr(elem_id);
468  const dof_id_type nid_0 = elem_old->node_ptr(node_shift % 3)->id();
469  const dof_id_type nid_1 = elem_old->node_ptr((1 + node_shift) % 3)->id();
470  const dof_id_type nid_2 = elem_old->node_ptr((2 + node_shift) % 3)->id();
471 
472  const bool m1_side_flag =
474  .cross(*(mesh.node_ptr(nid_1)) - *(mesh.node_ptr(nid_0)))
475  .norm(),
476  0.0);
477  const dof_id_type nid_m1 = m1_side_flag ? nid_3 : nid_4;
478  const dof_id_type nid_m2 = m1_side_flag ? nid_4 : nid_3;
479  // Build boundary information of the mesh
480  BoundaryInfo & boundary_info = mesh.get_boundary_info();
481  auto bdry_side_list = boundary_info.build_side_list();
482  // Create a list of sidesets involving the element to be split
483  std::vector<std::vector<boundary_id_type>> elem_side_list;
484  elem_side_list.resize(3);
485  for (unsigned int i = 0; i < bdry_side_list.size(); i++)
486  {
487  if (std::get<0>(bdry_side_list[i]) == elem_id)
488  {
489  elem_side_list[(std::get<1>(bdry_side_list[i]) + 3 - node_shift) % 3].push_back(
490  std::get<2>(bdry_side_list[i]));
491  }
492  }
493 
494  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
495  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
496  // Record all the element extra integers of the original element
497  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
498  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
499 
500  Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
501  elem_Tri3_0->set_node(0, mesh.node_ptr(nid_0));
502  elem_Tri3_0->set_node(1, mesh.node_ptr(nid_m1));
503  elem_Tri3_0->set_node(2, mesh.node_ptr(nid_m2));
504  elem_Tri3_0->subdomain_id() = single_elem_side_id;
505  Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
506  elem_Tri3_1->set_node(0, mesh.node_ptr(nid_1));
507  elem_Tri3_1->set_node(1, mesh.node_ptr(nid_m2));
508  elem_Tri3_1->set_node(2, mesh.node_ptr(nid_m1));
509  elem_Tri3_1->subdomain_id() = double_elem_side_id;
510  Elem * elem_Tri3_2 = mesh.add_elem(new Tri3);
511  elem_Tri3_2->set_node(0, mesh.node_ptr(nid_2));
512  elem_Tri3_2->set_node(1, mesh.node_ptr(nid_m2));
513  elem_Tri3_2->set_node(2, mesh.node_ptr(nid_1));
514  elem_Tri3_2->subdomain_id() = double_elem_side_id;
515  // Retain element extra integers
516  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
517  {
518  elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
519  elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
520  elem_Tri3_2->set_extra_integer(j, exist_extra_ids[j]);
521  }
522 
523  // Add sideset information to the new elements
524  for (const auto & side_info_0 : elem_side_list[0])
525  {
526  boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
527  boundary_info.add_side(elem_Tri3_1, 2, side_info_0);
528  }
529  for (const auto & side_info_1 : elem_side_list[1])
530  boundary_info.add_side(elem_Tri3_2, 2, side_info_1);
531  for (const auto & side_info_2 : elem_side_list[2])
532  {
533  boundary_info.add_side(elem_Tri3_0, 2, side_info_2);
534  boundary_info.add_side(elem_Tri3_2, 0, side_info_2);
535  }
536 }
537 
538 void
540  const dof_id_type elem_id,
541  const unsigned short node_shift,
542  const dof_id_type nid_m,
543  const subdomain_id_type first_elem_side_id,
544  const subdomain_id_type second_elem_side_id)
545 {
546  const auto elem_old = mesh.elem_ptr(elem_id);
547  const dof_id_type nid_0 = elem_old->node_ptr(node_shift % 3)->id();
548  const dof_id_type nid_1 = elem_old->node_ptr((1 + node_shift) % 3)->id();
549  const dof_id_type nid_2 = elem_old->node_ptr((2 + node_shift) % 3)->id();
550  // Build boundary information of the mesh
551  BoundaryInfo & boundary_info = mesh.get_boundary_info();
552  auto bdry_side_list = boundary_info.build_side_list();
553  // Create a list of sidesets involving the element to be split
554  std::vector<std::vector<boundary_id_type>> elem_side_list;
555  elem_side_list.resize(3);
556  for (unsigned int i = 0; i < bdry_side_list.size(); i++)
557  {
558  if (std::get<0>(bdry_side_list[i]) == elem_id)
559  {
560  elem_side_list[(std::get<1>(bdry_side_list[i]) + 3 - node_shift) % 3].push_back(
561  std::get<2>(bdry_side_list[i]));
562  }
563  }
564 
565  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
566  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
567  // Record all the element extra integers of the original element
568  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
569  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
570 
571  Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
572  elem_Tri3_0->set_node(0, mesh.node_ptr(nid_0));
573  elem_Tri3_0->set_node(1, mesh.node_ptr(nid_1));
574  elem_Tri3_0->set_node(2, mesh.node_ptr(nid_m));
575  elem_Tri3_0->subdomain_id() = first_elem_side_id;
576  Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
577  elem_Tri3_1->set_node(0, mesh.node_ptr(nid_0));
578  elem_Tri3_1->set_node(1, mesh.node_ptr(nid_m));
579  elem_Tri3_1->set_node(2, mesh.node_ptr(nid_2));
580  elem_Tri3_1->subdomain_id() = second_elem_side_id;
581  // Retain element extra integers
582  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
583  {
584  elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
585  elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
586  }
587 
588  // Add sideset information to the new elements
589  for (const auto & side_info_0 : elem_side_list[0])
590  boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
591  for (const auto & side_info_1 : elem_side_list[1])
592  {
593  boundary_info.add_side(elem_Tri3_0, 1, side_info_1);
594  boundary_info.add_side(elem_Tri3_1, 1, side_info_1);
595  }
596  for (const auto & side_info_2 : elem_side_list[2])
597  boundary_info.add_side(elem_Tri3_1, 2, side_info_2);
598 }
599 
600 void
602  const dof_id_type elem_id,
603  const subdomain_id_type tri_elem_subdomain_shift)
604 {
605  // Build boundary information of the mesh
606  BoundaryInfo & boundary_info = mesh.get_boundary_info();
607  auto bdry_side_list = boundary_info.build_side_list();
608  // Create a list of sidesets involving the element to be split
609  std::vector<std::vector<boundary_id_type>> elem_side_list;
610  elem_side_list.resize(4);
611  for (unsigned int i = 0; i < bdry_side_list.size(); i++)
612  {
613  if (std::get<0>(bdry_side_list[i]) == elem_id)
614  {
615  elem_side_list[std::get<1>(bdry_side_list[i])].push_back(std::get<2>(bdry_side_list[i]));
616  }
617  }
618 
619  auto node_0 = mesh.elem_ptr(elem_id)->node_ptr(0);
620  auto node_1 = mesh.elem_ptr(elem_id)->node_ptr(1);
621  auto node_2 = mesh.elem_ptr(elem_id)->node_ptr(2);
622  auto node_3 = mesh.elem_ptr(elem_id)->node_ptr(3);
623 
624  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
625  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
626  // Record all the element extra integers of the original quad element
627  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
628  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
629 
630  // There are two trivial ways to split a quad element
631  // We prefer the way that leads to triangles with similar areas
632  if (std::abs((*node_1 - *node_0).cross(*node_3 - *node_0).norm() -
633  (*node_1 - *node_2).cross(*node_3 - *node_2).norm()) >
634  std::abs((*node_0 - *node_1).cross(*node_2 - *node_1).norm() -
635  (*node_0 - *node_3).cross(*node_2 - *node_3).norm()))
636  {
637  Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
638  elem_Tri3_0->set_node(0, node_0);
639  elem_Tri3_0->set_node(1, node_1);
640  elem_Tri3_0->set_node(2, node_2);
641  elem_Tri3_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
642  Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
643  elem_Tri3_1->set_node(0, node_0);
644  elem_Tri3_1->set_node(1, node_2);
645  elem_Tri3_1->set_node(2, node_3);
646  elem_Tri3_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
647  // Retain element extra integers
648  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
649  {
650  elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
651  elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
652  }
653 
654  // Add sideset information to the new elements
655  for (const auto & side_info_0 : elem_side_list[0])
656  boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
657  for (const auto & side_info_1 : elem_side_list[1])
658  boundary_info.add_side(elem_Tri3_0, 1, side_info_1);
659  for (const auto & side_info_2 : elem_side_list[2])
660  boundary_info.add_side(elem_Tri3_1, 1, side_info_2);
661  for (const auto & side_info_3 : elem_side_list[3])
662  boundary_info.add_side(elem_Tri3_1, 2, side_info_3);
663  }
664  else
665  {
666  Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
667  elem_Tri3_0->set_node(0, node_0);
668  elem_Tri3_0->set_node(1, node_1);
669  elem_Tri3_0->set_node(2, node_3);
670  elem_Tri3_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
671  Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
672  elem_Tri3_1->set_node(0, node_1);
673  elem_Tri3_1->set_node(1, node_2);
674  elem_Tri3_1->set_node(2, node_3);
675  elem_Tri3_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
676  // Retain element extra integers
677  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
678  {
679  elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
680  elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
681  }
682 
683  // Add sideset information to the new elements
684  for (const auto & side_info_0 : elem_side_list[0])
685  boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
686  for (const auto & side_info_1 : elem_side_list[1])
687  boundary_info.add_side(elem_Tri3_1, 0, side_info_1);
688  for (const auto & side_info_2 : elem_side_list[2])
689  boundary_info.add_side(elem_Tri3_1, 1, side_info_2);
690  for (const auto & side_info_3 : elem_side_list[3])
691  boundary_info.add_side(elem_Tri3_0, 2, side_info_3);
692  }
693 }
694 
695 void
697  const std::vector<Real> & cut_line_params,
698  const dof_id_type tri_subdomain_id_shift,
699  const SubdomainName tri_elem_subdomain_name_suffix)
700 {
701  // Preprocess: find all the quad elements that are across the cutting line
702  std::vector<dof_id_type> cross_elems_quad;
703  std::set<subdomain_id_type> new_subdomain_ids;
704  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
705  elem_it++)
706  {
707  if ((*elem_it)->n_vertices() == 4)
708  {
709  std::vector<unsigned short> node_side_rec;
710  for (unsigned int i = 0; i < 4; i++)
711  {
712  const Point v_point = (*elem_it)->point(i);
713  node_side_rec.push_back(lineSideDeterminator(v_point(0),
714  v_point(1),
715  cut_line_params[0],
716  cut_line_params[1],
717  cut_line_params[2],
718  true));
719  }
720  if (std::accumulate(node_side_rec.begin(), node_side_rec.end(), 0) != 4 &&
721  std::accumulate(node_side_rec.begin(), node_side_rec.end(), 0) > 0)
722  {
723  cross_elems_quad.push_back((*elem_it)->id());
724  new_subdomain_ids.emplace((*elem_it)->subdomain_id() + tri_subdomain_id_shift);
725  }
726  }
727  }
728  // Then convert these quad elements into tri elements
729  for (const auto & cross_elem_quad : cross_elems_quad)
730  {
731  quadElemSplitter(mesh, cross_elem_quad, tri_subdomain_id_shift);
732  mesh.delete_elem(mesh.elem_ptr(cross_elem_quad));
733  }
734  for (auto & nid : new_subdomain_ids)
735  {
736  const SubdomainName old_name = mesh.subdomain_name(nid - tri_subdomain_id_shift);
738  (old_name.empty() ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
739  : old_name) +
740  "_" + tri_elem_subdomain_name_suffix,
742  throw MooseException("The new subdomain name already exists in the mesh.");
743  mesh.subdomain_name(nid) =
744  (old_name.empty() ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
745  : old_name) +
746  "_" + tri_elem_subdomain_name_suffix;
747  mooseWarning("QUAD elements have been converted into TRI elements with a new "
748  "subdomain name: " +
749  mesh.subdomain_name(nid) + ".");
750  }
751  mesh.contract();
752 }
753 
754 void
756  const std::vector<Real> & cut_line_params,
757  const subdomain_id_type block_id_to_remove,
758  const boundary_id_type new_boundary_id)
759 {
760  // Find all the elements that are across the cutting line
761  std::vector<dof_id_type> cross_elems;
762  // A vector for element specific information
763  std::vector<std::vector<std::pair<dof_id_type, dof_id_type>>> node_pairs_vec;
764  // A set for unique pairs
765  std::vector<std::pair<dof_id_type, dof_id_type>> node_pairs_unique_vec;
766  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
767  elem_it++)
768  {
769  std::vector<unsigned short> node_side_rec;
770  const auto n_vertices = (*elem_it)->n_vertices();
771  node_side_rec.resize(n_vertices);
772  for (unsigned int i = 0; i < n_vertices; i++)
773  {
774  // First check if the vertex is in the XY Plane
775  if (!MooseUtils::absoluteFuzzyEqual((*elem_it)->point(i)(2), 0.0))
776  mooseError("MooseMeshXYCuttingUtils::lineRemoverCutElemTri() only works for 2D meshes in "
777  "XY Plane.");
778  const Point v_point = (*elem_it)->point(i);
779  node_side_rec[i] = lineSideDeterminator(
780  v_point(0), v_point(1), cut_line_params[0], cut_line_params[1], cut_line_params[2], true);
781  }
782  if (std::accumulate(node_side_rec.begin(), node_side_rec.end(), 0) == (int)node_side_rec.size())
783  {
784  (*elem_it)->subdomain_id() = block_id_to_remove;
785  }
786  else if (std::accumulate(node_side_rec.begin(), node_side_rec.end(), 0) > 0)
787  {
788  if ((*elem_it)->n_vertices() != 3 || (*elem_it)->n_nodes() != 3)
789  mooseError("The element across the cutting line is not TRI3, which is not supported.");
790  cross_elems.push_back((*elem_it)->id());
791  // Then we need to check pairs of nodes that are on the different side
792  std::vector<std::pair<dof_id_type, dof_id_type>> node_pairs;
793  for (unsigned int i = 0; i < node_side_rec.size(); i++)
794  {
795  // first node on removal side and second node on retaining side
796  if (node_side_rec[i] > 0 && node_side_rec[(i + 1) % node_side_rec.size()] == 0)
797  {
798  // Removal side first
799  node_pairs.push_back(
800  std::make_pair((*elem_it)->node_ptr(i)->id(),
801  (*elem_it)->node_ptr((i + 1) % node_side_rec.size())->id()));
802  node_pairs_unique_vec.push_back(node_pairs.back());
803  }
804  // first node on retaining side and second node on removal side
805  else if (node_side_rec[i] == 0 && node_side_rec[(i + 1) % node_side_rec.size()] > 0)
806  {
807  // Removal side first
808  node_pairs.push_back(
809  std::make_pair((*elem_it)->node_ptr((i + 1) % node_side_rec.size())->id(),
810  (*elem_it)->node_ptr(i)->id()));
811  node_pairs_unique_vec.push_back(node_pairs.back());
812  }
813  }
814  node_pairs_vec.push_back(node_pairs);
815  }
816  }
817  auto vec_ip = std::unique(node_pairs_unique_vec.begin(), node_pairs_unique_vec.end());
818  node_pairs_unique_vec.resize(std::distance(node_pairs_unique_vec.begin(), vec_ip));
819 
820  // Loop over all the node pairs to define new nodes that sit on the cutting line
821  std::vector<Node *> nodes_on_line;
822  // whether the on-line node is overlapped with the node pairs or a brand new node
823  std::vector<unsigned short> nodes_on_line_overlap;
824  for (const auto & node_pair : node_pairs_unique_vec)
825  {
826  const Point pt1 = *mesh.node_ptr(node_pair.first);
827  const Point pt2 = *mesh.node_ptr(node_pair.second);
828  const Point pt_line = twoPointandLineIntersection(
829  pt1, pt2, cut_line_params[0], cut_line_params[1], cut_line_params[2]);
830  if ((pt_line - pt1).norm() < libMesh::TOLERANCE)
831  {
832  nodes_on_line.push_back(mesh.node_ptr(node_pair.first));
833  nodes_on_line_overlap.push_back(1);
834  }
835  else if ((pt_line - pt2).norm() < libMesh::TOLERANCE)
836  {
837  nodes_on_line.push_back(mesh.node_ptr(node_pair.second));
838  nodes_on_line_overlap.push_back(2);
839  }
840  else
841  {
842  nodes_on_line.push_back(mesh.add_point(pt_line));
843  nodes_on_line_overlap.push_back(0);
844  }
845  }
846 
847  // make new elements
848  for (unsigned int i = 0; i < cross_elems.size(); i++)
849  {
850  // Only TRI elements are involved after preprocessing
851  auto cross_elem = mesh.elem_ptr(cross_elems[i]);
852  auto node_0 = cross_elem->node_ptr(0);
853  auto node_1 = cross_elem->node_ptr(1);
854  auto node_2 = cross_elem->node_ptr(2);
855  const std::vector<dof_id_type> tri_nodes = {node_0->id(), node_1->id(), node_2->id()};
856 
857  const auto online_node_index_1 = std::distance(node_pairs_unique_vec.begin(),
858  std::find(node_pairs_unique_vec.begin(),
859  node_pairs_unique_vec.end(),
860  node_pairs_vec[i][0]));
861  const auto online_node_index_2 = std::distance(node_pairs_unique_vec.begin(),
862  std::find(node_pairs_unique_vec.begin(),
863  node_pairs_unique_vec.end(),
864  node_pairs_vec[i][1]));
865  auto node_3 = nodes_on_line[online_node_index_1];
866  auto node_4 = nodes_on_line[online_node_index_2];
867  const auto node_3_overlap_flag = nodes_on_line_overlap[online_node_index_1];
868  const auto node_4_overlap_flag = nodes_on_line_overlap[online_node_index_2];
869  // Most common case, no overlapped nodes
870  if (node_3_overlap_flag == 0 && node_4_overlap_flag == 0)
871  {
872  // True if the common node is on the removal side; false if on the retaining side
873  const bool common_node_side = node_pairs_vec[i][0].first == node_pairs_vec[i][1].first;
874  const subdomain_id_type block_id_to_assign_1 =
875  common_node_side ? block_id_to_remove : cross_elem->subdomain_id();
876  const subdomain_id_type block_id_to_assign_2 =
877  common_node_side ? cross_elem->subdomain_id() : block_id_to_remove;
878  // The reference node ids need to be adjusted according to the common node of the two cut
879  // sides
880  const dof_id_type common_node_id =
881  common_node_side ? node_pairs_vec[i][0].first : node_pairs_vec[i][0].second;
882 
884  cross_elem->id(),
885  std::distance(tri_nodes.begin(),
886  std::find(tri_nodes.begin(), tri_nodes.end(), common_node_id)),
887  node_3->id(),
888  node_4->id(),
889  block_id_to_assign_1,
890  block_id_to_assign_2);
891  mesh.delete_elem(cross_elem);
892  }
893  // both node_3 and node_4 are overlapped
894  else if (node_3_overlap_flag > 0 && node_4_overlap_flag > 0)
895  {
896  // In this case, the entire element is on one side of the cutting line
897  // No change needed just check which side the element is on
898  cross_elem->subdomain_id() = lineSideDeterminator(cross_elem->vertex_average()(0),
899  cross_elem->vertex_average()(1),
900  cut_line_params[0],
901  cut_line_params[1],
902  cut_line_params[2],
903  true)
904  ? block_id_to_remove
905  : cross_elem->subdomain_id();
906  }
907  // node_3 or node_4 is overlapped
908  else
909  {
910  const auto node_3_finder = std::distance(
911  tri_nodes.begin(), std::find(tri_nodes.begin(), tri_nodes.end(), node_3->id()));
912  const auto node_4_finder = std::distance(
913  tri_nodes.begin(), std::find(tri_nodes.begin(), tri_nodes.end(), node_4->id()));
914  // As only one of the two above values should be less than the three, the smaller one should
915  // be used
916  const dof_id_type node_id = node_3_finder < node_4_finder ? node_4->id() : node_3->id();
917  const auto node_finder = std::min(node_3_finder, node_4_finder);
918 
920  mesh,
921  cross_elem->id(),
922  node_finder,
923  node_id,
924  tri_nodes[(node_finder + 1) % 3] == node_pairs_vec[i][node_3_finder > node_4_finder].first
925  ? block_id_to_remove
926  : cross_elem->subdomain_id(),
927  tri_nodes[(node_finder + 1) % 3] == node_pairs_vec[i][node_3_finder > node_4_finder].first
928  ? cross_elem->subdomain_id()
929  : block_id_to_remove);
930  mesh.delete_elem(cross_elem);
931  }
932  }
933  mesh.contract();
934  // Due to the complexity, we identify the new boundary here together instead of during cutting of
935  // each element, because the preexisting element edges that are aligned with the cutting line also
936  // need to be added to the new boundary.
938  BoundaryInfo & boundary_info = mesh.get_boundary_info();
939  for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
940  elem_it++)
941  {
942  if ((*elem_it)->subdomain_id() != block_id_to_remove)
943  {
944  for (unsigned int j = 0; j < (*elem_it)->n_sides(); j++)
945  {
946  if ((*elem_it)->neighbor_ptr(j) != nullptr)
947  if ((*elem_it)->neighbor_ptr(j)->subdomain_id() == block_id_to_remove)
948  boundary_info.add_side(*elem_it, j, new_boundary_id);
949  }
950  }
951  }
952 
953  // Delete the block to remove
954  for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
955  elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
956  elem_it++)
957  mesh.delete_elem(*elem_it);
958  mesh.contract();
959 }
960 
961 void
963  const std::vector<Real> & cut_line_params,
964  const dof_id_type tri_subdomain_id_shift,
965  const SubdomainName tri_elem_subdomain_name_suffix,
966  const subdomain_id_type block_id_to_remove,
967  const boundary_id_type new_boundary_id,
968  const bool improve_boundary_tri_elems)
969 {
970  // Convert any quad elements crossed by the line into tri elements
971  quadToTriOnLine(mesh, cut_line_params, tri_subdomain_id_shift, tri_elem_subdomain_name_suffix);
972  // Then do the cutting for the preprocessed mesh that only contains tri elements crossed by the
973  // cut line
974  lineRemoverCutElemTri(mesh, cut_line_params, block_id_to_remove, new_boundary_id);
975 
976  if (improve_boundary_tri_elems)
977  boundaryTriElemImprover(mesh, new_boundary_id);
978 }
979 
980 void
981 boundaryTriElemImprover(ReplicatedMesh & mesh, const boundary_id_type boundary_to_improve)
982 {
983  if (!MooseMeshUtils::hasBoundaryID(mesh, boundary_to_improve))
984  mooseError(
985  "MooseMeshXYCuttingUtils::boundaryTriElemImprover(): The boundary_to_improve provided "
986  "does not exist in the given mesh.");
987  BoundaryInfo & boundary_info = mesh.get_boundary_info();
988  auto side_list = boundary_info.build_side_list();
989  // Here we would like to collect the following information for all the TRI3 elements on the
990  // boundary: Key: node id of the off-boundary node Value: a vector of tuples, each tuple contains
991  // the following information:
992  // 1. The element id of the element that is on the boundary to improve
993  // 2. the one node id of that element that is on the boundary to improve
994  // 3. the other node id of the element that is on the boundary to improve
995  std::map<dof_id_type, std::vector<std::tuple<dof_id_type, dof_id_type, dof_id_type>>>
996  tri3_elem_info;
997  for (const auto & side : side_list)
998  {
999  if (std::get<2>(side) == boundary_to_improve)
1000  {
1001  Elem * elem = mesh.elem_ptr(std::get<0>(side));
1002  if (elem->type() == TRI3)
1003  {
1004  const auto key_node_id = elem->node_id((std::get<1>(side) + 2) % 3);
1005  const auto value_elem_id = elem->id();
1006  const auto value_node_id_1 = elem->node_id(std::get<1>(side));
1007  const auto value_node_id_2 = elem->node_id((std::get<1>(side) + 1) % 3);
1008  tri3_elem_info[key_node_id].push_back(
1009  std::make_tuple(value_elem_id, value_node_id_1, value_node_id_2));
1010  }
1011  }
1012  }
1013  // Elements that need to be removed
1014  std::vector<dof_id_type> elems_to_remove;
1015  // Now check if any group of TRI3 sharing an off-boundary node can be improved.
1016  for (const auto & tri_group : tri3_elem_info)
1017  {
1018  // It is possible to improve only when more than one TRI3 elements share the same off-boundary
1019  // node
1020  std::vector<std::pair<dof_id_type, dof_id_type>> node_assm;
1021  std::vector<dof_id_type> elem_id_list;
1022  for (const auto & tri : tri_group.second)
1023  {
1024  node_assm.push_back(std::make_pair(std::get<1>(tri), std::get<2>(tri)));
1025  elem_id_list.push_back(std::get<0>(tri));
1026  }
1027  std::vector<dof_id_type> ordered_node_list;
1028  std::vector<dof_id_type> ordered_elem_list;
1030  node_assm, elem_id_list, ordered_node_list, ordered_elem_list);
1031 
1032  // For all the elements sharing the same off-boundary node, we need to know how many separated
1033  // subdomains are involved
1034  // If there are extra element ids defined on the mesh, they also want to retain their boundaries
1035  // Only triangle elements that share a side can be merged
1036  const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
1037  std::vector<std::tuple<subdomain_id_type, std::vector<dof_id_type>, unsigned int>> blocks_info;
1038  for (const auto & elem_id : ordered_elem_list)
1039  {
1040  std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
1041  // Record all the element extra integers of the original quad element
1042  for (unsigned int j = 0; j < n_elem_extra_ids; j++)
1043  exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
1044  if (!blocks_info.empty())
1045  {
1046  if (mesh.elem_ptr(elem_id)->subdomain_id() == std::get<0>(blocks_info.back()) &&
1047  exist_extra_ids == std::get<1>(blocks_info.back()))
1048  {
1049  std::get<2>(blocks_info.back())++;
1050  continue;
1051  }
1052  }
1053  blocks_info.push_back(
1054  std::make_tuple(mesh.elem_ptr(elem_id)->subdomain_id(), exist_extra_ids, 1));
1055  }
1056  // For each separated subdomain / set of extra ids, we try to improve the boundary elements
1057  unsigned int side_counter = 0;
1058  for (const auto & block_info : blocks_info)
1059  {
1060  const auto node_1 = mesh.node_ptr(ordered_node_list[side_counter]);
1061  // we do not need to subtract 1 for node_2
1062  const auto node_2 = mesh.node_ptr(ordered_node_list[side_counter + std::get<2>(block_info)]);
1063  const auto node_0 = mesh.node_ptr(tri_group.first);
1064  const Point v1 = *node_1 - *node_0;
1065  const Point v2 = *node_2 - *node_0;
1066  const Real angle = std::acos(v1 * v2 / v1.norm() / v2.norm()) / M_PI * 180.0;
1067  const std::vector<dof_id_type> block_elems(ordered_elem_list.begin() + side_counter,
1068  ordered_elem_list.begin() + side_counter +
1069  std::get<2>(block_info));
1070  // We assume that there are no sidesets defined inside a subdomain
1071  // For the first TRI3 element, we want to check if its side defined by node_0 and node_1 is
1072  // defined in any sidesets
1073  unsigned short side_id_0;
1074  unsigned short side_id_t;
1075  bool is_inverse_0;
1076  bool is_inverse_t;
1078  block_elems.front(),
1079  tri_group.first,
1080  ordered_node_list[side_counter],
1081  side_id_0,
1082  is_inverse_0);
1084  block_elems.back(),
1085  ordered_node_list[side_counter + std::get<2>(block_info)],
1086  tri_group.first,
1087  side_id_t,
1088  is_inverse_t);
1089  // Collect boundary information of the identified sides
1090  std::vector<boundary_id_type> side_0_boundary_ids;
1091  boundary_info.boundary_ids(
1092  mesh.elem_ptr(block_elems.front()), side_id_0, side_0_boundary_ids);
1093  std::vector<boundary_id_type> side_t_boundary_ids;
1094  boundary_info.boundary_ids(mesh.elem_ptr(block_elems.back()), side_id_t, side_t_boundary_ids);
1095 
1096  // Ideally we want this angle to be 60 degrees
1097  // In reality, we want one TRI3 element if the angle is less than 90 degrees;
1098  // we want two TRI3 elements if the angle is greater than 90 degrees and less than 135
1099  // degrees; we want three TRI3 elements if the angle is greater than 135 degrees and less than
1100  // 180 degrees.
1101  if (angle < 90.0)
1102  {
1103  if (std::get<2>(block_info) > 1)
1104  {
1106  tri_group.first,
1107  ordered_node_list[side_counter],
1108  ordered_node_list[side_counter + std::get<2>(block_info)],
1109  std::get<0>(block_info),
1110  std::get<1>(block_info),
1111  {boundary_to_improve},
1112  side_0_boundary_ids,
1113  side_t_boundary_ids);
1114  elems_to_remove.insert(elems_to_remove.end(), block_elems.begin(), block_elems.end());
1115  }
1116  }
1117  else if (angle < 135.0)
1118  {
1119  // We can just add the middle node because there's nothing on the other side
1120  const auto node_m = mesh.add_point((*node_1 + *node_2) / 2.0);
1122  tri_group.first,
1123  ordered_node_list[side_counter],
1124  node_m->id(),
1125  std::get<0>(block_info),
1126  std::get<1>(block_info),
1127  {boundary_to_improve},
1128  side_0_boundary_ids,
1129  std::vector<boundary_id_type>());
1131  tri_group.first,
1132  node_m->id(),
1133  ordered_node_list[side_counter + std::get<2>(block_info)],
1134  std::get<0>(block_info),
1135  std::get<1>(block_info),
1136  {boundary_to_improve},
1137  std::vector<boundary_id_type>(),
1138  side_t_boundary_ids);
1139  elems_to_remove.insert(elems_to_remove.end(), block_elems.begin(), block_elems.end());
1140  }
1141  else
1142  {
1143  const auto node_m1 = mesh.add_point((*node_1 * 2.0 + *node_2) / 3.0);
1144  const auto node_m2 = mesh.add_point((*node_1 + *node_2 * 2.0) / 3.0);
1146  tri_group.first,
1147  ordered_node_list[side_counter],
1148  node_m1->id(),
1149  std::get<0>(block_info),
1150  std::get<1>(block_info),
1151  {boundary_to_improve},
1152  side_0_boundary_ids,
1153  std::vector<boundary_id_type>());
1155  tri_group.first,
1156  node_m1->id(),
1157  node_m2->id(),
1158  std::get<0>(block_info),
1159  std::get<1>(block_info),
1160  {boundary_to_improve},
1161  std::vector<boundary_id_type>(),
1162  std::vector<boundary_id_type>());
1164  tri_group.first,
1165  node_m2->id(),
1166  ordered_node_list[side_counter + std::get<2>(block_info)],
1167  std::get<0>(block_info),
1168  std::get<1>(block_info),
1169  {boundary_to_improve},
1170  std::vector<boundary_id_type>(),
1171  side_t_boundary_ids);
1172  elems_to_remove.insert(elems_to_remove.end(), block_elems.begin(), block_elems.end());
1173  }
1174  side_counter += std::get<2>(block_info);
1175  }
1176  // TODO: Need to check if the new element is inverted?
1177  }
1178  // Delete the original elements
1179  for (const auto & elem_to_remove : elems_to_remove)
1180  mesh.delete_elem(mesh.elem_ptr(elem_to_remove));
1181  mesh.contract();
1182 }
1183 
1184 void
1186  const dof_id_type node_id_0,
1187  const dof_id_type node_id_1,
1188  const dof_id_type node_id_2,
1189  const subdomain_id_type subdomain_id,
1190  const std::vector<dof_id_type> & extra_elem_ids,
1191  const std::vector<boundary_id_type> & boundary_ids_for_side_1,
1192  const std::vector<boundary_id_type> & boundary_ids_for_side_0,
1193  const std::vector<boundary_id_type> & boundary_ids_for_side_2)
1194 {
1195  BoundaryInfo & boundary_info = mesh.get_boundary_info();
1196  Elem * elem_Tri3_new = mesh.add_elem(new Tri3);
1197  elem_Tri3_new->set_node(0, mesh.node_ptr(node_id_0));
1198  elem_Tri3_new->set_node(1, mesh.node_ptr(node_id_1));
1199  elem_Tri3_new->set_node(2, mesh.node_ptr(node_id_2));
1200  for (const auto & boundary_id_for_side_0 : boundary_ids_for_side_0)
1201  boundary_info.add_side(elem_Tri3_new, 0, boundary_id_for_side_0);
1202  for (const auto & boundary_id_for_side_1 : boundary_ids_for_side_1)
1203  boundary_info.add_side(elem_Tri3_new, 1, boundary_id_for_side_1);
1204  for (const auto & boundary_id_for_side_2 : boundary_ids_for_side_2)
1205  boundary_info.add_side(elem_Tri3_new, 2, boundary_id_for_side_2);
1206  elem_Tri3_new->subdomain_id() = subdomain_id;
1207  // Retain element extra integers
1208  for (unsigned int j = 0; j < extra_elem_ids.size(); j++)
1209  {
1210  elem_Tri3_new->set_extra_integer(j, extra_elem_ids[j]);
1211  }
1212 }
1213 
1214 bool
1216  const dof_id_type elem_id,
1217  const dof_id_type node_id_0,
1218  const dof_id_type node_id_1,
1219  unsigned short & side_id,
1220  bool & is_inverse)
1221 {
1222  Elem * elem = mesh.elem_ptr(elem_id);
1223  for (unsigned short i = 0; i < elem->n_sides(); i++)
1224  {
1225  if (elem->side_ptr(i)->node_ptr(0)->id() == node_id_0 &&
1226  elem->side_ptr(i)->node_ptr(1)->id() == node_id_1)
1227  {
1228  side_id = i;
1229  is_inverse = false;
1230  return true;
1231  }
1232  else if (elem->side_ptr(i)->node_ptr(0)->id() == node_id_1 &&
1233  elem->side_ptr(i)->node_ptr(1)->id() == node_id_0)
1234  {
1235  side_id = i;
1236  is_inverse = true;
1237  return true;
1238  }
1239  }
1240  return false;
1241 }
1242 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:42
void triElemSplitter(libMesh::ReplicatedMesh &mesh, const dof_id_type elem_id, const unsigned short node_shift, const dof_id_type nid_3, const dof_id_type nid_4, const subdomain_id_type single_elem_side_id, const subdomain_id_type double_elem_side_id)
Split a TRI3 element into three TRI3 elements based on two nodes on the two sides of the triangle...
void boundaryTriElemImprover(libMesh::ReplicatedMesh &mesh, const boundary_id_type boundary_to_improve)
Improve the element quality of the boundary TRI3 elements of the given boundary.
void lineRemoverCutElem(libMesh::ReplicatedMesh &mesh, const std::vector< Real > &cut_line_params, const dof_id_type tri_subdomain_id_shift, const SubdomainName tri_elem_subdomain_name_suffix, const subdomain_id_type block_id_to_remove, const boundary_id_type new_boundary_id, const bool improve_boundary_tri_elems=false)
Trim the 2D mesh by removing all the elements on one side of the given line.
std::vector< std::pair< Real, unsigned int > > vertex_angles(const Elem &elem)
void makeOrderedNodeList(std::vector< std::pair< dof_id_type, dof_id_type >> &node_assm, std::vector< dof_id_type > &elem_id_list, std::vector< dof_id_type > &midpoint_node_list, std::vector< dof_id_type > &ordered_node_list, std::vector< dof_id_type > &ordered_elem_id_list)
Convert a list of sides in the form of a vector of pairs of node ids into a list of ordered nodes bas...
virtual Node *& set_node(const unsigned int i)
auto norm() const -> decltype(std::norm(Real()))
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
Point twoPointandLineIntersection(const Point &pt1, const Point &pt2, const Real param_1, const Real param_2, const Real param_3)
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:302
static constexpr Real TOLERANCE
void makeImprovedTriElement(libMesh::ReplicatedMesh &mesh, const dof_id_type node_id_0, const dof_id_type node_id_1, const dof_id_type node_id_2, const subdomain_id_type subdomain_id, const std::vector< dof_id_type > &extra_elem_ids, const std::vector< boundary_id_type > &boundary_ids_for_side_1=std::vector< boundary_id_type >(), const std::vector< boundary_id_type > &boundary_ids_for_side_0=std::vector< boundary_id_type >(), const std::vector< boundary_id_type > &boundary_ids_for_side_2=std::vector< boundary_id_type >())
Make a TRI3 element with the given node ids and subdomain id with boundary information.
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition: MooseError.h:336
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
const boundary_id_type side_id
unsigned int n_elem_integers() const
MeshBase & mesh
void quadElemSplitter(libMesh::ReplicatedMesh &mesh, const dof_id_type elem_id, const subdomain_id_type tri_elem_subdomain_shift)
Split a QUAD4 element into two TRI3 elements.
void lineRemoverMoveNode(libMesh::ReplicatedMesh &mesh, const std::vector< Real > &bdry_pars, const subdomain_id_type block_id_to_remove, const std::set< subdomain_id_type > &subdomain_ids_set, const boundary_id_type trimming_section_boundary_id, const boundary_id_type external_boundary_id, const std::vector< boundary_id_type > &other_boundaries_to_conform=std::vector< boundary_id_type >(), const bool assign_ext_to_new=false, const bool side_to_remove=true)
Removes all the elements on one side of a given line and deforms the elements intercepted by the line...
bool hasBoundaryID(const MeshBase &input_mesh, const BoundaryID id)
Whether a particular boundary ID exists in the mesh.
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
const BoundaryInfo & get_boundary_info() const
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
auto max(const L &left, const R &right)
void build_side_list(std::vector< dof_id_type > &element_id_list, std::vector< unsigned short int > &side_list, std::vector< boundary_id_type > &bc_id_list) const
const SubdomainID INVALID_BLOCK_ID
Definition: MooseTypes.C:20
virtual void find_neighbors(const bool reset_remote_elements=false, const bool reset_current_list=true)=0
int8_t boundary_id_type
virtual void delete_elem(Elem *e)=0
dof_id_type id() const
std::vector< std::pair< Real, unsigned int > > vertex_distances(const Elem &elem)
virtual Elem * add_elem(Elem *e)=0
bool elemSideLocator(libMesh::ReplicatedMesh &mesh, const dof_id_type elem_id, const dof_id_type node_id_0, const dof_id_type node_id_1, unsigned short &side_id, bool &is_inverse)
Check if there is a side in an element that contains the given pair of nodes; if yes, also find the side id and the direction of the two nodes in the side.
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
bool quasiTriElementsFixer(libMesh::ReplicatedMesh &mesh, const std::set< subdomain_id_type > &subdomain_ids_set, const subdomain_id_type tri_elem_subdomain_shift=Moose::INVALID_BLOCK_ID, const SubdomainName tri_elem_subdomain_name_suffix="tri")
Fixes degenerate QUAD elements created by the hexagonal mesh trimming by converting them into TRI ele...
std::string & subdomain_name(subdomain_id_type id)
auto norm(const T &a) -> decltype(std::abs(a))
virtual const Elem * elem_ptr(const dof_id_type i) const=0
virtual unsigned int n_sides() const=0
const Elem * neighbor_ptr(unsigned int i) const
void remove_side(const Elem *elem, const unsigned short int side)
Provides a way for users to bail out of the current solve.
virtual unsigned int n_vertices() const=0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual bool contract()=0
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
subdomain_id_type subdomain_id() const
const Node * node_ptr(const unsigned int i) const
bool lineSideDeterminator(const Real px, const Real py, const Real param_1, const Real param_2, const Real param_3, const bool direction_param, const Real dis_tol=libMesh::TOLERANCE)
Determines whether a point on XY-plane is on the side of a given line that needs to be removed...
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
void quadToTriOnLine(libMesh::ReplicatedMesh &mesh, const std::vector< Real > &cut_line_params, const dof_id_type tri_subdomain_id_shift, const SubdomainName tri_elem_subdomain_name_suffix)
Convert all the QUAD4 elements in the mesh that are crossed by the given line into TRI3 elements...
virtual const Node * node_ptr(const dof_id_type i) const=0
auto min(const L &left, const R &right)
virtual ElemType type() const=0
dof_id_type node_id(const unsigned int i) const
void set_extra_integer(const unsigned int index, const dof_id_type value)
dof_id_type get_extra_integer(const unsigned int index) const
uint8_t dof_id_type
Point twoLineIntersection(const Real param_11, const Real param_12, const Real param_13, const Real param_21, const Real param_22, const Real param_23)
Calculates the intersection Point of two given straight lines.
void lineRemoverCutElemTri(libMesh::ReplicatedMesh &mesh, const std::vector< Real > &cut_line_params, const subdomain_id_type block_id_to_remove, const boundary_id_type new_boundary_id)
Trim the 2D mesh by removing all the elements on one side of the given line.