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