https://mooseframework.inl.gov
MeshDiagnosticsGenerator.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 
11 #include "MooseMeshUtils.h"
12 #include "CastUniquePointer.h"
13 #include "MeshCoarseningUtils.h"
15 
16 #include "libmesh/mesh_tools.h"
17 #include "libmesh/mesh_refinement.h"
18 #include "libmesh/fe.h"
19 #include "libmesh/quadrature_gauss.h"
20 #include "libmesh/face_tri3.h"
21 #include "libmesh/cell_tet4.h"
22 #include "libmesh/face_quad4.h"
23 #include "libmesh/cell_hex8.h"
24 #include "libmesh/string_to_enum.h"
25 #include "libmesh/enum_point_locator_type.h"
26 
27 // C++
28 #include <cstring>
29 
31 
34 {
35 
37 
38  params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to diagnose");
39  params.addClassDescription("Runs a series of diagnostics on the mesh to detect potential issues "
40  "such as unsupported features");
41 
42  // Options for the output level
43  MooseEnum chk_option("NO_CHECK INFO WARNING ERROR", "NO_CHECK");
44 
45  params.addParam<MooseEnum>(
46  "examine_sidesets_orientation",
47  chk_option,
48  "whether to check that sidesets are consistently oriented using neighbor subdomains. If a "
49  "sideset is inconsistently oriented within a subdomain, this will not be detected");
50  params.addParam<MooseEnum>(
51  "check_for_watertight_sidesets",
52  chk_option,
53  "whether to check for external sides that are not assigned to any sidesets");
54  params.addParam<MooseEnum>(
55  "check_for_watertight_nodesets",
56  chk_option,
57  "whether to check for external nodes that are not assigned to any nodeset");
58  params.addParam<std::vector<BoundaryName>>(
59  "boundaries_to_check",
60  {},
61  "Names boundaries that should form a watertight envelope around the mesh. Defaults to all "
62  "the boundaries combined.");
63  params.addParam<MooseEnum>(
64  "examine_element_volumes", chk_option, "whether to examine volume of the elements");
65  params.addParam<Real>("minimum_element_volumes", 1e-16, "minimum size for element volume");
66  params.addParam<Real>("maximum_element_volumes", 1e16, "Maximum size for element volume");
67 
68  params.addParam<MooseEnum>("examine_element_types",
69  chk_option,
70  "whether to look for multiple element types in the same sub-domain");
71  params.addParam<MooseEnum>(
72  "examine_element_overlap", chk_option, "whether to find overlapping elements");
73  params.addParam<MooseEnum>(
74  "examine_nonplanar_sides", chk_option, "whether to check element sides are planar");
75  params.addParam<MooseEnum>("examine_non_conformality",
76  chk_option,
77  "whether to examine the conformality of elements in the mesh");
78  params.addParam<MooseEnum>("examine_non_matching_edges",
79  chk_option,
80  "Whether to check if there are any intersecting edges");
81  params.addParam<Real>("intersection_tol", TOLERANCE, "tolerence for intersecting edges");
82  params.addParam<Real>("nonconformal_tol", TOLERANCE, "tolerance for element non-conformality");
83  params.addParam<MooseEnum>(
84  "search_for_adaptivity_nonconformality",
85  chk_option,
86  "whether to check for non-conformality arising from adaptive mesh refinement");
87  params.addParam<MooseEnum>("check_local_jacobian",
88  chk_option,
89  "whether to check the local Jacobian for bad (non-positive) values");
90  params.addParam<MooseEnum>(
91  "check_polygons", chk_option, "Whether to check that all C0 polygons are convex");
92  params.addParam<unsigned int>(
93  "log_length_limit",
94  10,
95  "How many problematic element/nodes/sides/etc are explicitly reported on by each check");
96  return params;
97 }
98 
100  : MeshGenerator(parameters),
101  _input(getMesh("input")),
102  _check_sidesets_orientation(getParam<MooseEnum>("examine_sidesets_orientation")),
103  _check_watertight_sidesets(getParam<MooseEnum>("check_for_watertight_sidesets")),
104  _check_watertight_nodesets(getParam<MooseEnum>("check_for_watertight_nodesets")),
105  _watertight_boundary_names(getParam<std::vector<BoundaryName>>("boundaries_to_check")),
106  _check_element_volumes(getParam<MooseEnum>("examine_element_volumes")),
107  _min_volume(getParam<Real>("minimum_element_volumes")),
108  _max_volume(getParam<Real>("maximum_element_volumes")),
109  _check_element_types(getParam<MooseEnum>("examine_element_types")),
110  _check_element_overlap(getParam<MooseEnum>("examine_element_overlap")),
111  _check_non_planar_sides(getParam<MooseEnum>("examine_nonplanar_sides")),
112  _check_non_conformal_mesh(getParam<MooseEnum>("examine_non_conformality")),
113  _non_conformality_tol(getParam<Real>("nonconformal_tol")),
114  _check_non_matching_edges(getParam<MooseEnum>("examine_non_matching_edges")),
115  _non_matching_edge_tol(getParam<Real>("intersection_tol")),
116  _check_adaptivity_non_conformality(
117  getParam<MooseEnum>("search_for_adaptivity_nonconformality")),
118  _check_local_jacobian(getParam<MooseEnum>("check_local_jacobian")),
119  _check_polygons(getParam<MooseEnum>("check_polygons")),
120  _num_outputs(getParam<unsigned int>("log_length_limit"))
121 {
122  // Check that no secondary parameters have been passed with the main check disabled
123  if ((isParamSetByUser("minimum_element_volumes") ||
124  isParamSetByUser("maximum_element_volumes")) &&
125  _check_element_volumes == "NO_CHECK")
126  paramError("examine_element_volumes",
127  "You must set this parameter to true to trigger element size checks");
128  if (isParamSetByUser("nonconformal_tol") && _check_non_conformal_mesh == "NO_CHECK")
129  paramError("examine_non_conformality",
130  "You must set this parameter to true to trigger mesh conformality check");
131  if (_check_sidesets_orientation == "NO_CHECK" && _check_watertight_sidesets == "NO_CHECK" &&
132  _check_watertight_nodesets == "NO_CHECK" && _check_element_volumes == "NO_CHECK" &&
133  _check_element_types == "NO_CHECK" && _check_element_overlap == "NO_CHECK" &&
134  _check_non_planar_sides == "NO_CHECK" && _check_non_conformal_mesh == "NO_CHECK" &&
135  _check_adaptivity_non_conformality == "NO_CHECK" && _check_local_jacobian == "NO_CHECK" &&
136  _check_non_matching_edges == "NO_CHECK" && _check_polygons == "NO_CHECK")
137  mooseError("You need to turn on at least one diagnostic. Did you misspell a parameter?");
138 }
139 
140 std::unique_ptr<MeshBase>
142 {
143  std::unique_ptr<MeshBase> mesh = std::move(_input);
144 
145  // Most of the checks assume we have the full mesh
146  if (!mesh->is_serial())
147  mooseError("Only serialized meshes are supported");
148 
149  // We prepare for use at the beginning to facilitate diagnosis
150  // This deliberately does not trust the mesh to know whether it's already prepared or not
151  mesh->prepare_for_use();
152 
153  // check that specified boundary is valid, convert BoundaryNames to BoundaryIDs, and sort
154  for (const auto & boundary_name : _watertight_boundary_names)
155  {
156  if (!MooseMeshUtils::hasBoundaryName(*mesh, boundary_name))
157  mooseError("User specified boundary_to_check \'", boundary_name, "\' does not exist");
158  }
160  std::sort(_watertight_boundaries.begin(), _watertight_boundaries.end());
161 
162  if (_check_sidesets_orientation != "NO_CHECK")
164 
165  if (_check_watertight_sidesets != "NO_CHECK")
167 
168  if (_check_watertight_nodesets != "NO_CHECK")
170 
171  if (_check_element_volumes != "NO_CHECK")
173 
174  if (_check_element_types != "NO_CHECK")
176 
177  if (_check_element_overlap != "NO_CHECK")
179 
180  if (_check_non_planar_sides != "NO_CHECK")
182 
183  if (_check_non_conformal_mesh != "NO_CHECK")
185 
186  if (_check_adaptivity_non_conformality != "NO_CHECK")
188 
189  if (_check_local_jacobian != "NO_CHECK")
191 
192  if (_check_non_matching_edges != "NO_CHECK")
194 
195  if (_check_polygons != "NO_CHECK")
197 
198  return dynamic_pointer_cast<MeshBase>(mesh);
199 }
200 
201 void
202 MeshDiagnosticsGenerator::checkSidesetsOrientation(const std::unique_ptr<MeshBase> & mesh) const
203 {
204  auto & boundary_info = mesh->get_boundary_info();
205  auto side_tuples = boundary_info.build_side_list();
206 
207  for (const auto bid : boundary_info.get_boundary_ids())
208  {
209  // This check only looks at subdomains on both sides of the sideset
210  // it wont pick up if the sideset is changing orientations while inside of a subdomain
211  std::set<std::pair<subdomain_id_type, subdomain_id_type>> block_neighbors;
212  for (const auto index : index_range(side_tuples))
213  {
214  if (std::get<2>(side_tuples[index]) != bid)
215  continue;
216  const auto elem_ptr = mesh->elem_ptr(std::get<0>(side_tuples[index]));
217  if (elem_ptr->neighbor_ptr(std::get<1>(side_tuples[index])))
218  block_neighbors.insert(std::make_pair(
219  elem_ptr->subdomain_id(),
220  elem_ptr->neighbor_ptr(std::get<1>(side_tuples[index]))->subdomain_id()));
221  }
222 
223  // Check that there is no flipped pair
224  std::set<std::pair<subdomain_id_type, subdomain_id_type>> flipped_pairs;
225  for (const auto & block_pair_1 : block_neighbors)
226  for (const auto & block_pair_2 : block_neighbors)
227  if (block_pair_1 != block_pair_2)
228  if (block_pair_1.first == block_pair_2.second &&
229  block_pair_1.second == block_pair_2.first)
230  flipped_pairs.insert(block_pair_1);
231 
232  std::string message;
233  const std::string sideset_full_name =
234  boundary_info.sideset_name(bid) + " (" + std::to_string(bid) + ")";
235  if (!flipped_pairs.empty())
236  {
237  std::string block_pairs_string = "";
238  for (const auto & pair : flipped_pairs)
239  block_pairs_string +=
240  " [" + mesh->subdomain_name(pair.first) + " (" + std::to_string(pair.first) + "), " +
241  mesh->subdomain_name(pair.second) + " (" + std::to_string(pair.second) + ")]";
242  message = "Inconsistent orientation of sideset " + sideset_full_name +
243  " with regards to subdomain pairs" + block_pairs_string;
244  }
245  else
246  message = "Sideset " + sideset_full_name +
247  " is consistently oriented with regards to the blocks it neighbors";
248 
249  diagnosticsLog(message, _check_sidesets_orientation, flipped_pairs.size());
250 
251  // Now check that there is no sideset radically flipping from one side's normal to another
252  // side next to it, in the same sideset
253  // We'll consider pi / 2 to be the most steep angle we'll pass
254  unsigned int num_normals_flipping = 0;
255  Real steepest_side_angles = 0;
256  for (const auto & [elem_id, side_id, side_bid] : side_tuples)
257  {
258  if (side_bid != bid)
259  continue;
260  const auto & elem_ptr = mesh->elem_ptr(elem_id);
261 
262  // Get side normal
263  const std::unique_ptr<const Elem> face = elem_ptr->build_side_ptr(side_id);
264  std::unique_ptr<libMesh::FEBase> fe(
265  libMesh::FEBase::build(elem_ptr->dim(), libMesh::FEType(elem_ptr->default_order())));
266  libMesh::QGauss qface(elem_ptr->dim() - 1, CONSTANT);
267  fe->attach_quadrature_rule(&qface);
268  const auto & normals = fe->get_normals();
269  fe->reinit(elem_ptr, side_id);
270  mooseAssert(normals.size() == 1, "We expected only one normal here");
271  const auto & side_normal = normals[0];
272 
273  // Compare to the sideset normals of neighbor sides in that sideset
274  for (const auto neighbor : elem_ptr->neighbor_ptr_range())
275  if (neighbor)
276  for (const auto neigh_side_index : neighbor->side_index_range())
277  {
278  // Check that the neighbor side is also in the sideset being examined
279  if (!boundary_info.has_boundary_id(neighbor, neigh_side_index, bid))
280  continue;
281 
282  // We re-init everything for the neighbor in case it's a different dimension
283  std::unique_ptr<libMesh::FEBase> fe_neighbor(libMesh::FEBase::build(
284  neighbor->dim(), libMesh::FEType(neighbor->default_order())));
285  libMesh::QGauss qface(neighbor->dim() - 1, CONSTANT);
286  fe_neighbor->attach_quadrature_rule(&qface);
287  const auto & neigh_normals = fe_neighbor->get_normals();
288  fe_neighbor->reinit(neighbor, neigh_side_index);
289  mooseAssert(neigh_normals.size() == 1, "We expected only one normal here");
290  const auto & neigh_side_normal = neigh_normals[0];
291 
292  // Check the angle by computing the dot product
293  if (neigh_side_normal * side_normal <= 0)
294  {
295  num_normals_flipping++;
296  steepest_side_angles =
297  std::max(std::acos(neigh_side_normal * side_normal), steepest_side_angles);
298  if (num_normals_flipping <= _num_outputs)
299  _console << "Side normals changed by more than pi/2 for sideset "
300  << sideset_full_name << " between side " << side_id << " of element "
301  << elem_ptr->id() << " and side " << neigh_side_index
302  << " of neighbor element " << neighbor->id() << std::endl;
303  else if (num_normals_flipping == _num_outputs + 1)
304  _console << "Maximum output reached for sideset normal flipping check. Silencing "
305  "output from now on"
306  << std::endl;
307  }
308  }
309  }
310 
311  if (num_normals_flipping)
312  message = "Sideset " + sideset_full_name +
313  " has two neighboring sides with a very large angle. Largest angle detected: " +
314  std::to_string(steepest_side_angles) + " rad (" +
315  std::to_string(steepest_side_angles * 180 / libMesh::pi) + " degrees).";
316  else
317  message = "Sideset " + sideset_full_name +
318  " does not appear to have side-to-neighbor-side orientation flips. All neighbor "
319  "sides normal differ by less than pi/2";
320 
321  diagnosticsLog(message, _check_sidesets_orientation, num_normals_flipping);
322  }
323 }
324 
325 void
326 MeshDiagnosticsGenerator::checkWatertightSidesets(const std::unique_ptr<MeshBase> & mesh) const
327 {
328  /*
329  Algorithm Overview:
330  1) Loop through all elements
331  2) For each element loop through all its sides
332  3) If it has no neighbors it's an external side
333  4) If external check if it's part of a sideset
334  */
335  if (mesh->mesh_dimension() < 2)
336  mooseError("The sideset check only works for 2D and 3D meshes");
337  auto & boundary_info = mesh->get_boundary_info();
338  boundary_info.build_side_list();
339  const auto sideset_map = boundary_info.get_sideset_map();
340  unsigned int num_faces_without_sideset = 0;
341 
342  for (const auto elem : mesh->active_element_ptr_range())
343  {
344  for (auto i : elem->side_index_range())
345  {
346  // Check if side is external
347  if (elem->neighbor_ptr(i) == nullptr)
348  {
349  // If external get the boundary ids associated with this side
350  std::vector<boundary_id_type> boundary_ids;
351  auto side_range = sideset_map.equal_range(elem);
352  for (const auto & itr : as_range(side_range))
353  if (itr.second.first == i)
354  boundary_ids.push_back(i);
355  // get intersection of boundary_ids and _watertight_boundaries
356  std::vector<boundary_id_type> intersections =
358 
359  bool no_specified_ids = boundary_ids.empty();
360  bool specified_ids = !_watertight_boundaries.empty() && intersections.empty();
361  std::string message;
362  if (mesh->mesh_dimension() == 3)
363  message = "Element " + std::to_string(elem->id()) +
364  " contains an external face which has not been assigned to ";
365  else
366  message = "Element " + std::to_string(elem->id()) +
367  " contains an external edge which has not been assigned to ";
368  if (no_specified_ids)
369  message = message + "a sideset";
370  else if (specified_ids)
371  message = message + "one of the specified sidesets";
372  if ((no_specified_ids || specified_ids) && num_faces_without_sideset < _num_outputs)
373  {
374  _console << message << std::endl;
375  num_faces_without_sideset++;
376  }
377  }
378  }
379  }
380  std::string message;
381  if (mesh->mesh_dimension() == 3)
382  message = "Number of external element faces that have not been assigned to a sideset: " +
383  std::to_string(num_faces_without_sideset);
384  else
385  message = "Number of external element edges that have not been assigned to a sideset: " +
386  std::to_string(num_faces_without_sideset);
387  diagnosticsLog(message, _check_watertight_sidesets, num_faces_without_sideset);
388 }
389 
390 void
391 MeshDiagnosticsGenerator::checkWatertightNodesets(const std::unique_ptr<MeshBase> & mesh) const
392 {
393  /*
394  Diagnostic Overview:
395  1) Mesh precheck
396  2) Loop through all elements
397  3) Loop through all sides of that element
398  4) If side is external loop through its nodes
399  5) If node is not associated with any nodeset add to list
400  6) Print out node id
401  */
402  if (mesh->mesh_dimension() < 2)
403  mooseError("The nodeset check only works for 2D and 3D meshes");
404  auto & boundary_info = mesh->get_boundary_info();
405  unsigned int num_nodes_without_nodeset = 0;
406  std::set<dof_id_type> checked_nodes_id;
407 
408  for (const auto elem : mesh->active_element_ptr_range())
409  {
410  for (const auto i : elem->side_index_range())
411  {
412  // Check if side is external
413  if (elem->neighbor_ptr(i) == nullptr)
414  {
415  // Side is external, now check nodes
416  auto side = elem->side_ptr(i);
417  const auto & node_list = side->get_nodes();
418  for (unsigned int j = 0; j < side->n_nodes(); j++)
419  {
420  const auto node = node_list[j];
421  if (checked_nodes_id.count(node->id()))
422  continue;
423  // get vector of node's boundaries (in most cases it will only have one)
424  std::vector<boundary_id_type> boundary_ids;
425  boundary_info.boundary_ids(node, boundary_ids);
426  std::vector<boundary_id_type> intersection =
428 
429  bool no_specified_ids = boundary_info.n_boundary_ids(node) == 0;
430  bool specified_ids = !_watertight_boundaries.empty() && intersection.empty();
431  std::string message =
432  "Node " + std::to_string(node->id()) +
433  " is on an external boundary of the mesh, but has not been assigned to ";
434  if (no_specified_ids)
435  message = message + "a nodeset";
436  else if (specified_ids)
437  message = message + "one of the specified nodesets";
438  if ((no_specified_ids || specified_ids) && num_nodes_without_nodeset < _num_outputs)
439  {
440  checked_nodes_id.insert(node->id());
441  num_nodes_without_nodeset++;
442  _console << message << std::endl;
443  }
444  }
445  }
446  }
447  }
448  std::string message;
449  message = "Number of external nodes that have not been assigned to a nodeset: " +
450  std::to_string(num_nodes_without_nodeset);
451  diagnosticsLog(message, _check_watertight_nodesets, num_nodes_without_nodeset);
452 }
453 
454 std::vector<boundary_id_type>
456  const std::vector<boundary_id_type> & watertight_boundaries,
457  std::vector<boundary_id_type> & boundary_ids) const
458 {
459  // Only the boundary_ids vector is sorted here. watertight_boundaries has to be sorted beforehand
460  // Returns their intersection (elements that they share)
461  std::sort(boundary_ids.begin(), boundary_ids.end());
462  std::vector<boundary_id_type> boundary_intersection;
463  std::set_intersection(watertight_boundaries.begin(),
464  watertight_boundaries.end(),
465  boundary_ids.begin(),
466  boundary_ids.end(),
467  std::back_inserter(boundary_intersection));
468  return boundary_intersection;
469 }
470 
471 void
472 MeshDiagnosticsGenerator::checkElementVolumes(const std::unique_ptr<MeshBase> & mesh) const
473 {
474  unsigned int num_tiny_elems = 0;
475  unsigned int num_negative_elems = 0;
476  unsigned int num_big_elems = 0;
477  // loop elements within the mesh (assumes replicated)
478  for (auto & elem : mesh->active_element_ptr_range())
479  {
480  Real vol = elem->volume();
481 
482  if (vol <= _min_volume)
483  {
484  if (num_tiny_elems < _num_outputs)
485  _console << "Element with volume below threshold detected : \n"
486  << "id " << elem->id() << " near point " << elem->vertex_average() << std::endl;
487  else if (num_tiny_elems == _num_outputs)
488  _console << "Maximum output reached, log is silenced" << std::endl;
489  num_tiny_elems++;
490  }
491  if (vol < 0)
492  {
493  if (num_negative_elems < _num_outputs)
494  _console << "Element with negative volume detected : \n"
495  << "id " << elem->id() << " near point " << elem->vertex_average() << std::endl;
496  else if (num_negative_elems == _num_outputs)
497  _console << "Maximum output reached, log is silenced" << std::endl;
498  num_negative_elems++;
499  }
500  if (vol >= _max_volume)
501  {
502  if (num_big_elems < _num_outputs)
503  _console << "Element with volume above threshold detected : \n"
504  << elem->get_info() << std::endl;
505  else if (num_big_elems == _num_outputs)
506  _console << "Maximum output reached, log is silenced" << std::endl;
507  num_big_elems++;
508  }
509  }
510  diagnosticsLog("Number of elements below prescribed minimum volume : " +
511  std::to_string(num_tiny_elems),
513  num_tiny_elems);
514  diagnosticsLog("Number of elements with negative volume : " + std::to_string(num_negative_elems),
516  num_negative_elems);
517  diagnosticsLog("Number of elements above prescribed maximum volume : " +
518  std::to_string(num_big_elems),
520  num_big_elems);
521 }
522 
523 void
524 MeshDiagnosticsGenerator::checkElementTypes(const std::unique_ptr<MeshBase> & mesh) const
525 {
526  std::set<subdomain_id_type> ids;
527  mesh->subdomain_ids(ids);
528  // loop on sub-domain
529  for (auto & id : ids)
530  {
531  // ElemType defines an enum for geometric element types
532  std::set<ElemType> types;
533  // loop on elements within this sub-domain
534  for (auto & elem : mesh->active_subdomain_elements_ptr_range(id))
535  types.insert(elem->type());
536 
537  std::string elem_type_names = "";
538  for (auto & elem_type : types)
539  elem_type_names += " " + Moose::stringify(elem_type);
540 
541  _console << "Element type in subdomain " + mesh->subdomain_name(id) + " (" +
542  std::to_string(id) + ") :" + elem_type_names
543  << std::endl;
544  if (types.size() > 1)
545  diagnosticsLog("Two different element types in subdomain " + std::to_string(id),
547  true);
548  }
549 }
550 
551 void
552 MeshDiagnosticsGenerator::checkElementOverlap(const std::unique_ptr<MeshBase> & mesh) const
553 {
554  {
555  unsigned int num_elem_overlaps = 0;
556  auto pl = mesh->sub_point_locator();
557  // loop on nodes, assumed replicated mesh
558  for (auto & node : mesh->node_ptr_range())
559  {
560  // find all the elements around this node
561  std::set<const Elem *> elements;
562  (*pl)(*node, elements);
563 
564  for (auto & elem : elements)
565  {
566  if (!elem->contains_point(*node))
567  continue;
568 
569  // not overlapping inside the element if part of its nodes
570  bool found = false;
571  for (auto & elem_node : elem->node_ref_range())
572  if (*node == elem_node)
573  {
574  found = true;
575  break;
576  }
577  // not overlapping inside the element if right on its side
578  bool on_a_side = false;
579  for (const auto & elem_side_index : elem->side_index_range())
580  if (elem->side_ptr(elem_side_index)->contains_point(*node, _non_conformality_tol))
581  on_a_side = true;
582  if (!found && !on_a_side)
583  {
584  num_elem_overlaps++;
585  if (num_elem_overlaps < _num_outputs)
586  _console << "Element overlap detected at : " << *node << std::endl;
587  else if (num_elem_overlaps == _num_outputs)
588  _console << "Maximum output reached, log is silenced" << std::endl;
589  }
590  }
591  }
592 
593  diagnosticsLog("Number of elements overlapping (node-based heuristics): " +
594  Moose::stringify(num_elem_overlaps),
596  num_elem_overlaps);
597  num_elem_overlaps = 0;
598 
599  // loop on all elements in mesh: assumes a replicated mesh
600  for (auto & elem : mesh->active_element_ptr_range())
601  {
602  // find all the elements around the centroid of this element
603  std::set<const Elem *> overlaps;
604  (*pl)(elem->vertex_average(), overlaps);
605 
606  if (overlaps.size() > 1)
607  {
608  num_elem_overlaps++;
609  if (num_elem_overlaps < _num_outputs)
610  _console << "Element overlap detected with element : " << elem->id() << " near point "
611  << elem->vertex_average() << std::endl;
612  else if (num_elem_overlaps == _num_outputs)
613  _console << "Maximum output reached, log is silenced" << std::endl;
614  }
615  }
616  diagnosticsLog("Number of elements overlapping (centroid-based heuristics): " +
617  Moose::stringify(num_elem_overlaps),
619  num_elem_overlaps);
620  }
621 }
622 
623 void
624 MeshDiagnosticsGenerator::checkNonPlanarSides(const std::unique_ptr<MeshBase> & mesh) const
625 {
626  unsigned int sides_non_planar = 0;
627  // loop on all elements in mesh: assumes a replicated mesh
628  for (auto & elem : mesh->active_element_ptr_range())
629  {
630  for (auto i : make_range(elem->n_sides()))
631  {
632  auto side = elem->side_ptr(i);
633  std::vector<const Point *> nodes;
634  for (auto & node : side->node_ref_range())
635  nodes.emplace_back(&node);
636 
637  if (nodes.size() <= 3)
638  continue;
639  // First vector of the base
640  const RealVectorValue v1 = *nodes[0] - *nodes[1];
641 
642  // Find another node so that we can form a basis. It should just be node 0, 1, 2
643  // to form two independent vectors, but degenerate elements can make them aligned
644  bool aligned = true;
645  unsigned int third_node_index = 2;
646  RealVectorValue v2;
647  while (aligned && third_node_index < nodes.size())
648  {
649  v2 = *nodes[0] - *nodes[third_node_index++];
650  aligned = MooseUtils::absoluteFuzzyEqual(v1 * v2 - v1.norm() * v2.norm(), 0);
651  }
652 
653  // Degenerate element, could not find a third node that is not aligned
654  if (aligned)
655  continue;
656 
657  bool found_non_planar = false;
658 
659  for (auto node_offset : make_range(nodes.size() - 3))
660  {
661  RealVectorValue v3 = *nodes[0] - *nodes[node_offset + 3];
662  bool planar = MooseUtils::absoluteFuzzyEqual(v2.cross(v1) * v3, 0);
663  if (!planar)
664  found_non_planar = true;
665  }
666 
667  if (found_non_planar)
668  {
669  sides_non_planar++;
670  if (sides_non_planar < _num_outputs)
671  _console << "Nonplanar side detected for side " << i
672  << " of element :" << elem->get_info() << std::endl;
673  else if (sides_non_planar == _num_outputs)
674  _console << "Maximum output reached, log is silenced" << std::endl;
675  }
676  }
677  }
678  diagnosticsLog("Number of non-planar element sides detected: " +
679  Moose::stringify(sides_non_planar),
681  sides_non_planar);
682 }
683 
684 void
685 MeshDiagnosticsGenerator::checkNonConformalMesh(const std::unique_ptr<MeshBase> & mesh) const
686 {
687  unsigned int num_nonconformal_nodes = 0;
689  mesh, _console, _num_outputs, _non_conformality_tol, num_nonconformal_nodes);
690  diagnosticsLog("Number of non-conformal nodes: " + Moose::stringify(num_nonconformal_nodes),
692  num_nonconformal_nodes);
693 }
694 
695 void
697  const std::unique_ptr<MeshBase> & mesh) const
698 {
699  unsigned int num_likely_AMR_created_nonconformality = 0;
700  auto pl = mesh->sub_point_locator();
701  pl->set_close_to_point_tol(_non_conformality_tol);
702 
703  // We have to make a copy because adding the new parent element to the mesh
704  // will modify the mesh for the analysis of the next nodes
705  // Make a copy of the mesh, add this element
706  auto mesh_copy = mesh->clone();
707  libMesh::MeshRefinement mesh_refiner(*mesh_copy);
708 
709  // loop on nodes, assumes a replicated mesh
710  for (auto & node : mesh->node_ptr_range())
711  {
712  // find all the elements around this node
713  std::set<const Elem *> elements_around;
714  (*pl)(*node, elements_around);
715 
716  // Keep track of the refined elements and the coarse element
717  std::set<const Elem *> fine_elements;
718  std::set<const Elem *> coarse_elements;
719 
720  // loop through the set of elements near this node
721  for (auto elem : elements_around)
722  {
723  // If the node is not part of this element's nodes, it is a
724  // case of non-conformality
725  bool node_on_elem = false;
726 
727  if (elem->get_node_index(node) != libMesh::invalid_uint)
728  {
729  node_on_elem = true;
730  // non-vertex nodes are not cause for the kind of non-conformality we are looking for
731  if (!elem->is_vertex(elem->get_node_index(node)))
732  continue;
733  }
734 
735  // Keep track of all the elements this node is a part of. They are potentially the
736  // 'fine' (refined) elements next to a coarser element
737  if (node_on_elem)
738  fine_elements.insert(elem);
739  // Else, the node is not part of the element considered, so if the element had been part
740  // of an AMR-created non-conformality, this element is on the coarse side
741  if (!node_on_elem)
742  coarse_elements.insert(elem);
743  }
744 
745  // all the elements around contained the node as one of their nodes
746  // if the coarse and refined sides are not stitched together, this check can fail,
747  // as nodes that are physically near one element are not part of it because of the lack of
748  // stitching (overlapping nodes)
749  if (fine_elements.size() == elements_around.size())
750  continue;
751 
752  if (fine_elements.empty())
753  continue;
754 
755  // Depending on the type of element, we already know the number of elements we expect
756  // to be part of this set of likely refined candidates for a given non-conformal node to
757  // examine. We can only decide if it was born out of AMR if it's the center node of the face
758  // of a coarse element near refined elements
759  const auto elem_type = (*fine_elements.begin())->type();
760  if ((elem_type == QUAD4 || elem_type == QUAD8 || elem_type == QUAD9) &&
761  fine_elements.size() != 2)
762  continue;
763  else if ((elem_type == HEX8 || elem_type == HEX20 || elem_type == HEX27) &&
764  fine_elements.size() != 4)
765  continue;
766  else if ((elem_type == TRI3 || elem_type == TRI6 || elem_type == TRI7) &&
767  fine_elements.size() != 3)
768  continue;
769  else if ((elem_type == TET4 || elem_type == TET10 || elem_type == TET14) &&
770  (fine_elements.size() % 2 != 0))
771  continue;
772 
773  // only one coarse element in front of refined elements except for tets. Whatever we're
774  // looking at is not the interface between coarse and refined elements
775  // Tets are split on their edges (rather than the middle of a face) so there could be any
776  // number of coarse elements in front of the node non-conformality created by refinement
777  if (elem_type != TET4 && elem_type != TET10 && elem_type != TET14 && coarse_elements.size() > 1)
778  continue;
779 
780  // There exists non-conformality, the node should have been a node of all the elements
781  // that are close enough to the node, and it is not
782 
783  // Nodes of the tentative parent element
784  std::vector<const Node *> tentative_coarse_nodes;
785 
786  // For quads and hexes, there is one (quad) or four (hexes) sides that are tied to this node
787  // at the non-conformal interface between the refined elements and a coarse element
788  if (elem_type == QUAD4 || elem_type == QUAD8 || elem_type == QUAD9 || elem_type == HEX8 ||
789  elem_type == HEX20 || elem_type == HEX27)
790  {
791  const auto elem = *fine_elements.begin();
792 
793  // Find which sides (of the elements) the node considered is part of
794  std::vector<Elem *> node_on_sides;
795  unsigned int side_inside_parent = std::numeric_limits<unsigned int>::max();
796  for (auto i : make_range(elem->n_sides()))
797  {
798  const auto side = elem->side_ptr(i);
799  std::vector<const Node *> other_nodes_on_side;
800  bool node_on_side = false;
801  for (const auto & elem_node : side->node_ref_range())
802  {
803  if (*node == elem_node)
804  node_on_side = true;
805  else
806  other_nodes_on_side.emplace_back(&elem_node);
807  }
808  // node is on the side, but is it the side that goes away from the coarse element?
809  if (node_on_side)
810  {
811  // if all the other nodes on this side are in one of the other potentially refined
812  // elements, it's one of the side(s) (4 sides in a 3D hex for example) inside the
813  // parent
814  bool all_side_nodes_are_shared = true;
815  for (const auto & other_node : other_nodes_on_side)
816  {
817  bool shared_with_a_fine_elem = false;
818  for (const auto & other_elem : fine_elements)
819  if (other_elem != elem &&
820  other_elem->get_node_index(other_node) != libMesh::invalid_uint)
821  shared_with_a_fine_elem = true;
822 
823  if (!shared_with_a_fine_elem)
824  all_side_nodes_are_shared = false;
825  }
826  if (all_side_nodes_are_shared)
827  {
828  side_inside_parent = i;
829  // We stop examining sides, it does not matter which side we pick inside the parent
830  break;
831  }
832  }
833  }
834  if (side_inside_parent == std::numeric_limits<unsigned int>::max())
835  continue;
836 
837  // Gather the other potential elements in the refined element:
838  // they are point neighbors of the node that is shared between all the elements flagged
839  // for the non-conformality
840  // Find shared node
841  const auto interior_side = elem->side_ptr(side_inside_parent);
842  const Node * interior_node = nullptr;
843  for (const auto & other_node : interior_side->node_ref_range())
844  {
845  if (other_node == *node)
846  continue;
847  bool in_all_node_neighbor_elements = true;
848  for (auto other_elem : fine_elements)
849  {
850  if (other_elem->get_node_index(&other_node) == libMesh::invalid_uint)
851  in_all_node_neighbor_elements = false;
852  }
853  if (in_all_node_neighbor_elements)
854  {
855  interior_node = &other_node;
856  break;
857  }
858  }
859  // Did not find interior node. Probably not AMR
860  if (!interior_node)
861  continue;
862 
863  // Add point neighbors of interior node to list of potentially refined elements
864  std::set<const Elem *> all_elements;
865  elem->find_point_neighbors(*interior_node, all_elements);
866 
867  if (elem_type == QUAD4 || elem_type == QUAD8 || elem_type == QUAD9)
868  {
870  *interior_node, *node, *elem, tentative_coarse_nodes, fine_elements);
871  if (!success)
872  continue;
873  }
874  // For hexes we first look at the fine-neighbors of the non-conformality
875  // then the fine elements neighbors of the center 'node' of the potential parent
876  else
877  {
878  // Get the coarse neighbor side to be able to recognize nodes that should become part of
879  // the coarse parent
880  const auto & coarse_elem = *coarse_elements.begin();
881  unsigned short coarse_side_i = 0;
882  for (const auto & coarse_side_index : coarse_elem->side_index_range())
883  {
884  const auto coarse_side_ptr = coarse_elem->side_ptr(coarse_side_index);
885  // The side of interest is the side that contains the non-conformality
886  if (!coarse_side_ptr->close_to_point(*node, 10 * _non_conformality_tol))
887  continue;
888  else
889  {
890  coarse_side_i = coarse_side_index;
891  break;
892  }
893  }
894  const auto coarse_side = coarse_elem->side_ptr(coarse_side_i);
895 
896  // We did not find the side of the coarse neighbor near the refined elements
897  // Try again at another node
898  if (!coarse_side)
899  continue;
900 
901  // We cant directly use the coarse neighbor nodes
902  // - The user might be passing a disjoint mesh
903  // - There could two levels of refinement separating the coarse neighbor and its refined
904  // counterparts
905  // We use the fine element nodes
906  unsigned int i = 0;
907  tentative_coarse_nodes.resize(4);
908  for (const auto & elem_1 : fine_elements)
909  for (const auto & coarse_node : elem_1->node_ref_range())
910  {
911  bool node_shared = false;
912  for (const auto & elem_2 : fine_elements)
913  {
914  if (elem_2 != elem_1)
915  if (elem_2->get_node_index(&coarse_node) != libMesh::invalid_uint)
916  node_shared = true;
917  }
918  // A node for the coarse parent will appear in only one fine neighbor (not shared)
919  // and will lay on the side of the coarse neighbor
920  // We only care about the coarse neighbor vertex nodes
921  if (!node_shared && coarse_side->close_to_point(coarse_node, _non_conformality_tol) &&
922  elem_1->is_vertex(elem_1->get_node_index(&coarse_node)))
923  tentative_coarse_nodes[i++] = &coarse_node;
924  mooseAssert(i <= 5, "We went too far in this index");
925  }
926 
927  // We did not find 4 coarse nodes. Mesh might be disjoint and the coarse element does not
928  // contain the fine elements nodes we found
929  if (i != 4)
930  continue;
931 
932  // Need to order these nodes to form a valid quad / base of an hex
933  // We go around the axis formed by the node and the interior node
934  Point axis = *interior_node - *node;
935  const auto start_circle = elem->vertex_average();
937  tentative_coarse_nodes, *interior_node, start_circle, axis);
938  tentative_coarse_nodes.resize(8);
939 
940  // Use the neighbors of the fine elements that contain these nodes to get the vertex
941  // nodes
942  for (const auto & elem : fine_elements)
943  {
944  // Find the index of the coarse node for the starting element
945  unsigned int node_index = 0;
946  for (const auto & coarse_node : tentative_coarse_nodes)
947  {
948  if (elem->get_node_index(coarse_node) != libMesh::invalid_uint)
949  break;
950  node_index++;
951  }
952 
953  // Get the neighbor element that is part of the fine elements to coarsen together
954  for (const auto & neighbor : elem->neighbor_ptr_range())
955  if (all_elements.count(neighbor) && !fine_elements.count(neighbor))
956  {
957  // Find the coarse node for the neighbor
958  const Node * coarse_elem_node = nullptr;
959  for (const auto & fine_node : neighbor->node_ref_range())
960  {
961  if (!neighbor->is_vertex(neighbor->get_node_index(&fine_node)))
962  continue;
963  bool node_shared = false;
964  for (const auto & elem_2 : all_elements)
965  if (elem_2 != neighbor &&
966  elem_2->get_node_index(&fine_node) != libMesh::invalid_uint)
967  node_shared = true;
968  if (!node_shared)
969  {
970  coarse_elem_node = &fine_node;
971  break;
972  }
973  }
974  // Insert the coarse node at the right place
975  tentative_coarse_nodes[node_index + 4] = coarse_elem_node;
976  mooseAssert(node_index + 4 < tentative_coarse_nodes.size(), "Indexed too far");
977  mooseAssert(coarse_elem_node, "Did not find last coarse element node");
978  }
979  }
980  }
981 
982  // No need to separate fine elements near the non-conformal node and away from it
983  fine_elements = all_elements;
984  }
985  // For TRI elements, we use the fine triangle element at the center of the potential
986  // coarse triangle element
987  else if (elem_type == TRI3 || elem_type == TRI6 || elem_type == TRI7)
988  {
989  // Find the center element
990  // It's the only element that shares a side with both of the other elements near the node
991  // considered
992  const Elem * center_elem = nullptr;
993  for (const auto refined_elem_1 : fine_elements)
994  {
995  unsigned int num_neighbors = 0;
996  for (const auto refined_elem_2 : fine_elements)
997  {
998  if (refined_elem_1 == refined_elem_2)
999  continue;
1000  if (refined_elem_1->has_neighbor(refined_elem_2))
1001  num_neighbors++;
1002  }
1003  if (num_neighbors >= 2)
1004  center_elem = refined_elem_1;
1005  }
1006  // Did not find the center fine element, probably not AMR
1007  if (!center_elem)
1008  continue;
1009  // Now get the tentative coarse element nodes
1010  for (const auto refined_elem : fine_elements)
1011  {
1012  if (refined_elem == center_elem)
1013  continue;
1014  for (const auto & other_node : refined_elem->node_ref_range())
1015  if (center_elem->get_node_index(&other_node) == libMesh::invalid_uint &&
1016  refined_elem->is_vertex(refined_elem->get_node_index(&other_node)))
1017  tentative_coarse_nodes.push_back(&other_node);
1018  }
1019 
1020  // Get the final tentative new coarse element node, on the other side of the center
1021  // element from the non-conformality
1022  unsigned int center_side_opposite_node = std::numeric_limits<unsigned int>::max();
1023  for (auto side_index : center_elem->side_index_range())
1024  if (center_elem->side_ptr(side_index)->get_node_index(node) == libMesh::invalid_uint)
1025  center_side_opposite_node = side_index;
1026  const auto neighbor_on_other_side_of_opposite_center_side =
1027  center_elem->neighbor_ptr(center_side_opposite_node);
1028 
1029  // Element is on a boundary, cannot form a coarse element
1030  if (!neighbor_on_other_side_of_opposite_center_side)
1031  continue;
1032 
1033  fine_elements.insert(neighbor_on_other_side_of_opposite_center_side);
1034  for (const auto & tri_node : neighbor_on_other_side_of_opposite_center_side->node_ref_range())
1035  if (neighbor_on_other_side_of_opposite_center_side->is_vertex(
1036  neighbor_on_other_side_of_opposite_center_side->get_node_index(&tri_node)) &&
1037  center_elem->side_ptr(center_side_opposite_node)->get_node_index(&tri_node) ==
1039  tentative_coarse_nodes.push_back(&tri_node);
1040 
1041  mooseAssert(center_side_opposite_node != std::numeric_limits<unsigned int>::max(),
1042  "Did not find the side opposite the non-conformality");
1043  mooseAssert(tentative_coarse_nodes.size() == 3,
1044  "We are forming a coarsened triangle element");
1045  }
1046  // For TET elements, it's very different because the non-conformality does not happen inside
1047  // of a face, but on an edge of one or more coarse elements
1048  else if (elem_type == TET4 || elem_type == TET10 || elem_type == TET14)
1049  {
1050  // There are 4 tets on the tips of the coarsened tet and 4 tets inside
1051  // let's identify all of them
1052  std::set<const Elem *> tips_tets;
1053  std::set<const Elem *> inside_tets;
1054 
1055  // pick a coarse element and work with its fine neighbors
1056  const Elem * coarse_elem = nullptr;
1057  std::set<const Elem *> fine_tets;
1058  for (auto & coarse_one : coarse_elements)
1059  {
1060  for (const auto & elem : fine_elements)
1061  // for two levels of refinement across, this is not working
1062  // we would need a "has_face_embedded_in_this_other_ones_face" routine
1063  if (elem->has_neighbor(coarse_one))
1064  fine_tets.insert(elem);
1065 
1066  if (fine_tets.size())
1067  {
1068  coarse_elem = coarse_one;
1069  break;
1070  }
1071  }
1072  // There's no coarse element neighbor to a group of finer tets, not AMR
1073  if (!coarse_elem)
1074  continue;
1075 
1076  // There is one last point neighbor of the node that is sandwiched between two neighbors
1077  for (const auto & elem : fine_elements)
1078  {
1079  int num_face_neighbors = 0;
1080  for (const auto & tet : fine_tets)
1081  if (tet->has_neighbor(elem))
1082  num_face_neighbors++;
1083  if (num_face_neighbors == 2)
1084  {
1085  fine_tets.insert(elem);
1086  break;
1087  }
1088  }
1089 
1090  // There should be two other nodes with non-conformality near this coarse element
1091  // Find both, as they will be nodes of the rest of the elements to add to the potential
1092  // fine tet list. They are shared by two of the fine tets we have already found
1093  std::set<const Node *> other_nodes;
1094  for (const auto & tet_1 : fine_tets)
1095  {
1096  for (const auto & node_1 : tet_1->node_ref_range())
1097  {
1098  if (&node_1 == node)
1099  continue;
1100  if (!tet_1->is_vertex(tet_1->get_node_index(&node_1)))
1101  continue;
1102  for (const auto & tet_2 : fine_tets)
1103  {
1104  if (tet_2 == tet_1)
1105  continue;
1106  if (tet_2->get_node_index(&node_1) != libMesh::invalid_uint)
1107  // check that it's near the coarse element as well
1108  if (coarse_elem->close_to_point(node_1, 10 * _non_conformality_tol))
1109  other_nodes.insert(&node_1);
1110  }
1111  }
1112  }
1113  mooseAssert(other_nodes.size() == 2,
1114  "Should find only two extra non-conformal nodes near the coarse element");
1115 
1116  // Now we can go towards this tip element next to two non-conformalities
1117  for (const auto & tet_1 : fine_tets)
1118  {
1119  for (const auto & neighbor : tet_1->neighbor_ptr_range())
1120  if (neighbor->get_node_index(*other_nodes.begin()) != libMesh::invalid_uint &&
1121  neighbor->is_vertex(neighbor->get_node_index(*other_nodes.begin())) &&
1122  neighbor->get_node_index(*other_nodes.rbegin()) != libMesh::invalid_uint &&
1123  neighbor->is_vertex(neighbor->get_node_index(*other_nodes.rbegin())))
1124  fine_tets.insert(neighbor);
1125  }
1126  // Now that the element next to the time is in the fine_tets, we can get the tip
1127  for (const auto & tet_1 : fine_tets)
1128  {
1129  for (const auto & neighbor : tet_1->neighbor_ptr_range())
1130  if (neighbor->get_node_index(*other_nodes.begin()) != libMesh::invalid_uint &&
1131  neighbor->is_vertex(neighbor->get_node_index(*other_nodes.begin())) &&
1132  neighbor->get_node_index(*other_nodes.rbegin()) != libMesh::invalid_uint &&
1133  neighbor->is_vertex(neighbor->get_node_index(*other_nodes.rbegin())))
1134  fine_tets.insert(neighbor);
1135  }
1136 
1137  // Get the sandwiched tets between the tets we already found
1138  for (const auto & tet_1 : fine_tets)
1139  for (const auto & neighbor : tet_1->neighbor_ptr_range())
1140  for (const auto & tet_2 : fine_tets)
1141  if (tet_1 != tet_2 && tet_2->has_neighbor(neighbor) && neighbor != coarse_elem)
1142  fine_tets.insert(neighbor);
1143 
1144  // tips tests are the only ones to have a node that is shared by no other tet in the group
1145  for (const auto & tet_1 : fine_tets)
1146  {
1147  unsigned int unshared_nodes = 0;
1148  for (const auto & other_node : tet_1->node_ref_range())
1149  {
1150  if (!tet_1->is_vertex(tet_1->get_node_index(&other_node)))
1151  continue;
1152  bool node_shared = false;
1153  for (const auto & tet_2 : fine_tets)
1154  if (tet_2 != tet_1 && tet_2->get_node_index(&other_node) != libMesh::invalid_uint)
1155  node_shared = true;
1156  if (!node_shared)
1157  unshared_nodes++;
1158  }
1159  if (unshared_nodes == 1)
1160  tips_tets.insert(tet_1);
1161  else if (unshared_nodes == 0)
1162  inside_tets.insert(tet_1);
1163  else
1164  mooseError("Did not expect a tet to have two unshared vertex nodes here");
1165  }
1166 
1167  // Finally grab the last tip of the tentative coarse tet. It shares:
1168  // - 3 nodes with the other tips, only one with each
1169  // - 1 face with only one tet of the fine tet group
1170  // - it has a node that no other fine tet shares (the tip node)
1171  for (const auto & tet : inside_tets)
1172  {
1173  for (const auto & neighbor : tet->neighbor_ptr_range())
1174  {
1175  // Check that it shares a face with no other potential fine tet
1176  bool shared_with_another_tet = false;
1177  for (const auto & tet_2 : fine_tets)
1178  {
1179  if (tet_2 == tet)
1180  continue;
1181  if (tet_2->has_neighbor(neighbor))
1182  shared_with_another_tet = true;
1183  }
1184  if (shared_with_another_tet)
1185  continue;
1186 
1187  // Used to count the nodes shared with tip tets. Can only be 1 per tip tet
1188  std::vector<const Node *> tip_nodes_shared;
1189  unsigned int unshared_nodes = 0;
1190  for (const auto & other_node : neighbor->node_ref_range())
1191  {
1192  if (!neighbor->is_vertex(neighbor->get_node_index(&other_node)))
1193  continue;
1194 
1195  // Check for being a node-neighbor of the 3 other tip tets
1196  for (const auto & tip_tet : tips_tets)
1197  {
1198  if (neighbor == tip_tet)
1199  continue;
1200 
1201  // we could break here but we want to check that no other tip shares that node
1202  if (tip_tet->get_node_index(&other_node) != libMesh::invalid_uint)
1203  tip_nodes_shared.push_back(&other_node);
1204  }
1205  // Check for having a node shared with no other tet
1206  bool node_shared = false;
1207  for (const auto & tet_2 : fine_tets)
1208  if (tet_2 != neighbor && tet_2->get_node_index(&other_node) != libMesh::invalid_uint)
1209  node_shared = true;
1210  if (!node_shared)
1211  unshared_nodes++;
1212  }
1213  if (tip_nodes_shared.size() == 3 && unshared_nodes == 1)
1214  tips_tets.insert(neighbor);
1215  }
1216  }
1217 
1218  // append the missing fine tets (inside the coarse element, away from the node considered)
1219  // into the fine elements set for the check on "did it refine the tentative coarse tet
1220  // onto the same fine tets"
1221  fine_elements.clear();
1222  for (const auto & elem : tips_tets)
1223  fine_elements.insert(elem);
1224  for (const auto & elem : inside_tets)
1225  fine_elements.insert(elem);
1226 
1227  // get the vertex of the coarse element from the tip tets
1228  for (const auto & tip : tips_tets)
1229  {
1230  for (const auto & node : tip->node_ref_range())
1231  {
1232  bool outside = true;
1233 
1234  const auto id = tip->get_node_index(&node);
1235  if (!tip->is_vertex(id))
1236  continue;
1237  for (const auto & tet : inside_tets)
1238  if (tet->get_node_index(&node) != libMesh::invalid_uint)
1239  outside = false;
1240  if (outside)
1241  {
1242  tentative_coarse_nodes.push_back(&node);
1243  // only one tip node per tip tet
1244  break;
1245  }
1246  }
1247  }
1248 
1249  std::sort(tentative_coarse_nodes.begin(), tentative_coarse_nodes.end());
1250  tentative_coarse_nodes.erase(
1251  std::unique(tentative_coarse_nodes.begin(), tentative_coarse_nodes.end()),
1252  tentative_coarse_nodes.end());
1253 
1254  // The group of fine elements ended up having less or more than 4 tips, so it's clearly
1255  // not forming a coarse tetrahedral
1256  if (tentative_coarse_nodes.size() != 4)
1257  continue;
1258  }
1259  else
1260  {
1261  mooseInfo("Unsupported element type ",
1262  elem_type,
1263  ". Skipping detection for this node and all future nodes near only this "
1264  "element type");
1265  continue;
1266  }
1267 
1268  // Check the fine element types: if not all the same then it's not uniform AMR
1269  for (auto elem : fine_elements)
1270  if (elem->type() != elem_type)
1271  continue;
1272 
1273  // Check the number of coarse element nodes gathered
1274  for (const auto & check_node : tentative_coarse_nodes)
1275  if (check_node == nullptr)
1276  continue;
1277 
1278  // Form a parent, of a low order type as we only have the extreme vertex nodes
1279  std::unique_ptr<Elem> parent = Elem::build(Elem::first_order_equivalent_type(elem_type));
1280  auto parent_ptr = mesh_copy->add_elem(parent.release());
1281 
1282  // Set the nodes to the coarse element
1283  for (auto i : index_range(tentative_coarse_nodes))
1284  parent_ptr->set_node(i, mesh_copy->node_ptr(tentative_coarse_nodes[i]->id()));
1285 
1286  // Refine this parent
1287  parent_ptr->set_refinement_flag(Elem::REFINE);
1288  parent_ptr->refine(mesh_refiner);
1289  const auto num_children = parent_ptr->n_children();
1290 
1291  // Compare with the original set of elements
1292  // We already know the child share the exterior node. If they share the same vertex
1293  // average as the group of unrefined elements we will call this good enough for now
1294  // For tetrahedral elements we cannot rely on the children all matching as the choice in
1295  // the diagonal selection can be made differently. We'll just say 4 matching children is
1296  // good enough for the heuristic
1297  unsigned int num_children_match = 0;
1298  for (const auto & child : parent_ptr->child_ref_range())
1299  {
1300  for (const auto & potential_children : fine_elements)
1301  if (MooseUtils::absoluteFuzzyEqual(child.vertex_average()(0),
1302  potential_children->vertex_average()(0),
1304  MooseUtils::absoluteFuzzyEqual(child.vertex_average()(1),
1305  potential_children->vertex_average()(1),
1307  MooseUtils::absoluteFuzzyEqual(child.vertex_average()(2),
1308  potential_children->vertex_average()(2),
1310  {
1311  num_children_match++;
1312  break;
1313  }
1314  }
1315 
1316  if (num_children_match == num_children ||
1317  ((elem_type == TET4 || elem_type == TET10 || elem_type == TET14) &&
1318  num_children_match == 4))
1319  {
1320  num_likely_AMR_created_nonconformality++;
1321  if (num_likely_AMR_created_nonconformality < _num_outputs)
1322  {
1323  _console << "Detected non-conformality likely created by AMR near" << *node
1324  << Moose::stringify(elem_type)
1325  << " elements that could be merged into a coarse element:" << std::endl;
1326  for (const auto & elem : fine_elements)
1327  _console << elem->id() << " ";
1328  _console << std::endl;
1329  }
1330  else if (num_likely_AMR_created_nonconformality == _num_outputs)
1331  _console << "Maximum log output reached, silencing output" << std::endl;
1332  }
1333  }
1334 
1336  "Number of non-conformal nodes likely due to mesh refinement detected by heuristic: " +
1337  Moose::stringify(num_likely_AMR_created_nonconformality),
1339  num_likely_AMR_created_nonconformality);
1340  pl->unset_close_to_point_tol();
1341 }
1342 
1343 void
1344 MeshDiagnosticsGenerator::checkLocalJacobians(const std::unique_ptr<MeshBase> & mesh) const
1345 {
1346  unsigned int num_bad_elem_qp_jacobians = 0;
1347  // Get a high-ish order quadrature
1348  auto qrule_dimension = mesh->mesh_dimension();
1349  libMesh::QGauss qrule(qrule_dimension, FIFTH);
1350 
1351  // Use a constant monomial
1352  const libMesh::FEType fe_type(CONSTANT, libMesh::MONOMIAL);
1353 
1354  // Initialize a basic constant monomial shape function everywhere
1355  std::unique_ptr<libMesh::FEBase> fe_elem;
1356  if (mesh->mesh_dimension() == 1)
1357  fe_elem = std::make_unique<libMesh::FEMonomial<1>>(fe_type);
1358  if (mesh->mesh_dimension() == 2)
1359  fe_elem = std::make_unique<libMesh::FEMonomial<2>>(fe_type);
1360  else
1361  fe_elem = std::make_unique<libMesh::FEMonomial<3>>(fe_type);
1362 
1363  fe_elem->get_JxW();
1364  fe_elem->attach_quadrature_rule(&qrule);
1365 
1366  // Check elements (assumes serialized mesh)
1367  for (const auto & elem : mesh->element_ptr_range())
1368  {
1369  // Handle mixed-dimensional meshes
1370  if (qrule_dimension != elem->dim())
1371  {
1372  // Re-initialize a quadrature
1373  qrule_dimension = elem->dim();
1374  qrule = libMesh::QGauss(qrule_dimension, FIFTH);
1375 
1376  // Re-initialize a monomial FE
1377  if (elem->dim() == 1)
1378  fe_elem = std::make_unique<libMesh::FEMonomial<1>>(fe_type);
1379  if (elem->dim() == 2)
1380  fe_elem = std::make_unique<libMesh::FEMonomial<2>>(fe_type);
1381  else
1382  fe_elem = std::make_unique<libMesh::FEMonomial<3>>(fe_type);
1383 
1384  fe_elem->get_JxW();
1385  fe_elem->attach_quadrature_rule(&qrule);
1386  }
1387 
1388  try
1389  {
1390  fe_elem->reinit(elem);
1391  }
1392  catch (std::exception & e)
1393  {
1394  if (!strstr(e.what(), "Jacobian"))
1395  throw;
1396 
1397  num_bad_elem_qp_jacobians++;
1398  if (num_bad_elem_qp_jacobians < _num_outputs)
1399  _console << "Bad Jacobian found in element " << elem->id() << " near point "
1400  << elem->vertex_average() << std::endl;
1401  else if (num_bad_elem_qp_jacobians == _num_outputs)
1402  _console << "Maximum log output reached, silencing output" << std::endl;
1403  }
1404  }
1405  diagnosticsLog("Number of elements with a bad Jacobian: " +
1406  Moose::stringify(num_bad_elem_qp_jacobians),
1408  num_bad_elem_qp_jacobians);
1409 
1410  unsigned int num_bad_side_qp_jacobians = 0;
1411  // Get a high-ish order side quadrature
1412  auto qrule_side_dimension = mesh->mesh_dimension() - 1;
1413  libMesh::QGauss qrule_side(qrule_side_dimension, FIFTH);
1414 
1415  // Use the side quadrature now
1416  fe_elem->attach_quadrature_rule(&qrule_side);
1417 
1418  // Check element sides
1419  for (const auto & elem : mesh->element_ptr_range())
1420  {
1421  // Handle mixed-dimensional meshes
1422  if (int(qrule_side_dimension) != elem->dim() - 1)
1423  {
1424  qrule_side_dimension = elem->dim() - 1;
1425  qrule_side = libMesh::QGauss(qrule_side_dimension, FIFTH);
1426 
1427  // Re-initialize a side FE
1428  if (elem->dim() == 1)
1429  fe_elem = std::make_unique<libMesh::FEMonomial<1>>(fe_type);
1430  if (elem->dim() == 2)
1431  fe_elem = std::make_unique<libMesh::FEMonomial<2>>(fe_type);
1432  else
1433  fe_elem = std::make_unique<libMesh::FEMonomial<3>>(fe_type);
1434 
1435  fe_elem->get_JxW();
1436  fe_elem->attach_quadrature_rule(&qrule_side);
1437  }
1438 
1439  for (const auto & side : elem->side_index_range())
1440  {
1441  try
1442  {
1443  fe_elem->reinit(elem, side);
1444  }
1445  catch (std::exception & e)
1446  {
1447  // In 2D dbg/devel modes libMesh could hit
1448  // libmesh_assert_not_equal_to on a side reinit
1449  if (!strstr(e.what(), "Jacobian") && !strstr(e.what(), "det != 0"))
1450  throw;
1451 
1452  num_bad_side_qp_jacobians++;
1453  if (num_bad_side_qp_jacobians < _num_outputs)
1454  _console << "Bad Jacobian found in side " << side << " of element" << elem->id()
1455  << " near point " << elem->vertex_average() << std::endl;
1456  else if (num_bad_side_qp_jacobians == _num_outputs)
1457  _console << "Maximum log output reached, silencing output" << std::endl;
1458  }
1459  }
1460  }
1461  diagnosticsLog("Number of element sides with bad Jacobians: " +
1462  Moose::stringify(num_bad_side_qp_jacobians),
1464  num_bad_side_qp_jacobians);
1465 }
1466 
1467 void
1468 MeshDiagnosticsGenerator::checkNonMatchingEdges(const std::unique_ptr<MeshBase> & mesh) const
1469 {
1470  /*Algorithm Overview
1471  1)Prechecks
1472  a)This algorithm only works for 3D so check for that first
1473  2)Loop
1474  a)Loop through every element
1475  b)For each element get the edges associated with it
1476  c)For each edge check overlap with any edges of nearby elements
1477  d)Have check to make sure the same pair of edges are not being tested twice for overlap
1478  3)Overlap check
1479  a)Shortest line that connects both lines is perpendicular to both lines
1480  b)A good overview of the math for finding intersecting lines can be found
1481  here->paulbourke.net/geometry/pointlineplane/
1482  */
1483  if (mesh->mesh_dimension() != 3)
1484  {
1485  mooseWarning("The edge intersection algorithm only works with 3D meshes. "
1486  "'examine_non_matching_edges' is skipped");
1487  return;
1488  }
1489  if (!mesh->is_serial())
1490  mooseError("Only serialized/replicated meshes are supported");
1491  unsigned int num_intersecting_edges = 0;
1492 
1493  // Create map of element to bounding box to avoing reinitializing the same bounding box multiple
1494  // times
1495  std::unordered_map<Elem *, BoundingBox> bounding_box_map;
1496  for (const auto elem : mesh->active_element_ptr_range())
1497  {
1498  const auto boundingBox = elem->loose_bounding_box();
1499  bounding_box_map.insert({elem, boundingBox});
1500  }
1501 
1502  std::unique_ptr<PointLocatorBase> point_locator = mesh->sub_point_locator();
1503  std::set<std::array<dof_id_type, 4>> overlapping_edges_nodes;
1504  for (const auto elem : mesh->active_element_ptr_range())
1505  {
1506  // loop through elem's nodes and find nearby elements with it
1507  std::set<const Elem *> candidate_elems;
1508  std::set<const Elem *> nearby_elems;
1509  for (unsigned int i = 0; i < elem->n_nodes(); i++)
1510  {
1511  (*point_locator)(elem->point(i), candidate_elems);
1512  nearby_elems.insert(candidate_elems.begin(), candidate_elems.end());
1513  }
1514  std::vector<std::unique_ptr<const Elem>> elem_edges(elem->n_edges());
1515  for (auto i : elem->edge_index_range())
1516  elem_edges[i] = elem->build_edge_ptr(i);
1517  for (const auto other_elem : nearby_elems)
1518  {
1519  // If they're the same element then there's no need to check for overlap
1520  if (elem->id() >= other_elem->id())
1521  continue;
1522 
1523  std::vector<std::unique_ptr<const Elem>> other_edges(other_elem->n_edges());
1524  for (auto j : other_elem->edge_index_range())
1525  other_edges[j] = other_elem->build_edge_ptr(j);
1526  for (auto & edge : elem_edges)
1527  {
1528  for (auto & other_edge : other_edges)
1529  {
1530  // Get nodes from edges
1531  const Node * n1 = edge->get_nodes()[0];
1532  const Node * n2 = edge->get_nodes()[1];
1533  const Node * n3 = other_edge->get_nodes()[0];
1534  const Node * n4 = other_edge->get_nodes()[1];
1535 
1536  // Create array<dof_id_type, 4> to check against set
1537  std::array<dof_id_type, 4> node_id_array = {n1->id(), n2->id(), n3->id(), n4->id()};
1538  std::sort(node_id_array.begin(), node_id_array.end());
1539 
1540  // Check if the edges have already been added to our check_edges list
1541  if (overlapping_edges_nodes.count(node_id_array))
1542  {
1543  continue;
1544  }
1545 
1546  // Check element/edge type
1547  if (edge->type() != EDGE2)
1548  {
1549  std::string element_message = "Edge of type " + Utility::enum_to_string(edge->type()) +
1550  " was found in cell " + std::to_string(elem->id()) +
1551  " which is of type " +
1552  Utility::enum_to_string(elem->type()) + '\n' +
1553  "The edge intersection check only works for EDGE2 "
1554  "elements.\nThis message will not be output again";
1555  mooseDoOnce(_console << element_message << std::endl);
1556  continue;
1557  }
1558  if (other_edge->type() != EDGE2)
1559  continue;
1560 
1561  // Now compare edge with other_edge
1562  Point intersection_coords;
1564  *edge, *other_edge, intersection_coords, _non_matching_edge_tol);
1565  if (overlap)
1566  {
1567  // Add the nodes that make up the 2 edges to the vector overlapping_edges_nodes
1568  overlapping_edges_nodes.insert(node_id_array);
1569  num_intersecting_edges += 2;
1570  if (num_intersecting_edges < _num_outputs)
1571  {
1572  // Print error message
1573  std::string elem_id = std::to_string(elem->id());
1574  std::string other_elem_id = std::to_string(other_elem->id());
1575  std::string x_coord = std::to_string(intersection_coords(0));
1576  std::string y_coord = std::to_string(intersection_coords(1));
1577  std::string z_coord = std::to_string(intersection_coords(2));
1578  std::string message = "Intersecting edges found between elements " + elem_id +
1579  " and " + other_elem_id + " near point (" + x_coord + ", " +
1580  y_coord + ", " + z_coord + ")";
1581  _console << message << std::endl;
1582  }
1583  }
1584  }
1585  }
1586  }
1587  }
1588  diagnosticsLog("Number of intersecting element edges: " +
1589  Moose::stringify(num_intersecting_edges),
1591  num_intersecting_edges);
1592 }
1593 
1594 void
1595 MeshDiagnosticsGenerator::checkPolygons(const std::unique_ptr<MeshBase> & mesh) const
1596 {
1597  unsigned int num_polygons = 0;
1598  unsigned int num_nonconvex = 0;
1599  unsigned int num_nonplanar = 0;
1600  unsigned int num_flat_consecutive_sides = 0;
1601 
1602  for (const auto & elem : mesh->element_ptr_range())
1603  if (elem->type() == libMesh::C0POLYGON)
1604  {
1605  num_polygons++;
1606  const auto n_nodes = elem->n_nodes();
1607  Point base_top_dir(0, 0, 0);
1608  bool nonconvex = false;
1609  bool nonplanar = false;
1610  for (const auto & i : make_range(n_nodes))
1611  {
1612  const auto n1 = elem->point(i);
1613  const auto n2 = elem->point((i + 1) % n_nodes);
1614  const auto n3 = elem->point((i + 2) % n_nodes);
1615  // can't be const with unit
1616  Point top_dir = (n2 - n1).cross(n3 - n2);
1617 
1618  if (top_dir.norm_sq() > 0 && base_top_dir.norm() == 0)
1619  {
1620  base_top_dir = top_dir.unit();
1621  continue;
1622  }
1623  if (base_top_dir * top_dir < 0)
1624  nonconvex = true;
1625  if (top_dir.norm_sq() > 0)
1626  top_dir = top_dir.unit();
1627  else
1628  num_flat_consecutive_sides++;
1629  if (!MooseUtils::absoluteFuzzyEqual((top_dir - base_top_dir).norm_sq(), 0, TOLERANCE) &&
1630  !MooseUtils::absoluteFuzzyEqual((top_dir + base_top_dir).norm_sq(), 0, TOLERANCE))
1631  nonplanar = true;
1632  }
1633 
1634  if (nonconvex)
1635  {
1636  num_nonconvex++;
1637  if (num_nonconvex < _num_outputs)
1638  _console << "Non convex C0 polygon detected:" << elem->get_info() << std::endl;
1639  else if (num_nonconvex == _num_outputs)
1640  _console << "Ouptut limit reached for non-convex polygons" << std::endl;
1641  }
1642  if (nonplanar)
1643  {
1644  num_nonplanar++;
1645  if (num_nonconvex < _num_outputs)
1646  _console << "Non planar C0 polygon detected:" << elem->get_info() << std::endl;
1647  else if (num_nonconvex == _num_outputs)
1648  _console << "Ouptut limit reached for non-planar polygons" << std::endl;
1649  }
1650  }
1651 
1652  if (!num_polygons)
1653  mooseWarning("No C0 polygons in geometry: polyon check did nothing");
1654  else
1655  {
1656  diagnosticsLog("Number of non convex polygons: " + Moose::stringify(num_nonconvex),
1658  num_nonconvex);
1659  diagnosticsLog("Number of non planar polygons: " + Moose::stringify(num_nonplanar),
1661  num_nonplanar);
1662  diagnosticsLog("Number of colinear consecutive sides of polygons: " +
1663  Moose::stringify(num_flat_consecutive_sides),
1665  num_flat_consecutive_sides);
1666  }
1667 }
1668 
1669 void
1671  const MooseEnum & log_level,
1672  bool problem_detected) const
1673 {
1674  mooseAssert(log_level != "NO_CHECK",
1675  "We should not be outputting logs if the check had been disabled");
1676  if (log_level == "INFO" || !problem_detected)
1677  mooseInfoRepeated(msg);
1678  else if (log_level == "WARNING")
1679  mooseWarning(msg);
1680  else if (log_level == "ERROR")
1681  mooseError(msg);
1682  else
1683  mooseError("Should not reach here");
1684 }
void mooseInfo(Args &&... args) const
Definition: MooseBase.h:344
void checkNonMatchingEdges(const std::unique_ptr< MeshBase > &mesh) const
Routine to check for non matching edges.
std::unique_ptr< FEGenericBase< Real > > build(const unsigned int dim, const FEType &fet)
void checkWatertightNodesets(const std::unique_ptr< MeshBase > &mesh) const
auto norm() const
QUAD8
const unsigned int invalid_uint
HEX8
bool hasBoundaryName(const MeshBase &input_mesh, const BoundaryName &name)
Whether a particular boundary name exists in the mesh.
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:467
std::unique_ptr< MeshBase > & _input
the input mesh to be diagnosed
const MooseEnum _check_sidesets_orientation
whether to check that sidesets are consistently oriented using neighbor subdomains ...
auto norm_sq(const T &a)
TET10
const MooseEnum _check_element_types
whether to check different element types in the same sub-domain
bool checkFirstOrderEdgeOverlap(const Elem &edge1, const Elem &edge2, Point &intersection_point, const Real intersection_tol)
const boundary_id_type side_id
MeshBase & mesh
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
const unsigned int _num_outputs
number of logs to output at most for each check
void mooseInfoRepeated(Args &&... args)
Emit an informational message with the given stringified, concatenated args.
Definition: MooseError.h:409
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
void checkPolygons(const std::unique_ptr< MeshBase > &mesh) const
Routine to check for non-convex polygons.
const MooseEnum _check_non_conformal_mesh
whether to check for non-conformal meshes
HEX20
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
std::vector< BoundaryID > _watertight_boundaries
IDs of boundaries to be checked in watertight checks.
void checkNonConformalMeshFromAdaptivity(const std::unique_ptr< MeshBase > &mesh) const
Routine to check whether a mesh presents non-conformality born from adaptivity.
MeshDiagnosticsGenerator(const InputParameters &parameters)
const MooseEnum _check_local_jacobian
whether to check for negative jacobians in the domain
static InputParameters validParams()
bool getFineElementsFromInteriorNode(const libMesh::Node &interior_node, const libMesh::Node &reference_node, const libMesh::Elem &elem, std::vector< const libMesh::Node *> &tentative_coarse_nodes, std::set< const libMesh::Elem *> &fine_elements)
Utility routine to gather vertex nodes for, and elements contained in, for a coarse QUAD or HEX eleme...
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)
TRI3
const Real _non_conformality_tol
tolerance for detecting when meshes are not conformal
QUAD4
FIFTH
void mooseWarning(Args &&... args) const
registerMooseObject("MooseApp", MeshDiagnosticsGenerator)
CONSTANT
const dof_id_type n_nodes
std::vector< BoundaryName > _watertight_boundary_names
Names of boundaries to be checked in watertight checks.
void checkLocalJacobians(const std::unique_ptr< MeshBase > &mesh) const
Routine to check whether the Jacobians (elem and side) are not negative.
void checkNonConformalMesh(const std::unique_ptr< MeshBase > &mesh) const
Routine to check whether a mesh presents non-conformality.
void checkElementTypes(const std::unique_ptr< MeshBase > &mesh) const
Routine to check the element types in each subdomain.
TET4
TRI6
const MooseEnum _check_adaptivity_non_conformality
whether to check for the adaptivity of non-conformal meshes
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
const Real _min_volume
minimum size for element volume to be counted as a tiny element
const std::string & type() const
Get the type of this class.
Definition: MooseBase.h:93
virtual_for_inffe const std::vector< Real > & get_JxW() const
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
Gets the boundary IDs with their names.
HEX27
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
const Real _max_volume
maximum size for element volume to be counted as a big element
void checkElementOverlap(const std::unique_ptr< MeshBase > &mesh) const
Routine to check whether elements overlap in the mesh.
TET14
static InputParameters validParams()
Definition: MeshGenerator.C:23
std::string stringify(const T &t)
conversion to string
Definition: Conversion.h:64
const MooseEnum _check_element_volumes
whether to check element volumes
void checkElementVolumes(const std::unique_ptr< MeshBase > &mesh) const
Routine to check the element volumes.
const MooseEnum _check_watertight_nodesets
whether to check that each external node is assigned to a nodeset
const MooseEnum _check_non_planar_sides
whether to check for elements in different planes (non_planar)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
EDGE2
void diagnosticsLog(std::string msg, const MooseEnum &log_level, bool problem_detected) const
Utility routine to output the final diagnostics level in the desired mode.
std::vector< boundary_id_type > findBoundaryOverlap(const std::vector< boundary_id_type > &watertight_boundaries, std::vector< boundary_id_type > &boundary_ids) const
Helper function that finds the intersection between the given vectors.
IntRange< T > make_range(T beg, T end)
const MooseEnum _check_watertight_sidesets
whether to check that each external side is assigned to a sideset
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
TRI7
void checkSidesetsOrientation(const std::unique_ptr< MeshBase > &mesh) const
Routine to check sideset orientation near subdomains.
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...
const MooseEnum _check_polygons
whether to check for non-convex polygons in the mesh
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...
QUAD9
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
const MooseEnum _check_element_overlap
whether to check for intersecting elements
void reorderNodes(std::vector< const libMesh::Node *> &nodes, const libMesh::Point &origin, const libMesh::Point &clock_start, libMesh::Point &axis)
Utility routine to re-order a vector of nodes so that they can form a valid quad element.
void checkNonConformalMesh(const std::unique_ptr< libMesh::MeshBase > &mesh, const ConsoleStream &console, const unsigned int num_outputs, const Real conformality_tol, unsigned int &num_nonconformal_nodes)
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:215
void checkNonPlanarSides(const std::unique_ptr< MeshBase > &mesh) const
Routine to check whether there are non-planar sides in the mesh.
MeshGenerators are objects that can modify or add to an existing mesh.
Definition: MeshGenerator.h:33
void checkWatertightSidesets(const std::unique_ptr< MeshBase > &mesh) const
void ErrorVector unsigned int
auto index_range(const T &sizable)
const Real pi