From a5aef60e986b96b54ff625254211c25c972ac872 Mon Sep 17 00:00:00 2001 From: Connor Ethan Ouellette Date: Thu, 16 Jul 2026 14:30:06 -0600 Subject: [PATCH 1/6] Added edge length ratio, jacobian, scaled jacobian, max and min angle diagnostics to elem quality testing --- src/geom/elem_quality.C | 12 ++++++++++++ tests/geom/elem_test.C | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/geom/elem_quality.C b/src/geom/elem_quality.C index 5975eb30455..799b3f1e168 100644 --- a/src/geom/elem_quality.C +++ b/src/geom/elem_quality.C @@ -489,6 +489,18 @@ std::vector Quality::valid(const ElemType t) break; } + case C0POLYGON: + { + v = { + EDGE_LENGTH_RATIO, + JACOBIAN, + SCALED_JACOBIAN, + MAX_ANGLE, + MIN_ANGLE + }; + + break; + } diff --git a/tests/geom/elem_test.C b/tests/geom/elem_test.C index d9458c2ba27..bfbc7f1e5be 100644 --- a/tests/geom/elem_test.C +++ b/tests/geom/elem_test.C @@ -7,6 +7,7 @@ #include #include #include +#include using namespace libMesh; @@ -84,6 +85,27 @@ public: // that has a 135 degree angle CPPUNIT_ASSERT_LESSEQUAL(135 + TOLERANCE, max_angle); + if (elem->type() == C0POLYGON) + { + const std::vector expected = { + EDGE_LENGTH_RATIO, + JACOBIAN, + SCALED_JACOBIAN, + MAX_ANGLE, + MIN_ANGLE + }; + + CPPUNIT_ASSERT(expected == Quality::valid(C0POLYGON)); + + for (const auto quality : expected) + { + const auto bounds = elem->qual_bounds(quality); + CPPUNIT_ASSERT(bounds.first >= 0.); + CPPUNIT_ASSERT(bounds.second >= bounds.first); + CPPUNIT_ASSERT(std::isfinite(elem->quality(quality))); + } + } + // Notes on minimum angle we expect to see: // 1.) 1D Elements don't have interior angles, so the base // class implementation currently returns 0 for those From a386399930ba0e5be044885a2ecba984fd41be44 Mon Sep 17 00:00:00 2001 From: Connor Ethan Ouellette Date: Thu, 16 Jul 2026 15:24:39 -0600 Subject: [PATCH 2/6] Added polyhedral quality testing --- src/geom/elem_quality.C | 19 ++++++++++--------- tests/geom/elem_test.C | 5 +++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/geom/elem_quality.C b/src/geom/elem_quality.C index 799b3f1e168..c13e0619d31 100644 --- a/src/geom/elem_quality.C +++ b/src/geom/elem_quality.C @@ -490,17 +490,18 @@ std::vector Quality::valid(const ElemType t) break; } case C0POLYGON: + case C0POLYHEDRON: { v = { - EDGE_LENGTH_RATIO, - JACOBIAN, - SCALED_JACOBIAN, - MAX_ANGLE, - MIN_ANGLE - }; - - break; - } + EDGE_LENGTH_RATIO, + JACOBIAN, + SCALED_JACOBIAN, + MAX_ANGLE, + MIN_ANGLE + }; + + break; + } diff --git a/tests/geom/elem_test.C b/tests/geom/elem_test.C index bfbc7f1e5be..4cd9dd7fc37 100644 --- a/tests/geom/elem_test.C +++ b/tests/geom/elem_test.C @@ -85,7 +85,8 @@ public: // that has a 135 degree angle CPPUNIT_ASSERT_LESSEQUAL(135 + TOLERANCE, max_angle); - if (elem->type() == C0POLYGON) + if (elem->type() == C0POLYGON || + elem->type() == C0POLYHEDRON) { const std::vector expected = { EDGE_LENGTH_RATIO, @@ -95,7 +96,7 @@ public: MIN_ANGLE }; - CPPUNIT_ASSERT(expected == Quality::valid(C0POLYGON)); + CPPUNIT_ASSERT(expected == Quality::valid(elem->type())); for (const auto quality : expected) { From 5011b7823dd6d19cfe23b2d0c17a2af175a58833 Mon Sep 17 00:00:00 2001 From: Connor Ethan Ouellette Date: Mon, 27 Jul 2026 09:36:45 -0600 Subject: [PATCH 3/6] Removed unsupported quality metrics, leaving JACOBIAN, MAX_ANGLE, MIN_ANGLE --- src/geom/elem_quality.C | 5 +---- tests/geom/elem_test.C | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/geom/elem_quality.C b/src/geom/elem_quality.C index c13e0619d31..c03c2161239 100644 --- a/src/geom/elem_quality.C +++ b/src/geom/elem_quality.C @@ -489,13 +489,12 @@ std::vector Quality::valid(const ElemType t) break; } + case C0POLYGON: case C0POLYHEDRON: { v = { - EDGE_LENGTH_RATIO, JACOBIAN, - SCALED_JACOBIAN, MAX_ANGLE, MIN_ANGLE }; @@ -503,8 +502,6 @@ std::vector Quality::valid(const ElemType t) break; } - - #ifdef LIBMESH_ENABLE_INFINITE_ELEMENTS case INFEDGE2: diff --git a/tests/geom/elem_test.C b/tests/geom/elem_test.C index 4cd9dd7fc37..0030b2f60e8 100644 --- a/tests/geom/elem_test.C +++ b/tests/geom/elem_test.C @@ -89,9 +89,7 @@ public: elem->type() == C0POLYHEDRON) { const std::vector expected = { - EDGE_LENGTH_RATIO, JACOBIAN, - SCALED_JACOBIAN, MAX_ANGLE, MIN_ANGLE }; From 05105c254f0645e8d4e51d2bc8432ad8fe8dd2e6 Mon Sep 17 00:00:00 2001 From: Connor Ethan Ouellette Date: Mon, 27 Jul 2026 10:09:24 -0600 Subject: [PATCH 4/6] Split C0polygon and c0Polyhedron quality metrics. The JACOBIAN metric is only accurate for vertices with exactly 3 adjacent edges; not reliable for polyhedra --- src/geom/elem_quality.C | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/geom/elem_quality.C b/src/geom/elem_quality.C index c03c2161239..a0cd47ae7e2 100644 --- a/src/geom/elem_quality.C +++ b/src/geom/elem_quality.C @@ -46,6 +46,10 @@ std::string Quality::name (const ElemQuality q) switch (q) { + case EDGE_LENGTH_RATIO: + its_name = "Edge Length Ratio"; + break; + case ASPECT_RATIO: its_name = "Aspect Ratio"; break; @@ -491,10 +495,25 @@ std::vector Quality::valid(const ElemType t) } case C0POLYGON: - case C0POLYHEDRON: { v = { + EDGE_LENGTH_RATIO, JACOBIAN, + SCALED_JACOBIAN, + MAX_ANGLE, + MIN_ANGLE + }; + + break; + } + + case C0POLYHEDRON: + { + // The generic Jacobian metrics only inspect vertices with + // exactly three adjacent edges, but arbitrary polyhedra may + // have vertices of higher valence. + v = { + EDGE_LENGTH_RATIO, MAX_ANGLE, MIN_ANGLE }; From 7e90a106f08a23ffc765a6a44959eb114636f0bf Mon Sep 17 00:00:00 2001 From: Connor Ethan Ouellette Date: Mon, 27 Jul 2026 10:42:30 -0600 Subject: [PATCH 5/6] Verifies supported metric lists / recommended bounds. Stopped applying JACOBIAN to C0Polyhedron. --- tests/geom/elem_test.C | 146 ++++++++++++++++++++++++++--------------- tests/geom/elem_test.h | 18 ++--- 2 files changed, 103 insertions(+), 61 deletions(-) diff --git a/tests/geom/elem_test.C b/tests/geom/elem_test.C index 0030b2f60e8..c08900dc02c 100644 --- a/tests/geom/elem_test.C +++ b/tests/geom/elem_test.C @@ -66,8 +66,9 @@ public: // We're building isotropic meshes, where even elements // dissected from cubes ought to have tolerable quality. // - // Worst I see is 2 on tets, but let's add a little tolerance - // in case we decide to play with rotated meshes here later + // Worst I see is 2 on tets and the skewed C0Polyhedron, but + // let's add a little tolerance in case we decide to play with + // rotated meshes here later. CPPUNIT_ASSERT_LESSEQUAL(Real(2+TOLERANCE), edge_length_ratio); // edge_length_ratio <= 2 // The MIN_ANGLE and MAX_ANGLE quality metrics are also defined on all elements @@ -85,24 +86,57 @@ public: // that has a 135 degree angle CPPUNIT_ASSERT_LESSEQUAL(135 + TOLERANCE, max_angle); - if (elem->type() == C0POLYGON || - elem->type() == C0POLYHEDRON) + const auto assert_quality = + [elem](const ElemQuality quality, + const Real expected_value, + const Real expected_lower_bound, + const Real expected_upper_bound) + { + const auto bounds = elem->qual_bounds(quality); + LIBMESH_ASSERT_FP_EQUAL(expected_lower_bound, bounds.first, TOLERANCE); + LIBMESH_ASSERT_FP_EQUAL(expected_upper_bound, bounds.second, TOLERANCE); + LIBMESH_ASSERT_FP_EQUAL(expected_value, elem->quality(quality), TOLERANCE); + }; + + if (elem->type() == C0POLYGON) { const std::vector expected = { + EDGE_LENGTH_RATIO, JACOBIAN, + SCALED_JACOBIAN, + MAX_ANGLE, + MIN_ANGLE + }; + + CPPUNIT_ASSERT(expected == Quality::valid(elem->type())); + CPPUNIT_ASSERT_EQUAL( + std::string("Edge Length Ratio"), + Quality::name(EDGE_LENGTH_RATIO)); + + assert_quality(EDGE_LENGTH_RATIO, std::sqrt(Real(2)), 1., 4.); + assert_quality(JACOBIAN, 0.5, 0.5, 1.); + assert_quality( + SCALED_JACOBIAN, Real(1) / std::sqrt(Real(2)), 0.5, 1.); + assert_quality(MAX_ANGLE, 135., 108., 144.); + assert_quality(MIN_ANGLE, 90., 54., 108.); + } + else if (elem->type() == C0POLYHEDRON) + { + const std::vector expected = { + EDGE_LENGTH_RATIO, MAX_ANGLE, MIN_ANGLE }; CPPUNIT_ASSERT(expected == Quality::valid(elem->type())); - for (const auto quality : expected) - { - const auto bounds = elem->qual_bounds(quality); - CPPUNIT_ASSERT(bounds.first >= 0.); - CPPUNIT_ASSERT(bounds.second >= bounds.first); - CPPUNIT_ASSERT(std::isfinite(elem->quality(quality))); - } + const Real min_polyhedron_angle = + std::acos(Real(1) / std::sqrt(Real(17))) * + Real(180) / libMesh::pi; + assert_quality(EDGE_LENGTH_RATIO, 2., 1., 4.); + assert_quality( + MAX_ANGLE, Real(180) - min_polyhedron_angle, 60., 180.); + assert_quality(MIN_ANGLE, min_polyhedron_angle, 30., 180.); } // Notes on minimum angle we expect to see: @@ -116,8 +150,10 @@ public: if (elem->dim() > 1) CPPUNIT_ASSERT_GREATEREQUAL((std::acos(Real(2)/std::sqrt(Real(6))) * 180 / libMesh::pi) - TOLERANCE, min_angle); - // MIN,MAX_DIHEDRAL_ANGLE are implemented for all 3D elements - if (elem->dim() > 2) + // MIN,MAX_DIHEDRAL_ANGLE are implemented for the fixed-topology + // 3D elements tested here. The generic implementation is not + // suitable for arbitrary C0Polyhedron faces and angles. + if (elem->dim() > 2 && elem->type() != C0POLYHEDRON) { const Real min_dihedral_angle = elem->quality(MIN_DIHEDRAL_ANGLE); const Real max_dihedral_angle = elem->quality(MAX_DIHEDRAL_ANGLE); @@ -141,46 +177,50 @@ public: CPPUNIT_ASSERT_GREATEREQUAL(45 - TOLERANCE, min_dihedral_angle); } - // The JACOBIAN and SCALED_JACOBIAN quality metrics are also defined on all elements + // The generic JACOBIAN metrics require exactly dim() adjacent + // edges at a vertex, which is not guaranteed for arbitrary + // C0Polyhedron topology. + if (elem->type() != C0POLYHEDRON) + { + // The largest non-infinite elements in this test (Hexes and + // Prisms) have a nodal Jacobian (volume) of 8, e.g. the 2x2x2 + // reference Hex. The infinite elements that we construct for + // this testing have a max nodal area of 64 since they are + // created (see setUp()) with max side lengths of 4 (4*4*4=64). + const Real jac = elem->quality(JACOBIAN); + if (elem->infinite()) + CPPUNIT_ASSERT_LESSEQUAL (64 + TOLERANCE, jac); + else + CPPUNIT_ASSERT_LESSEQUAL (8 + TOLERANCE, jac); + + // The smallest 2D/3D nodal areas for regular elements in this + // test are found in tetrahedra, which have a minimum value of + // 2.0. However, we return a default value of 1.0 for 0D and + // 1D elements here, and we have a custom distorted-pentagon + // C0Polygon with a 0.5 at 3 nodes, so we handle those cases + // too. + if (elem->dim() < 2) + CPPUNIT_ASSERT_GREATEREQUAL(1 - TOLERANCE, jac); + else if (elem->type() == C0POLYGON) + CPPUNIT_ASSERT_GREATEREQUAL(0.5 - TOLERANCE, jac); + else + CPPUNIT_ASSERT_GREATEREQUAL(2 - TOLERANCE, jac); - // The largest non-infinite elements in this test (Hexes and - // Prisms) have a nodal Jacobian (volume) of 8, e.g. the 2x2x2 - // reference Hex. The infinite elements that we construct for - // this testing have a max nodal area of 64 since they are - // created (see setUp()) with max side lengths of 4 (4*4*4=64). - const Real jac = elem->quality(JACOBIAN); - if (elem->infinite()) - CPPUNIT_ASSERT_LESSEQUAL (64 + TOLERANCE, jac); - else - CPPUNIT_ASSERT_LESSEQUAL (8 + TOLERANCE, jac); - - // The smallest 2D/3D nodal areas for regular elements in this - // test are found in tetrahedra, which have a minimum value of - // 2.0. However, we return a default value of 1.0 for 0D and - // 1D elements here, and we have a custom distorted-pentagon - // C0Polygon with a 0.5 at 2 nodes and a custom C0Polyhedron - // with a 1, so we handle those cases too. - if (elem->dim() < 2 || elem->type() == C0POLYHEDRON) - CPPUNIT_ASSERT_GREATEREQUAL(1 - TOLERANCE, jac); - else if (elem->type() == C0POLYGON) - CPPUNIT_ASSERT_GREATEREQUAL(0.5 - TOLERANCE, jac); - else - CPPUNIT_ASSERT_GREATEREQUAL(2 - TOLERANCE, jac); - - // The scaled Jacobian should always be <= 1. The minimum - // scaled Jacobian value I observed was ~0.408248 for the - // tetrahedral meshes. This is consistent with the way that - // we generate Tet meshes with build_cube(), as we don't - // simply refine the reference tetrahedron in that case. - const Real scaled_jac = elem->quality(SCALED_JACOBIAN); - CPPUNIT_ASSERT_LESSEQUAL (1 + TOLERANCE, scaled_jac); - CPPUNIT_ASSERT_GREATEREQUAL( Real(0.4), scaled_jac); - - // Debugging - // libMesh::out << "elem->type() = " << Utility::enum_to_string(elem->type()) - // << ", jac = " << jac - // << ", scaled_jac = " << scaled_jac - // << std::endl; + // The scaled Jacobian should always be <= 1. The minimum + // scaled Jacobian value I observed was ~0.408248 for the + // tetrahedral meshes. This is consistent with the way that + // we generate Tet meshes with build_cube(), as we don't + // simply refine the reference tetrahedron in that case. + const Real scaled_jac = elem->quality(SCALED_JACOBIAN); + CPPUNIT_ASSERT_LESSEQUAL (1 + TOLERANCE, scaled_jac); + CPPUNIT_ASSERT_GREATEREQUAL( Real(0.4), scaled_jac); + + // Debugging + // libMesh::out << "elem->type() = " << Utility::enum_to_string(elem->type()) + // << ", jac = " << jac + // << ", scaled_jac = " << scaled_jac + // << std::endl; + } } } @@ -956,7 +996,7 @@ INSTANTIATE_ELEMTEST(QUADSHELL9); // This just tests with one pentagon, but better than nothing INSTANTIATE_ELEMTEST(C0POLYGON); -// And this is just one truncated cube; also better than nothing +// And this is just one skewed box; also better than nothing INSTANTIATE_ELEMTEST(C0POLYHEDRON); #ifdef LIBMESH_ENABLE_INFINITE_ELEMENTS diff --git a/tests/geom/elem_test.h b/tests/geom/elem_test.h index e72d7c57451..e0f227ebfd6 100644 --- a/tests/geom/elem_test.h +++ b/tests/geom/elem_test.h @@ -214,18 +214,20 @@ class PerElemTest : public CppUnit::TestCase #endif #if 1 - // Or try a plain cube, for simpler debugging + // Or try an affine-skewed box. Unlike a unit cube, its + // non-unit quality values cannot be confused with the generic + // fallback value of 1. _mesh->add_point(Point(0, 0, 0), 0); - _mesh->add_point(Point(1, 0, 0), 1); - _mesh->add_point(Point(1, 1, 0), 2); - _mesh->add_point(Point(0, 1, 0), 3); + _mesh->add_point(Point(2, 0, 0), 1); + _mesh->add_point(Point(2.25, 1, 0), 2); + _mesh->add_point(Point(0.25, 1, 0), 3); _mesh->add_point(Point(0, 0, 1), 4); - _mesh->add_point(Point(1, 0, 1), 5); - _mesh->add_point(Point(1, 1, 1), 6); - _mesh->add_point(Point(0, 1, 1), 7); + _mesh->add_point(Point(2, 0, 1), 5); + _mesh->add_point(Point(2.25, 1, 1), 6); + _mesh->add_point(Point(0.25, 1, 1), 7); // With some combinations of face triangulations, even a - // simple cube has no tetrahedralization that doesn't have + // simple box has no tetrahedralization that doesn't have // either interior discontinuities or a 0-volume pancake tet! // // The initial "natural" way to orient our sides is commented From c5106b1fe8c55b16e9daf308fe91baca93afc452 Mon Sep 17 00:00:00 2001 From: Connor Ethan Ouellette Date: Mon, 27 Jul 2026 14:55:30 -0600 Subject: [PATCH 6/6] Added min/max dihedral angle metrics for c0polyhedron --- include/geom/elem.h | 13 ++++++++----- src/geom/cell_polyhedron.C | 13 +++++++++++++ src/geom/elem_quality.C | 12 +++++++++--- tests/geom/elem_test.C | 14 +++++++++----- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/include/geom/elem.h b/include/geom/elem.h index 5ce4e229aee..75611008ce7 100644 --- a/include/geom/elem.h +++ b/include/geom/elem.h @@ -1087,11 +1087,14 @@ class Elem : public ReferenceCountedObject, * .) EDGE_LENGTH_RATIO - ratio of maximum to minimum edge (in 2D, * side) length, where the min/max is taken over all Elem edges. * .) MIN,MAX_ANGLE - The minimum (respectively maximum) angle - * between all pairs of adjacent Elem edges, in degrees. Note - * that, in 3D, these are *not* the dihedral angles (angle - * between adjacent planar faces of the element), which we plan to - * add support for in the future. In 2D, we compute the angle - * between adjacent sides for this metric. + * between all pairs of adjacent Elem edges, in degrees. In 3D, + * these are *not* the dihedral angles between adjacent planar + * faces of the element. In 2D, we compute the angle between + * adjacent sides for this metric. + * .) MIN,MAX_DIHEDRAL_ANGLE - In 3D, the minimum (respectively + * maximum) unoriented angle between adjacent side planes, folded + * into the range [0, 90] degrees. In 2D, these are equivalent to + * MIN,MAX_ANGLE. */ virtual Real quality (const ElemQuality q) const; diff --git a/src/geom/cell_polyhedron.C b/src/geom/cell_polyhedron.C index decfea6f2d4..9f1bde025f3 100644 --- a/src/geom/cell_polyhedron.C +++ b/src/geom/cell_polyhedron.C @@ -494,6 +494,19 @@ std::pair Polyhedron::qual_bounds (const ElemQuality q) const bounds.second = 180.; break; + // The generic dihedral metrics measure the unoriented angle + // between adjacent face planes, so their values are in [0, 90]. + // Use the same recommended lower bounds as MIN,MAX_ANGLE. + case MIN_DIHEDRAL_ANGLE: + bounds.first = 30.; + bounds.second = 90.; + break; + + case MAX_DIHEDRAL_ANGLE: + bounds.first = 60.; + bounds.second = 90.; + break; + case JACOBIAN: case SCALED_JACOBIAN: bounds.first = 0.5; diff --git a/src/geom/elem_quality.C b/src/geom/elem_quality.C index a0cd47ae7e2..2353fb72d6e 100644 --- a/src/geom/elem_quality.C +++ b/src/geom/elem_quality.C @@ -215,18 +215,22 @@ std::string Quality::describe (const ElemQuality q) case MAX_DIHEDRAL_ANGLE: desc << "Largest angle between all adjacent pairs of sides (in 2D, equivalent to MAX_ANGLE).\n" + << "In 3D, this is the largest unoriented angle between adjacent side planes, in the range [0, 90].\n" << '\n' << "Suggested ranges:\n" << "Quads: (90 -> 135)\n" - << "Triangles: (60 -> 90)"; + << "Triangles: (60 -> 90)\n" + << "C0Polyhedra: (60 -> 90)"; break; case MIN_DIHEDRAL_ANGLE: desc << "Smallest angle between all adjacent pairs of sides (in 2D, equivalent to MIN_ANGLE).\n" + << "In 3D, this is the smallest unoriented angle between adjacent side planes, in the range [0, 90].\n" << '\n' << "Suggested ranges:\n" << "Quads: (45 -> 90)\n" - << "Triangles: (30 -> 60)"; + << "Triangles: (30 -> 60)\n" + << "C0Polyhedra: (30 -> 90)"; break; case CONDITION: @@ -515,7 +519,9 @@ std::vector Quality::valid(const ElemType t) v = { EDGE_LENGTH_RATIO, MAX_ANGLE, - MIN_ANGLE + MIN_ANGLE, + MAX_DIHEDRAL_ANGLE, + MIN_DIHEDRAL_ANGLE }; break; diff --git a/tests/geom/elem_test.C b/tests/geom/elem_test.C index c08900dc02c..41ced6f6b73 100644 --- a/tests/geom/elem_test.C +++ b/tests/geom/elem_test.C @@ -125,7 +125,9 @@ public: const std::vector expected = { EDGE_LENGTH_RATIO, MAX_ANGLE, - MIN_ANGLE + MIN_ANGLE, + MAX_DIHEDRAL_ANGLE, + MIN_DIHEDRAL_ANGLE }; CPPUNIT_ASSERT(expected == Quality::valid(elem->type())); @@ -137,6 +139,9 @@ public: assert_quality( MAX_ANGLE, Real(180) - min_polyhedron_angle, 60., 180.); assert_quality(MIN_ANGLE, min_polyhedron_angle, 30., 180.); + assert_quality(MAX_DIHEDRAL_ANGLE, 90., 60., 90.); + assert_quality( + MIN_DIHEDRAL_ANGLE, min_polyhedron_angle, 30., 90.); } // Notes on minimum angle we expect to see: @@ -150,10 +155,9 @@ public: if (elem->dim() > 1) CPPUNIT_ASSERT_GREATEREQUAL((std::acos(Real(2)/std::sqrt(Real(6))) * 180 / libMesh::pi) - TOLERANCE, min_angle); - // MIN,MAX_DIHEDRAL_ANGLE are implemented for the fixed-topology - // 3D elements tested here. The generic implementation is not - // suitable for arbitrary C0Polyhedron faces and angles. - if (elem->dim() > 2 && elem->type() != C0POLYHEDRON) + // MIN,MAX_DIHEDRAL_ANGLE are implemented for all 3D elements + // tested here. + if (elem->dim() > 2) { const Real min_dihedral_angle = elem->quality(MIN_DIHEDRAL_ANGLE); const Real max_dihedral_angle = elem->quality(MAX_DIHEDRAL_ANGLE);