https://mooseframework.inl.gov
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 
25  params.addClassDescription(
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 
71 std::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 
117 void
118 MeshRepairGenerator::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 
200 void
201 MeshRepairGenerator::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->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 
236 void
237 MeshRepairGenerator::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 }
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:242
const unsigned int invalid_uint
auto norm_sq(const T &a)
Mesh generator to perform various improvement / fixing operations on an input mesh.
const bool _split_nonconvex_polygons
Whether to split non-convex polygons.
MeshBase & mesh
registerMooseObject("MooseApp", MeshRepairGenerator)
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
void fixOverlappingNodes(std::unique_ptr< MeshBase > &mesh) const
Removes nodes that overlap.
void splitNonConvexPolygons(std::unique_ptr< MeshBase > &mesh) const
Splits non-convex polygonal elements to keep only convex elements.
const bool _boundary_id_merge
Whether to merge boundaries with the same name but different ID.
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...
auto max(const L &left, const R &right)
void mooseWarning(Args &&... args) const
const dof_id_type n_nodes
const bool _elem_type_separation
whether to split subdomains using each element&#39;s type
const bool _fix_overlapping_nodes
fixing mesh by deleting overlapping nodes
const std::string & type() const
Get the type of this class.
Definition: MooseBase.h:93
void separateSubdomainsByElementType(std::unique_ptr< MeshBase > &mesh) const
Separate subdomain by element type because some output format (Exodus) do not support mixed element t...
static InputParameters validParams()
Definition: MeshGenerator.C:23
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
const bool _fix_element_orientation
whether to flip element orientation such that they no longer have a negative volume ...
std::unique_ptr< MeshBase > & _input
the input mesh
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const Real _node_overlap_tol
tolerance for merging overlapping nodes
IntRange< T > make_range(T beg, T end)
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:281
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 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...
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
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...
auto min(const L &left, const R &right)
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:33
MeshRepairGenerator(const InputParameters &parameters)
static InputParameters validParams()