https://mooseframework.inl.gov
Loading...
Searching...
No Matches
MeshRepairGenerator.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 "MeshRepairGenerator.h"
11#include "CastUniquePointer.h"
12#include "MooseMeshUtils.h"
13
14#include "libmesh/mesh_tools.h"
15#include "libmesh/mesh_modification.h"
16#include "libmesh/face_c0polygon.h"
17
19
22{
23
26 "Mesh generator to perform various improvement / fixing operations on an input mesh");
27 params.addRequiredParam<MeshGeneratorName>("input",
28 "Name of the mesh generator providing the mesh");
29
30 params.addParam<bool>("fix_node_overlap", false, "Whether to merge overlapping nodes");
31 params.addParam<Real>(
32 "node_overlap_tol", 1e-8, "Absolute tolerance for merging overlapping nodes");
33
34 params.addParam<bool>(
35 "fix_elements_orientation", false, "Whether to flip elements with negative volumes");
36
37 params.addParam<bool>("separate_blocks_by_element_types",
38 false,
39 "Create new blocks if multiple element types are present in a block");
40
41 params.addParam<bool>("merge_boundary_ids_with_same_name",
42 false,
43 "Merge boundaries if they have the same name but different boundary IDs");
44
45 params.addParam<bool>(
46 "renumber_contiguously",
47 false,
48 "Whether to renumber the elements of the mesh so the numbering is contiguous");
49
50 params.addParam<bool>(
51 "split_nonconvex_polygons", false, "Split non-convex polygons to form convex ones");
52
53 return params;
54}
55
57 : MeshGenerator(parameters),
58 _input(getMesh("input")),
59 _fix_overlapping_nodes(getParam<bool>("fix_node_overlap")),
60 _node_overlap_tol(getParam<Real>("node_overlap_tol")),
61 _fix_element_orientation(getParam<bool>("fix_elements_orientation")),
62 _elem_type_separation(getParam<bool>("separate_blocks_by_element_types")),
63 _boundary_id_merge(getParam<bool>("merge_boundary_ids_with_same_name")),
64 _split_nonconvex_polygons(getParam<bool>("split_nonconvex_polygons"))
65{
67 !_boundary_id_merge && !getParam<bool>("renumber_contiguously") && !_split_nonconvex_polygons)
68 mooseError("No specific item to fix. Are any of the parameters misspelled?");
69}
70
71std::unique_ptr<MeshBase>
73{
74 std::unique_ptr<MeshBase> mesh = std::move(_input);
75
76 // We're trying to repair a potentially broken mesh; we'll just
77 // start with a full prepare rather than trying to be efficient and
78 // risking missing something.
79 mesh->prepare_for_use();
80
81 // Blanket ban on distributed. This can be relaxed for some operations if needed
82 if (!mesh->is_serial())
83 mooseError("MeshRepairGenerator requires a serial mesh. The mesh should not be distributed.");
84
87
88 // Flip orientation of elements to keep positive volumes
90 MeshTools::Modification::orient_elements(*mesh);
91
92 // Disambiguate any block that has elements of multiple types
95
96 // Assign a single boundary ID to boundaries that have the same name
99
100 // Renumber the mesh despite any mesh flag
101 if (getParam<bool>("renumber_contiguously"))
102 {
103 const auto prev_status = mesh->allow_renumbering();
104 mesh->allow_renumbering(true);
105 mesh->renumber_nodes_and_elements();
106 mesh->allow_renumbering(prev_status);
107 }
108
109 // Split non-convex polygons to make convex ones
112
113 mesh->unset_is_prepared();
114 return mesh;
115}
116
117void
118MeshRepairGenerator::fixOverlappingNodes(std::unique_ptr<MeshBase> & mesh) const
119{
120 unsigned int num_fixed_nodes = 0;
121 auto pl = mesh->sub_point_locator();
122 pl->set_close_to_point_tol(_node_overlap_tol);
123
124 std::unordered_set<dof_id_type> nodes_removed;
125 // loop on nodes
126 for (auto & node : mesh->node_ptr_range())
127 {
128 // this node has already been removed
129 if (nodes_removed.count(node->id()))
130 continue;
131
132 // find all the elements around this node
133 std::set<const Elem *> elements;
134 (*pl)(*node, elements);
135
136 for (auto & elem : elements)
137 {
138 bool found = false;
139 for (auto & elem_node : elem->node_ref_range())
140 {
141 if (node->id() == elem_node.id())
142 {
143 found = true;
144 break;
145 }
146 }
147 if (!found)
148 {
149 for (auto & elem_node : elem->node_ref_range())
150 {
151 if (elem_node.id() == node->id())
152 continue;
153 const Real tol = _node_overlap_tol;
154 // Compares the coordinates
155 const auto x_node = (*node)(0);
156 const auto x_elem_node = elem_node(0);
157 const auto y_node = (*node)(1);
158 const auto y_elem_node = elem_node(1);
159 const auto z_node = (*node)(2);
160 const auto z_elem_node = elem_node(2);
161
162 if (MooseUtils::absoluteFuzzyEqual(x_node, x_elem_node, tol) &&
163 MooseUtils::absoluteFuzzyEqual(y_node, y_elem_node, tol) &&
164 MooseUtils::absoluteFuzzyEqual(z_node, z_elem_node, tol))
165 {
166 // Merging two nodes from the same element is almost never a good idea
167 if (elem->get_node_index(node) != libMesh::invalid_uint)
168 {
169 _console << "Two overlapping nodes in element " << elem->id() << " right by "
170 << elem->vertex_average() << ".\n They will not be stitched" << std::endl;
171 continue;
172 }
173
174 // Coordinates are the same but it's not the same node
175 // Replace the node in the element
176 const_cast<Elem *>(elem)->set_node(elem->get_node_index(&elem_node), node);
177 nodes_removed.insert(elem_node.id());
178
179 num_fixed_nodes++;
180 if (num_fixed_nodes < 10)
181 _console << "Stitching nodes " << *node << " and " << elem_node
182 << std::endl;
183 else if (num_fixed_nodes == 10)
184 _console << "Node stitching will now proceed silently." << std::endl;
185 }
186 }
187 }
188 }
189 }
190 _console << "Number of overlapping nodes which got merged: " << num_fixed_nodes << std::endl;
191 if (mesh->allow_renumbering())
192 mesh->renumber_nodes_and_elements();
193 else
194 {
195 mesh->remove_orphaned_nodes();
196 mesh->update_parallel_id_counts();
197 }
198}
199
200void
201MeshRepairGenerator::separateSubdomainsByElementType(std::unique_ptr<MeshBase> & mesh) const
202{
203 std::set<subdomain_id_type> ids;
204 mesh->subdomain_ids(ids);
205 // loop on sub-domain
206 for (const auto id : ids)
207 {
208 // Gather all the element types and blocks
209 // ElemType defines an enum for geometric element types
210 std::set<ElemType> types;
211 // loop on elements within this sub-domain
212 for (auto & elem : mesh->active_subdomain_elements_ptr_range(id))
213 types.insert(elem->type());
214
215 // This call must be performed on all processes
216 auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
217
218 if (types.size() > 1)
219 {
220 subdomain_id_type i = 0;
221 for (const auto type : types)
222 {
223 auto new_id = next_block_id + i++;
224 // Create blocks when a block has multiple element types
225 mesh->set_subdomain_name(new_id, mesh->subdomain_name(id) + "_" + Moose::stringify(type));
226
227 // Re-assign elements to the new blocks
228 for (auto elem : mesh->active_subdomain_elements_ptr_range(id))
229 if (elem->type() == type)
230 elem->subdomain_id() = new_id;
231 }
232 }
233 }
234}
235
236void
237MeshRepairGenerator::splitNonConvexPolygons(std::unique_ptr<MeshBase> & mesh) const
238{
239 // Counters to keep track of what happened in the splitting
240 unsigned int num_polygons = 0;
241 unsigned int num_nonconvex = 0;
242 unsigned int num_triangulated = 0;
243 std::vector<Elem *> elems_to_delete;
244 std::vector<std::unique_ptr<libMesh::C0Polygon>> elements_to_add_to_mesh;
245
246 // Missing parallel packing for polys
247 if (!mesh->is_serial())
248 mooseError("MeshRepairGenerator requires a serial mesh for this operation. "
249 "The mesh should not be distributed.");
250
251 for (auto elem : mesh->element_ptr_range())
252 {
253 // We are not splitting non-convex quads. Maybe later.
254 if (elem->type() == libMesh::C0POLYGON)
255 {
256 num_polygons++;
257
258 // Lambda function to determine if an element is convex
259 auto is_convex = [](const Elem * elem,
260 std::vector<std::pair<int, Real>> & obtuse_angle_nodes) -> bool
261 {
262 mooseAssert(elem, "Should have an elem");
263 mooseAssert(obtuse_angle_nodes.empty(), "Should not be empty");
264
265 // Find the normal to the element plane
266 const auto elem_centroid = elem->vertex_average();
267 const auto n_nodes = elem->n_nodes();
268 Point plane_normal;
269 // Two nodes could be aligned with the centroid in a non-convex polygon
270 for (const auto i : make_range(n_nodes))
271 {
272 const auto v1 = elem_centroid - elem->point(i);
273 const auto v2 = elem_centroid - elem->point((i + 1) % n_nodes);
274 if (!MooseUtils::absoluteFuzzyEqual((v1.cross(v2)).norm_sq(), 0))
275 {
276 plane_normal = v1.cross(v2).unit();
277 break;
278 }
279 }
280
281 // Look for angles > 180 deg with the cross product
282 bool convex = true;
283 for (const auto i : make_range(n_nodes))
284 {
285 const auto n1 = elem->point(i);
286 const auto n2 = elem->point((i + 1) % n_nodes);
287 const auto n3 = elem->point((i + 2) % n_nodes);
288 const auto top_dir = (n2 - n1).cross(n3 - n2);
289
290 if (plane_normal * top_dir <= 0)
291 {
292 convex = false;
293 if (!MooseUtils::absoluteFuzzyEqual(plane_normal * top_dir, 0))
294 obtuse_angle_nodes.push_back(
295 std::make_pair<int, Real>(i, plane_normal * top_dir / top_dir.norm()));
296 // n1, n2 and n3 are likely aligned
297 else
298 obtuse_angle_nodes.push_back(std::make_pair<int, Real>(i, -1));
299 }
300 }
301 return convex;
302 };
303
304 std::vector<std::pair<int, Real>> parent_obtuse_angles;
305 const auto convex = is_convex(elem, parent_obtuse_angles);
306
307 if (!convex)
308 num_nonconvex++;
309 // Move to next one if it is convex, no need to fix it
310 else
311 continue;
312
313 // Lambda function to split a polygon into two polygons at the specified nodes
314 // TODO: we could try to split a polygon into more than two polygons
315 auto cut_polygon = [&mesh](const Elem * elem, unsigned int node_i_cut1, unsigned node_i_cut2)
316 -> std::pair<std::unique_ptr<libMesh::C0Polygon>, std::unique_ptr<libMesh::C0Polygon>>
317 {
318 const auto cut1 = std::min(node_i_cut1, node_i_cut2);
319 const auto cut2 = std::max(node_i_cut1, node_i_cut2);
320
321 // Count the number of sides on each side of the cut
322 const auto ns1 = cut2 - cut1 + 1;
323 const auto ns2 = elem->n_nodes() - ns1 + 2;
324 mooseAssert(ns1 >= 3, "Should cut at least a triangle");
325 mooseAssert(ns2 >= 3, "Should cut at least a triangle");
326
327 // Create the children and add their nodes
328 auto child1 = std::make_unique<libMesh::C0Polygon>(ns1);
329 for (const auto i_n : make_range(cut1, cut2 + 1))
330 child1->set_node(i_n - cut1, mesh->node_ptr(elem->node_ptr(i_n)->id()));
331
332 auto child2 = std::make_unique<libMesh::C0Polygon>(ns2);
333 for (const auto i_n : make_range(cut2, elem->n_nodes() + cut1 + 1))
334 child2->set_node((i_n - cut2) % child2->n_nodes(),
335 mesh->node_ptr(elem->node_ptr(i_n % elem->n_nodes())->id()));
336
337 return std::make_pair(std::move(child1), std::move(child2));
338 };
339
340 // If not convex, try to split it.
341 // Let's first try to split it recursively at every large angle
342 // NOTE: These vectors of unique pointer keep the memory ownership of the elements
343 // during the loop attempting to fix the polygon with the heuristic below
344 bool failed = false;
345 std::vector<std::unique_ptr<libMesh::C0Polygon>> elements_to_add_to_mesh_temp;
346 std::vector<std::pair<std::unique_ptr<Elem>, std::vector<std::pair<int, Real>>>> elems_to_cut;
347
348 // Create a clone of the first element to simplify logic
349 std::unique_ptr<libMesh::C0Polygon> base_elem =
350 std::make_unique<libMesh::C0Polygon>(elem->n_nodes());
351 for (const auto i_n : make_range(elem->n_nodes()))
352 base_elem->set_node(i_n, elem->node_ptr(i_n));
353 elems_to_cut.push_back(std::make_pair(std::move(base_elem), parent_obtuse_angles));
354
355 while (!failed && !elems_to_cut.empty())
356 {
357 auto & [current_elem, large_angles] = elems_to_cut.back();
358
359 // Split the element on one side at the node with the worst obtuse angle
360 // TODO: try all of them instead to see if a single cut can fix the element
361 auto worst_angle_it =
362 std::min_element(large_angles.begin(),
363 large_angles.end(),
364 [](std::pair<int, Real> lhs, std::pair<int, Real> rhs) -> bool
365 { return lhs.second < rhs.second; });
366 unsigned int worst_angle_i = (*worst_angle_it).first;
367 const auto n_nodes = current_elem->n_nodes();
368
369 // Split the element on the other side at roughly the opposite node
370 // TODO: we could try all of the 'other' nodes here too to see if a single cut can fix the
371 // element NOTE: if trying multiple cuts (not implemented), we should remove the opposite
372 // from the large_angles
373 const auto opposite = ((worst_angle_i + n_nodes / 2) % n_nodes);
374
375 auto [child1, child2] = cut_polygon(current_elem.get(), worst_angle_i, opposite);
376
377 // Check the two fragments
378 std::vector<std::pair<int, Real>> child1_large_angles;
379 bool is_convex1 = is_convex(child1.get(), child1_large_angles);
380 std::vector<std::pair<int, Real>> child2_large_angles;
381 bool is_convex2 = is_convex(child2.get(), child2_large_angles);
382
383 // Can we keep going?
384 if (child1->n_nodes() < 4 && !is_convex1)
385 failed = true;
386 if (child2->n_nodes() < 4 && !is_convex2)
387 failed = true;
388
389 // We managed to cut the elements into a convex part, but it's flipped
390 // so the element is likely "outside" of the starting element
391 if (is_convex1 && child1->volume() <= 0)
392 failed = true;
393 if (is_convex2 && child2->volume() <= 0)
394 failed = true;
395
396 // Current element is done
397 // NOTE: if trying multiple cuts (not implemented), we should not erase yet, we should
398 // instead only remove the cut we tried from the 'large_angles' vector used to pick cuts
399 large_angles.erase(worst_angle_it);
400 elems_to_cut.pop_back();
401
402 // Add non convex elements to the list of elements to cut
403 if (!is_convex1)
404 elems_to_cut.push_back(std::make_pair(std::move(child1), child1_large_angles));
405 if (!is_convex2)
406 elems_to_cut.push_back(std::make_pair(std::move(child2), child2_large_angles));
407
408 // Add convex elements to the mesh
409 // NOTE: if trying multiple cuts, we should not do this yet
410 if (is_convex1)
411 elements_to_add_to_mesh_temp.push_back(std::move(child1));
412 if (is_convex2)
413 elements_to_add_to_mesh_temp.push_back(std::move(child2));
414 }
415 bool fixed_it = !failed;
416
417 // Keep all the cut elements, to be added at the end to avoid invalidating iterators
418 if (fixed_it)
419 for (auto & elem_uptr : elements_to_add_to_mesh_temp)
420 {
421 elem_uptr->inherit_data_from(*elem);
422 elements_to_add_to_mesh.push_back(std::move(elem_uptr));
423 }
424
425 // Heuristic did not work, just use a triangulation
426 // NOTE: This is not the triangulation of the polygon. It is simple though
427 if (!fixed_it)
428 {
429 const auto centroid_node = mesh->add_point(elem->true_centroid());
430 num_triangulated++;
431 const auto * poly = dynamic_cast<libMesh::C0Polygon *>(elem);
432 const auto n_sides = poly->n_sides();
433 for (const auto i_tr : make_range(n_sides))
434 {
435 auto new_elem = std::make_unique<libMesh::C0Polygon>(3);
436 new_elem->set_node(0, mesh->node_ptr(elem->node_ptr(i_tr)->id()));
437 new_elem->set_node(1, centroid_node);
438 new_elem->set_node(2, mesh->node_ptr(elem->node_ptr((i_tr + 1) % n_sides)->id()));
439
440 // Check for degenerate case
441 if (MooseUtils::absoluteFuzzyEqual(
442 (*(Point *)centroid_node - *(Point *)elem->node_ptr(i_tr))
443 .cross(*(Point *)centroid_node -
444 *(Point *)elem->node_ptr((i_tr + 1) % n_sides))
445 .norm_sq(),
446 0))
447 mooseError("Manual triangulation failed as two consecutive nodes are aligned with the "
448 "centroid");
449
450 new_elem->inherit_data_from(*elem);
451 elements_to_add_to_mesh.push_back(std::move(new_elem));
452 }
453 }
454 // Element got fixed, can be deleted after (can't while using the range)
455 elems_to_delete.push_back(elem);
456 }
457 }
458 // Delete the original element
459 for (auto elem : elems_to_delete)
460 mesh->delete_elem(elem);
461 // Add the new ones
462 for (auto & new_elem_ptr : elements_to_add_to_mesh)
463 mesh->add_elem(std::move(new_elem_ptr));
464
465 _console << "Number of non-convex polygons which got split into convex polygons: "
466 << num_nonconvex << std::endl;
467 if (num_triangulated)
468 _console << "Number of non-convex polygons split using a triangulation: " << num_triangulated
469 << ", using heuristic: " << num_nonconvex - num_triangulated << std::endl;
470 if (!num_polygons)
471 mooseWarning("No C0 polygons in mesh: the polyon convexity fix did nothing");
472}
registerMooseObject("MooseApp", MeshRepairGenerator)
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
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.
MeshGenerators are objects that can modify or add to an existing mesh.
static InputParameters validParams()
Mesh generator to perform various improvement / fixing operations on an input mesh.
MeshRepairGenerator(const InputParameters &parameters)
const bool _fix_element_orientation
whether to flip element orientation such that they no longer have a negative volume
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
const Real _node_overlap_tol
tolerance for merging overlapping nodes
std::unique_ptr< MeshBase > & _input
the input mesh
void separateSubdomainsByElementType(std::unique_ptr< MeshBase > &mesh) const
Separate subdomain by element type because some output format (Exodus) do not support mixed element t...
const bool _elem_type_separation
whether to split subdomains using each element's type
void splitNonConvexPolygons(std::unique_ptr< MeshBase > &mesh) const
Splits non-convex polygonal elements to keep only convex elements.
static InputParameters validParams()
void fixOverlappingNodes(std::unique_ptr< MeshBase > &mesh) const
Removes nodes that overlap.
const bool _boundary_id_merge
Whether to merge boundaries with the same name but different ID.
const bool _fix_overlapping_nodes
fixing mesh by deleting overlapping nodes
const bool _split_nonconvex_polygons
Whether to split non-convex polygons.
const std::string & type() const
Get the type of this class.
Definition MooseBase.h:93
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition MooseBase.h:271
void mooseWarning(Args &&... args) const
MeshBase & mesh
void mergeBoundaryIDsWithSameName(MeshBase &mesh)
Merges the boundary IDs of boundaries that have the same names but different IDs.
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
Checks input mesh and returns max(block ID) + 1, which represents a block ID that is not currently in...
std::string stringify(const T &t)
conversion to string
Definition Conversion.h:64
const unsigned int invalid_uint
const dof_id_type n_nodes