https://mooseframework.inl.gov
Loading...
Searching...
No Matches
TriToQuadConverter.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#include "TriToQuadConverter.h"
11
12#include "GeometryUtils.h"
14#include "MooseMeshUtils.h"
15#include "MooseUtils.h"
16
17#include "libmesh/boundary_info.h"
18#include "libmesh/elem.h"
19#include "libmesh/enum_elem_type.h"
20#include "libmesh/int_range.h"
21#include "libmesh/libmesh.h"
22#include "libmesh/mesh_base.h"
23#include "libmesh/node.h"
24#include "libmesh/replicated_mesh.h"
25#include "libmesh/string_to_enum.h"
26
27// C++ includes
28#include <algorithm>
29#include <cmath>
30#include <map>
31#include <set>
32#include <unordered_set>
33
35
36// The mesh generator sources are compiled as one unity translation unit, which puts this file scope
37// and that of every sibling generator together, so no name here may collide with a sibling's
38namespace
39{
41constexpr unsigned int n_tri_sides = 3;
42
44constexpr unsigned int n_quad_sides = 4;
45
53std::pair<dof_id_type, dof_id_type>
54edgeKey(const dof_id_type node_id_1, const dof_id_type node_id_2)
55{
56 return std::make_pair(std::min(node_id_1, node_id_2), std::max(node_id_1, node_id_2));
57}
58
68Node *
69edgeMidpointNode(MeshBase & mesh,
70 std::map<std::pair<dof_id_type, dof_id_type>, Node *> & midpoints,
71 const Node & node_1,
72 const Node & node_2)
73{
74 const auto edge = edgeKey(node_1.id(), node_2.id());
75 const auto it = midpoints.find(edge);
76 if (it != midpoints.end())
77 return it->second;
78
79 Node * const midpoint = mesh.add_point((node_1 + node_2) / 2.0);
80 midpoints.emplace(edge, midpoint);
81
82 return midpoint;
83}
84
93std::vector<TriToQuadConverter::QuadCorners>
94subdivisionTemplate(const unsigned int n_corners)
95{
96 std::vector<TriToQuadConverter::QuadCorners> quads;
97 for (const auto k : make_range(n_corners))
98 quads.push_back({k, n_corners + k, 2 * n_corners, n_corners + (k + n_corners - 1) % n_corners});
99
100 return quads;
101}
102
110unsigned int
111quadSideOnEdge(const std::array<dof_id_type, 4> & quad_node_ids,
112 const dof_id_type node_id_1,
113 const dof_id_type node_id_2)
114{
115 for (const auto s : index_range(quad_node_ids))
116 {
117 const dof_id_type end_1 = quad_node_ids[s];
118 const dof_id_type end_2 = quad_node_ids[(s + 1) % quad_node_ids.size()];
119 if ((end_1 == node_id_1 && end_2 == node_id_2) || (end_1 == node_id_2 && end_2 == node_id_1))
120 return cast_int<unsigned int>(s);
121 }
122
124}
125
132void
133removeScratchElements(MeshBase & mesh, const subdomain_id_type scratch_subdomain_id)
134{
135 for (auto elem_it = mesh.active_subdomain_elements_begin(scratch_subdomain_id);
136 elem_it != mesh.active_subdomain_elements_end(scratch_subdomain_id);
137 ++elem_it)
138 mesh.delete_elem(*elem_it);
139
140 mesh.contract();
141 mesh.prepare_for_use();
142
143 // build_node_list_from_side_list() only adds nodes, so the node sets of the input mesh survive
144 // and the nodes created on a boundary edge join the node set of that boundary
145 mesh.get_boundary_info().build_node_list_from_side_list();
146}
147}
148
151{
153
154 params.addRequiredParam<MeshGeneratorName>("input",
155 "The TRI3 mesh to convert into QUAD4 elements.");
156
157 MooseEnum algorithm("SUBDIVISION RECOMBINE", "RECOMBINE");
158 params.addParam<MooseEnum>("algorithm",
159 algorithm,
160 "The algorithm used to build the quadrilaterals. 'SUBDIVISION' splits "
161 "every triangle into three quadrilaterals. 'RECOMBINE' merges pairs "
162 "of adjacent triangles into quadrilaterals.");
163
164 params.addRangeCheckedParam<Real>(
165 "eta_min",
166 0.3,
167 "eta_min > 0 & eta_min <= 1",
168 "'RECOMBINE' algorithm only: the quality score eta = 1 - (2 / pi) max_k |pi / 2 - alpha_k| "
169 "of the quadrilateral, in which alpha_k are its four internal angles, that a pair of "
170 "adjacent triangles must reach to be merged. A rectangle scores 1 and a non-convex "
171 "quadrilateral 0.");
172
173 params.addParam<SubdomainName>(
174 "tri_subdomain_name_suffix",
175 "tri",
176 "'RECOMBINE' algorithm only: the triangles which could not be merged are moved out of each "
177 "subdomain into a new subdomain named after it, with an underscore and this suffix "
178 "appended. A subdomain without a name contributes its id instead.");
179
180 params.addParam<bool>("all_quad",
181 false,
182 "'RECOMBINE' algorithm only: whether the triangles that could not be "
183 "merged are eliminated so that the converted mesh consists exclusively of "
184 "quadrilaterals.");
185
186 params.addParamNamesToGroup("eta_min tri_subdomain_name_suffix all_quad", "Recombination");
187
188 params.addClassDescription("Converts a mesh consisting of TRI3 elements into a mesh consisting "
189 "of QUAD4 elements, either by splitting every triangle into three "
190 "quadrilaterals or by merging pairs of adjacent triangles.");
191
192 return params;
193}
194
196 : MeshGenerator(parameters),
197 _input(getMesh("input")),
198 _algorithm(getParam<MooseEnum>("algorithm")),
199 _eta_min(getParam<Real>("eta_min")),
200 _tri_subdomain_name_suffix(getParam<SubdomainName>("tri_subdomain_name_suffix")),
201 _all_quad(getParam<bool>("all_quad"))
202{
203 if (_all_quad && _algorithm == "SUBDIVISION")
204 paramError("all_quad",
205 "The 'all_quad' option is only available with the 'RECOMBINE' algorithm.");
206
207 if (_all_quad && isParamSetByUser("tri_subdomain_name_suffix"))
208 paramError("all_quad",
209 "The 'all_quad' option leaves no triangle for 'tri_subdomain_name_suffix' to name.");
210}
211
212std::unique_ptr<MeshBase>
214{
215 auto replicated_mesh_ptr = dynamic_cast<ReplicatedMesh *>(_input.get());
216 if (!replicated_mesh_ptr)
217 paramError("input", "Input is not a replicated mesh, which is required");
218
219 ReplicatedMesh & mesh = *replicated_mesh_ptr;
220
221 for (const auto & elem : mesh.element_ptr_range())
222 if (elem->type() != libMesh::ElemType::TRI3)
223 paramError("input",
224 "Element ",
225 elem->id(),
226 " is a ",
228 " element. Only meshes consisting exclusively of TRI3 elements are supported.");
229
230 // Both algorithms measure the areas and the angles that decide the conversion in the XY plane.
231 // The comparison is fuzzy so that z coordinates carrying round-off, for instance from a
232 // TransformGenerator rotation into the plane, are not rejected
233 for (const auto & node : mesh.node_ptr_range())
234 if (!MooseUtils::absoluteFuzzyEqual((*node)(2), 0.0))
235 paramError("input",
236 "Node ",
237 node->id(),
238 " is at z = ",
239 (*node)(2),
240 ". Only meshes in the XY plane are supported.");
241
242 if (_algorithm == "SUBDIVISION")
244 else
246
247 return std::move(_input);
248}
249
250Real
251TriToQuadConverter::quadQuality(const std::array<Point, 4> & quad_points)
252{
253 Real max_deviation = 0.0;
254 for (const auto k : index_range(quad_points))
255 {
256 const Point incoming = quad_points[k] - quad_points[(k + 3) % quad_points.size()];
257 const Point outgoing = quad_points[(k + 1) % quad_points.size()] - quad_points[k];
258
259 // For a counter-clockwise ordering the turn from the incoming to the outgoing edge is positive
260 // at a convex corner, so a non-positive turn is what makes an internal angle reach pi
261 const Real turn = std::atan2(incoming(0) * outgoing(1) - incoming(1) * outgoing(0),
262 incoming(0) * outgoing(0) + incoming(1) * outgoing(1));
263 const Real alpha = libMesh::pi - turn;
264 if (alpha >= libMesh::pi)
265 return 0.0;
266
267 max_deviation = std::max(max_deviation, std::abs(libMesh::pi / 2.0 - alpha));
268 }
269
270 return std::max(0.0, 1.0 - 2.0 / libMesh::pi * max_deviation);
271}
272
273std::vector<TriToQuadConverter::RecombineCandidate>
274TriToQuadConverter::greedyMatching(std::vector<RecombineCandidate> & candidates, const Real eta_min)
275{
276 // Ties are broken by element id so that the same pairs are merged from one run to the next
277 std::sort(candidates.begin(),
278 candidates.end(),
279 [](const RecombineCandidate & a, const RecombineCandidate & b)
280 {
281 if (a.eta > b.eta)
282 return true;
283 if (b.eta > a.eta)
284 return false;
285 if (a.first_elem_id != b.first_elem_id)
286 return a.first_elem_id < b.first_elem_id;
287 return a.second_elem_id < b.second_elem_id;
288 });
289
290 std::vector<RecombineCandidate> selected;
291 std::unordered_set<dof_id_type> consumed;
292 for (const auto & candidate : candidates)
293 {
294 // The candidates are sorted by decreasing score, so nothing below the threshold is left
295 if (candidate.eta < eta_min)
296 break;
297 if (consumed.count(candidate.first_elem_id) || consumed.count(candidate.second_elem_id))
298 continue;
299
300 consumed.insert(candidate.first_elem_id);
301 consumed.insert(candidate.second_elem_id);
302 selected.push_back(candidate);
303 }
304
305 return selected;
306}
307
308std::vector<TriToQuadConverter::QuadCorners>
310{
311 return subdivisionTemplate(n_tri_sides);
312}
313
314std::vector<TriToQuadConverter::QuadCorners>
316{
317 return subdivisionTemplate(n_quad_sides);
318}
319
322 const unsigned int side,
323 const Elem & neighbor)
324{
325 // The quadrilateral runs from the apex of one triangle, along the shared edge, to the apex of
326 // the other triangle and back along the shared edge
327 const unsigned int neighbor_side = neighbor.which_neighbor_am_i(&elem);
328 std::array<const Node *, 4> corners = {elem.node_ptr((side + 2) % n_tri_sides),
329 elem.node_ptr(side),
330 neighbor.node_ptr((neighbor_side + 2) % n_tri_sides),
331 elem.node_ptr((side + 1) % n_tri_sides)};
332
333 // Order the corners counter-clockwise so that the quadrilateral has a positive Jacobian
334 if (geom_utils::signedArea2D(*corners[0], *corners[1], *corners[2]) +
335 geom_utils::signedArea2D(*corners[0], *corners[2], *corners[3]) <
336 0.0)
337 std::reverse(corners.begin(), corners.end());
338
339 const std::array<Point, 4> quad_points = {*corners[0], *corners[1], *corners[2], *corners[3]};
340
341 RecombineCandidate candidate;
342 candidate.eta = quadQuality(quad_points);
343 candidate.first_elem_id = std::min(elem.id(), neighbor.id());
344 candidate.second_elem_id = std::max(elem.id(), neighbor.id());
345 for (const auto k : index_range(corners))
346 candidate.quad_node_ids[k] = corners[k]->id();
347
348 return candidate;
349}
350
351void
352TriToQuadConverter::subdivide(ReplicatedMesh & mesh) const
353{
354 BoundaryInfo & boundary_info = mesh.get_boundary_info();
355 const auto bdry_side_list = boundary_info.build_side_list();
356
357 const auto scratch_subdomain_id = MooseMeshUtils::getNextFreeSubdomainID(mesh);
358
359 // Keying the midpoints on the sorted node id pair of their edge makes the two elements sharing
360 // that edge use the same node, which is what keeps the converted mesh conformal
361 std::map<std::pair<dof_id_type, dof_id_type>, Node *> edge_midpoints;
362
363 // The elements are collected up front because the loop below adds elements to the mesh
364 std::vector<dof_id_type> elem_ids;
365 for (const auto & elem : mesh.active_element_ptr_range())
366 elem_ids.push_back(elem->id());
367
368 // The subdivision an element is split by depends on nothing but its number of corners
369 const std::vector<QuadCorners> tri_subdivision = triSubdivisionTemplate();
370 const std::vector<QuadCorners> quad_subdivision = quadSubdivisionTemplate();
371
372 // Held across the loop so that the elements of the mesh are walked without reallocating them
373 std::vector<unsigned int> vertex_index;
374 std::vector<unsigned int> side_index;
375 std::vector<Node *> vertices;
376 std::vector<Node *> midpoints;
377 std::vector<Node *> points;
378 std::vector<Elem *> quads;
379
380 for (const auto elem_id : elem_ids)
381 {
382 Elem * const parent = mesh.elem_ptr(elem_id);
383 const unsigned int n_corners = parent->n_nodes();
384
385 std::vector<std::vector<boundary_id_type>> elem_side_list;
387 bdry_side_list, elem_id, cast_int<unsigned short>(n_corners), elem_side_list);
388
389 // Walking the vertices counter-clockwise gives the quadrilaterals a positive Jacobian
390 // whichever way the element itself is oriented. Every element here is convex, so the turn at
391 // its first corner is the turn of the whole element
392 const bool clockwise =
393 geom_utils::signedArea2D(*parent->node_ptr(0), *parent->node_ptr(1), *parent->node_ptr(2)) <
394 0.0;
395
396 vertex_index.assign(n_corners, 0);
397 side_index.assign(n_corners, 0);
398 for (const auto k : make_range(n_corners))
399 {
400 vertex_index[k] = clockwise ? (n_corners - k) % n_corners : k;
401 side_index[k] = clockwise ? (2 * n_corners - k - 1) % n_corners : k;
402 }
403
404 vertices.assign(n_corners, nullptr);
405 for (const auto k : index_range(vertices))
406 vertices[k] = parent->node_ptr(vertex_index[k]);
407
408 midpoints.assign(n_corners, nullptr);
409 for (const auto k : index_range(midpoints))
410 {
411 const Node & next_vertex = *vertices[(k + 1) % vertices.size()];
412 midpoints[k] = edgeMidpointNode(mesh, edge_midpoints, *vertices[k], next_vertex);
413 }
414
415 // Unlike the edge midpoints, the vertex average is not shared with any other element
416 Node * const centroid = mesh.add_point(parent->vertex_average());
417
418 // The point list the template indexes into holds the corners, then the midpoints, then the
419 // centroid
420 points.clear();
421 points.insert(points.end(), vertices.begin(), vertices.end());
422 points.insert(points.end(), midpoints.begin(), midpoints.end());
423 points.push_back(centroid);
424
425 mooseAssert(n_corners == n_tri_sides || n_corners == n_quad_sides,
426 "Only triangles and quadrilaterals have a subdivision.");
427 const auto & quad_template = (n_corners == n_tri_sides) ? tri_subdivision : quad_subdivision;
428
429 quads.clear();
430 for (const auto & quad_corners : quad_template)
431 {
432 Elem * const quad = mesh.add_elem(Elem::build(libMesh::ElemType::QUAD4));
433 for (const auto k : index_range(quad_corners))
434 quad->set_node(k, points[quad_corners[k]]);
435 quad->subdomain_id() = parent->subdomain_id();
437
438 quads.push_back(quad);
439 }
440
441 // Each side of the element is split between two of the quadrilaterals, which both inherit the
442 // boundary ids that the side carried
443 for (const auto k : index_range(quads))
444 for (const auto bid : elem_side_list[side_index[k]])
445 {
446 boundary_info.add_side(quads[k], 0, bid);
447 boundary_info.add_side(quads[(k + 1) % quads.size()], 3, bid);
448 }
449
450 parent->subdomain_id() = scratch_subdomain_id;
451 }
452
453 removeScratchElements(mesh, scratch_subdomain_id);
454}
455
456void
457TriToQuadConverter::recombine(ReplicatedMesh & mesh) const
458{
459 BoundaryInfo & boundary_info = mesh.get_boundary_info();
460 const auto bdry_side_list = boundary_info.build_side_list();
461
462 // The candidate pairs are read off the neighbor links, which are all the recombination needs of
463 // the mesh preparation; the full preparation is left to the end, once the elements are final
464 if (!mesh.preparation().has_neighbor_ptrs)
465 mesh.find_neighbors();
466
467 // Keyed by edge rather than by element side so that an id registered on either of the two sides
468 // of an interior edge is found
469 std::set<std::pair<dof_id_type, dof_id_type>> boundary_edges;
470 for (const auto & [elem_id, side, _] : bdry_side_list)
471 {
472 const Elem & elem = *mesh.elem_ptr(elem_id);
473 boundary_edges.insert(edgeKey(elem.node_id(side), elem.node_id((side + 1) % n_tri_sides)));
474 }
475
476 std::vector<RecombineCandidate> candidates;
477 for (const auto & elem : mesh.active_element_ptr_range())
478 for (const auto s : make_range(elem->n_sides()))
479 {
480 const Elem * const neighbor = elem->neighbor_ptr(s);
481 // An edge on the exterior boundary has nothing to merge with, and merging across a block
482 // interface would move that interface
483 if (!neighbor || neighbor->subdomain_id() != elem->subdomain_id())
484 continue;
485 // Each interior edge is visited from its lower numbered element only
486 if (neighbor->id() < elem->id())
487 continue;
488 // Merging deletes the shared edge, which would take any sideset on it with it, and the
489 // conversion has to preserve every sideset of the input mesh
490 if (boundary_edges.count(edgeKey(elem->node_id(s), elem->node_id((s + 1) % n_tri_sides))))
491 continue;
492
493 candidates.push_back(buildCandidate(*elem, s, *neighbor));
494 }
495
496 const auto merges = greedyMatching(candidates, _eta_min);
497
498 const auto scratch_subdomain_id = MooseMeshUtils::getNextFreeSubdomainID(mesh);
499
500 for (const auto & merge : merges)
501 {
502 Elem * const first = mesh.elem_ptr(merge.first_elem_id);
503 Elem * const second = mesh.elem_ptr(merge.second_elem_id);
504
505 Elem * const quad = mesh.add_elem(Elem::build(libMesh::ElemType::QUAD4));
506 for (const auto k : index_range(merge.quad_node_ids))
507 quad->set_node(k, mesh.node_ptr(merge.quad_node_ids[k]));
508 quad->subdomain_id() = first->subdomain_id();
509
510 // The two triangles may carry different extra integers, so they always come from the lower
511 // numbered one to keep the result reproducible
513
514 for (const Elem * const parent : {first, second})
515 {
516 std::vector<std::vector<boundary_id_type>> elem_side_list;
518 bdry_side_list, parent->id(), cast_int<unsigned short>(n_tri_sides), elem_side_list);
519
520 for (const auto s : index_range(elem_side_list))
521 {
522 if (elem_side_list[s].empty())
523 continue;
524
525 const auto quad_side = quadSideOnEdge(
526 merge.quad_node_ids, parent->node_id(s), parent->node_id((s + 1) % n_tri_sides));
527 mooseAssert(quad_side != libMesh::invalid_uint,
528 "A parent side carrying boundary ids must be a side of the merged "
529 "quadrilateral, because pairs whose shared edge carries any boundary id are "
530 "never merged.");
531
532 for (const auto bid : elem_side_list[s])
533 boundary_info.add_side(quad, quad_side, bid);
534 }
535 }
536
537 first->subdomain_id() = scratch_subdomain_id;
538 second->subdomain_id() = scratch_subdomain_id;
539 }
540
541 // Once the elimination has run there is no triangle left to move
542 if (!_all_quad)
543 moveSurvivingTriangles(mesh, scratch_subdomain_id);
544
545 removeScratchElements(mesh, scratch_subdomain_id);
546
547 // Splitting every element, rather than only the triangles, is what keeps the mesh conformal: a
548 // triangle split on its own would leave the elements next to it with a node in the middle of a
549 // side. The merges come first so that the pairs that did merge only split into four
550 if (_all_quad)
552}
553
554void
556 const subdomain_id_type scratch_subdomain_id) const
557{
558 // The triangles that were not merged still carry the subdomain they came from, and they are
559 // grouped by it so that each original subdomain gets one new subdomain, in the order of the
560 // original ids
561 std::map<subdomain_id_type, std::vector<Elem *>> surviving_tris;
562 for (const auto & elem : mesh.active_element_ptr_range())
563 if (elem->type() == libMesh::ElemType::TRI3 && elem->subdomain_id() != scratch_subdomain_id)
564 surviving_tris[elem->subdomain_id()].push_back(elem);
565
566 // The scratch subdomain holds the highest id in use, so the ids past it are free; the scratch
567 // subdomain itself is emptied before the mesh is returned
568 const auto & subdomain_names = mesh.get_subdomain_name_map();
569 subdomain_id_type tri_subdomain_id = scratch_subdomain_id + 1;
570 for (const auto & [original_id, tris] : surviving_tris)
571 {
572 const auto name_it = subdomain_names.find(original_id);
573 const SubdomainName original_name =
574 (name_it == subdomain_names.end() || name_it->second.empty()) ? std::to_string(original_id)
575 : name_it->second;
576 const SubdomainName tri_subdomain_name = original_name + "_" + _tri_subdomain_name_suffix;
577
578 // Two subdomain ids sharing a name would make that name ambiguous everywhere it is used
580 paramError("tri_subdomain_name_suffix",
581 "The subdomain name '",
582 tri_subdomain_name,
583 "' that this suffix gives the unmerged triangles of subdomain ",
584 original_id,
585 " already exists in the mesh.");
586
587 for (Elem * const tri : tris)
588 tri->subdomain_id() = tri_subdomain_id;
589 mesh.set_subdomain_name(tri_subdomain_id, tri_subdomain_name);
590 ++tri_subdomain_id;
591 }
592}
registerMooseObject("MooseApp", TriToQuadConverter)
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
This method takes a space delimited list of parameter names and adds them to the specified group name...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object.
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump.
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
MeshGenerators are objects that can modify or add to an existing mesh.
static InputParameters validParams()
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition MooseBase.h:457
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition MooseBase.h:205
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
This TriToQuadConverter object converts a mesh made of TRI3 elements into a mesh made of QUAD4 elemen...
static std::vector< RecombineCandidate > greedyMatching(std::vector< RecombineCandidate > &candidates, const Real eta_min)
Select the pairs of triangles to merge, taking the highest scoring candidates first and consuming eac...
const SubdomainName _tri_subdomain_name_suffix
Suffix appended to the name of a subdomain to name the one its surviving triangles move into.
void subdivide(ReplicatedMesh &mesh) const
Replace every element of the mesh by one quadrilateral per corner, built on its centroid and its edge...
std::unique_ptr< MeshBase > & _input
Mesh that possibly comes from another generator.
const bool _all_quad
Whether the triangles that survive recombination are eliminated.
const Real _eta_min
Score below which a pair of adjacent triangles is not recombined.
static std::vector< QuadCorners > triSubdivisionTemplate()
The three quadrilaterals that a triangle is split into, one per corner, each of them built on that co...
TriToQuadConverter(const InputParameters &parameters)
static Real quadQuality(const std::array< Point, 4 > &quad_points)
Compute the quality score of a planar quadrilateral, eta = max(0, 1 - (2 / pi) * max_k |pi / 2 - alph...
void moveSurvivingTriangles(ReplicatedMesh &mesh, const subdomain_id_type scratch_subdomain_id) const
Move the triangles that recombination did not consume out of the subdomains they came from,...
const MooseEnum _algorithm
Algorithm used to build the quadrilaterals.
void recombine(ReplicatedMesh &mesh) const
Replace the highest scoring pairs of adjacent triangles of the mesh by quadrilaterals.
static InputParameters validParams()
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
static RecombineCandidate buildCandidate(const Elem &elem, const unsigned int side, const Elem &neighbor)
Build the merge candidate for the two triangles that share a side.
static std::vector< QuadCorners > quadSubdivisionTemplate()
The four quadrilaterals that a quadrilateral is split into, one per corner, each of them built on tha...
MeshBase & mesh
void elementBoundaryInfoCollector(const std::vector< libMesh::BoundaryInfo::BCTuple > &bdry_side_list, const dof_id_type elem_id, const unsigned short n_elem_sides, std::vector< std::vector< boundary_id_type > > &elem_side_list)
Collect the boundary information of the given element in a mesh.
void retainEEID(MeshBase &mesh, const dof_id_type &elem_id, Elem *new_elem_ptr)
Retain the extra integer of the original element in a new element.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
SubdomainID getSubdomainID(const SubdomainName &subdomain_name, const MeshBase &mesh)
Gets the subdomain ID associated with the given SubdomainName.
const SubdomainID INVALID_BLOCK_ID
Definition MooseTypes.C:20
libMesh::Real signedArea2D(const libMesh::Point &pt1, const libMesh::Point &pt2, const libMesh::Point &pt3)
Twice the signed area of a triangle in the xy plane, which is positive when its corners are ordered c...
std::string enum_to_string(const T e)
auto index_range(const T &sizable)
const unsigned int invalid_uint
const Real pi
uint8_t dof_id_type
IntRange< T > make_range(T beg, T end)
A pair of adjacent triangles that the recombination algorithm can merge into one quadrilateral.
dof_id_type first_elem_id
Id of the lower numbered triangle of the pair.
std::array< dof_id_type, 4 > quad_node_ids
Node ids of the quadrilateral, in counter-clockwise order.
dof_id_type second_elem_id
Id of the higher numbered triangle of the pair.
Real eta
Quality score of the quadrilateral that the two triangles would form.