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