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