libMesh
volume_test.C
Go to the documentation of this file.
1 // libmesh includes
2 #include <libmesh/cell_c0polyhedron.h>
3 #include <libmesh/elem.h>
4 #include <libmesh/enum_elem_type.h>
5 #include <libmesh/face_c0polygon.h>
6 #include <libmesh/mesh_generation.h>
7 #include <libmesh/mesh_modification.h>
8 #include <libmesh/mesh.h>
9 #include <libmesh/reference_elem.h>
10 #include <libmesh/replicated_mesh.h>
11 #include <libmesh/node.h>
12 #include <libmesh/enum_to_string.h>
13 #include <libmesh/tensor_value.h>
14 #include <libmesh/enum_elem_quality.h>
15 
16 // unit test includes
17 #include "test_comm.h"
18 #include "libmesh_cppunit.h"
19 
20 // C++ includes
21 #include <iomanip>
22 
23 using namespace libMesh;
24 
25 class VolumeTest : public CppUnit::TestCase
26 {
27 
28 public:
29  LIBMESH_CPPUNIT_TEST_SUITE( VolumeTest );
30  CPPUNIT_TEST( testTwistedVolume );
31  CPPUNIT_TEST( testEdge3Volume );
32  CPPUNIT_TEST( testEdge3Invertible );
33  CPPUNIT_TEST( testEdge4Invertible );
34  CPPUNIT_TEST( testQuad4Invertible );
35  CPPUNIT_TEST( testTri3TrueCentroid );
36  CPPUNIT_TEST( testQuad4TrueCentroid );
37  CPPUNIT_TEST( testPyramid5TrueCentroid );
38  CPPUNIT_TEST( testHex8TrueCentroid );
39  CPPUNIT_TEST( testPrism6TrueCentroid );
40  CPPUNIT_TEST( testHex20PLevelTrueCentroid );
41  CPPUNIT_TEST( testQuad4AspectRatio );
42  CPPUNIT_TEST( testQuad4Warpage );
43  CPPUNIT_TEST( testQuad4MinMaxAngle );
44  CPPUNIT_TEST( testQuad4Jacobian );
45  CPPUNIT_TEST( testTri3AspectRatio );
46  CPPUNIT_TEST( testTet4DihedralAngle );
47  CPPUNIT_TEST( testTet4Jacobian );
48  CPPUNIT_TEST( testC0PolygonSquare );
49  CPPUNIT_TEST( testC0PolygonQuad );
50  CPPUNIT_TEST( testC0PolygonPentagon );
51  CPPUNIT_TEST( testC0PolygonHexagon );
52  CPPUNIT_TEST( testC0PolyhedronCube );
53  CPPUNIT_TEST( testC0PolyhedronHexagonalPrism );
54  CPPUNIT_TEST_SUITE_END();
55 
56 public:
57  void setUp()
58  {
59  }
60 
61  void tearDown()
62  {
63  }
64 
66  {
67  LOG_UNIT_TEST;
68 
69  // The true_centroid() == vertex_average() == (1/3, 1/3) for reference Tri3
70  {
71  const Elem & tri3 = ReferenceElem::get(TRI3);
72  Point true_centroid = tri3.true_centroid();
73  LIBMESH_ASSERT_FP_EQUAL(Real(1)/3, true_centroid(0), TOLERANCE*TOLERANCE);
74  LIBMESH_ASSERT_FP_EQUAL(Real(1)/3, true_centroid(1), TOLERANCE*TOLERANCE);
75  }
76  }
77 
79  {
80  LOG_UNIT_TEST;
81 
82  // Test Quad4::true_centroid() override
83  {
84  const Elem & quad4 = ReferenceElem::get(QUAD4);
85  Point true_centroid = quad4.true_centroid();
86  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(0), TOLERANCE*TOLERANCE);
87  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(1), TOLERANCE*TOLERANCE);
88 
89  // Compare to centroid computed via generic base class implementation
90  Point base_class_centroid = quad4.Elem::true_centroid();
91  CPPUNIT_ASSERT(true_centroid.absolute_fuzzy_equals(base_class_centroid, TOLERANCE*TOLERANCE));
92  }
93 
94  // Check that "optimized" Quad4::true_centroid() gives same result
95  // as "generic" Elem::true_centroid() on a mesh of 10% distorted elements.
96  {
98 
100  /*nx=*/3, /*ny=*/3,
101  /*xmin=*/0., /*xmax=*/1.,
102  /*ymin=*/0., /*ymax=*/1.,
103  QUAD4);
104 
106  /*factor=*/0.1,
107  /*perturb_boundary=*/false);
108 
109  for (const auto & elem : mesh.element_ptr_range())
110  {
111  Point derived_centroid = elem->true_centroid();
112  Point base_centroid = elem->Elem::true_centroid();
113 
114  // Debugging: check results in detail
115  // auto flags = libMesh::out.flags();
116  // libMesh::out << std::scientific << std::setprecision(16);
117  // libMesh::out << "derived_centroid = " << derived_centroid << std::endl;
118  // libMesh::out << "base_centroid = " << base_centroid << std::endl;
119  // libMesh::out.flags(flags);
120 
121  CPPUNIT_ASSERT(derived_centroid.absolute_fuzzy_equals(base_centroid, TOLERANCE*TOLERANCE));
122 
123  Real derived_volume = elem->volume();
124  Real base_volume = elem->Elem::volume();
125  LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, TOLERANCE*TOLERANCE);
126  }
127  }
128  }
129 
131  {
132  LOG_UNIT_TEST;
133 
134  // Test Pyramid5::true_centroid() gives the correct result for a reference element
135  {
136  const Elem & pyr5 = ReferenceElem::get(PYRAMID5);
137  Point true_centroid = pyr5.true_centroid();
138  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(0), TOLERANCE*TOLERANCE);
139  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(1), TOLERANCE*TOLERANCE);
140  LIBMESH_ASSERT_FP_EQUAL(0.25, true_centroid(2), TOLERANCE*TOLERANCE);
141  }
142 
143  // Currently there is not an optimized Pyramid5::true_centroid() to compare against
144  test_true_centroid_and_volume(PYRAMID5);
145  }
146 
147  void testHex8TrueCentroid() { LOG_UNIT_TEST; test_true_centroid_and_volume(HEX8); }
148  void testPrism6TrueCentroid() { LOG_UNIT_TEST; test_true_centroid_and_volume(PRISM6); }
149 
151  {
152  LOG_UNIT_TEST;
153 
154  // Test that Elem base class true_centroid() implementation works
155  // for an elevated p_level HEX20
156  {
157 #ifdef LIBMESH_ENABLE_AMR
160  /*nelem=*/1, /*nelem=*/1, /*nelem=*/1,
161  /*xmin=*/-1, /*xmax=*/1,
162  /*ymin=*/-1, /*ymax=*/1,
163  /*zmin=*/-1, /*zmax=*/1,
164  HEX20);
165  Elem * hex20 = mesh.elem_ptr(0);
166  hex20->set_p_level(1);
167  Point true_centroid = hex20->true_centroid();
168  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(0), TOLERANCE*TOLERANCE);
169  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(1), TOLERANCE*TOLERANCE);
170  LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(2), TOLERANCE*TOLERANCE);
171 #endif // LIBMESH_ENABLE_AMR
172  }
173  }
174 
176  {
177  LOG_UNIT_TEST;
178 
180 
181  // Build an element type that will fall back on our generic
182  // quadrature-based Elem::volume()
184  /*nelem=*/1, /*nelem=*/1, /*nelem=*/1,
185  /*xmin=*/-1, /*xmax=*/1,
186  /*ymin=*/-1, /*ymax=*/1,
187  /*zmin=*/-1, /*zmax=*/1,
188  PRISM21);
189 
190  // Pick an element and twist it
191  Elem * prism6 = mesh.elem_ptr(0);
192  prism6->point(1) *= -1;
193  prism6->point(1) += 2*prism6->point(0);
194 
195  // The real test here is that volume() doesn't throw
196  const Real vol = prism6->volume();
197  const Real gold_vol = 3+Real(5)/9;
198  CPPUNIT_ASSERT_LESS(TOLERANCE, std::abs(vol-gold_vol));
199  }
200 
202  {
203  LOG_UNIT_TEST;
204 
206  MeshTools::Generation::build_line (mesh, /*nelem=*/1, 0., 1., EDGE3);
207  CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(3), mesh.n_nodes());
208 
209  auto edge3 = mesh.query_elem_ptr(0);
210  if (!edge3) // We may be on a distributed mesh
211  return;
212 
213  // Check unperturbed, straight edge case
214  LIBMESH_ASSERT_FP_EQUAL(1.0, edge3->volume(), TOLERANCE*TOLERANCE);
215 
216  // Get references to the individual Edge3 nodes
217  auto & middle_node = edge3->node_ref(2);
218  auto & right_node = edge3->node_ref(1);
219 
220  // Check middle node perturbed in +x direction case. This should
221  // not change the volume because it's still a straight line
222  // element.
223  middle_node = Point(0.5 + 1.e-3, 0., 0.);
224  LIBMESH_ASSERT_FP_EQUAL(1.0, edge3->volume(), TOLERANCE*TOLERANCE);
225 
226  // Check middle node perturbed in -x direction case. This should
227  // not change the volume because it's still a straight line
228  // element.
229  middle_node = Point(0.5 - 1.e-3, 0., 0.);
230  LIBMESH_ASSERT_FP_EQUAL(1.0, edge3->volume(), TOLERANCE*TOLERANCE);
231 
232  // Check volume of actual curved element against pre-computed value.
233  middle_node = Point(0.5, 0.25, 0.);
234  right_node = Point(1., 1., 0.);
235  LIBMESH_ASSERT_FP_EQUAL(1.4789428575446, edge3->volume(), TOLERANCE);
236 
237  // Compare with volume computed by base class Elem::volume() call
238  // which uses quadrature. We don't expect this to have full
239  // floating point accuracy.
240  middle_node = Point(0.5, 0.1, 0.);
241  right_node = Point(1., 0., 0.);
242  LIBMESH_ASSERT_FP_EQUAL(edge3->Elem::volume(), edge3->volume(), 1e-4);
243  }
244 
246  {
247  LOG_UNIT_TEST;
248 
249  // 1.) This is the original test which started the investigation
250  // of determining invertibility. In this test, the actual
251  // midpoint of nodes 0 and 1 is 0.5*(1.100328e2 + 1.176528e2) =
252  // 113.8428, so we can see that the middle node is closer to the
253  // left endpoint. In this case, it is too close and the element is
254  // not invertible.
255  bool invertible = test_elem_invertible({
256  Point(-3.566160e1, -6.690970e-1, 1.100328e2),
257  Point(-3.566160e1, -6.690970e-1, 1.176528e2),
258  Point(-3.566160e1, -6.690970e-1, 1.115568e2)}, EDGE3);
259  CPPUNIT_ASSERT(!invertible);
260 
261  // 2.) Just like case 1, but now node 2 is at the midpoint, so
262  // this case is invertible.
263  invertible = test_elem_invertible({
264  Point(-3.566160e1, -6.690970e-1, 1.100328e2),
265  Point(-3.566160e1, -6.690970e-1, 1.176528e2),
266  Point(-3.566160e1, -6.690970e-1, 113.8428)}, EDGE3);
267  CPPUNIT_ASSERT(invertible);
268 
269  // 3.) Non-collinear case where the mid-edge node is "above" and "way
270  // past" the right endpoint. This case is not invertible
271  invertible = test_elem_invertible({Point(0, 0, 0), Point(1, 0, 0), Point(3.5, 1.5, 0)}, EDGE3);
272  CPPUNIT_ASSERT(!invertible);
273  }
274 
276  {
277  LOG_UNIT_TEST;
278 
279  // Reference Elem should be invertible
280  {
281  const Elem & edge4 = ReferenceElem::get(EDGE4);
282  CPPUNIT_ASSERT(edge4.has_invertible_map());
283  }
284 
285  // If node 2 goes to the left past -5/9 = -.555, the element becomes non-invertible
286  {
287  // x2 > -5/9, the map is still invertible
288  bool invertible =
289  test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(-0.5, 0, 0), Point(Real(1)/3, 0, 0)},
290  EDGE4);
291  CPPUNIT_ASSERT(invertible);
292 
293  // x2 < -5/9, it is too close to x0 now
294  invertible =
295  test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(-0.57, 0, 0), Point(Real(1)/3, 0, 0)},
296  EDGE4);
297  CPPUNIT_ASSERT(!invertible);
298  }
299 
300  // If node 2 goes to the right past 5/21 ~ 0.2381, the element becomes non-invertible
301  {
302  // x2 < 5/21, the map should still be invertible
303  bool invertible =
304  test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(Real(3)/21, 0, 0), Point(Real(1)/3, 0, 0)},
305  EDGE4);
306  CPPUNIT_ASSERT(invertible);
307 
308  // x2 > 5/21, x2 is too close to x3 now
309  invertible =
310  test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(Real(6)/21, 0, 0), Point(Real(1)/3, 0, 0)},
311  EDGE4);
312  CPPUNIT_ASSERT(!invertible);
313  }
314  }
315 
317  {
318  LOG_UNIT_TEST;
319 
320  // Case 1: Test that rigid body rotations have no effect on the
321  // invertibility of the reference element
322  {
323  // 1a) The reference element rotated into various different different planes.
324  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
325  bool invertible = test_elem_invertible(pts, QUAD4);
326  CPPUNIT_ASSERT(invertible);
327 
328  // 1b) Rotate all points about x-axis by 90 degrees
329  Real cost = std::cos(.5*libMesh::pi);
330  Real sint = std::sin(.5*libMesh::pi);
331  RealTensorValue Rx(1, 0, 0,
332  0, cost, sint,
333  0, sint, cost);
334 
335  for (auto & pt : pts)
336  pt = Rx * pt;
337 
338  invertible = test_elem_invertible(pts, QUAD4);
339  CPPUNIT_ASSERT(invertible);
340 
341  // 1c) Rotate all points about z-axis by 90 degrees
342  RealTensorValue Rz(cost, -sint, 0,
343  sint, cost, 0,
344  0, 0, 1);
345 
346  for (auto & pt : pts)
347  pt = Rz * pt;
348 
349  invertible = test_elem_invertible(pts, QUAD4);
350  CPPUNIT_ASSERT(invertible);
351 
352  // 1d) Rotate all points about y-axis by 270 degrees
353  RealTensorValue Ry(cost, 0, sint,
354  0, 1, 0,
355  -sint, 0, cost);
356 
357  for (int cnt=0; cnt<3; ++cnt)
358  for (auto & pt : pts)
359  pt = Ry * pt;
360 
361  invertible = test_elem_invertible(pts, QUAD4);
362  CPPUNIT_ASSERT(invertible);
363  }
364 
365  // Case 2: Planar quad with top right vertex displaced to the position
366  // (alpha, alpha). Some different cases are described below.
367  // .) alpha==1: affine case, always invertible
368  // .) 1/2 < alpha < 1: planar case, invertible
369  // .) alpha<=1/2: planar case but node is now at center of the
370  // element, should give a zero/negative Jacobian on the displaced
371  // Node -> not invertible.
372  {
373  const Real alpha = .5;
374 
375  bool invertible =
376  test_elem_invertible({Point(0, 0, 0), Point(1, 0, 0), Point(alpha, alpha, 0), Point(0, 1, 0)}, QUAD4);
377 
378  CPPUNIT_ASSERT(!invertible);
379  }
380 
381  // Case 3) Top right corner is moved to (alpha, 1, 0). Element
382  // becomes non-invertible when alpha < 0.
383  {
384  const Real alpha = -0.25;
385 
386  bool invertible =
387  test_elem_invertible({Point(0, 0, 0), Point(1, 0, 0), Point(alpha, 1, 0), Point(0, 1, 0)}, QUAD4);
388 
389  CPPUNIT_ASSERT(!invertible);
390  }
391 
392  // Case 4) Degenerate case - all 4 points at same location. This
393  // zero-volume element does not have an invertible map.
394  {
395  const Real alpha = std::log(2);
396 
397  bool invertible =
398  test_elem_invertible({Point(alpha, alpha, alpha),
399  Point(alpha, alpha, alpha),
400  Point(alpha, alpha, alpha),
401  Point(alpha, alpha, alpha)}, QUAD4);
402 
403  CPPUNIT_ASSERT(!invertible);
404  }
405  }
406 
408  {
409  LOG_UNIT_TEST;
410 
411  // Case 1: Test that rigid body rotations of a unit square
412  // quadrilateral that have no effect on the quality of the
413  // element.
414  {
415  // Construct unit square QUAD4
416  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
417  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
418  libmesh_ignore(nodes);
419 
420  // 1a) Unit square aspect ratio should be == 1
421  Real aspect_ratio = elem->quality(ASPECT_RATIO);
422  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
423 
424  // 1b) Rotate all points about x-axis by 90 degrees
425  Real cost = std::cos(.5*libMesh::pi);
426  Real sint = std::sin(.5*libMesh::pi);
427  RealTensorValue Rx(1, 0, 0,
428  0, cost, sint,
429  0, sint, cost);
430 
431  for (auto & pt : pts)
432  pt = Rx * pt;
433 
434  aspect_ratio = elem->quality(ASPECT_RATIO);
435  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
436 
437  // 1c) Rotate all points about z-axis by 90 degrees
438  RealTensorValue Rz(cost, -sint, 0,
439  sint, cost, 0,
440  0, 0, 1);
441 
442  for (auto & pt : pts)
443  pt = Rz * pt;
444 
445  aspect_ratio = elem->quality(ASPECT_RATIO);
446  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
447 
448  // 1d) Rotate all points about y-axis by 270 degrees
449  RealTensorValue Ry(cost, 0, sint,
450  0, 1, 0,
451  -sint, 0, cost);
452 
453  for (int cnt=0; cnt<3; ++cnt)
454  for (auto & pt : pts)
455  pt = Ry * pt;
456 
457  aspect_ratio = elem->quality(ASPECT_RATIO);
458  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
459  }
460 
461  // Case 2: Rhombus QUAD4. This case should have an aspect ratio of
462  // 1/sin(theta), where theta is the acute interior angle of the
463  // rhombus.
464  {
465  // Helper lambda function that constructs a rhombus quad with
466  // interior acute angle theta.
467  auto test_rhombus_quad = [this](Real theta)
468  {
469  Real ct = std::cos(theta);
470  Real st = std::sin(theta);
471  std::vector<Point> pts = {
472  Point(0, 0, 0),
473  Point(1, 0, 0),
474  Point(1. + ct, st, 0),
475  Point( ct, st, 0)};
476  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
477  libmesh_ignore(nodes);
478 
479  // The expected aspect ratio for the rhombus is 1/sin(theta)
480  Real aspect_ratio = elem->quality(ASPECT_RATIO);
481  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0/st, /*actual=*/aspect_ratio, TOLERANCE);
482  };
483 
484  // 2a) Rhombus with interior angle theta=pi/6. The expected
485  // aspect ratio in this case is 1/std::sin(pi/6) = 2
486  test_rhombus_quad(libMesh::pi / 6);
487 
488  // 2b) Rhombus with interior angle theta=pi/3. The expected
489  // aspect ratio in this case is 1/std::sin(pi/3) = 2/sqrt(3) = 1.155
490  test_rhombus_quad(libMesh::pi / 3);
491  }
492 
493  // Case 3) Rectangle QUAD4. The "old" and "new" aspect ratio metrics
494  // return the same result for this case, whih is simply the ratio of
495  // the longest to shortest side.
496  {
497  auto test_rectangle_quad = [this](Real a)
498  {
499  std::vector<Point> pts = {Point(0, 0, 0), Point(a, 0, 0), Point(a, 1, 0), Point(0, 1, 0)};
500  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
501  libmesh_ignore(nodes);
502 
503  // The expected aspect ratio for the rectangle is "a"
504  Real aspect_ratio = elem->quality(ASPECT_RATIO);
505  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/a, /*actual=*/aspect_ratio, TOLERANCE);
506  };
507 
508  // 3a) Test case twice as long as it is tall
509  test_rectangle_quad(2.0);
510 
511  // 3b) Test no funny numerical stuff with extremely stretched case
512  test_rectangle_quad(1e6);
513  }
514 
515  // Case 4) Degenerate QUAD with zero length side. In the "old"
516  // aspect ratio metric we'd just get zero (as a stand-in for
517  // infinity) for this case since the minimum side length is zero,
518  // but using the new metric we get a non-zero value of 2.5. Thus,
519  // in the new metric it is possible to compare the aspect ratios
520  // of different degenerate QUAD elements rather than simply
521  // assigning all such elements a quality value of 0. Degenerate
522  // quadrilaterals are sometimes used as a "hack" to avoid having a
523  // separate subdomain of TRIs, and (surprisingly) many finite
524  // element operations just "work" on such elements.
525  {
526  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 0, 0), Point(0, 1, 0)};
527  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
528  libmesh_ignore(nodes);
529 
530  Real aspect_ratio = elem->quality(ASPECT_RATIO);
531  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/2.5, /*actual=*/aspect_ratio, TOLERANCE);
532  }
533 
534  // Case 5) Trapezoid QUAD
535  {
536  auto test_trapezoid_quad = [this](Real a)
537  {
538  // 0 <= a <= 1/2
539  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1-a, 1, 0), Point(a, 1, 0)};
540  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
541  libmesh_ignore(nodes);
542 
543  Real aspect_ratio = elem->quality(ASPECT_RATIO);
544  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1./(1. - a), /*actual=*/aspect_ratio, TOLERANCE);
545  };
546 
547  // 3a) Test "simple" trapezoid with expected aspect ratio 1.5
548  test_trapezoid_quad(1./3);
549 
550  // 3b) Test "degenerate" trapezoid with one zero length base
551  test_trapezoid_quad(0.5);
552  }
553  }
554 
556  {
557  LOG_UNIT_TEST;
558 
559  // Case 1) Reference TRI3
560  {
561  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0)};
562  auto [elem, nodes] = this->construct_elem(pts, TRI3);
563  libmesh_ignore(nodes);
564 
565  // Compute the aspect ratio for the reference Tri
566  // The expected value is ~ 1.44338
567  Real aspect_ratio = elem->quality(ASPECT_RATIO);
568  // libMesh::out << "Unit TRI3 aspect ratio = " << aspect_ratio << std::endl;
569  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(5)/2/std::sqrt(Real(3)), /*actual=*/aspect_ratio, TOLERANCE);
570  }
571 
572  // Case 2) Equilateral TRI3
573  {
574  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0.5, 0.5*std::sqrt(3), 0)};
575  auto [elem, nodes] = this->construct_elem(pts, TRI3);
576  libmesh_ignore(nodes);
577 
578  // Compute the aspect ratio for the reference Tri
579  // The expected value is 1.0
580  Real aspect_ratio = elem->quality(ASPECT_RATIO);
581  // libMesh::out << "Equilateral TRI3 aspect ratio = " << aspect_ratio << std::endl;
582  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1), /*actual=*/aspect_ratio, TOLERANCE);
583  }
584 
585  // Case 3) Reference TRI3 with one leg length = L >> 1
586  {
587  Real L = 10.;
588  std::vector<Point> pts = {Point(0, 0, 0), Point(L, 0, 0), Point(0, 1, 0)};
589  auto [elem, nodes] = this->construct_elem(pts, TRI3);
590  libmesh_ignore(nodes);
591 
592  // Compute the aspect ratio for the reference Tri
593  // The expected value is ~ 11.5759
594  Real aspect_ratio = elem->quality(ASPECT_RATIO);
595  // libMesh::out << "TRI3 with leg length L = " << L << ", aspect ratio = " << aspect_ratio << std::endl;
596  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1)/std::sqrt(Real(3)) * (Real(2)*L + Real(1)/(2*L)), /*actual=*/aspect_ratio, TOLERANCE);
597  }
598  }
599 
601  {
602  LOG_UNIT_TEST;
603 
604  // Case 1: Test that rigid body rotations of a unit square
605  // quadrilateral that have no effect on the quality of the
606  // element.
607  {
608  // Construct unit square QUAD4
609  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
610  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
611  libmesh_ignore(nodes);
612 
613  // 1a) Any flat element should have warp == 1
614  Real warpage = elem->quality(WARP);
615  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
616 
617  // 1b) Rotate all points about x-axis by 90 degrees
618  Real cost = std::cos(.5*libMesh::pi);
619  Real sint = std::sin(.5*libMesh::pi);
620  RealTensorValue Rx(1, 0, 0,
621  0, cost, sint,
622  0, sint, cost);
623 
624  for (auto & pt : pts)
625  pt = Rx * pt;
626 
627  warpage = elem->quality(WARP);
628  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
629 
630  // 1c) Rotate all points about z-axis by 90 degrees
631  RealTensorValue Rz(cost, -sint, 0,
632  sint, cost, 0,
633  0, 0, 1);
634 
635  for (auto & pt : pts)
636  pt = Rz * pt;
637 
638  warpage = elem->quality(WARP);
639  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
640 
641  // 1d) Rotate all points about y-axis by 270 degrees
642  RealTensorValue Ry(cost, 0, sint,
643  0, 1, 0,
644  -sint, 0, cost);
645 
646  for (int cnt=0; cnt<3; ++cnt)
647  for (auto & pt : pts)
648  pt = Ry * pt;
649 
650  warpage = elem->quality(WARP);
651  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
652  }
653 
654  // Case 2: Unit square quadrilateral with Node 2 displaced by a distance h in the z-direction.
655  {
656  auto test_warped_quad = [this](Real h)
657  {
658  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, h), Point(0, 1, 0)};
659  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
660  libmesh_ignore(nodes);
661 
662  Real warpage = elem->quality(WARP);
663  // libMesh::out << "QUAD with node 3 displaced by h = "
664  // << h << " in the z-direction , warpage = " << warpage
665  // << std::endl;
666  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1) / (h*h + 1), /*actual=*/warpage, TOLERANCE);
667  };
668 
669  // For h = 0.1, the expected warpage value is ~ 0.991, so not
670  // much different than the value of 1.0 for a flat element.
671  test_warped_quad(0.1);
672 
673  // For h = 0.3, the expected warpage value is ~ 0.917431, which
674  // is pretty close to the lower bound (0.9) of "acceptable"
675  // warpage, as suggested in the Cubit manual.
676  test_warped_quad(0.3);
677  }
678  }
679 
681  {
682  LOG_UNIT_TEST;
683 
684  // Case 1: Test that rigid body rotations of a unit square
685  // quadrilateral that have no effect on the quality of the
686  // element.
687  {
688  // Construct unit square QUAD4
689  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
690  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
691  libmesh_ignore(nodes);
692 
693  // 1a) Reference Elem should have min angle == max angle == pi/2
694  {
695  Real min_angle = elem->quality(MIN_ANGLE);
696  Real max_angle = elem->quality(MAX_ANGLE);
697  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
698  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
699  }
700 
701  // 1b) Rotate all points about x-axis by 90 degrees
702  Real cost = std::cos(.5*libMesh::pi);
703  Real sint = std::sin(.5*libMesh::pi);
704  RealTensorValue Rx(1, 0, 0,
705  0, cost, sint,
706  0, sint, cost);
707 
708  for (auto & pt : pts)
709  pt = Rx * pt;
710 
711  {
712  Real min_angle = elem->quality(MIN_ANGLE);
713  Real max_angle = elem->quality(MAX_ANGLE);
714  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
715  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
716  }
717 
718  // 1c) Rotate all points about z-axis by 90 degrees
719  RealTensorValue Rz(cost, -sint, 0,
720  sint, cost, 0,
721  0, 0, 1);
722 
723  for (auto & pt : pts)
724  pt = Rz * pt;
725 
726  {
727  Real min_angle = elem->quality(MIN_ANGLE);
728  Real max_angle = elem->quality(MAX_ANGLE);
729  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
730  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
731  }
732 
733  // 1d) Rotate all points about y-axis by 270 degrees
734  RealTensorValue Ry(cost, 0, sint,
735  0, 1, 0,
736  -sint, 0, cost);
737 
738  for (int cnt=0; cnt<3; ++cnt)
739  for (auto & pt : pts)
740  pt = Ry * pt;
741 
742  {
743  Real min_angle = elem->quality(MIN_ANGLE);
744  Real max_angle = elem->quality(MAX_ANGLE);
745  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
746  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
747  }
748  }
749 
750  // Case 2: Rhombus QUAD4. This case should have an interior min
751  // angle of theta and an interior max angle of pi-theta, where
752  // "theta" is the specified amount that we "sheared" the element
753  // by on creation
754  {
755  // Helper lambda function that constructs a rhombus quad with
756  // interior acute angle theta.
757  auto test_rhombus_quad = [this](Real theta)
758  {
759  Real ct = std::cos(theta);
760  Real st = std::sin(theta);
761  std::vector<Point> pts = {
762  Point(0, 0, 0),
763  Point(1, 0, 0),
764  Point(1. + ct, st, 0),
765  Point( ct, st, 0)};
766  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
767  libmesh_ignore(nodes);
768 
769  Real min_angle = elem->quality(MIN_ANGLE);
770  Real max_angle = elem->quality(MAX_ANGLE);
771  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(180) / libMesh::pi * theta, /*actual=*/min_angle, TOLERANCE);
772  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(180) / libMesh::pi * (libMesh::pi - theta), /*actual=*/max_angle, TOLERANCE);
773  };
774 
775  // 2a) Rhombus with interior angle theta=pi/6.
776  test_rhombus_quad(libMesh::pi / 6);
777 
778  // 2b) Rhombus with interior angle theta=pi/3.
779  test_rhombus_quad(libMesh::pi / 3);
780  }
781  }
782 
784  {
785  LOG_UNIT_TEST;
786 
787  // Rhombus QUAD4. This case should have a JACOBIAN and
788  // SCALED_JACOBIAN value of sin(theta), where "theta" is the
789  // specified amount that we "sheared" the element by on creation.
790  // The JACOBIAN and SCALED_JACOBIAN are the same for this element
791  // because the edge lengths are all = 1.
792  {
793  // Helper lambda function that constructs a rhombus quad with
794  // interior acute angle theta.
795  auto test_rhombus_quad = [this](Real theta)
796  {
797  Real ct = std::cos(theta);
798  Real st = std::sin(theta);
799  std::vector<Point> pts = {
800  Point(0, 0, 0),
801  Point(1, 0, 0),
802  Point(1. + ct, st, 0),
803  Point( ct, st, 0)};
804  auto [elem, nodes] = this->construct_elem(pts, QUAD4);
805  libmesh_ignore(nodes);
806 
807  Real jac = elem->quality(JACOBIAN);
808  Real scaled_jac = elem->quality(SCALED_JACOBIAN);
809 
810  // Debugging
811  // libMesh::out << "jac = " << jac << std::endl;
812  // libMesh::out << "scaled_jac = " << scaled_jac << std::endl;
813 
814  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::abs(std::sin(theta)), /*actual=*/jac, TOLERANCE);
815  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::abs(std::sin(theta)), /*actual=*/scaled_jac, TOLERANCE);
816  };
817 
818  // 2a) Rhombus with interior angle theta=pi/6.
819  test_rhombus_quad(libMesh::pi / 6);
820 
821  // 2b) Rhombus with interior angle theta=pi/3.
822  test_rhombus_quad(libMesh::pi / 3);
823  }
824  }
825 
827  {
828  LOG_UNIT_TEST;
829 
830  // Construct a tetrahedron whose projection into the x-y plane looks like the unit square,
831  // and where the apex node is just slightly out of the x-y plane. This element should have
832  // reasonable MIN,MAX_ANGLE values but MIN,MAX_DIHEDRAL angles of nearly 0. This is an
833  // example that demonstrates one should not solely use edge angles to determine Elem quality
834  // in 3D.
835  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0), Point(1, 1, 0.01)};
836  auto [elem, nodes] = this->construct_elem(pts, TET4);
837  libmesh_ignore(nodes);
838 
839  Real min_angle = elem->quality(MIN_ANGLE);
840  Real max_angle = elem->quality(MAX_ANGLE);
841  Real min_dihedral_angle = elem->quality(MIN_DIHEDRAL_ANGLE);
842  Real max_dihedral_angle = elem->quality(MAX_DIHEDRAL_ANGLE);
843 
844  // Debugging
845  // libMesh::out << "Squashed Tet4 min_angle = " << min_angle << " degrees" << std::endl;
846  // libMesh::out << "Squashed Tet4 max_angle = " << max_angle << " degrees" << std::endl;
847  // libMesh::out << "Squashed Tet4 min_dihedral_angle = " << min_dihedral_angle << " degrees" << std::endl;
848  // libMesh::out << "Squashed Tet4 max_dihedral_angle = " << max_dihedral_angle << " degrees" << std::endl;
849 
850  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/44.9985676771277, /*actual=*/min_angle, TOLERANCE);
851  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
852 
853  // Assert that both min and max dihedral angles are less than 1 degree (a very low quality element)
854  CPPUNIT_ASSERT_LESS(Real(1), min_dihedral_angle);
855  CPPUNIT_ASSERT_LESS(Real(1), max_dihedral_angle);
856  }
857 
859  {
860  LOG_UNIT_TEST;
861 
862  {
863  // Same element as in testTet4DihedralAngle(). Here we verify that
864  // the element is low quality since the SCALED_JACOBIAN is < O(h)
865  // as h -> 0, and h can be arbitrarily small in the squashed element.
866  Real h = Real(1)/100;
867  std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0), Point(1, 1, h)};
868  auto [elem, nodes] = this->construct_elem(pts, TET4);
869  libmesh_ignore(nodes);
870 
871  Real jac = elem->quality(JACOBIAN);
872  Real scaled_jac = elem->quality(SCALED_JACOBIAN);
873 
874  // Debugging
875  // libMesh::out << "Squashed Tet4 jac = " << jac << std::endl;
876  // libMesh::out << "Squashed Tet4 scaled_jac = " << scaled_jac << std::endl;
877 
878  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/h, /*actual=*/jac, TOLERANCE);
879  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/h/(h*h+1)/std::sqrt(h*h+2),
880  /*actual=*/scaled_jac, TOLERANCE * TOLERANCE);
881  }
882 
883  {
884  // A representative Tet generated by calling build_cube(). This
885  // element has minimum interior edge angle of approximately
886  // 35.26 degrees, so it it is not a simple refinement of the
887  // reference element. It is not located at the origin, but still
888  // has a simple to compute value for the JACOBIAN and
889  // SCALED_JACOBIAN quality metrics.
890  std::vector<Point> pts = {
891  Point(2.5, 2.5, 4.5),
892  Point(3.5, 1.5, 5.5),
893  Point(1.5, 1.5, 5.5),
894  Point(2.5, 2.5, 5.5)
895  };
896  auto [elem, nodes] = this->construct_elem(pts, TET4);
897  libmesh_ignore(nodes);
898 
899  Real jac = elem->quality(JACOBIAN);
900  Real scaled_jac = elem->quality(SCALED_JACOBIAN);
901 
902  // Debugging
903  // libMesh::out << "build_cube() Tet4 jac = " << jac << std::endl;
904  // libMesh::out << "build_cube() Tet4 scaled_jac = " << scaled_jac << std::endl;
905 
906  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/2, /*actual=*/jac, TOLERANCE);
907  LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1)/std::sqrt(Real(6)), /*actual=*/scaled_jac, TOLERANCE);
908  }
909  }
910 
911 
912 
913  void testC0Polygon(const std::vector<Point> & points,
914  Real expected_volume)
915  {
917 
918  const auto np = points.size();
919  std::unique_ptr<Elem> polygon = std::make_unique<C0Polygon>(np);
920 
921  for (auto p : make_range(np))
922  polygon->set_node(p, mesh.add_point(points[p], p));
923 
924  polygon->set_id() = 0;
925  Elem * elem = mesh.add_elem(std::move(polygon));
926 
927  const Real derived_volume = elem->volume();
928  const Real base_volume = elem->Elem::volume();
929  LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, TOLERANCE*TOLERANCE);
930  LIBMESH_ASSERT_FP_EQUAL(derived_volume, expected_volume, TOLERANCE*TOLERANCE);
931 
932  this->testC0PolygonMethods(mesh, np);
933  }
934 
935 
936 
938  {
939  LOG_UNIT_TEST;
940  testC0Polygon({{0,0}, {1,0}, {1,1}, {0,1}}, 1);
941  }
942 
944  {
945  LOG_UNIT_TEST;
946  testC0Polygon({{0,0}, {1,0}, {1,2}, {-1,1}}, 2.5);
947  }
948 
950  {
951  LOG_UNIT_TEST;
952  testC0Polygon({{0,0}, {1,0}, {1.5,0.5}, {1,1}, {0,1}}, 1.25);
953  }
954 
956  {
957  LOG_UNIT_TEST;
958  testC0Polygon({{0,0}, {1,0}, {1.5,0.5}, {1,1}, {0,1}, {-0.5, 0.5}}, 1.5);
959  }
960 
961 protected:
962 
963  // Helper function that is called by test_elem_invertible() to build an Elem
964  // of the requested elem_type from the provided Points. Note: the
965  // Nodes which are constructed in order to construct the Elem are
966  // also returned since
967  std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
968  construct_elem(const std::vector<Point> & pts,
969  ElemType elem_type)
970  {
971  const unsigned int n_points = pts.size();
972 
973  // Create Nodes
974  std::vector<std::unique_ptr<Node>> nodes(n_points);
975  for (unsigned int i=0; i<n_points; i++)
976  nodes[i] = Node::build(pts[i], /*id*/ i);
977 
978  // Create Elem, assign nodes
979  std::unique_ptr<Elem> elem = Elem::build(elem_type, /*parent*/ nullptr);
980 
981  // Make sure we were passed consistent input to build this type of Elem
982  libmesh_error_msg_if(elem->n_nodes() != n_points,
983  "Wrong number of points "
984  << n_points
985  << " provided to build a "
986  << Utility::enum_to_string(elem_type));
987 
988  for (unsigned int i=0; i<n_points; i++)
989  elem->set_node(i, nodes[i].get());
990 
991  // Return Elem and Nodes we created
992  return std::make_pair(std::move(elem), std::move(nodes));
993  }
994 
995  // Helper function to create the polyhedron element
996  Elem *
997  buildC0Polyhedron(std::vector<std::shared_ptr<Polygon>> sides, MeshBase & mesh)
998  {
999  std::unique_ptr<libMesh::Node> mid_elem_node;
1000  std::unique_ptr<Elem> polyhedron = std::make_unique<C0Polyhedron>(sides, mid_elem_node);
1001  if (mid_elem_node)
1002  mesh.add_node(std::move(mid_elem_node));
1003  polyhedron->set_id() = 0;
1004  Elem * elem = mesh.add_elem(std::move(polyhedron));
1005  libmesh_assert(dynamic_cast<C0Polyhedron *>(elem));
1007 
1008  return elem;
1009  }
1010 
1011  // Helper function to factor out common tests
1013  unsigned int n_sides)
1014  {
1015  Elem * elem = mesh.query_elem_ptr(0);
1016  bool found_elem = elem;
1017  mesh.comm().max(found_elem);
1018  CPPUNIT_ASSERT(found_elem);
1019 
1020  if (!elem)
1021  return;
1022 
1023  CPPUNIT_ASSERT_EQUAL(elem->type(), C0POLYGON);
1024  CPPUNIT_ASSERT_EQUAL(elem->n_nodes(), n_sides);
1025  CPPUNIT_ASSERT_EQUAL(elem->n_sub_elem(), n_sides);
1026  CPPUNIT_ASSERT_EQUAL(elem->n_sides(), n_sides);
1027  CPPUNIT_ASSERT_EQUAL(elem->n_vertices(), n_sides);
1028  CPPUNIT_ASSERT_EQUAL(elem->n_edges(), n_sides);
1029 
1030  // Even number of sides
1031  if (!(n_sides%2))
1032  {
1033  const unsigned int nsover2 = n_sides/2;
1034  for (auto i : make_range(nsover2))
1035  {
1036  CPPUNIT_ASSERT_EQUAL(elem->opposite_side(i), i+nsover2);
1037  CPPUNIT_ASSERT_EQUAL(elem->opposite_side(i+nsover2), i);
1038  }
1039  }
1040 
1041  for (unsigned int i : make_range(n_sides))
1042  {
1043  CPPUNIT_ASSERT(elem->is_vertex(i));
1044  CPPUNIT_ASSERT(!elem->is_edge(i));
1045  CPPUNIT_ASSERT(!elem->is_face(i));
1046  CPPUNIT_ASSERT(elem->is_node_on_side(i,i));
1047  CPPUNIT_ASSERT(elem->is_node_on_edge(i,i));
1048  CPPUNIT_ASSERT(elem->is_node_on_side((i+1)%n_sides,i));
1049  CPPUNIT_ASSERT(elem->is_node_on_edge((i+1)%n_sides,i));
1050  std::vector<unsigned int> nodes = elem->nodes_on_side(i);
1051  CPPUNIT_ASSERT_EQUAL(nodes.size(), std::size_t(2));
1052  CPPUNIT_ASSERT(nodes[0] == i ||
1053  nodes[1] == i);
1054  CPPUNIT_ASSERT(nodes[0] == (i+1)%n_sides ||
1055  nodes[1] == (i+1)%n_sides);
1056  std::vector<unsigned int> edge_nodes = elem->nodes_on_edge(i);
1057  CPPUNIT_ASSERT(nodes == edge_nodes);
1058 
1059 
1060  CPPUNIT_ASSERT_EQUAL(elem->local_side_node(i,0), i);
1061  CPPUNIT_ASSERT_EQUAL(elem->local_side_node(i,1), (i+1)%n_sides);
1062  CPPUNIT_ASSERT_EQUAL(elem->local_edge_node(i,0), i);
1063  CPPUNIT_ASSERT_EQUAL(elem->local_edge_node(i,1), (i+1)%n_sides);
1064 
1065  auto edge = elem->side_ptr(i);
1066  CPPUNIT_ASSERT_EQUAL(edge->type(), EDGE2);
1067  CPPUNIT_ASSERT_EQUAL(edge->node_ptr(0), mesh.node_ptr(i));
1068  CPPUNIT_ASSERT_EQUAL(edge->node_ptr(1), mesh.node_ptr((i+1)%n_sides));
1069  }
1070 
1071  CPPUNIT_ASSERT(!elem->is_flipped());
1072  elem->flip(&mesh.get_boundary_info());
1073  CPPUNIT_ASSERT(elem->is_flipped());
1074  elem->flip(&mesh.get_boundary_info());
1075  }
1076 
1077 
1078  // Helper function to factor out common tests
1079  void testC0PolyhedronMethods(MeshBase & mesh, bool has_midnode)
1080  {
1081  Elem * elem = mesh.query_elem_ptr(0);
1082  bool found_elem = elem;
1083  mesh.comm().max(found_elem);
1084  CPPUNIT_ASSERT(found_elem);
1085 
1086  if (!elem)
1087  return;
1088 
1089  CPPUNIT_ASSERT_EQUAL(elem->type(), C0POLYHEDRON);
1090 
1091  const int V = elem->n_vertices();
1092  const int E = elem->n_edges();
1093  const int F = elem->n_faces();
1094 
1095  if (!has_midnode)
1096  CPPUNIT_ASSERT_EQUAL(int(elem->n_nodes()), V);
1097  else
1098  CPPUNIT_ASSERT_EQUAL(int(elem->n_nodes()), V + 1);
1099  CPPUNIT_ASSERT_EQUAL(int(elem->n_sides()), F);
1100 
1101  // Euler characteristic for polygons homeomorphic to spheres is 2
1102  CPPUNIT_ASSERT_EQUAL(V-E+F, 2);
1103 
1104  int FE = 0;
1105  int FV = 0;
1106 
1107  std::vector<int> sides_on_vertex(V, 0);
1108  for (auto s : make_range(F))
1109  {
1110  auto side = elem->build_side_ptr(s);
1111  CPPUNIT_ASSERT_EQUAL(side->type(), C0POLYGON);
1112  FE += side->n_edges();
1113  const int SV = side->n_vertices();
1114  FV += SV;
1115 
1116  auto nos = elem->nodes_on_side(s);
1117  CPPUNIT_ASSERT_EQUAL(nos.size(), std::size_t(SV));
1118 
1119  for (auto v : make_range(SV))
1120  {
1121  Node * sidenode = side->node_ptr(v);
1122  const unsigned int n = elem->get_node_index(sidenode);
1123  CPPUNIT_ASSERT(n != invalid_uint);
1124  ++sides_on_vertex[n];
1125 
1126  // We're just looking at first order so far
1127  CPPUNIT_ASSERT(side->is_vertex(v));
1128  CPPUNIT_ASSERT(!side->is_edge(v));
1129  CPPUNIT_ASSERT(elem->is_vertex(n));
1130  CPPUNIT_ASSERT(!elem->is_edge(n));
1131  CPPUNIT_ASSERT(!elem->is_face(n));
1132  CPPUNIT_ASSERT(elem->is_node_on_side(n, s));
1133  CPPUNIT_ASSERT(std::find(nos.begin(), nos.end(), n) != nos.end());
1134  }
1135  }
1136 
1137  // We hit every edge from 2 faces
1138  CPPUNIT_ASSERT_EQUAL(E*2, FE);
1139 
1140  // We hit every vertex from at least 3 faces
1141  CPPUNIT_ASSERT_LESSEQUAL(FV, V*3); // V*3 <= FV
1142  for (auto sov : sides_on_vertex)
1143  CPPUNIT_ASSERT_LESSEQUAL(sov, 3); // 3 <= sov
1144 
1145  CPPUNIT_ASSERT(!elem->is_flipped());
1146  }
1147 
1148 
1149 
1150  void testElemVolume(const Elem * elem,
1151  Real expected_volume)
1152  {
1153  const Real derived_volume = elem->volume();
1154  const Real base_volume = elem->Elem::volume();
1155  LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, TOLERANCE*TOLERANCE);
1156  LIBMESH_ASSERT_FP_EQUAL(derived_volume, expected_volume, TOLERANCE*TOLERANCE);
1157  }
1158 
1159 
1160 
1162  {
1164 
1165  mesh.add_point(Point(0, 0, 0), 0);
1166  mesh.add_point(Point(1, 0, 0), 1);
1167  mesh.add_point(Point(1, 1, 0), 2);
1168  mesh.add_point(Point(0, 1, 0), 3);
1169  mesh.add_point(Point(0, 0, 1), 4);
1170  mesh.add_point(Point(1, 0, 1), 5);
1171  mesh.add_point(Point(1, 1, 1), 6);
1172  mesh.add_point(Point(0, 1, 1), 7);
1173 
1174  // See notes in elem_test.h
1175  const std::vector<std::vector<unsigned int>> nodes_on_side =
1176  { {0, 1, 2, 3}, // min z
1177  {0, 1, 5, 4}, // min y
1178  {2, 6, 5, 1}, // max x
1179  {2, 3, 7, 6}, // max y
1180  {0, 4, 7, 3}, // min x
1181  {5, 6, 7, 4} }; // max z
1182 
1183  // Build all the sides.
1184  std::vector<std::shared_ptr<Polygon>> sides(nodes_on_side.size());
1185 
1186  for (auto s : index_range(nodes_on_side))
1187  {
1188  const auto & nodes_on_s = nodes_on_side[s];
1189  sides[s] = std::make_shared<C0Polygon>(nodes_on_s.size());
1190  for (auto i : index_range(nodes_on_s))
1191  sides[s]->set_node(i, mesh.node_ptr(nodes_on_s[i]));
1192  }
1193 
1194  const auto poly = dynamic_cast<C0Polyhedron *>(buildC0Polyhedron(sides, mesh));
1195  testElemVolume(poly, 1);
1196  #ifdef LIBMESH_ENABLE_EXCEPTIONS
1197  testC0PolyhedronMethods(mesh, /*midnode*/false);
1198 
1199  // Check routine for subtet side to poly side mapping
1200  // NOTE: this test is hard coded to the elements used right now (to be reworked)
1201  const auto subtet0_sides_to_poly_sides = poly->subelement_sides_to_poly_sides(0);
1202  CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[0]);
1203  CPPUNIT_ASSERT_EQUAL(1, subtet0_sides_to_poly_sides[1]);
1204  CPPUNIT_ASSERT_EQUAL(0, subtet0_sides_to_poly_sides[2]);
1205  CPPUNIT_ASSERT_EQUAL(2, subtet0_sides_to_poly_sides[3]);
1206  #else
1207  testC0PolyhedronMethods(mesh, /*midnode*/true);
1208  #endif
1209  }
1210 
1211 
1212 
1214  {
1216 
1217  mesh.add_point(Point(0, -2, 0), 0);
1218  mesh.add_point(Point(-1, -1, 0), 1);
1219  mesh.add_point(Point(-1, 1, 0), 2);
1220  mesh.add_point(Point(0, 2, 0), 3);
1221  mesh.add_point(Point(1, 1, 0), 4);
1222  mesh.add_point(Point(1, -1, 0), 5);
1223  mesh.add_point(Point(0, -2, 1), 6);
1224  mesh.add_point(Point(-1, -1, 1), 7);
1225  mesh.add_point(Point(-1, 1, 1), 8);
1226  mesh.add_point(Point(0, 2, 1), 9);
1227  mesh.add_point(Point(1, 1, 1), 10);
1228  mesh.add_point(Point(1, -1, 1), 11);
1229 
1230  // See notes in elem_test.h
1231  // With this ordering, we'll need to use a mid-node to tetrahedralize it
1232  const std::vector<std::vector<unsigned int>> nodes_on_side =
1233  { {0, 1, 2, 3, 4, 5},
1234  {0, 1, 7, 6},
1235  {1, 2, 8, 7},
1236  {2, 3, 9, 8},
1237  {3, 4, 10, 9},
1238  {4, 5, 11, 10},
1239  {5, 0, 6, 11},
1240  {6, 7, 8, 9, 10, 11} };
1241 
1242  // Build all the sides.
1243  std::vector<std::shared_ptr<Polygon>> sides(nodes_on_side.size());
1244 
1245  for (auto s : index_range(nodes_on_side))
1246  {
1247  const auto & nodes_on_s = nodes_on_side[s];
1248  sides[s] = std::make_shared<C0Polygon>(nodes_on_s.size());
1249  for (auto i : index_range(nodes_on_s))
1250  sides[s]->set_node(i, mesh.node_ptr(nodes_on_s[i]));
1251  }
1252 
1253  const auto poly = dynamic_cast<C0Polyhedron *>(buildC0Polyhedron(sides, mesh));
1254  CPPUNIT_ASSERT_EQUAL((unsigned int)13, poly->n_nodes()); // we should have a mid node
1255  testElemVolume(poly, 6);
1256  testC0PolyhedronMethods(mesh, /*midnode*/true);
1257 
1258  // Check routine for subtet side to poly side mapping
1259  const auto subtet0_sides_to_poly_sides = poly->subelement_sides_to_poly_sides(0);
1260  CPPUNIT_ASSERT_EQUAL(0, subtet0_sides_to_poly_sides[0]);
1261  CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[1]);
1262  CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[2]);
1263  CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[3]);
1264  }
1265 
1266 
1267 
1268  // Helper function that builds the specified type of Elem from a
1269  // vector of Points and returns the value of has_invertible_map()
1270  // for that Elem.
1271  bool test_elem_invertible(const std::vector<Point> & pts,
1272  ElemType elem_type)
1273  {
1274  // Construct Elem of desired type
1275  auto [elem, nodes] = this->construct_elem(pts, elem_type);
1276  libmesh_ignore(nodes);
1277 
1278  // Return whether or not this Elem has an invertible map
1279  return elem->has_invertible_map();
1280  }
1281 
1282  // Helper function for testing true_centroid() and volume() implementations for
1283  // 3D elements
1285  {
1286  // Check that derived class true_centroid() gives same result as
1287  // the base class Elem::true_centroid() on a 2x2x2 mesh of 10%
1288  // distorted elements.
1289  {
1291 
1293  /*nelem=*/2, /*nelem=*/2, /*nelem=*/2,
1294  /*xmin=*/-1, /*xmax=*/1,
1295  /*ymin=*/-1, /*ymax=*/1,
1296  /*zmin=*/-1, /*zmax=*/1,
1297  elem_type);
1298 
1300  /*factor=*/0.1,
1301  /*perturb_boundary=*/false);
1302 
1303  for (const auto & elem : mesh.element_ptr_range())
1304  {
1305  Point derived_centroid = elem->true_centroid();
1306  Point base_centroid = elem->Elem::true_centroid();
1307 
1308  // Debugging: check results in detail
1309  // auto flags = libMesh::out.flags();
1310  // libMesh::out << std::scientific << std::setprecision(16);
1311  // libMesh::out << "derived_centroid = " << derived_centroid << std::endl;
1312  // libMesh::out << "base_centroid = " << base_centroid << std::endl;
1313  // libMesh::out.flags(flags);
1314 
1315  CPPUNIT_ASSERT(derived_centroid.absolute_fuzzy_equals(base_centroid, 50*TOLERANCE*TOLERANCE));
1316 
1317  // Make sure that base class and "optimized" routines for computing the cell volume agree
1318  Real derived_volume = elem->volume();
1319  Real base_volume = elem->Elem::volume();
1320  LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, 50*TOLERANCE*TOLERANCE);
1321  }
1322  }
1323  }
1324 };
1325 
virtual Point true_centroid() const
Definition: elem.C:575
void set_p_level(const unsigned int p)
Sets the value of the p-refinement level for the element.
Elem * buildC0Polyhedron(std::vector< std::shared_ptr< Polygon >> sides, MeshBase &mesh)
Definition: volume_test.C:997
ElemType
Defines an enum for geometric element types.
void testQuad4TrueCentroid()
Definition: volume_test.C:78
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
void testQuad4Invertible()
Definition: volume_test.C:316
A Node is like a Point, but with more information.
Definition: node.h:52
void testEdge4Invertible()
Definition: volume_test.C:275
const unsigned int invalid_uint
A number which is used quite often to represent an invalid or uninitialized value for an unsigned int...
Definition: libmesh.h:303
unsigned int get_node_index(const Node *node_ptr) const
Definition: elem.h:2551
void testPrism6TrueCentroid()
Definition: volume_test.C:148
virtual bool is_face(const unsigned int i) const =0
virtual std::unique_ptr< Elem > build_side_ptr(const unsigned int i)=0
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:218
static constexpr Real TOLERANCE
void testQuad4MinMaxAngle()
Definition: volume_test.C:680
void testC0PolygonHexagon()
Definition: volume_test.C:955
void testEdge3Volume()
Definition: volume_test.C:201
void distort(MeshBase &mesh, const Real factor, const bool perturb_boundary=false)
Randomly perturb the nodal locations.
void testC0PolyhedronHexagonalPrism()
Definition: volume_test.C:1213
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition: mesh_base.C:824
bool test_elem_invertible(const std::vector< Point > &pts, ElemType elem_type)
Definition: volume_test.C:1271
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
void testQuad4Jacobian()
Definition: volume_test.C:783
MeshBase & mesh
void testC0PolyhedronMethods(MeshBase &mesh, bool has_midnode)
Definition: volume_test.C:1079
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const =0
const Parallel::Communicator & comm() const
void build_square(UnstructuredMesh &mesh, const unsigned int nx, const unsigned int ny, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 2D meshes.
void testPyramid5TrueCentroid()
Definition: volume_test.C:130
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
void testC0PolygonSquare()
Definition: volume_test.C:937
virtual bool is_flipped() const =0
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
This is the MeshBase class.
Definition: mesh_base.h:80
void testC0Polygon(const std::vector< Point > &points, Real expected_volume)
Definition: volume_test.C:913
void testElemVolume(const Elem *elem, Real expected_volume)
Definition: volume_test.C:1150
dof_id_type & set_id()
Definition: dof_object.h:827
void testEdge3Invertible()
Definition: volume_test.C:245
virtual bool is_node_on_edge(const unsigned int n, const unsigned int e) const =0
A specific instantiation of the FEBase class.
Definition: fe.h:127
void libmesh_ignore(const Args &...)
void testC0PolygonPentagon()
Definition: volume_test.C:949
const int invalid_int
A number which is used quite often to represent an invalid or uninitialized value for an integer...
Definition: libmesh.h:309
const Node & node_ref(const unsigned int i) const
Definition: elem.h:2535
virtual unsigned int n_nodes() const =0
virtual unsigned int local_side_node(unsigned int side, unsigned int side_node) const =0
void testC0PolygonMethods(MeshBase &mesh, unsigned int n_sides)
Definition: volume_test.C:1012
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition: elem.C:442
void testTri3TrueCentroid()
Definition: volume_test.C:65
void testTwistedVolume()
Definition: volume_test.C:175
virtual unsigned int local_edge_node(unsigned int edge, unsigned int edge_node) const =0
Similar to Elem::local_side_node(), but instead of a side id, takes an edge id and a node id on that ...
void testC0PolygonQuad()
Definition: volume_test.C:943
libmesh_assert(ctx)
void testTet4DihedralAngle()
Definition: volume_test.C:826
virtual unsigned int n_edges() const =0
CPPUNIT_TEST_SUITE_REGISTRATION(VolumeTest)
bool absolute_fuzzy_equals(const TypeVector< T > &rhs, Real tol=TOLERANCE) const
Definition: type_vector.h:975
void testTri3AspectRatio()
Definition: volume_test.C:555
void test_true_centroid_and_volume(ElemType elem_type)
Definition: volume_test.C:1284
virtual void flip(BoundaryInfo *boundary_info)=0
Flips the element (by swapping node and neighbor pointers) to have a mapping Jacobian of opposite sig...
std::string enum_to_string(const T e)
void testHex20PLevelTrueCentroid()
Definition: volume_test.C:150
virtual std::vector< unsigned int > nodes_on_edge(const unsigned int) const =0
void testQuad4Warpage()
Definition: volume_test.C:600
virtual Node * add_node(Node *n)=0
Add Node n to the end of the vertex array.
virtual const Elem * elem_ptr(const dof_id_type i) const =0
virtual unsigned int n_sides() const =0
static std::unique_ptr< Node > build(const Node &n)
Definition: node.h:315
virtual unsigned int n_vertices() const =0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
virtual const Elem * query_elem_ptr(const dof_id_type i) const =0
void max(const T &r, T &o, Request &req) const
std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > construct_elem(const std::vector< Point > &pts, ElemType elem_type)
Definition: volume_test.C:968
virtual bool is_vertex(const unsigned int i) const =0
virtual Real volume() const
Definition: elem.C:3462
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:176
void build_line(UnstructuredMesh &mesh, const unsigned int nx, const Real xmin=0., const Real xmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 1D meshes.
virtual bool has_invertible_map(Real tol=TOLERANCE *TOLERANCE) const
Definition: elem.C:2914
virtual unsigned int n_faces() const =0
The C0Polyhedron is an element in 3D with an arbitrary (but fixed) number of polygonal first-order (C...
void setUp()
Definition: volume_test.C:57
virtual unsigned int n_sub_elem() const =0
void testQuad4AspectRatio()
Definition: volume_test.C:407
virtual const Node * node_ptr(const dof_id_type i) const =0
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition: mesh.h:50
void testTet4Jacobian()
Definition: volume_test.C:858
virtual unsigned int opposite_side(const unsigned int s) const
Definition: elem.C:3556
virtual ElemType type() const =0
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
const Point & point(const unsigned int i) const
Definition: elem.h:2459
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:153
void testC0PolyhedronCube()
Definition: volume_test.C:1161
virtual bool is_edge(const unsigned int i) const =0
const Elem & get(const ElemType type_in)
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.
virtual dof_id_type n_nodes() const =0
virtual std::vector< unsigned int > nodes_on_side(const unsigned int) const =0
void testHex8TrueCentroid()
Definition: volume_test.C:147
This class defines a tensor in LIBMESH_DIM dimensional Real or Complex space.
void tearDown()
Definition: volume_test.C:61
const Real pi
.
Definition: libmesh.h:292