https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
21using namespace libMesh;
22
24{
25
26void
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 (const auto i : index_range(bdry_side_list))
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 (const auto i : make_range((*elem_it)->n_vertices()))
58 {
59 // First check if the vertex is on the XY-Plane
60 if (!MooseUtils::absoluteFuzzyEqual((*elem_it)->point(i)(2), 0.0))
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 (const auto i : make_range(mesh.elem_ptr(elem_id)->n_sides()))
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 (const auto i : make_range((*elem_it)->n_sides()))
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 (const auto i : index_range(slc_bdry_side_list))
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 (const auto i : index_range(node_list))
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 (const auto i : make_range((*elem_it)->n_sides()))
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
263bool
265 const Real py,
266 const Real param_1,
267 const Real param_2,
268 const Real param_3,
269 const Real dis_tol)
270{
271 return std::abs(px * param_1 + py * param_2 + param_3) <= dis_tol;
272}
273
274bool
276 const Real py,
277 const Real param_1,
278 const Real param_2,
279 const Real param_3,
280 const bool direction_param,
281 const Real dis_tol)
282{
283 const Real tmp = px * param_1 + py * param_2 + param_3;
284 return direction_param ? tmp >= dis_tol : tmp <= dis_tol;
285}
286
287Point
289 const Real param_12,
290 const Real param_13,
291 const Real param_21,
292 const Real param_22,
293 const Real param_23)
294{
295 return Point(
296 (param_12 * param_23 - param_22 * param_13) / (param_11 * param_22 - param_21 * param_12),
297 (param_13 * param_21 - param_23 * param_11) / (param_11 * param_22 - param_21 * param_12),
298 0.0);
299}
300
301Point
303 const Point & pt2,
304 const Real param_1,
305 const Real param_2,
306 const Real param_3)
307{
308 return twoLineIntersection(param_1,
309 param_2,
310 param_3,
311 pt2(1) - pt1(1),
312 pt1(0) - pt2(0),
313 pt2(0) * pt1(1) - pt1(0) * pt2(1));
314}
315
316bool
318 const std::set<subdomain_id_type> & subdomain_ids_set,
319 const subdomain_id_type tri_elem_subdomain_shift,
320 const SubdomainName tri_elem_subdomain_name_suffix)
321{
322 BoundaryInfo & boundary_info = mesh.get_boundary_info();
323 // Define the subdomain id shift for the new TRI3 element subdomain(s)
324 const subdomain_id_type max_subdomain_id(*subdomain_ids_set.rbegin());
325 const subdomain_id_type tri_subdomain_id_shift =
326 tri_elem_subdomain_shift == Moose::INVALID_BLOCK_ID ? max_subdomain_id
327 : tri_elem_subdomain_shift;
328 mooseAssert(std::numeric_limits<subdomain_id_type>::max() - max_subdomain_id >
329 tri_subdomain_id_shift,
330 "The TRI elements subdomain id to be assigned may exceed the numeric limit.");
331 const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
332 std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
333 std::vector<std::tuple<Elem *, unsigned int, bool, bool>> bad_elems_rec;
334 // Loop over all the active elements to find any degenerate QUAD elements
335 for (auto & elem : as_range(mesh.active_elements_begin(), mesh.active_elements_end()))
336 {
337 // Two types of degenerate QUAD elements are identified:
338 // (1) QUAD elements with three collinear vertices
339 // (2) QUAD elements with two overlapped vertices
340 const auto elem_angles = vertex_angles(*elem);
341 const auto elem_distances = vertex_distances(*elem);
342 // Type 1
343 if (MooseUtils::absoluteFuzzyEqual(elem_angles.front().first, M_PI, 0.001))
344 {
345 bad_elems_rec.push_back(std::make_tuple(elem, elem_angles.front().second, false, true));
346 continue;
347 }
348 // Type 2
349 if (MooseUtils::absoluteFuzzyEqual(elem_distances.front().first, 0.0))
350 {
351 bad_elems_rec.push_back(std::make_tuple(elem, elem_distances.front().second, false, false));
352 }
353 }
354 std::set<subdomain_id_type> new_subdomain_ids;
355 // Loop over all the identified degenerate QUAD elements
356 for (const auto & bad_elem : bad_elems_rec)
357 {
358 std::vector<boundary_id_type> elem_bdry_container_0;
359 std::vector<boundary_id_type> elem_bdry_container_1;
360 std::vector<boundary_id_type> elem_bdry_container_2;
361
362 Elem * elem_0 = std::get<0>(bad_elem);
363 if (std::get<3>(bad_elem))
364 {
365 // elems 1 and 2 are the neighboring elements of the degenerate element corresponding to the
366 // two collinear sides.
367 // For the degenerated element with three colinear vertices, if the elems 1 and 2 do not
368 // exist, the two sides are on the external boundary formed by trimming.
369 Elem * elem_1 = elem_0->neighbor_ptr(std::get<1>(bad_elem));
370 Elem * elem_2 = elem_0->neighbor_ptr((std::get<1>(bad_elem) - 1) % elem_0->n_vertices());
371 if ((elem_1 != nullptr || elem_2 != nullptr))
372 throw MooseException("The input mesh has degenerate quad element before trimming.");
373 }
374 mesh.get_boundary_info().boundary_ids(elem_0, std::get<1>(bad_elem), elem_bdry_container_0);
376 elem_0, (std::get<1>(bad_elem) + 1) % elem_0->n_vertices(), elem_bdry_container_1);
378 elem_0, (std::get<1>(bad_elem) + 2) % elem_0->n_vertices(), elem_bdry_container_2);
380 elem_0, (std::get<1>(bad_elem) + 3) % elem_0->n_vertices(), elem_bdry_container_0);
381
382 // Record subdomain id of the degenerate element
383 auto elem_block_id = elem_0->subdomain_id();
384 // Define the three of four nodes that will be used to generate the TRI element
385 auto pt0 = elem_0->node_ptr((std::get<1>(bad_elem) + 1) % elem_0->n_vertices());
386 auto pt1 = elem_0->node_ptr((std::get<1>(bad_elem) + 2) % elem_0->n_vertices());
387 auto pt2 = elem_0->node_ptr((std::get<1>(bad_elem) + 3) % elem_0->n_vertices());
388 // Record all the element extra integers of the degenerate element
389 for (const auto j : make_range(n_elem_extra_ids))
390 exist_extra_ids[j] = elem_0->get_extra_integer(j);
391 // Delete the degenerate QUAD element
392 mesh.delete_elem(elem_0);
393 // Create the new TRI element
394 Elem * elem_Tri3 = mesh.add_elem(new Tri3);
395 elem_Tri3->set_node(0, pt0);
396 elem_Tri3->set_node(1, pt1);
397 elem_Tri3->set_node(2, pt2);
398 // Retain the boundary information
399 for (auto bdry_id : elem_bdry_container_0)
400 boundary_info.add_side(elem_Tri3, 2, bdry_id);
401 for (auto bdry_id : elem_bdry_container_1)
402 boundary_info.add_side(elem_Tri3, 0, bdry_id);
403 for (auto bdry_id : elem_bdry_container_2)
404 boundary_info.add_side(elem_Tri3, 1, bdry_id);
405 // Assign subdomain id for the TRI element by shifting its original subdomain id
406 elem_Tri3->subdomain_id() = elem_block_id + tri_subdomain_id_shift;
407 new_subdomain_ids.emplace(elem_block_id + tri_subdomain_id_shift);
408 // Retain element extra integers
409 for (const auto j : make_range(n_elem_extra_ids))
410 elem_Tri3->set_extra_integer(j, exist_extra_ids[j]);
411 }
412 // Assign subdomain names for the new TRI elements
413 for (auto & nid : new_subdomain_ids)
414 {
415 const SubdomainName old_name = mesh.subdomain_name(nid - tri_subdomain_id_shift);
417 (old_name.empty() ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
418 : old_name) +
419 "_" + tri_elem_subdomain_name_suffix,
421 throw MooseException("The new subdomain name already exists in the mesh.");
423 (old_name.empty()
424 ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
425 : old_name) +
426 "_" + tri_elem_subdomain_name_suffix);
427 mooseWarning("Degenerate QUAD elements have been converted into TRI elements with a new "
428 "subdomain name: " +
429 mesh.subdomain_name(nid) + ".");
430 }
431 return bad_elems_rec.size();
432}
433
434std::vector<std::pair<Real, unsigned int>>
435vertex_angles(const Elem & elem)
436{
437 std::vector<std::pair<Real, unsigned int>> angles;
438 const unsigned int n_vertices = elem.n_vertices();
439
440 for (const auto i : make_range(n_vertices))
441 {
442 Point v1 = (*elem.node_ptr((i - 1) % n_vertices) - *elem.node_ptr(i % n_vertices));
443 Point v2 = (*elem.node_ptr((i + 1) % n_vertices) - *elem.node_ptr(i % n_vertices));
444 Real tmp = v1 * v2 / v1.norm() / v2.norm();
445 if (tmp > 1.0)
446 tmp = 1.0;
447 else if (tmp < -1.0)
448 tmp = -1.0;
449 angles.push_back(std::make_pair(acos(tmp), i));
450 }
451 std::sort(angles.begin(), angles.end(), std::greater<>());
452 return angles;
453}
454
455std::vector<std::pair<Real, unsigned int>>
457{
458 std::vector<std::pair<Real, unsigned int>> distances;
459 const unsigned int n_vertices = elem.n_vertices();
460
461 for (const auto i : make_range(n_vertices))
462 {
463 Point v1 = (*elem.node_ptr((i + 1) % n_vertices) - *elem.node_ptr(i % n_vertices));
464 distances.push_back(std::make_pair(v1.norm(), i));
465 }
466 std::sort(distances.begin(), distances.end());
467 return distances;
468}
469
470void
472 const dof_id_type elem_id,
473 const unsigned short node_shift,
474 const dof_id_type nid_3,
475 const dof_id_type nid_4,
476 const subdomain_id_type single_elem_side_id,
477 const subdomain_id_type double_elem_side_id)
478{
479 const auto elem_old = mesh.elem_ptr(elem_id);
480 const dof_id_type nid_0 = elem_old->node_ptr(node_shift % 3)->id();
481 const dof_id_type nid_1 = elem_old->node_ptr((1 + node_shift) % 3)->id();
482 const dof_id_type nid_2 = elem_old->node_ptr((2 + node_shift) % 3)->id();
483
484 const bool m1_side_flag =
485 MooseUtils::absoluteFuzzyEqual((*(mesh.node_ptr(nid_3)) - *(mesh.node_ptr(nid_0)))
486 .cross(*(mesh.node_ptr(nid_1)) - *(mesh.node_ptr(nid_0)))
487 .norm(),
488 0.0);
489 const dof_id_type nid_m1 = m1_side_flag ? nid_3 : nid_4;
490 const dof_id_type nid_m2 = m1_side_flag ? nid_4 : nid_3;
491 // Build boundary information of the mesh
492 BoundaryInfo & boundary_info = mesh.get_boundary_info();
493 auto bdry_side_list = boundary_info.build_side_list();
494 // Create a list of sidesets involving the element to be split
495 std::vector<std::vector<boundary_id_type>> elem_side_list;
496 elem_side_list.resize(3);
497 for (const auto i : index_range(bdry_side_list))
498 {
499 if (std::get<0>(bdry_side_list[i]) == elem_id)
500 {
501 elem_side_list[(std::get<1>(bdry_side_list[i]) + 3 - node_shift) % 3].push_back(
502 std::get<2>(bdry_side_list[i]));
503 }
504 }
505
506 const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
507 std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
508 // Record all the element extra integers of the original element
509 for (const auto j : make_range(n_elem_extra_ids))
510 exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
511
512 Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
513 elem_Tri3_0->set_node(0, mesh.node_ptr(nid_0));
514 elem_Tri3_0->set_node(1, mesh.node_ptr(nid_m1));
515 elem_Tri3_0->set_node(2, mesh.node_ptr(nid_m2));
516 elem_Tri3_0->subdomain_id() = single_elem_side_id;
517 Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
518 elem_Tri3_1->set_node(0, mesh.node_ptr(nid_1));
519 elem_Tri3_1->set_node(1, mesh.node_ptr(nid_m2));
520 elem_Tri3_1->set_node(2, mesh.node_ptr(nid_m1));
521 elem_Tri3_1->subdomain_id() = double_elem_side_id;
522 Elem * elem_Tri3_2 = mesh.add_elem(new Tri3);
523 elem_Tri3_2->set_node(0, mesh.node_ptr(nid_2));
524 elem_Tri3_2->set_node(1, mesh.node_ptr(nid_m2));
525 elem_Tri3_2->set_node(2, mesh.node_ptr(nid_1));
526 elem_Tri3_2->subdomain_id() = double_elem_side_id;
527 // Retain element extra integers
528 for (const auto j : make_range(n_elem_extra_ids))
529 {
530 elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
531 elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
532 elem_Tri3_2->set_extra_integer(j, exist_extra_ids[j]);
533 }
534
535 // Add sideset information to the new elements
536 for (const auto & side_info_0 : elem_side_list[0])
537 {
538 boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
539 boundary_info.add_side(elem_Tri3_1, 2, side_info_0);
540 }
541 for (const auto & side_info_1 : elem_side_list[1])
542 boundary_info.add_side(elem_Tri3_2, 2, side_info_1);
543 for (const auto & side_info_2 : elem_side_list[2])
544 {
545 boundary_info.add_side(elem_Tri3_0, 2, side_info_2);
546 boundary_info.add_side(elem_Tri3_2, 0, side_info_2);
547 }
548}
549
550void
552 const dof_id_type elem_id,
553 const unsigned short node_shift,
554 const dof_id_type nid_m,
555 const subdomain_id_type first_elem_side_id,
556 const subdomain_id_type second_elem_side_id)
557{
558 const auto elem_old = mesh.elem_ptr(elem_id);
559 const dof_id_type nid_0 = elem_old->node_ptr(node_shift % 3)->id();
560 const dof_id_type nid_1 = elem_old->node_ptr((1 + node_shift) % 3)->id();
561 const dof_id_type nid_2 = elem_old->node_ptr((2 + node_shift) % 3)->id();
562 // Build boundary information of the mesh
563 BoundaryInfo & boundary_info = mesh.get_boundary_info();
564 auto bdry_side_list = boundary_info.build_side_list();
565 // Create a list of sidesets involving the element to be split
566 std::vector<std::vector<boundary_id_type>> elem_side_list;
567 elem_side_list.resize(3);
568 for (const auto i : index_range(bdry_side_list))
569 {
570 if (std::get<0>(bdry_side_list[i]) == elem_id)
571 {
572 elem_side_list[(std::get<1>(bdry_side_list[i]) + 3 - node_shift) % 3].push_back(
573 std::get<2>(bdry_side_list[i]));
574 }
575 }
576
577 const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
578 std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
579 // Record all the element extra integers of the original element
580 for (const auto j : make_range(n_elem_extra_ids))
581 exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
582
583 Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
584 elem_Tri3_0->set_node(0, mesh.node_ptr(nid_0));
585 elem_Tri3_0->set_node(1, mesh.node_ptr(nid_1));
586 elem_Tri3_0->set_node(2, mesh.node_ptr(nid_m));
587 elem_Tri3_0->subdomain_id() = first_elem_side_id;
588 Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
589 elem_Tri3_1->set_node(0, mesh.node_ptr(nid_0));
590 elem_Tri3_1->set_node(1, mesh.node_ptr(nid_m));
591 elem_Tri3_1->set_node(2, mesh.node_ptr(nid_2));
592 elem_Tri3_1->subdomain_id() = second_elem_side_id;
593 // Retain element extra integers
594 for (const auto j : make_range(n_elem_extra_ids))
595 {
596 elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
597 elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
598 }
599
600 // Add sideset information to the new elements
601 for (const auto & side_info_0 : elem_side_list[0])
602 boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
603 for (const auto & side_info_1 : elem_side_list[1])
604 {
605 boundary_info.add_side(elem_Tri3_0, 1, side_info_1);
606 boundary_info.add_side(elem_Tri3_1, 1, side_info_1);
607 }
608 for (const auto & side_info_2 : elem_side_list[2])
609 boundary_info.add_side(elem_Tri3_1, 2, side_info_2);
610}
611
612void
614 const dof_id_type elem_id,
615 const subdomain_id_type tri_elem_subdomain_shift)
616{
617 // Build boundary information of the mesh
618 BoundaryInfo & boundary_info = mesh.get_boundary_info();
619 auto bdry_side_list = boundary_info.build_side_list();
620 // Create a list of sidesets involving the element to be split
621 std::vector<std::vector<boundary_id_type>> elem_side_list;
622 elem_side_list.resize(4);
623 for (const auto i : index_range(bdry_side_list))
624 {
625 if (std::get<0>(bdry_side_list[i]) == elem_id)
626 {
627 elem_side_list[std::get<1>(bdry_side_list[i])].push_back(std::get<2>(bdry_side_list[i]));
628 }
629 }
630
631 auto node_0 = mesh.elem_ptr(elem_id)->node_ptr(0);
632 auto node_1 = mesh.elem_ptr(elem_id)->node_ptr(1);
633 auto node_2 = mesh.elem_ptr(elem_id)->node_ptr(2);
634 auto node_3 = mesh.elem_ptr(elem_id)->node_ptr(3);
635
636 const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
637 std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
638 // Record all the element extra integers of the original quad element
639 for (const auto j : make_range(n_elem_extra_ids))
640 exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
641
642 // There are two trivial ways to split a quad element
643 // We prefer the way that leads to triangles with similar areas
644 if (std::abs((*node_1 - *node_0).cross(*node_3 - *node_0).norm() -
645 (*node_1 - *node_2).cross(*node_3 - *node_2).norm()) >
646 std::abs((*node_0 - *node_1).cross(*node_2 - *node_1).norm() -
647 (*node_0 - *node_3).cross(*node_2 - *node_3).norm()))
648 {
649 Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
650 elem_Tri3_0->set_node(0, node_0);
651 elem_Tri3_0->set_node(1, node_1);
652 elem_Tri3_0->set_node(2, node_2);
653 elem_Tri3_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
654 Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
655 elem_Tri3_1->set_node(0, node_0);
656 elem_Tri3_1->set_node(1, node_2);
657 elem_Tri3_1->set_node(2, node_3);
658 elem_Tri3_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
659 // Retain element extra integers
660 for (const auto j : make_range(n_elem_extra_ids))
661 {
662 elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
663 elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
664 }
665
666 // Add sideset information to the new elements
667 for (const auto & side_info_0 : elem_side_list[0])
668 boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
669 for (const auto & side_info_1 : elem_side_list[1])
670 boundary_info.add_side(elem_Tri3_0, 1, side_info_1);
671 for (const auto & side_info_2 : elem_side_list[2])
672 boundary_info.add_side(elem_Tri3_1, 1, side_info_2);
673 for (const auto & side_info_3 : elem_side_list[3])
674 boundary_info.add_side(elem_Tri3_1, 2, side_info_3);
675 }
676 else
677 {
678 Elem * elem_Tri3_0 = mesh.add_elem(new Tri3);
679 elem_Tri3_0->set_node(0, node_0);
680 elem_Tri3_0->set_node(1, node_1);
681 elem_Tri3_0->set_node(2, node_3);
682 elem_Tri3_0->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
683 Elem * elem_Tri3_1 = mesh.add_elem(new Tri3);
684 elem_Tri3_1->set_node(0, node_1);
685 elem_Tri3_1->set_node(1, node_2);
686 elem_Tri3_1->set_node(2, node_3);
687 elem_Tri3_1->subdomain_id() = mesh.elem_ptr(elem_id)->subdomain_id() + tri_elem_subdomain_shift;
688 // Retain element extra integers
689 for (const auto j : make_range(n_elem_extra_ids))
690 {
691 elem_Tri3_0->set_extra_integer(j, exist_extra_ids[j]);
692 elem_Tri3_1->set_extra_integer(j, exist_extra_ids[j]);
693 }
694
695 // Add sideset information to the new elements
696 for (const auto & side_info_0 : elem_side_list[0])
697 boundary_info.add_side(elem_Tri3_0, 0, side_info_0);
698 for (const auto & side_info_1 : elem_side_list[1])
699 boundary_info.add_side(elem_Tri3_1, 0, side_info_1);
700 for (const auto & side_info_2 : elem_side_list[2])
701 boundary_info.add_side(elem_Tri3_1, 1, side_info_2);
702 for (const auto & side_info_3 : elem_side_list[3])
703 boundary_info.add_side(elem_Tri3_0, 2, side_info_3);
704 }
705}
706
707void
709 const std::vector<Real> & cut_line_params,
710 const dof_id_type tri_subdomain_id_shift,
711 const SubdomainName tri_elem_subdomain_name_suffix)
712{
713 // Preprocess: find all the quad elements that are across the cutting line
714 std::vector<dof_id_type> cross_elems_quad;
715 std::set<subdomain_id_type> new_subdomain_ids;
716 for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
717 elem_it++)
718 {
719 if ((*elem_it)->n_vertices() == 4)
720 {
721 std::vector<unsigned short> node_side_rec;
722 for (const auto i : make_range(4))
723 {
724 const Point v_point = (*elem_it)->point(i);
725 if (!pointOnLine(
726 v_point(0), v_point(1), cut_line_params[0], cut_line_params[1], cut_line_params[2]))
727 node_side_rec.push_back(lineSideDeterminator(v_point(0),
728 v_point(1),
729 cut_line_params[0],
730 cut_line_params[1],
731 cut_line_params[2],
732 true));
733 }
734 // This counts the booleans in node_side_rec, which does not include nodes
735 // that are exactly on the line (these nodes are excluded from the
736 // decision). In this case, num_nodes node lie on one side of the line and
737 // node_side_rec.size() - n_nodes lie on the other side. In the case that
738 // there are nodes on both sides of the line, we mark the element for
739 // conversion.
740 const auto num_nodes = std::accumulate(node_side_rec.begin(), node_side_rec.end(), 0);
741 if (num_nodes != (int)node_side_rec.size() && num_nodes > 0)
742 {
743 cross_elems_quad.push_back((*elem_it)->id());
744 new_subdomain_ids.emplace((*elem_it)->subdomain_id() + tri_subdomain_id_shift);
745 }
746 }
747 }
748 // Then convert these quad elements into tri elements
749 for (const auto & cross_elem_quad : cross_elems_quad)
750 {
751 quadElemSplitter(mesh, cross_elem_quad, tri_subdomain_id_shift);
752 mesh.delete_elem(mesh.elem_ptr(cross_elem_quad));
753 }
754 for (auto & nid : new_subdomain_ids)
755 {
756 const SubdomainName old_name = mesh.subdomain_name(nid - tri_subdomain_id_shift);
758 (old_name.empty() ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
759 : old_name) +
760 "_" + tri_elem_subdomain_name_suffix,
762 throw MooseException("The new subdomain name already exists in the mesh.");
764 (old_name.empty()
765 ? (SubdomainName)(std::to_string(nid - tri_subdomain_id_shift))
766 : old_name) +
767 "_" + tri_elem_subdomain_name_suffix);
768 mooseWarning("QUAD elements have been converted into TRI elements with a new "
769 "subdomain name: " +
770 mesh.subdomain_name(nid) + ".");
771 }
772 mesh.contract();
773}
774
775void
777 const std::vector<Real> & cut_line_params,
778 const subdomain_id_type block_id_to_remove,
779 const boundary_id_type new_boundary_id)
780{
781 // Find all the elements that are across the cutting line
782 std::vector<dof_id_type> cross_elems;
783 // A vector for element specific information
784 std::vector<std::vector<std::pair<dof_id_type, dof_id_type>>> node_pairs_vec;
785 // A set for unique pairs
786 std::vector<std::pair<dof_id_type, dof_id_type>> node_pairs_unique_vec;
787 for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
788 elem_it++)
789 {
790 const auto n_vertices = (*elem_it)->n_vertices();
791 unsigned int n_points_on_line = 0;
792 std::vector<unsigned short> node_side_rec(n_vertices, 0);
793 for (const auto i : make_range(n_vertices))
794 {
795 // First check if the vertex is in the XY Plane
796 if (!MooseUtils::absoluteFuzzyEqual((*elem_it)->point(i)(2), 0.0))
797 mooseError("MooseMeshXYCuttingUtils::lineRemoverCutElemTri() only works for 2D meshes in "
798 "XY Plane.");
799 const Point v_point = (*elem_it)->point(i);
800 if (pointOnLine(
801 v_point(0), v_point(1), cut_line_params[0], cut_line_params[1], cut_line_params[2]))
802 ++n_points_on_line;
803 else
804 node_side_rec[i] = lineSideDeterminator(v_point(0),
805 v_point(1),
806 cut_line_params[0],
807 cut_line_params[1],
808 cut_line_params[2],
809 true);
810 }
811 // This counts the booleans in node_side_rec, which does not include nodes
812 // that are exactly on the line (these nodes are excluded from the
813 // decision). In this case, num_nodes node lie on one side of the line and
814 // node_side_rec.size() - n_nodes lie on the other side. In the case that
815 // there are nodes on both sides of the line, we mark the element for
816 // removal.
817 const unsigned int num_nodes = std::accumulate(node_side_rec.begin(), node_side_rec.end(), 0);
818 if (num_nodes == node_side_rec.size() - n_points_on_line)
819 {
820 (*elem_it)->subdomain_id() = block_id_to_remove;
821 }
822 else if (num_nodes > 0)
823 {
824 if ((*elem_it)->n_vertices() != 3 || (*elem_it)->n_nodes() != 3)
825 mooseError("The element across the cutting line is not TRI3, which is not supported.");
826 cross_elems.push_back((*elem_it)->id());
827 // Then we need to check pairs of nodes that are on the different side
828 std::vector<std::pair<dof_id_type, dof_id_type>> node_pairs;
829 for (const auto i : index_range(node_side_rec))
830 {
831 // first node on removal side and second node on retaining side
832 if (node_side_rec[i] > 0 && node_side_rec[(i + 1) % node_side_rec.size()] == 0)
833 {
834 // Removal side first
835 node_pairs.push_back(
836 std::make_pair((*elem_it)->node_ptr(i)->id(),
837 (*elem_it)->node_ptr((i + 1) % node_side_rec.size())->id()));
838 node_pairs_unique_vec.push_back(node_pairs.back());
839 }
840 // first node on retaining side and second node on removal side
841 else if (node_side_rec[i] == 0 && node_side_rec[(i + 1) % node_side_rec.size()] > 0)
842 {
843 // Removal side first
844 node_pairs.push_back(
845 std::make_pair((*elem_it)->node_ptr((i + 1) % node_side_rec.size())->id(),
846 (*elem_it)->node_ptr(i)->id()));
847 node_pairs_unique_vec.push_back(node_pairs.back());
848 }
849 }
850 node_pairs_vec.push_back(node_pairs);
851 }
852 }
853 auto vec_ip = std::unique(node_pairs_unique_vec.begin(), node_pairs_unique_vec.end());
854 node_pairs_unique_vec.resize(std::distance(node_pairs_unique_vec.begin(), vec_ip));
855
856 // Loop over all the node pairs to define new nodes that sit on the cutting line
857 std::vector<Node *> nodes_on_line;
858 // whether the on-line node is overlapped with the node pairs or a brand new node
859 std::vector<unsigned short> nodes_on_line_overlap;
860 for (const auto & node_pair : node_pairs_unique_vec)
861 {
862 const Point pt1 = *mesh.node_ptr(node_pair.first);
863 const Point pt2 = *mesh.node_ptr(node_pair.second);
864 const Point pt_line = twoPointandLineIntersection(
865 pt1, pt2, cut_line_params[0], cut_line_params[1], cut_line_params[2]);
866 if ((pt_line - pt1).norm() < libMesh::TOLERANCE)
867 {
868 nodes_on_line.push_back(mesh.node_ptr(node_pair.first));
869 nodes_on_line_overlap.push_back(1);
870 }
871 else if ((pt_line - pt2).norm() < libMesh::TOLERANCE)
872 {
873 nodes_on_line.push_back(mesh.node_ptr(node_pair.second));
874 nodes_on_line_overlap.push_back(2);
875 }
876 else
877 {
878 nodes_on_line.push_back(mesh.add_point(pt_line));
879 nodes_on_line_overlap.push_back(0);
880 }
881 }
882
883 // make new elements
884 for (const auto i : index_range(cross_elems))
885 {
886 // Only TRI elements are involved after preprocessing
887 auto cross_elem = mesh.elem_ptr(cross_elems[i]);
888 auto node_0 = cross_elem->node_ptr(0);
889 auto node_1 = cross_elem->node_ptr(1);
890 auto node_2 = cross_elem->node_ptr(2);
891 const std::vector<dof_id_type> tri_nodes = {node_0->id(), node_1->id(), node_2->id()};
892
893 const auto online_node_index_1 = std::distance(node_pairs_unique_vec.begin(),
894 std::find(node_pairs_unique_vec.begin(),
895 node_pairs_unique_vec.end(),
896 node_pairs_vec[i][0]));
897 const auto online_node_index_2 = std::distance(node_pairs_unique_vec.begin(),
898 std::find(node_pairs_unique_vec.begin(),
899 node_pairs_unique_vec.end(),
900 node_pairs_vec[i][1]));
901 auto node_3 = nodes_on_line[online_node_index_1];
902 auto node_4 = nodes_on_line[online_node_index_2];
903 const auto node_3_overlap_flag = nodes_on_line_overlap[online_node_index_1];
904 const auto node_4_overlap_flag = nodes_on_line_overlap[online_node_index_2];
905 // Most common case, no overlapped nodes
906 if (node_3_overlap_flag == 0 && node_4_overlap_flag == 0)
907 {
908 // True if the common node is on the removal side; false if on the retaining side
909 const bool common_node_side = node_pairs_vec[i][0].first == node_pairs_vec[i][1].first;
910 const subdomain_id_type block_id_to_assign_1 =
911 common_node_side ? block_id_to_remove : cross_elem->subdomain_id();
912 const subdomain_id_type block_id_to_assign_2 =
913 common_node_side ? cross_elem->subdomain_id() : block_id_to_remove;
914 // The reference node ids need to be adjusted according to the common node of the two cut
915 // sides
916 const dof_id_type common_node_id =
917 common_node_side ? node_pairs_vec[i][0].first : node_pairs_vec[i][0].second;
918
920 cross_elem->id(),
921 std::distance(tri_nodes.begin(),
922 std::find(tri_nodes.begin(), tri_nodes.end(), common_node_id)),
923 node_3->id(),
924 node_4->id(),
925 block_id_to_assign_1,
926 block_id_to_assign_2);
927 mesh.delete_elem(cross_elem);
928 }
929 // both node_3 and node_4 are overlapped
930 else if (node_3_overlap_flag > 0 && node_4_overlap_flag > 0)
931 {
932 // In this case, the entire element is on one side of the cutting line
933 // No change needed just check which side the element is on
934 cross_elem->subdomain_id() = lineSideDeterminator(cross_elem->vertex_average()(0),
935 cross_elem->vertex_average()(1),
936 cut_line_params[0],
937 cut_line_params[1],
938 cut_line_params[2],
939 true)
940 ? block_id_to_remove
941 : cross_elem->subdomain_id();
942 }
943 // node_3 or node_4 is overlapped
944 else
945 {
946 const auto node_3_finder = std::distance(
947 tri_nodes.begin(), std::find(tri_nodes.begin(), tri_nodes.end(), node_3->id()));
948 const auto node_4_finder = std::distance(
949 tri_nodes.begin(), std::find(tri_nodes.begin(), tri_nodes.end(), node_4->id()));
950 // As only one of the two above values should be less than the three, the smaller one should
951 // be used
952 const dof_id_type node_id = node_3_finder < node_4_finder ? node_4->id() : node_3->id();
953 const auto node_finder = std::min(node_3_finder, node_4_finder);
954
956 mesh,
957 cross_elem->id(),
958 node_finder,
959 node_id,
960 tri_nodes[(node_finder + 1) % 3] == node_pairs_vec[i][node_3_finder > node_4_finder].first
961 ? block_id_to_remove
962 : cross_elem->subdomain_id(),
963 tri_nodes[(node_finder + 1) % 3] == node_pairs_vec[i][node_3_finder > node_4_finder].first
964 ? cross_elem->subdomain_id()
965 : block_id_to_remove);
966 mesh.delete_elem(cross_elem);
967 }
968 }
969 mesh.contract();
970 // Due to the complexity, we identify the new boundary here together instead of during cutting of
971 // each element, because the preexisting element edges that are aligned with the cutting line also
972 // need to be added to the new boundary.
974 BoundaryInfo & boundary_info = mesh.get_boundary_info();
975 for (auto elem_it = mesh.active_elements_begin(); elem_it != mesh.active_elements_end();
976 elem_it++)
977 {
978 if ((*elem_it)->subdomain_id() != block_id_to_remove)
979 {
980 for (const auto j : make_range((*elem_it)->n_sides()))
981 {
982 if ((*elem_it)->neighbor_ptr(j) != nullptr)
983 if ((*elem_it)->neighbor_ptr(j)->subdomain_id() == block_id_to_remove)
984 boundary_info.add_side(*elem_it, j, new_boundary_id);
985 }
986 }
987 }
988
989 // Delete the block to remove
990 for (auto elem_it = mesh.active_subdomain_elements_begin(block_id_to_remove);
991 elem_it != mesh.active_subdomain_elements_end(block_id_to_remove);
992 elem_it++)
993 mesh.delete_elem(*elem_it);
994 mesh.contract();
995}
996
997void
999 const std::vector<Real> & cut_line_params,
1000 const dof_id_type tri_subdomain_id_shift,
1001 const SubdomainName tri_elem_subdomain_name_suffix,
1002 const subdomain_id_type block_id_to_remove,
1003 const boundary_id_type new_boundary_id,
1004 const bool improve_boundary_tri_elems)
1005{
1006 // Convert any quad elements crossed by the line into tri elements
1007 quadToTriOnLine(mesh, cut_line_params, tri_subdomain_id_shift, tri_elem_subdomain_name_suffix);
1008 // Then do the cutting for the preprocessed mesh that only contains tri elements crossed by the
1009 // cut line
1010 lineRemoverCutElemTri(mesh, cut_line_params, block_id_to_remove, new_boundary_id);
1011
1012 if (improve_boundary_tri_elems)
1013 boundaryTriElemImprover(mesh, new_boundary_id);
1014}
1015
1016void
1018{
1019 if (!MooseMeshUtils::hasBoundaryID(mesh, boundary_to_improve))
1020 mooseError(
1021 "MooseMeshXYCuttingUtils::boundaryTriElemImprover(): The boundary_to_improve provided "
1022 "does not exist in the given mesh.");
1023 BoundaryInfo & boundary_info = mesh.get_boundary_info();
1024 auto side_list = boundary_info.build_side_list();
1025 // Here we would like to collect the following information for all the TRI3 elements on the
1026 // boundary: Key: node id of the off-boundary node Value: a vector of tuples, each tuple contains
1027 // the following information:
1028 // 1. The element id of the element that is on the boundary to improve
1029 // 2. the one node id of that element that is on the boundary to improve
1030 // 3. the other node id of the element that is on the boundary to improve
1031 std::map<dof_id_type, std::vector<std::tuple<dof_id_type, dof_id_type, dof_id_type>>>
1032 tri3_elem_info;
1033 for (const auto & side : side_list)
1034 {
1035 if (std::get<2>(side) == boundary_to_improve)
1036 {
1037 Elem * elem = mesh.elem_ptr(std::get<0>(side));
1038 if (elem->type() == TRI3)
1039 {
1040 const auto key_node_id = elem->node_id((std::get<1>(side) + 2) % 3);
1041 const auto value_elem_id = elem->id();
1042 const auto value_node_id_1 = elem->node_id(std::get<1>(side));
1043 const auto value_node_id_2 = elem->node_id((std::get<1>(side) + 1) % 3);
1044 tri3_elem_info[key_node_id].push_back(
1045 std::make_tuple(value_elem_id, value_node_id_1, value_node_id_2));
1046 }
1047 }
1048 }
1049 // Elements that need to be removed
1050 std::vector<dof_id_type> elems_to_remove;
1051 // Now check if any group of TRI3 sharing an off-boundary node can be improved.
1052 for (const auto & tri_group : tri3_elem_info)
1053 {
1054 // It is possible to improve only when more than one TRI3 elements share the same off-boundary
1055 // node
1056 std::vector<std::pair<dof_id_type, dof_id_type>> node_assm;
1057 std::vector<dof_id_type> elem_id_list;
1058 for (const auto & tri : tri_group.second)
1059 {
1060 node_assm.push_back(std::make_pair(std::get<1>(tri), std::get<2>(tri)));
1061 elem_id_list.push_back(std::get<0>(tri));
1062 }
1063 std::vector<dof_id_type> ordered_node_list;
1064 std::vector<dof_id_type> ordered_elem_list;
1066 node_assm, elem_id_list, ordered_node_list, ordered_elem_list);
1067
1068 // For all the elements sharing the same off-boundary node, we need to know how many separated
1069 // subdomains are involved
1070 // If there are extra element ids defined on the mesh, they also want to retain their boundaries
1071 // Only triangle elements that share a side can be merged
1072 const unsigned int n_elem_extra_ids = mesh.n_elem_integers();
1073 std::vector<std::tuple<subdomain_id_type, std::vector<dof_id_type>, unsigned int>> blocks_info;
1074 for (const auto & elem_id : ordered_elem_list)
1075 {
1076 std::vector<dof_id_type> exist_extra_ids(n_elem_extra_ids);
1077 // Record all the element extra integers of the original quad element
1078 for (const auto j : make_range(n_elem_extra_ids))
1079 exist_extra_ids[j] = mesh.elem_ptr(elem_id)->get_extra_integer(j);
1080 if (!blocks_info.empty())
1081 {
1082 if (mesh.elem_ptr(elem_id)->subdomain_id() == std::get<0>(blocks_info.back()) &&
1083 exist_extra_ids == std::get<1>(blocks_info.back()))
1084 {
1085 std::get<2>(blocks_info.back())++;
1086 continue;
1087 }
1088 }
1089 blocks_info.push_back(
1090 std::make_tuple(mesh.elem_ptr(elem_id)->subdomain_id(), exist_extra_ids, 1));
1091 }
1092 // For each separated subdomain / set of extra ids, we try to improve the boundary elements
1093 unsigned int side_counter = 0;
1094 for (const auto & block_info : blocks_info)
1095 {
1096 const auto node_1 = mesh.node_ptr(ordered_node_list[side_counter]);
1097 // we do not need to subtract 1 for node_2
1098 const auto node_2 = mesh.node_ptr(ordered_node_list[side_counter + std::get<2>(block_info)]);
1099 const auto node_0 = mesh.node_ptr(tri_group.first);
1100 const Point v1 = *node_1 - *node_0;
1101 const Point v2 = *node_2 - *node_0;
1102 const Real angle = std::acos(v1 * v2 / v1.norm() / v2.norm()) / M_PI * 180.0;
1103 const std::vector<dof_id_type> block_elems(ordered_elem_list.begin() + side_counter,
1104 ordered_elem_list.begin() + side_counter +
1105 std::get<2>(block_info));
1106 // We assume that there are no sidesets defined inside a subdomain
1107 // For the first TRI3 element, we want to check if its side defined by node_0 and node_1 is
1108 // defined in any sidesets
1109 unsigned short side_id_0;
1110 unsigned short side_id_t;
1111 bool is_inverse_0;
1112 bool is_inverse_t;
1114 block_elems.front(),
1115 tri_group.first,
1116 ordered_node_list[side_counter],
1117 side_id_0,
1118 is_inverse_0);
1120 block_elems.back(),
1121 ordered_node_list[side_counter + std::get<2>(block_info)],
1122 tri_group.first,
1123 side_id_t,
1124 is_inverse_t);
1125 // Collect boundary information of the identified sides
1126 std::vector<boundary_id_type> side_0_boundary_ids;
1127 boundary_info.boundary_ids(
1128 mesh.elem_ptr(block_elems.front()), side_id_0, side_0_boundary_ids);
1129 std::vector<boundary_id_type> side_t_boundary_ids;
1130 boundary_info.boundary_ids(mesh.elem_ptr(block_elems.back()), side_id_t, side_t_boundary_ids);
1131
1132 // Ideally we want this angle to be 60 degrees
1133 // In reality, we want one TRI3 element if the angle is less than 90 degrees;
1134 // we want two TRI3 elements if the angle is greater than 90 degrees and less than 135
1135 // degrees; we want three TRI3 elements if the angle is greater than 135 degrees and less than
1136 // 180 degrees.
1137 if (angle < 90.0)
1138 {
1139 if (std::get<2>(block_info) > 1)
1140 {
1142 tri_group.first,
1143 ordered_node_list[side_counter],
1144 ordered_node_list[side_counter + std::get<2>(block_info)],
1145 std::get<0>(block_info),
1146 std::get<1>(block_info),
1147 {boundary_to_improve},
1148 side_0_boundary_ids,
1149 side_t_boundary_ids);
1150 elems_to_remove.insert(elems_to_remove.end(), block_elems.begin(), block_elems.end());
1151 }
1152 }
1153 else if (angle < 135.0)
1154 {
1155 // We can just add the middle node because there's nothing on the other side
1156 const auto node_m = mesh.add_point((*node_1 + *node_2) / 2.0);
1158 tri_group.first,
1159 ordered_node_list[side_counter],
1160 node_m->id(),
1161 std::get<0>(block_info),
1162 std::get<1>(block_info),
1163 {boundary_to_improve},
1164 side_0_boundary_ids,
1165 std::vector<boundary_id_type>());
1167 tri_group.first,
1168 node_m->id(),
1169 ordered_node_list[side_counter + std::get<2>(block_info)],
1170 std::get<0>(block_info),
1171 std::get<1>(block_info),
1172 {boundary_to_improve},
1173 std::vector<boundary_id_type>(),
1174 side_t_boundary_ids);
1175 elems_to_remove.insert(elems_to_remove.end(), block_elems.begin(), block_elems.end());
1176 }
1177 else
1178 {
1179 const auto node_m1 = mesh.add_point((*node_1 * 2.0 + *node_2) / 3.0);
1180 const auto node_m2 = mesh.add_point((*node_1 + *node_2 * 2.0) / 3.0);
1182 tri_group.first,
1183 ordered_node_list[side_counter],
1184 node_m1->id(),
1185 std::get<0>(block_info),
1186 std::get<1>(block_info),
1187 {boundary_to_improve},
1188 side_0_boundary_ids,
1189 std::vector<boundary_id_type>());
1191 tri_group.first,
1192 node_m1->id(),
1193 node_m2->id(),
1194 std::get<0>(block_info),
1195 std::get<1>(block_info),
1196 {boundary_to_improve},
1197 std::vector<boundary_id_type>(),
1198 std::vector<boundary_id_type>());
1200 tri_group.first,
1201 node_m2->id(),
1202 ordered_node_list[side_counter + std::get<2>(block_info)],
1203 std::get<0>(block_info),
1204 std::get<1>(block_info),
1205 {boundary_to_improve},
1206 std::vector<boundary_id_type>(),
1207 side_t_boundary_ids);
1208 elems_to_remove.insert(elems_to_remove.end(), block_elems.begin(), block_elems.end());
1209 }
1210 side_counter += std::get<2>(block_info);
1211 }
1212 // TODO: Need to check if the new element is inverted?
1213 }
1214 // Delete the original elements
1215 for (const auto & elem_to_remove : elems_to_remove)
1216 mesh.delete_elem(mesh.elem_ptr(elem_to_remove));
1217 mesh.contract();
1218}
1219
1220void
1222 const dof_id_type node_id_0,
1223 const dof_id_type node_id_1,
1224 const dof_id_type node_id_2,
1225 const subdomain_id_type subdomain_id,
1226 const std::vector<dof_id_type> & extra_elem_ids,
1227 const std::vector<boundary_id_type> & boundary_ids_for_side_1,
1228 const std::vector<boundary_id_type> & boundary_ids_for_side_0,
1229 const std::vector<boundary_id_type> & boundary_ids_for_side_2)
1230{
1231 BoundaryInfo & boundary_info = mesh.get_boundary_info();
1232 Elem * elem_Tri3_new = mesh.add_elem(new Tri3);
1233 elem_Tri3_new->set_node(0, mesh.node_ptr(node_id_0));
1234 elem_Tri3_new->set_node(1, mesh.node_ptr(node_id_1));
1235 elem_Tri3_new->set_node(2, mesh.node_ptr(node_id_2));
1236 for (const auto & boundary_id_for_side_0 : boundary_ids_for_side_0)
1237 boundary_info.add_side(elem_Tri3_new, 0, boundary_id_for_side_0);
1238 for (const auto & boundary_id_for_side_1 : boundary_ids_for_side_1)
1239 boundary_info.add_side(elem_Tri3_new, 1, boundary_id_for_side_1);
1240 for (const auto & boundary_id_for_side_2 : boundary_ids_for_side_2)
1241 boundary_info.add_side(elem_Tri3_new, 2, boundary_id_for_side_2);
1242 elem_Tri3_new->subdomain_id() = subdomain_id;
1243 // Retain element extra integers
1244 for (const auto j : index_range(extra_elem_ids))
1245 {
1246 elem_Tri3_new->set_extra_integer(j, extra_elem_ids[j]);
1247 }
1248}
1249
1250bool
1252 const dof_id_type elem_id,
1253 const dof_id_type node_id_0,
1254 const dof_id_type node_id_1,
1255 unsigned short & side_id,
1256 bool & is_inverse)
1257{
1258 Elem * elem = mesh.elem_ptr(elem_id);
1259 for (unsigned short i = 0; i < elem->n_sides(); i++)
1260 {
1261 if (elem->side_ptr(i)->node_ptr(0)->id() == node_id_0 &&
1262 elem->side_ptr(i)->node_ptr(1)->id() == node_id_1)
1263 {
1264 side_id = i;
1265 is_inverse = false;
1266 return true;
1267 }
1268 else if (elem->side_ptr(i)->node_ptr(0)->id() == node_id_1 &&
1269 elem->side_ptr(i)->node_ptr(1)->id() == node_id_0)
1270 {
1271 side_id = i;
1272 is_inverse = true;
1273 return true;
1274 }
1275 }
1276 return false;
1277}
1278}
void mooseWarning(Args &&... args)
Emit a warning message with the given stringified, concatenated args.
Definition MooseError.h:345
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
Provides a way for users to bail out of the current solve.
std::vector< BCTuple > build_side_list(BCTupleSortBy sort_by=BCTupleSortBy::ELEM_ID) const
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
void remove_side(const Elem *elem, const unsigned short int side)
dof_id_type get_extra_integer(const unsigned int index) const
dof_id_type id() const
void set_extra_integer(const unsigned int index, const dof_id_type value)
virtual Node *& set_node(const unsigned int i)
virtual unsigned int n_vertices() const=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
virtual ElemType type() const=0
virtual unsigned int n_sides() const=0
dof_id_type node_id(const unsigned int i) const
const Elem * neighbor_ptr(unsigned int i) const
void set_subdomain_name(subdomain_id_type id, const std::string &name, bool synchronous=false)
virtual bool contract()=0
unsigned int n_elem_integers() const
const BoundaryInfo & get_boundary_info() const
virtual const Node * node_ptr(const dof_id_type i) const=0
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
virtual void delete_elem(Elem *e)=0
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
virtual const Elem * elem_ptr(const dof_id_type i) const=0
virtual Elem * add_elem(Elem *e)=0
std::string & subdomain_name(subdomain_id_type id)
virtual void find_neighbors(const bool reset_remote_elements=false, const bool reset_current_list=true, const bool assert_valid=true)=0
auto norm() const
MeshBase & mesh
bool hasBoundaryID(const MeshBase &input_mesh, const BoundaryID id)
Whether a particular boundary ID exists in the mesh.
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...
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
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.
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.
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 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,...
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.
Point twoPointandLineIntersection(const Point &pt1, const Point &pt2, const Real param_1, const Real param_2, const Real param_3)
Calculates the intersection Point of a straight line defined by two given points and another straight...
bool pointOnLine(const Real px, const Real py, const Real param_1, const Real param_2, const Real param_3, const Real dis_tol=libMesh::TOLERANCE)
Determines whether a point on XY-plane is on a given line, to within a tolerance.
std::vector< std::pair< Real, unsigned int > > vertex_distances(const Elem &elem)
Calculates the distances between the vertices of a given 2D element.
std::vector< std::pair< Real, unsigned int > > vertex_angles(const Elem &elem)
Calculates the internal angles of a given 2D element.
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 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.
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 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 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.
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...
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.
const SubdomainID INVALID_BLOCK_ID
Definition MooseTypes.C:20
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
auto index_range(const T &sizable)
int8_t boundary_id_type
static constexpr Real TOLERANCE
uint8_t dof_id_type
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
const boundary_id_type side_id