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