libMesh
Loading...
Searching...
No Matches
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
23using namespace libMesh;
24
25class VolumeTest : public CppUnit::TestCase
26{
27
28public:
62
63public:
64 void setUp()
65 {
66 }
67
68 void tearDown()
69 {
70 }
71
73 {
74 LOG_UNIT_TEST;
75
76 // The true_centroid() == vertex_average() == (1/3, 1/3) for reference Tri3
77 {
78 const Elem & tri3 = ReferenceElem::get(TRI3);
79 Point true_centroid = tri3.true_centroid();
80 LIBMESH_ASSERT_FP_EQUAL(Real(1)/3, true_centroid(0), TOLERANCE*TOLERANCE);
81 LIBMESH_ASSERT_FP_EQUAL(Real(1)/3, true_centroid(1), TOLERANCE*TOLERANCE);
82 }
83 }
84
86 {
87 LOG_UNIT_TEST;
88
89 // Test Quad4::true_centroid() override
90 {
91 const Elem & quad4 = ReferenceElem::get(QUAD4);
92 Point true_centroid = quad4.true_centroid();
93 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(0), TOLERANCE*TOLERANCE);
94 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(1), TOLERANCE*TOLERANCE);
95
96 // Compare to centroid computed via generic base class implementation
97 Point base_class_centroid = quad4.Elem::true_centroid();
98 CPPUNIT_ASSERT(true_centroid.absolute_fuzzy_equals(base_class_centroid, TOLERANCE*TOLERANCE));
99 }
100
101 // Check that "optimized" Quad4::true_centroid() gives same result
102 // as "generic" Elem::true_centroid() on a mesh of 10% distorted elements.
103 {
105
107 /*nx=*/3, /*ny=*/3,
108 /*xmin=*/0., /*xmax=*/1.,
109 /*ymin=*/0., /*ymax=*/1.,
110 QUAD4);
111
113 /*factor=*/0.1,
114 /*perturb_boundary=*/false);
115
116 for (const auto & elem : mesh.element_ptr_range())
117 {
118 Point derived_centroid = elem->true_centroid();
119 Point base_centroid = elem->Elem::true_centroid();
120
121 // Debugging: check results in detail
122 // auto flags = libMesh::out.flags();
123 // libMesh::out << std::scientific << std::setprecision(16);
124 // libMesh::out << "derived_centroid = " << derived_centroid << std::endl;
125 // libMesh::out << "base_centroid = " << base_centroid << std::endl;
126 // libMesh::out.flags(flags);
127
128 CPPUNIT_ASSERT(derived_centroid.absolute_fuzzy_equals(base_centroid, TOLERANCE*TOLERANCE));
129
130 Real derived_volume = elem->volume();
131 Real base_volume = elem->Elem::volume();
132 LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, TOLERANCE*TOLERANCE);
133 }
134 }
135 }
136
138 {
139 LOG_UNIT_TEST;
140
141 // Test Pyramid5::true_centroid() gives the correct result for a reference element
142 {
143 const Elem & pyr5 = ReferenceElem::get(PYRAMID5);
144 Point true_centroid = pyr5.true_centroid();
145 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(0), TOLERANCE*TOLERANCE);
146 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(1), TOLERANCE*TOLERANCE);
147 LIBMESH_ASSERT_FP_EQUAL(0.25, true_centroid(2), TOLERANCE*TOLERANCE);
148 }
149
150 // Currently there is not an optimized Pyramid5::true_centroid() to compare against
152 }
153
156
158 {
159 LOG_UNIT_TEST;
160
161 // Test that Elem base class true_centroid() implementation works
162 // for an elevated p_level HEX20
163 {
164#ifdef LIBMESH_ENABLE_AMR
167 /*nelem=*/1, /*nelem=*/1, /*nelem=*/1,
168 /*xmin=*/-1, /*xmax=*/1,
169 /*ymin=*/-1, /*ymax=*/1,
170 /*zmin=*/-1, /*zmax=*/1,
171 HEX20);
172 Elem * hex20 = mesh.elem_ptr(0);
173 hex20->set_p_level(1);
174 Point true_centroid = hex20->true_centroid();
175 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(0), TOLERANCE*TOLERANCE);
176 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(1), TOLERANCE*TOLERANCE);
177 LIBMESH_ASSERT_FP_EQUAL(0, true_centroid(2), TOLERANCE*TOLERANCE);
178#endif // LIBMESH_ENABLE_AMR
179 }
180 }
181
183 {
184 LOG_UNIT_TEST;
185
187
188 // Build an element type that will fall back on our generic
189 // quadrature-based Elem::volume()
191 /*nelem=*/1, /*nelem=*/1, /*nelem=*/1,
192 /*xmin=*/-1, /*xmax=*/1,
193 /*ymin=*/-1, /*ymax=*/1,
194 /*zmin=*/-1, /*zmax=*/1,
195 PRISM21);
196
197 // Pick an element and twist it
198 Elem * prism6 = mesh.elem_ptr(0);
199 prism6->point(1) *= -1;
200 prism6->point(1) += 2*prism6->point(0);
201
202 // The real test here is that volume() doesn't throw
203 const Real vol = prism6->volume();
204 const Real gold_vol = 3+Real(5)/9;
205 CPPUNIT_ASSERT_LESS(TOLERANCE, std::abs(vol-gold_vol));
206 }
207
209 {
210 LOG_UNIT_TEST;
211
213 MeshTools::Generation::build_line (mesh, /*nelem=*/1, 0., 1., EDGE3);
214 CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(3), mesh.n_nodes());
215
216 auto edge3 = mesh.query_elem_ptr(0);
217 if (!edge3) // We may be on a distributed mesh
218 return;
219
220 // Check unperturbed, straight edge case
221 LIBMESH_ASSERT_FP_EQUAL(1.0, edge3->volume(), TOLERANCE*TOLERANCE);
222
223 // Get references to the individual Edge3 nodes
224 auto & middle_node = edge3->node_ref(2);
225 auto & right_node = edge3->node_ref(1);
226
227 // Check middle node perturbed in +x direction case. This should
228 // not change the volume because it's still a straight line
229 // element.
230 middle_node = Point(0.5 + 1.e-3, 0., 0.);
231 LIBMESH_ASSERT_FP_EQUAL(1.0, edge3->volume(), TOLERANCE*TOLERANCE);
232
233 // Check middle node perturbed in -x direction case. This should
234 // not change the volume because it's still a straight line
235 // element.
236 middle_node = Point(0.5 - 1.e-3, 0., 0.);
237 LIBMESH_ASSERT_FP_EQUAL(1.0, edge3->volume(), TOLERANCE*TOLERANCE);
238
239 // Check volume of actual curved element against pre-computed value.
240 middle_node = Point(0.5, 0.25, 0.);
241 right_node = Point(1., 1., 0.);
242 LIBMESH_ASSERT_FP_EQUAL(1.4789428575446, edge3->volume(), TOLERANCE);
243
244 // Compare with volume computed by base class Elem::volume() call
245 // which uses quadrature. We don't expect this to have full
246 // floating point accuracy.
247 middle_node = Point(0.5, 0.1, 0.);
248 right_node = Point(1., 0., 0.);
249 LIBMESH_ASSERT_FP_EQUAL(edge3->Elem::volume(), edge3->volume(), 1e-4);
250 }
251
253 {
254 LOG_UNIT_TEST;
255
256 // 1.) This is the original test which started the investigation
257 // of determining invertibility. In this test, the actual
258 // midpoint of nodes 0 and 1 is 0.5*(1.100328e2 + 1.176528e2) =
259 // 113.8428, so we can see that the middle node is closer to the
260 // left endpoint. In this case, it is too close and the element is
261 // not invertible.
262 bool invertible = test_elem_invertible({
263 Point(-3.566160e1, -6.690970e-1, 1.100328e2),
264 Point(-3.566160e1, -6.690970e-1, 1.176528e2),
265 Point(-3.566160e1, -6.690970e-1, 1.115568e2)}, EDGE3);
266 CPPUNIT_ASSERT(!invertible);
267
268 // 2.) Just like case 1, but now node 2 is at the midpoint, so
269 // this case is invertible.
270 invertible = test_elem_invertible({
271 Point(-3.566160e1, -6.690970e-1, 1.100328e2),
272 Point(-3.566160e1, -6.690970e-1, 1.176528e2),
273 Point(-3.566160e1, -6.690970e-1, 113.8428)}, EDGE3);
274 CPPUNIT_ASSERT(invertible);
275
276 // 3.) Non-collinear case where the mid-edge node is "above" and "way
277 // past" the right endpoint. This case is not invertible
278 invertible = test_elem_invertible({Point(0, 0, 0), Point(1, 0, 0), Point(3.5, 1.5, 0)}, EDGE3);
279 CPPUNIT_ASSERT(!invertible);
280 }
281
283 {
284 LOG_UNIT_TEST;
285
286 // Reference Elem should be invertible
287 {
288 const Elem & edge4 = ReferenceElem::get(EDGE4);
289 CPPUNIT_ASSERT(edge4.has_invertible_map());
290 }
291
292 // If node 2 goes to the left past -5/9 = -.555, the element becomes non-invertible
293 {
294 // x2 > -5/9, the map is still invertible
295 bool invertible =
296 test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(-0.5, 0, 0), Point(Real(1)/3, 0, 0)},
297 EDGE4);
298 CPPUNIT_ASSERT(invertible);
299
300 // x2 < -5/9, it is too close to x0 now
301 invertible =
302 test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(-0.57, 0, 0), Point(Real(1)/3, 0, 0)},
303 EDGE4);
304 CPPUNIT_ASSERT(!invertible);
305 }
306
307 // If node 2 goes to the right past 5/21 ~ 0.2381, the element becomes non-invertible
308 {
309 // x2 < 5/21, the map should still be invertible
310 bool invertible =
311 test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(Real(3)/21, 0, 0), Point(Real(1)/3, 0, 0)},
312 EDGE4);
313 CPPUNIT_ASSERT(invertible);
314
315 // x2 > 5/21, x2 is too close to x3 now
316 invertible =
317 test_elem_invertible({Point(-1, 0, 0), Point(1, 0, 0), Point(Real(6)/21, 0, 0), Point(Real(1)/3, 0, 0)},
318 EDGE4);
319 CPPUNIT_ASSERT(!invertible);
320 }
321 }
322
324 {
325 LOG_UNIT_TEST;
326
327 // Case 1: Test that rigid body rotations have no effect on the
328 // invertibility of the reference element
329 {
330 // 1a) The reference element rotated into various different different planes.
331 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
332 bool invertible = test_elem_invertible(pts, QUAD4);
333 CPPUNIT_ASSERT(invertible);
334
335 // 1b) Rotate all points about x-axis by 90 degrees
336 Real cost = std::cos(.5*libMesh::pi);
337 Real sint = std::sin(.5*libMesh::pi);
338 RealTensorValue Rx(1, 0, 0,
339 0, cost, sint,
340 0, sint, cost);
341
342 for (auto & pt : pts)
343 pt = Rx * pt;
344
345 invertible = test_elem_invertible(pts, QUAD4);
346 CPPUNIT_ASSERT(invertible);
347
348 // 1c) Rotate all points about z-axis by 90 degrees
349 RealTensorValue Rz(cost, -sint, 0,
350 sint, cost, 0,
351 0, 0, 1);
352
353 for (auto & pt : pts)
354 pt = Rz * pt;
355
356 invertible = test_elem_invertible(pts, QUAD4);
357 CPPUNIT_ASSERT(invertible);
358
359 // 1d) Rotate all points about y-axis by 270 degrees
360 RealTensorValue Ry(cost, 0, sint,
361 0, 1, 0,
362 -sint, 0, cost);
363
364 for (int cnt=0; cnt<3; ++cnt)
365 for (auto & pt : pts)
366 pt = Ry * pt;
367
368 invertible = test_elem_invertible(pts, QUAD4);
369 CPPUNIT_ASSERT(invertible);
370 }
371
372 // Case 2: Planar quad with top right vertex displaced to the position
373 // (alpha, alpha). Some different cases are described below.
374 // .) alpha==1: affine case, always invertible
375 // .) 1/2 < alpha < 1: planar case, invertible
376 // .) alpha<=1/2: planar case but node is now at center of the
377 // element, should give a zero/negative Jacobian on the displaced
378 // Node -> not invertible.
379 {
380 const Real alpha = .5;
381
382 bool invertible =
383 test_elem_invertible({Point(0, 0, 0), Point(1, 0, 0), Point(alpha, alpha, 0), Point(0, 1, 0)}, QUAD4);
384
385 CPPUNIT_ASSERT(!invertible);
386 }
387
388 // Case 3) Top right corner is moved to (alpha, 1, 0). Element
389 // becomes non-invertible when alpha < 0.
390 {
391 const Real alpha = -0.25;
392
393 bool invertible =
394 test_elem_invertible({Point(0, 0, 0), Point(1, 0, 0), Point(alpha, 1, 0), Point(0, 1, 0)}, QUAD4);
395
396 CPPUNIT_ASSERT(!invertible);
397 }
398
399 // Case 4) Degenerate case - all 4 points at same location. This
400 // zero-volume element does not have an invertible map.
401 {
402 const Real alpha = std::log(2);
403
404 bool invertible =
405 test_elem_invertible({Point(alpha, alpha, alpha),
406 Point(alpha, alpha, alpha),
407 Point(alpha, alpha, alpha),
408 Point(alpha, alpha, alpha)}, QUAD4);
409
410 CPPUNIT_ASSERT(!invertible);
411 }
412 }
413
415 {
416 LOG_UNIT_TEST;
417
418 // Case 1: Test that rigid body rotations of a unit square
419 // quadrilateral that have no effect on the quality of the
420 // element.
421 {
422 // Construct unit square QUAD4
423 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
424 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
425 libmesh_ignore(nodes);
426
427 // 1a) Unit square aspect ratio should be == 1
428 Real aspect_ratio = elem->quality(ASPECT_RATIO);
429 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
430
431 // 1b) Rotate all points about x-axis by 90 degrees
432 Real cost = std::cos(.5*libMesh::pi);
433 Real sint = std::sin(.5*libMesh::pi);
434 RealTensorValue Rx(1, 0, 0,
435 0, cost, sint,
436 0, sint, cost);
437
438 for (auto & pt : pts)
439 pt = Rx * pt;
440
441 aspect_ratio = elem->quality(ASPECT_RATIO);
442 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
443
444 // 1c) Rotate all points about z-axis by 90 degrees
445 RealTensorValue Rz(cost, -sint, 0,
446 sint, cost, 0,
447 0, 0, 1);
448
449 for (auto & pt : pts)
450 pt = Rz * pt;
451
452 aspect_ratio = elem->quality(ASPECT_RATIO);
453 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
454
455 // 1d) Rotate all points about y-axis by 270 degrees
456 RealTensorValue Ry(cost, 0, sint,
457 0, 1, 0,
458 -sint, 0, cost);
459
460 for (int cnt=0; cnt<3; ++cnt)
461 for (auto & pt : pts)
462 pt = Ry * pt;
463
464 aspect_ratio = elem->quality(ASPECT_RATIO);
465 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/aspect_ratio, TOLERANCE);
466 }
467
468 // Case 2: Rhombus QUAD4. This case should have an aspect ratio of
469 // 1/sin(theta), where theta is the acute interior angle of the
470 // rhombus.
471 {
472 // Helper lambda function that constructs a rhombus quad with
473 // interior acute angle theta.
474 auto test_rhombus_quad = [this](Real theta)
475 {
476 Real ct = std::cos(theta);
477 Real st = std::sin(theta);
478 std::vector<Point> pts = {
479 Point(0, 0, 0),
480 Point(1, 0, 0),
481 Point(1. + ct, st, 0),
482 Point( ct, st, 0)};
483 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
484 libmesh_ignore(nodes);
485
486 // The expected aspect ratio for the rhombus is 1/sin(theta)
487 Real aspect_ratio = elem->quality(ASPECT_RATIO);
488 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0/st, /*actual=*/aspect_ratio, TOLERANCE);
489 };
490
491 // 2a) Rhombus with interior angle theta=pi/6. The expected
492 // aspect ratio in this case is 1/std::sin(pi/6) = 2
493 test_rhombus_quad(libMesh::pi / 6);
494
495 // 2b) Rhombus with interior angle theta=pi/3. The expected
496 // aspect ratio in this case is 1/std::sin(pi/3) = 2/sqrt(3) = 1.155
497 test_rhombus_quad(libMesh::pi / 3);
498 }
499
500 // Case 3) Rectangle QUAD4. The "old" and "new" aspect ratio metrics
501 // return the same result for this case, whih is simply the ratio of
502 // the longest to shortest side.
503 {
504 auto test_rectangle_quad = [this](Real a)
505 {
506 std::vector<Point> pts = {Point(0, 0, 0), Point(a, 0, 0), Point(a, 1, 0), Point(0, 1, 0)};
507 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
508 libmesh_ignore(nodes);
509
510 // The expected aspect ratio for the rectangle is "a"
511 Real aspect_ratio = elem->quality(ASPECT_RATIO);
512 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/a, /*actual=*/aspect_ratio, TOLERANCE);
513 };
514
515 // 3a) Test case twice as long as it is tall
516 test_rectangle_quad(2.0);
517
518 // 3b) Test no funny numerical stuff with extremely stretched case
519 test_rectangle_quad(1e6);
520 }
521
522 // Case 4) Degenerate QUAD with zero length side. In the "old"
523 // aspect ratio metric we'd just get zero (as a stand-in for
524 // infinity) for this case since the minimum side length is zero,
525 // but using the new metric we get a non-zero value of 2.5. Thus,
526 // in the new metric it is possible to compare the aspect ratios
527 // of different degenerate QUAD elements rather than simply
528 // assigning all such elements a quality value of 0. Degenerate
529 // quadrilaterals are sometimes used as a "hack" to avoid having a
530 // separate subdomain of TRIs, and (surprisingly) many finite
531 // element operations just "work" on such elements.
532 {
533 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 0, 0), Point(0, 1, 0)};
534 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
535 libmesh_ignore(nodes);
536
537 Real aspect_ratio = elem->quality(ASPECT_RATIO);
538 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/2.5, /*actual=*/aspect_ratio, TOLERANCE);
539 }
540
541 // Case 5) Trapezoid QUAD
542 {
543 auto test_trapezoid_quad = [this](Real a)
544 {
545 // 0 <= a <= 1/2
546 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1-a, 1, 0), Point(a, 1, 0)};
547 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
548 libmesh_ignore(nodes);
549
550 Real aspect_ratio = elem->quality(ASPECT_RATIO);
551 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1./(1. - a), /*actual=*/aspect_ratio, TOLERANCE);
552 };
553
554 // 3a) Test "simple" trapezoid with expected aspect ratio 1.5
555 test_trapezoid_quad(1./3);
556
557 // 3b) Test "degenerate" trapezoid with one zero length base
558 test_trapezoid_quad(0.5);
559 }
560 }
561
563 {
564 LOG_UNIT_TEST;
565
566 // Case 1) Reference TRI3
567 {
568 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0)};
569 auto [elem, nodes] = this->construct_elem(pts, TRI3);
570 libmesh_ignore(nodes);
571
572 // Compute the aspect ratio for the reference Tri
573 // The expected value is ~ 1.44338
574 Real aspect_ratio = elem->quality(ASPECT_RATIO);
575 // libMesh::out << "Unit TRI3 aspect ratio = " << aspect_ratio << std::endl;
576 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(5)/2/std::sqrt(Real(3)), /*actual=*/aspect_ratio, TOLERANCE);
577 }
578
579 // Case 2) Equilateral TRI3
580 {
581 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0.5, 0.5*std::sqrt(3), 0)};
582 auto [elem, nodes] = this->construct_elem(pts, TRI3);
583 libmesh_ignore(nodes);
584
585 // Compute the aspect ratio for the reference Tri
586 // The expected value is 1.0
587 Real aspect_ratio = elem->quality(ASPECT_RATIO);
588 // libMesh::out << "Equilateral TRI3 aspect ratio = " << aspect_ratio << std::endl;
589 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1), /*actual=*/aspect_ratio, TOLERANCE);
590 }
591
592 // Case 3) Reference TRI3 with one leg length = L >> 1
593 {
594 Real L = 10.;
595 std::vector<Point> pts = {Point(0, 0, 0), Point(L, 0, 0), Point(0, 1, 0)};
596 auto [elem, nodes] = this->construct_elem(pts, TRI3);
597 libmesh_ignore(nodes);
598
599 // Compute the aspect ratio for the reference Tri
600 // The expected value is ~ 11.5759
601 Real aspect_ratio = elem->quality(ASPECT_RATIO);
602 // libMesh::out << "TRI3 with leg length L = " << L << ", aspect ratio = " << aspect_ratio << std::endl;
603 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1)/std::sqrt(Real(3)) * (Real(2)*L + Real(1)/(2*L)), /*actual=*/aspect_ratio, TOLERANCE);
604 }
605 }
606
608 {
609 LOG_UNIT_TEST;
610
611 // Case 1: Test that rigid body rotations of a unit square
612 // quadrilateral that have no effect on the quality of the
613 // element.
614 {
615 // Construct unit square QUAD4
616 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
617 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
618 libmesh_ignore(nodes);
619
620 // 1a) Any flat element should have warp == 1
621 Real warpage = elem->quality(WARP);
622 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
623
624 // 1b) Rotate all points about x-axis by 90 degrees
625 Real cost = std::cos(.5*libMesh::pi);
626 Real sint = std::sin(.5*libMesh::pi);
627 RealTensorValue Rx(1, 0, 0,
628 0, cost, sint,
629 0, sint, cost);
630
631 for (auto & pt : pts)
632 pt = Rx * pt;
633
634 warpage = elem->quality(WARP);
635 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
636
637 // 1c) Rotate all points about z-axis by 90 degrees
638 RealTensorValue Rz(cost, -sint, 0,
639 sint, cost, 0,
640 0, 0, 1);
641
642 for (auto & pt : pts)
643 pt = Rz * pt;
644
645 warpage = elem->quality(WARP);
646 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
647
648 // 1d) Rotate all points about y-axis by 270 degrees
649 RealTensorValue Ry(cost, 0, sint,
650 0, 1, 0,
651 -sint, 0, cost);
652
653 for (int cnt=0; cnt<3; ++cnt)
654 for (auto & pt : pts)
655 pt = Ry * pt;
656
657 warpage = elem->quality(WARP);
658 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0, /*actual=*/warpage, TOLERANCE);
659 }
660
661 // Case 2: Unit square quadrilateral with Node 2 displaced by a distance h in the z-direction.
662 {
663 auto test_warped_quad = [this](Real h)
664 {
665 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, h), Point(0, 1, 0)};
666 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
667 libmesh_ignore(nodes);
668
669 Real warpage = elem->quality(WARP);
670 // libMesh::out << "QUAD with node 3 displaced by h = "
671 // << h << " in the z-direction , warpage = " << warpage
672 // << std::endl;
673 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1) / (h*h + 1), /*actual=*/warpage, TOLERANCE);
674 };
675
676 // For h = 0.1, the expected warpage value is ~ 0.991, so not
677 // much different than the value of 1.0 for a flat element.
678 test_warped_quad(0.1);
679
680 // For h = 0.3, the expected warpage value is ~ 0.917431, which
681 // is pretty close to the lower bound (0.9) of "acceptable"
682 // warpage, as suggested in the Cubit manual.
683 test_warped_quad(0.3);
684 }
685 }
686
688 {
689 LOG_UNIT_TEST;
690
691 // Case 1: Test that rigid body rotations of a unit square
692 // quadrilateral that have no effect on the quality of the
693 // element.
694 {
695 // Construct unit square QUAD4
696 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
697 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
698 libmesh_ignore(nodes);
699
700 // 1a) Reference Elem should have min angle == max angle == pi/2
701 {
702 Real min_angle = elem->quality(MIN_ANGLE);
703 Real max_angle = elem->quality(MAX_ANGLE);
704 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
705 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
706 }
707
708 // 1b) Rotate all points about x-axis by 90 degrees
709 Real cost = std::cos(.5*libMesh::pi);
710 Real sint = std::sin(.5*libMesh::pi);
711 RealTensorValue Rx(1, 0, 0,
712 0, cost, sint,
713 0, sint, cost);
714
715 for (auto & pt : pts)
716 pt = Rx * pt;
717
718 {
719 Real min_angle = elem->quality(MIN_ANGLE);
720 Real max_angle = elem->quality(MAX_ANGLE);
721 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
722 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
723 }
724
725 // 1c) Rotate all points about z-axis by 90 degrees
726 RealTensorValue Rz(cost, -sint, 0,
727 sint, cost, 0,
728 0, 0, 1);
729
730 for (auto & pt : pts)
731 pt = Rz * pt;
732
733 {
734 Real min_angle = elem->quality(MIN_ANGLE);
735 Real max_angle = elem->quality(MAX_ANGLE);
736 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
737 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
738 }
739
740 // 1d) Rotate all points about y-axis by 270 degrees
741 RealTensorValue Ry(cost, 0, sint,
742 0, 1, 0,
743 -sint, 0, cost);
744
745 for (int cnt=0; cnt<3; ++cnt)
746 for (auto & pt : pts)
747 pt = Ry * pt;
748
749 {
750 Real min_angle = elem->quality(MIN_ANGLE);
751 Real max_angle = elem->quality(MAX_ANGLE);
752 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/min_angle, TOLERANCE);
753 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
754 }
755 }
756
757 // Case 2: Rhombus QUAD4. This case should have an interior min
758 // angle of theta and an interior max angle of pi-theta, where
759 // "theta" is the specified amount that we "sheared" the element
760 // by on creation
761 {
762 // Helper lambda function that constructs a rhombus quad with
763 // interior acute angle theta.
764 auto test_rhombus_quad = [this](Real theta)
765 {
766 Real ct = std::cos(theta);
767 Real st = std::sin(theta);
768 std::vector<Point> pts = {
769 Point(0, 0, 0),
770 Point(1, 0, 0),
771 Point(1. + ct, st, 0),
772 Point( ct, st, 0)};
773 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
774 libmesh_ignore(nodes);
775
776 Real min_angle = elem->quality(MIN_ANGLE);
777 Real max_angle = elem->quality(MAX_ANGLE);
778 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(180) / libMesh::pi * theta, /*actual=*/min_angle, TOLERANCE);
779 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(180) / libMesh::pi * (libMesh::pi - theta), /*actual=*/max_angle, TOLERANCE);
780 };
781
782 // 2a) Rhombus with interior angle theta=pi/6.
783 test_rhombus_quad(libMesh::pi / 6);
784
785 // 2b) Rhombus with interior angle theta=pi/3.
786 test_rhombus_quad(libMesh::pi / 3);
787 }
788 }
789
791 {
792 LOG_UNIT_TEST;
793
794 // Rhombus QUAD4. This case should have a JACOBIAN and
795 // SCALED_JACOBIAN value of sin(theta), where "theta" is the
796 // specified amount that we "sheared" the element by on creation.
797 // The JACOBIAN and SCALED_JACOBIAN are the same for this element
798 // because the edge lengths are all = 1.
799 {
800 // Helper lambda function that constructs a rhombus quad with
801 // interior acute angle theta.
802 auto test_rhombus_quad = [this](Real theta)
803 {
804 Real ct = std::cos(theta);
805 Real st = std::sin(theta);
806 std::vector<Point> pts = {
807 Point(0, 0, 0),
808 Point(1, 0, 0),
809 Point(1. + ct, st, 0),
810 Point( ct, st, 0)};
811 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
812 libmesh_ignore(nodes);
813
814 Real jac = elem->quality(JACOBIAN);
815 Real scaled_jac = elem->quality(SCALED_JACOBIAN);
816
817 // Debugging
818 // libMesh::out << "jac = " << jac << std::endl;
819 // libMesh::out << "scaled_jac = " << scaled_jac << std::endl;
820
821 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::abs(std::sin(theta)), /*actual=*/jac, TOLERANCE);
822 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::abs(std::sin(theta)), /*actual=*/scaled_jac, TOLERANCE);
823 };
824
825 // 2a) Rhombus with interior angle theta=pi/6.
826 test_rhombus_quad(libMesh::pi / 6);
827
828 // 2b) Rhombus with interior angle theta=pi/3.
829 test_rhombus_quad(libMesh::pi / 3);
830 }
831 }
832
834 {
835 LOG_UNIT_TEST;
836
837 // The SKEW_ANGLE metric is the Verdict "skew": the maximum |cos A|
838 // between the element's principal axes. 0 means perfectly
839 // orthogonal (unskewed), larger (up to 1) means more skewed.
840
841 // Case 1: A unit square has orthogonal principal axes, so its skew
842 // angle is exactly 0.
843 {
844 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0)};
845 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
846 libmesh_ignore(nodes);
847
848 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0.0, /*actual=*/elem->quality(SKEW_ANGLE), TOLERANCE);
849 }
850
851 // Case 2: For a rhombus with interior angle theta, the two
852 // principal axes are separated by theta, so the skew metric is
853 // |cos(theta)|. This is also invariant to rigid body rotation, so
854 // we rotate the rhombus about the z-axis before checking.
855 {
856 auto test_rhombus_quad = [this](Real theta)
857 {
858 const Real ct = std::cos(theta);
859 const Real st = std::sin(theta);
860 std::vector<Point> pts = {
861 Point(0, 0, 0),
862 Point(1, 0, 0),
863 Point(1. + ct, st, 0),
864 Point( ct, st, 0)};
865
866 // Rotate all points about the z-axis by 30 degrees to confirm
867 // the metric is rotation invariant.
868 const Real cr = std::cos(libMesh::pi / 6);
869 const Real sr = std::sin(libMesh::pi / 6);
870 RealTensorValue Rz(cr, -sr, 0,
871 sr, cr, 0,
872 0, 0, 1);
873 for (auto & pt : pts)
874 pt = Rz * pt;
875
876 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
877 libmesh_ignore(nodes);
878
879 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::abs(ct), /*actual=*/elem->quality(SKEW_ANGLE), TOLERANCE);
880 };
881
882 // theta = pi/2 -> |cos| = 0 (orthogonal)
883 test_rhombus_quad(libMesh::pi / 2);
884 // theta = pi/3 -> |cos| = 0.5
885 test_rhombus_quad(libMesh::pi / 3);
886 // theta = pi/6 -> |cos| = sqrt(3)/2
887 test_rhombus_quad(libMesh::pi / 6);
888 }
889
890 // Case 3: A degenerate quad with a zero-length principal axis
891 // returns 0 by the Verdict convention.
892 {
893 std::vector<Point> pts = {Point(0, 0, 0), Point(0, 0, 0), Point(1, 1, 0), Point(1, 1, 0)};
894 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
895 libmesh_ignore(nodes);
896
897 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0.0, /*actual=*/elem->quality(SKEW_ANGLE), TOLERANCE);
898 }
899 }
900
902 {
903 LOG_UNIT_TEST;
904
905 // Case 1: A unit cube has mutually orthogonal principal axes, so
906 // its skew angle is exactly 0.
907 {
908 std::vector<Point> pts = {
909 Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0),
910 Point(0, 0, 1), Point(1, 0, 1), Point(1, 1, 1), Point(0, 1, 1)};
911 auto [elem, nodes] = this->construct_elem(pts, HEX8);
912 libmesh_ignore(nodes);
913
914 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0.0, /*actual=*/elem->quality(SKEW_ANGLE), TOLERANCE);
915 }
916
917 // Case 2: Shear the top face of a unit cube by k in the
918 // x-direction. Only the zeta principal axis tilts, becoming
919 // (k, 0, 1); the xi and eta axes stay orthogonal. The largest
920 // |cos| between any pair of axes is then k / sqrt(k^2 + 1).
921 {
922 auto test_sheared_hex = [this](Real k)
923 {
924 std::vector<Point> pts = {
925 Point(0, 0, 0), Point(1, 0, 0), Point(1, 1, 0), Point(0, 1, 0),
926 Point(k, 0, 1), Point(1+k, 0, 1), Point(1+k, 1, 1), Point(k, 1, 1)};
927 auto [elem, nodes] = this->construct_elem(pts, HEX8);
928 libmesh_ignore(nodes);
929
930 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::abs(k) / std::sqrt(k*k + 1),
931 /*actual=*/elem->quality(SKEW_ANGLE), TOLERANCE);
932 };
933
934 // k = 1 -> 1/sqrt(2) ~ 0.7071
935 test_sheared_hex(1.0);
936 // k = 0.5 -> 0.5/sqrt(1.25) ~ 0.4472
937 test_sheared_hex(0.5);
938 }
939 }
940
942 {
943 LOG_UNIT_TEST;
944
945 // Relative SIZE = min over corners of min(tau, 1/tau), where tau is
946 // the corner nodal Jacobian determinant divided by that of an ideal
947 // element of the same volume (the mean nodal determinant). It is 1
948 // for any element with a uniform Jacobian (any affine element, at
949 // any scale) and drops below 1 as the Jacobian varies across the
950 // element.
951 auto size_of = [this](const std::vector<Point> & pts)
952 {
953 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
954 libmesh_ignore(nodes);
955 return elem->quality(SIZE);
956 };
957
958 // Affine elements (uniform Jacobian) all score 1, regardless of
959 // scale or shape: unit square, a larger square, a stretched
960 // rectangle, and a sheared rhombus.
961 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
962 /*actual=*/size_of({Point(0,0,0), Point(1,0,0), Point(1,1,0), Point(0,1,0)}), TOLERANCE);
963 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
964 /*actual=*/size_of({Point(0,0,0), Point(2,0,0), Point(2,2,0), Point(0,2,0)}), TOLERANCE);
965 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
966 /*actual=*/size_of({Point(0,0,0), Point(2,0,0), Point(2,1,0), Point(0,1,0)}), TOLERANCE);
967 {
968 const Real c = std::cos(libMesh::pi/6), s = std::sin(libMesh::pi/6);
969 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
970 /*actual=*/size_of({Point(0,0,0), Point(1,0,0), Point(1.+c,s,0), Point(c,s,0)}), TOLERANCE);
971 }
972
973 // Trapezoid with corner nodal areas {6, 6, 4, 4} (mean 5): the
974 // worst corner ratio is 4/5 = 0.8.
975 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0.8,
976 /*actual=*/size_of({Point(0,0,0), Point(3,0,0), Point(2,2,0), Point(0,2,0)}), TOLERANCE);
977 }
978
980 {
981 LOG_UNIT_TEST;
982
983 auto size_of = [this](const std::vector<Point> & pts)
984 {
985 auto [elem, nodes] = this->construct_elem(pts, HEX8);
986 libmesh_ignore(nodes);
987 return elem->quality(SIZE);
988 };
989
990 // Affine boxes (uniform Jacobian) score 1 at any scale.
991 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
992 /*actual=*/size_of({Point(0,0,0), Point(1,0,0), Point(1,1,0), Point(0,1,0),
993 Point(0,0,1), Point(1,0,1), Point(1,1,1), Point(0,1,1)}), TOLERANCE);
994 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
995 /*actual=*/size_of({Point(0,0,0), Point(2,0,0), Point(2,1,0), Point(0,1,0),
996 Point(0,0,1), Point(2,0,1), Point(2,1,1), Point(0,1,1)}), TOLERANCE);
997
998 // A frustum: 2x2 base, unit top shrunk toward the axis. Bottom
999 // corners have nodal volume 4, top corners 1; the element volume is
1000 // 7/3 and the ideal cube's nodal-det/volume ratio is 1, so the ideal
1001 // nodal volume at this element's volume is 7/3. The worst corner
1002 // ratio is (1)/(7/3) = 3/7.
1003 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(3)/7,
1004 /*actual=*/size_of({Point(0,0,0), Point(2,0,0), Point(2,2,0), Point(0,2,0),
1005 Point(0.5,0.5,1), Point(1.5,0.5,1), Point(1.5,1.5,1), Point(0.5,1.5,1)}), TOLERANCE);
1006 }
1007
1009 {
1010 LOG_UNIT_TEST;
1011
1012 // TAPER = min over the two opposite-edge pairs of (shorter/longer
1013 // length), in (0, 1], with 1 meaning no taper.
1014 auto taper_of = [this](const std::vector<Point> & pts)
1015 {
1016 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
1017 libmesh_ignore(nodes);
1018 return elem->quality(TAPER);
1019 };
1020
1021 // Unit square -> both pairs equal -> 1
1022 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
1023 /*actual=*/taper_of({Point(0,0,0), Point(1,0,0), Point(1,1,0), Point(0,1,0)}), TOLERANCE);
1024
1025 // 2x1 rectangle -> opposite edges equal (parallelogram) -> no taper -> 1
1026 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
1027 /*actual=*/taper_of({Point(0,0,0), Point(2,0,0), Point(2,1,0), Point(0,1,0)}), TOLERANCE);
1028
1029 // Rhombus (unit edges) -> all edges equal -> no taper -> 1
1030 {
1031 const Real c = std::cos(libMesh::pi/3), s = std::sin(libMesh::pi/3);
1032 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
1033 /*actual=*/taper_of({Point(0,0,0), Point(1,0,0), Point(1.+c,s,0), Point(c,s,0)}), TOLERANCE);
1034 }
1035
1036 // Symmetric trapezoid: bottom edge length 4, top edge length 2, the
1037 // two slanted edges equal -> worst pair ratio = 2/4 = 0.5
1038 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/0.5,
1039 /*actual=*/taper_of({Point(0,0,0), Point(4,0,0), Point(3,1,0), Point(1,1,0)}), TOLERANCE);
1040 }
1041
1043 {
1044 LOG_UNIT_TEST;
1045
1046 // CONDITION = max over corners of the Jacobian condition number
1047 // kappa = |A|_F |A^-1|_F / 2 = (|e0|^2 + |e1|^2) / (2 |e0 x e1|),
1048 // which is 1 for a square corner and grows with stretch or skew.
1049 auto cond_of = [this](const std::vector<Point> & pts)
1050 {
1051 auto [elem, nodes] = this->construct_elem(pts, QUAD4);
1052 libmesh_ignore(nodes);
1053 return elem->quality(CONDITION);
1054 };
1055
1056 // Unit square -> 1
1057 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
1058 /*actual=*/cond_of({Point(0,0,0), Point(1,0,0), Point(1,1,0), Point(0,1,0)}), TOLERANCE);
1059
1060 // 2x1 rectangle -> (4 + 1) / (2 * 2) = 1.25 at every corner
1061 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.25,
1062 /*actual=*/cond_of({Point(0,0,0), Point(2,0,0), Point(2,1,0), Point(0,1,0)}), TOLERANCE);
1063
1064 // Unit-edge rhombus with interior angle theta -> kappa = 1/sin(theta)
1065 {
1066 auto rhombus_condition = [&cond_of](Real theta)
1067 {
1068 const Real c = std::cos(theta), s = std::sin(theta);
1069 return cond_of({Point(0,0,0), Point(1,0,0), Point(1.+c,s,0), Point(c,s,0)});
1070 };
1071
1072 // theta = pi/6 -> 1/sin(pi/6) = 2
1073 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1./std::sin(libMesh::pi/6),
1074 /*actual=*/rhombus_condition(libMesh::pi/6), TOLERANCE);
1075 // theta = pi/3 -> 1/sin(pi/3) = 2/sqrt(3)
1076 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1./std::sin(libMesh::pi/3),
1077 /*actual=*/rhombus_condition(libMesh::pi/3), TOLERANCE);
1078 }
1079 }
1080
1082 {
1083 LOG_UNIT_TEST;
1084
1085 auto cond_of = [this](const std::vector<Point> & pts)
1086 {
1087 auto [elem, nodes] = this->construct_elem(pts, HEX8);
1088 libmesh_ignore(nodes);
1089 return elem->quality(CONDITION);
1090 };
1091
1092 // Unit cube -> 1
1093 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/1.0,
1094 /*actual=*/cond_of({Point(0,0,0), Point(1,0,0), Point(1,1,0), Point(0,1,0),
1095 Point(0,0,1), Point(1,0,1), Point(1,1,1), Point(0,1,1)}), TOLERANCE);
1096
1097 // 2x1x1 box: at each corner |A|_F^2 = 6, |det| = 2,
1098 // |A^-1|_F^2 = 9/4, so kappa = sqrt(6 * 9/4)/3 = sqrt(6)/2
1099 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/std::sqrt(Real(6))/2.,
1100 /*actual=*/cond_of({Point(0,0,0), Point(2,0,0), Point(2,1,0), Point(0,1,0),
1101 Point(0,0,1), Point(2,0,1), Point(2,1,1), Point(0,1,1)}), TOLERANCE);
1102 }
1103
1105 {
1106 LOG_UNIT_TEST;
1107
1108 // Construct a tetrahedron whose projection into the x-y plane looks like the unit square,
1109 // and where the apex node is just slightly out of the x-y plane. This element should have
1110 // reasonable MIN,MAX_ANGLE values but MIN,MAX_DIHEDRAL angles of nearly 0. This is an
1111 // example that demonstrates one should not solely use edge angles to determine Elem quality
1112 // in 3D.
1113 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0), Point(1, 1, 0.01)};
1114 auto [elem, nodes] = this->construct_elem(pts, TET4);
1115 libmesh_ignore(nodes);
1116
1117 Real min_angle = elem->quality(MIN_ANGLE);
1118 Real max_angle = elem->quality(MAX_ANGLE);
1119 Real min_dihedral_angle = elem->quality(MIN_DIHEDRAL_ANGLE);
1120 Real max_dihedral_angle = elem->quality(MAX_DIHEDRAL_ANGLE);
1121
1122 // Debugging
1123 // libMesh::out << "Squashed Tet4 min_angle = " << min_angle << " degrees" << std::endl;
1124 // libMesh::out << "Squashed Tet4 max_angle = " << max_angle << " degrees" << std::endl;
1125 // libMesh::out << "Squashed Tet4 min_dihedral_angle = " << min_dihedral_angle << " degrees" << std::endl;
1126 // libMesh::out << "Squashed Tet4 max_dihedral_angle = " << max_dihedral_angle << " degrees" << std::endl;
1127
1128 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/44.9985676771277, /*actual=*/min_angle, TOLERANCE);
1129 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/90, /*actual=*/max_angle, TOLERANCE);
1130
1131 // Assert that both min and max dihedral angles are less than 1 degree (a very low quality element)
1132 CPPUNIT_ASSERT_LESS(Real(1), min_dihedral_angle);
1133 CPPUNIT_ASSERT_LESS(Real(1), max_dihedral_angle);
1134 }
1135
1137 {
1138 LOG_UNIT_TEST;
1139
1140 {
1141 // Same element as in testTet4DihedralAngle(). Here we verify that
1142 // the element is low quality since the SCALED_JACOBIAN is < O(h)
1143 // as h -> 0, and h can be arbitrarily small in the squashed element.
1144 Real h = Real(1)/100;
1145 std::vector<Point> pts = {Point(0, 0, 0), Point(1, 0, 0), Point(0, 1, 0), Point(1, 1, h)};
1146 auto [elem, nodes] = this->construct_elem(pts, TET4);
1147 libmesh_ignore(nodes);
1148
1149 Real jac = elem->quality(JACOBIAN);
1150 Real scaled_jac = elem->quality(SCALED_JACOBIAN);
1151
1152 // Debugging
1153 // libMesh::out << "Squashed Tet4 jac = " << jac << std::endl;
1154 // libMesh::out << "Squashed Tet4 scaled_jac = " << scaled_jac << std::endl;
1155
1156 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/h, /*actual=*/jac, TOLERANCE);
1157 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/h/(h*h+1)/std::sqrt(h*h+2),
1158 /*actual=*/scaled_jac, TOLERANCE * TOLERANCE);
1159 }
1160
1161 {
1162 // A representative Tet generated by calling build_cube(). This
1163 // element has minimum interior edge angle of approximately
1164 // 35.26 degrees, so it it is not a simple refinement of the
1165 // reference element. It is not located at the origin, but still
1166 // has a simple to compute value for the JACOBIAN and
1167 // SCALED_JACOBIAN quality metrics.
1168 std::vector<Point> pts = {
1169 Point(2.5, 2.5, 4.5),
1170 Point(3.5, 1.5, 5.5),
1171 Point(1.5, 1.5, 5.5),
1172 Point(2.5, 2.5, 5.5)
1173 };
1174 auto [elem, nodes] = this->construct_elem(pts, TET4);
1175 libmesh_ignore(nodes);
1176
1177 Real jac = elem->quality(JACOBIAN);
1178 Real scaled_jac = elem->quality(SCALED_JACOBIAN);
1179
1180 // Debugging
1181 // libMesh::out << "build_cube() Tet4 jac = " << jac << std::endl;
1182 // libMesh::out << "build_cube() Tet4 scaled_jac = " << scaled_jac << std::endl;
1183
1184 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/2, /*actual=*/jac, TOLERANCE);
1185 LIBMESH_ASSERT_FP_EQUAL(/*expected=*/Real(1)/std::sqrt(Real(6)), /*actual=*/scaled_jac, TOLERANCE);
1186 }
1187 }
1188
1189
1190
1191 void testC0Polygon(const std::vector<Point> & points,
1192 Real expected_volume)
1193 {
1195
1196 const auto np = points.size();
1197 std::unique_ptr<Elem> polygon = std::make_unique<C0Polygon>(np);
1198
1199 for (auto p : make_range(np))
1200 polygon->set_node(p, mesh.add_point(points[p], p));
1201
1202 polygon->set_id() = 0;
1203 Elem * elem = mesh.add_elem(std::move(polygon));
1204
1205 const Real derived_volume = elem->volume();
1206 const Real base_volume = elem->Elem::volume();
1207 LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, TOLERANCE*TOLERANCE);
1208 LIBMESH_ASSERT_FP_EQUAL(derived_volume, expected_volume, TOLERANCE*TOLERANCE);
1209
1210 this->testC0PolygonMethods(mesh, np);
1211 }
1212
1213
1214
1216 {
1217 LOG_UNIT_TEST;
1218 testC0Polygon({{0,0}, {1,0}, {1,1}, {0,1}}, 1);
1219 }
1220
1222 {
1223 LOG_UNIT_TEST;
1224 testC0Polygon({{0,0}, {1,0}, {1,2}, {-1,1}}, 2.5);
1225 }
1226
1228 {
1229 LOG_UNIT_TEST;
1230 testC0Polygon({{0,0}, {1,0}, {1.5,0.5}, {1,1}, {0,1}}, 1.25);
1231 }
1232
1234 {
1235 LOG_UNIT_TEST;
1236 testC0Polygon({{0,0}, {1,0}, {1.5,0.5}, {1,1}, {0,1}, {-0.5, 0.5}}, 1.5);
1237 }
1238
1239protected:
1240
1241 // Helper function that is called by test_elem_invertible() to build an Elem
1242 // of the requested elem_type from the provided Points. Note: the
1243 // Nodes which are constructed in order to construct the Elem are
1244 // also returned since
1245 std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
1246 construct_elem(const std::vector<Point> & pts,
1247 ElemType elem_type)
1248 {
1249 const unsigned int n_points = pts.size();
1250
1251 // Create Nodes
1252 std::vector<std::unique_ptr<Node>> nodes(n_points);
1253 for (unsigned int i=0; i<n_points; i++)
1254 nodes[i] = Node::build(pts[i], /*id*/ i);
1255
1256 // Create Elem, assign nodes
1257 std::unique_ptr<Elem> elem = Elem::build(elem_type, /*parent*/ nullptr);
1258
1259 // Make sure we were passed consistent input to build this type of Elem
1260 libmesh_error_msg_if(elem->n_nodes() != n_points,
1261 "Wrong number of points "
1262 << n_points
1263 << " provided to build a "
1264 << Utility::enum_to_string(elem_type));
1265
1266 for (unsigned int i=0; i<n_points; i++)
1267 elem->set_node(i, nodes[i].get());
1268
1269 // Return Elem and Nodes we created
1270 return std::make_pair(std::move(elem), std::move(nodes));
1271 }
1272
1273 // Helper function to create the polyhedron element
1274 Elem *
1275 buildC0Polyhedron(std::vector<std::shared_ptr<Polygon>> sides, MeshBase & mesh)
1276 {
1277 std::unique_ptr<libMesh::Node> mid_elem_node;
1278 std::unique_ptr<Elem> polyhedron = std::make_unique<C0Polyhedron>(sides, mid_elem_node);
1279 if (mid_elem_node)
1280 mesh.add_node(std::move(mid_elem_node));
1281 polyhedron->set_id() = 0;
1282 Elem * elem = mesh.add_elem(std::move(polyhedron));
1283 libmesh_assert(dynamic_cast<C0Polyhedron *>(elem));
1285
1286 return elem;
1287 }
1288
1289 // Helper function to factor out common tests
1291 unsigned int n_sides)
1292 {
1293 Elem * elem = mesh.query_elem_ptr(0);
1294 bool found_elem = elem;
1295 mesh.comm().max(found_elem);
1296 CPPUNIT_ASSERT(found_elem);
1297
1298 if (!elem)
1299 return;
1300
1301 CPPUNIT_ASSERT_EQUAL(elem->type(), C0POLYGON);
1302 CPPUNIT_ASSERT_EQUAL(elem->n_nodes(), n_sides);
1303 CPPUNIT_ASSERT_EQUAL(elem->n_sub_elem(), n_sides);
1304 CPPUNIT_ASSERT_EQUAL(elem->n_sides(), n_sides);
1305 CPPUNIT_ASSERT_EQUAL(elem->n_vertices(), n_sides);
1306 CPPUNIT_ASSERT_EQUAL(elem->n_edges(), n_sides);
1307
1308 // Even number of sides
1309 if (!(n_sides%2))
1310 {
1311 const unsigned int nsover2 = n_sides/2;
1312 for (auto i : make_range(nsover2))
1313 {
1314 CPPUNIT_ASSERT_EQUAL(elem->opposite_side(i), i+nsover2);
1315 CPPUNIT_ASSERT_EQUAL(elem->opposite_side(i+nsover2), i);
1316 }
1317 }
1318
1319 for (unsigned int i : make_range(n_sides))
1320 {
1321 CPPUNIT_ASSERT(elem->is_vertex(i));
1322 CPPUNIT_ASSERT(!elem->is_edge(i));
1323 CPPUNIT_ASSERT(!elem->is_face(i));
1324 CPPUNIT_ASSERT(elem->is_node_on_side(i,i));
1325 CPPUNIT_ASSERT(elem->is_node_on_edge(i,i));
1326 CPPUNIT_ASSERT(elem->is_node_on_side((i+1)%n_sides,i));
1327 CPPUNIT_ASSERT(elem->is_node_on_edge((i+1)%n_sides,i));
1328 std::vector<unsigned int> nodes = elem->nodes_on_side(i);
1329 CPPUNIT_ASSERT_EQUAL(nodes.size(), std::size_t(2));
1330 CPPUNIT_ASSERT(nodes[0] == i ||
1331 nodes[1] == i);
1332 CPPUNIT_ASSERT(nodes[0] == (i+1)%n_sides ||
1333 nodes[1] == (i+1)%n_sides);
1334 std::vector<unsigned int> edge_nodes = elem->nodes_on_edge(i);
1335 CPPUNIT_ASSERT(nodes == edge_nodes);
1336
1337
1338 CPPUNIT_ASSERT_EQUAL(elem->local_side_node(i,0), i);
1339 CPPUNIT_ASSERT_EQUAL(elem->local_side_node(i,1), (i+1)%n_sides);
1340 CPPUNIT_ASSERT_EQUAL(elem->local_edge_node(i,0), i);
1341 CPPUNIT_ASSERT_EQUAL(elem->local_edge_node(i,1), (i+1)%n_sides);
1342
1343 auto edge = elem->side_ptr(i);
1344 CPPUNIT_ASSERT_EQUAL(edge->type(), EDGE2);
1345 CPPUNIT_ASSERT_EQUAL(edge->node_ptr(0), mesh.node_ptr(i));
1346 CPPUNIT_ASSERT_EQUAL(edge->node_ptr(1), mesh.node_ptr((i+1)%n_sides));
1347 }
1348
1349 CPPUNIT_ASSERT(!elem->is_flipped());
1350 elem->flip(&mesh.get_boundary_info());
1351 CPPUNIT_ASSERT(elem->is_flipped());
1352 elem->flip(&mesh.get_boundary_info());
1353 }
1354
1355
1356 // Helper function to factor out common tests
1357 void testC0PolyhedronMethods(MeshBase & mesh, bool has_midnode)
1358 {
1359 Elem * elem = mesh.query_elem_ptr(0);
1360 bool found_elem = elem;
1361 mesh.comm().max(found_elem);
1362 CPPUNIT_ASSERT(found_elem);
1363
1364 if (!elem)
1365 return;
1366
1367 CPPUNIT_ASSERT_EQUAL(elem->type(), C0POLYHEDRON);
1368
1369 const int V = elem->n_vertices();
1370 const int E = elem->n_edges();
1371 const int F = elem->n_faces();
1372
1373 if (!has_midnode)
1374 CPPUNIT_ASSERT_EQUAL(int(elem->n_nodes()), V);
1375 else
1376 CPPUNIT_ASSERT_EQUAL(int(elem->n_nodes()), V + 1);
1377 CPPUNIT_ASSERT_EQUAL(int(elem->n_sides()), F);
1378
1379 // Euler characteristic for polygons homeomorphic to spheres is 2
1380 CPPUNIT_ASSERT_EQUAL(V-E+F, 2);
1381
1382 int FE = 0;
1383 int FV = 0;
1384
1385 std::vector<int> sides_on_vertex(V, 0);
1386 for (auto s : make_range(F))
1387 {
1388 auto side = elem->build_side_ptr(s);
1389 CPPUNIT_ASSERT_EQUAL(side->type(), C0POLYGON);
1390 FE += side->n_edges();
1391 const int SV = side->n_vertices();
1392 FV += SV;
1393
1394 auto nos = elem->nodes_on_side(s);
1395 CPPUNIT_ASSERT_EQUAL(nos.size(), std::size_t(SV));
1396
1397 for (auto v : make_range(SV))
1398 {
1399 Node * sidenode = side->node_ptr(v);
1400 const unsigned int n = elem->get_node_index(sidenode);
1401 CPPUNIT_ASSERT(n != invalid_uint);
1402 ++sides_on_vertex[n];
1403
1404 // We're just looking at first order so far
1405 CPPUNIT_ASSERT(side->is_vertex(v));
1406 CPPUNIT_ASSERT(!side->is_edge(v));
1407 CPPUNIT_ASSERT(elem->is_vertex(n));
1408 CPPUNIT_ASSERT(!elem->is_edge(n));
1409 CPPUNIT_ASSERT(!elem->is_face(n));
1410 CPPUNIT_ASSERT(elem->is_node_on_side(n, s));
1411 CPPUNIT_ASSERT(std::find(nos.begin(), nos.end(), n) != nos.end());
1412 }
1413 }
1414
1415 // We hit every edge from 2 faces
1416 CPPUNIT_ASSERT_EQUAL(E*2, FE);
1417
1418 // We hit every vertex from at least 3 faces
1419 CPPUNIT_ASSERT_LESSEQUAL(FV, V*3); // V*3 <= FV
1420 for (auto sov : sides_on_vertex)
1421 CPPUNIT_ASSERT_LESSEQUAL(sov, 3); // 3 <= sov
1422
1423 CPPUNIT_ASSERT(!elem->is_flipped());
1424 }
1425
1426
1427
1428 void testElemVolume(const Elem * elem,
1429 Real expected_volume)
1430 {
1431 const Real derived_volume = elem->volume();
1432 const Real base_volume = elem->Elem::volume();
1433 LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, TOLERANCE*TOLERANCE);
1434 LIBMESH_ASSERT_FP_EQUAL(derived_volume, expected_volume, TOLERANCE*TOLERANCE);
1435 }
1436
1437
1438
1440 {
1442
1443 mesh.add_point(Point(0, 0, 0), 0);
1444 mesh.add_point(Point(1, 0, 0), 1);
1445 mesh.add_point(Point(1, 1, 0), 2);
1446 mesh.add_point(Point(0, 1, 0), 3);
1447 mesh.add_point(Point(0, 0, 1), 4);
1448 mesh.add_point(Point(1, 0, 1), 5);
1449 mesh.add_point(Point(1, 1, 1), 6);
1450 mesh.add_point(Point(0, 1, 1), 7);
1451
1452 // See notes in elem_test.h
1453 const std::vector<std::vector<unsigned int>> nodes_on_side =
1454 { {0, 1, 2, 3}, // min z
1455 {0, 1, 5, 4}, // min y
1456 {2, 6, 5, 1}, // max x
1457 {2, 3, 7, 6}, // max y
1458 {0, 4, 7, 3}, // min x
1459 {5, 6, 7, 4} }; // max z
1460
1461 // Build all the sides.
1462 std::vector<std::shared_ptr<Polygon>> sides(nodes_on_side.size());
1463
1464 for (auto s : index_range(nodes_on_side))
1465 {
1466 const auto & nodes_on_s = nodes_on_side[s];
1467 sides[s] = std::make_shared<C0Polygon>(nodes_on_s.size());
1468 for (auto i : index_range(nodes_on_s))
1469 sides[s]->set_node(i, mesh.node_ptr(nodes_on_s[i]));
1470 }
1471
1472 const auto poly = dynamic_cast<C0Polyhedron *>(buildC0Polyhedron(sides, mesh));
1473 testElemVolume(poly, 1);
1474 #ifdef LIBMESH_ENABLE_EXCEPTIONS
1475 testC0PolyhedronMethods(mesh, /*midnode*/false);
1476
1477 // Check routine for subtet side to poly side mapping
1478 // NOTE: this test is hard coded to the elements used right now (to be reworked)
1479 const auto subtet0_sides_to_poly_sides = poly->subelement_sides_to_poly_sides(0);
1480 CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[0]);
1481 CPPUNIT_ASSERT_EQUAL(1, subtet0_sides_to_poly_sides[1]);
1482 CPPUNIT_ASSERT_EQUAL(0, subtet0_sides_to_poly_sides[2]);
1483 CPPUNIT_ASSERT_EQUAL(2, subtet0_sides_to_poly_sides[3]);
1484 #else
1485 testC0PolyhedronMethods(mesh, /*midnode*/true);
1486 #endif
1487 }
1488
1489
1490
1492 {
1494
1495 mesh.add_point(Point(0, -2, 0), 0);
1496 mesh.add_point(Point(-1, -1, 0), 1);
1497 mesh.add_point(Point(-1, 1, 0), 2);
1498 mesh.add_point(Point(0, 2, 0), 3);
1499 mesh.add_point(Point(1, 1, 0), 4);
1500 mesh.add_point(Point(1, -1, 0), 5);
1501 mesh.add_point(Point(0, -2, 1), 6);
1502 mesh.add_point(Point(-1, -1, 1), 7);
1503 mesh.add_point(Point(-1, 1, 1), 8);
1504 mesh.add_point(Point(0, 2, 1), 9);
1505 mesh.add_point(Point(1, 1, 1), 10);
1506 mesh.add_point(Point(1, -1, 1), 11);
1507
1508 // See notes in elem_test.h
1509 // With this ordering, we'll need to use a mid-node to tetrahedralize it
1510 const std::vector<std::vector<unsigned int>> nodes_on_side =
1511 { {0, 1, 2, 3, 4, 5},
1512 {0, 1, 7, 6},
1513 {1, 2, 8, 7},
1514 {2, 3, 9, 8},
1515 {3, 4, 10, 9},
1516 {4, 5, 11, 10},
1517 {5, 0, 6, 11},
1518 {6, 7, 8, 9, 10, 11} };
1519
1520 // Build all the sides.
1521 std::vector<std::shared_ptr<Polygon>> sides(nodes_on_side.size());
1522
1523 for (auto s : index_range(nodes_on_side))
1524 {
1525 const auto & nodes_on_s = nodes_on_side[s];
1526 sides[s] = std::make_shared<C0Polygon>(nodes_on_s.size());
1527 for (auto i : index_range(nodes_on_s))
1528 sides[s]->set_node(i, mesh.node_ptr(nodes_on_s[i]));
1529 }
1530
1531 const auto poly = dynamic_cast<C0Polyhedron *>(buildC0Polyhedron(sides, mesh));
1532 CPPUNIT_ASSERT_EQUAL((unsigned int)13, poly->n_nodes()); // we should have a mid node
1533 testElemVolume(poly, 6);
1534 testC0PolyhedronMethods(mesh, /*midnode*/true);
1535
1536 // Check routine for subtet side to poly side mapping
1537 const auto subtet0_sides_to_poly_sides = poly->subelement_sides_to_poly_sides(0);
1538 CPPUNIT_ASSERT_EQUAL(0, subtet0_sides_to_poly_sides[0]);
1539 CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[1]);
1540 CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[2]);
1541 CPPUNIT_ASSERT_EQUAL(libMesh::invalid_int, subtet0_sides_to_poly_sides[3]);
1542 }
1543
1544
1545
1546 // Helper function that builds the specified type of Elem from a
1547 // vector of Points and returns the value of has_invertible_map()
1548 // for that Elem.
1549 bool test_elem_invertible(const std::vector<Point> & pts,
1550 ElemType elem_type)
1551 {
1552 // Construct Elem of desired type
1553 auto [elem, nodes] = this->construct_elem(pts, elem_type);
1554 libmesh_ignore(nodes);
1555
1556 // Return whether or not this Elem has an invertible map
1557 return elem->has_invertible_map();
1558 }
1559
1560 // Helper function for testing true_centroid() and volume() implementations for
1561 // 3D elements
1563 {
1564 // Check that derived class true_centroid() gives same result as
1565 // the base class Elem::true_centroid() on a 2x2x2 mesh of 10%
1566 // distorted elements.
1567 {
1569
1571 /*nelem=*/2, /*nelem=*/2, /*nelem=*/2,
1572 /*xmin=*/-1, /*xmax=*/1,
1573 /*ymin=*/-1, /*ymax=*/1,
1574 /*zmin=*/-1, /*zmax=*/1,
1575 elem_type);
1576
1578 /*factor=*/0.1,
1579 /*perturb_boundary=*/false);
1580
1581 for (const auto & elem : mesh.element_ptr_range())
1582 {
1583 Point derived_centroid = elem->true_centroid();
1584 Point base_centroid = elem->Elem::true_centroid();
1585
1586 // Debugging: check results in detail
1587 // auto flags = libMesh::out.flags();
1588 // libMesh::out << std::scientific << std::setprecision(16);
1589 // libMesh::out << "derived_centroid = " << derived_centroid << std::endl;
1590 // libMesh::out << "base_centroid = " << base_centroid << std::endl;
1591 // libMesh::out.flags(flags);
1592
1593 CPPUNIT_ASSERT(derived_centroid.absolute_fuzzy_equals(base_centroid, 50*TOLERANCE*TOLERANCE));
1594
1595 // Make sure that base class and "optimized" routines for computing the cell volume agree
1596 Real derived_volume = elem->volume();
1597 Real base_volume = elem->Elem::volume();
1598 LIBMESH_ASSERT_FP_EQUAL(base_volume, derived_volume, 50*TOLERANCE*TOLERANCE);
1599 }
1600 }
1601 }
1602};
1603
void max(const T &r, T &o, Request &req) const
void testHex20PLevelTrueCentroid()
void test_true_centroid_and_volume(ElemType elem_type)
void testC0Polygon(const std::vector< Point > &points, Real expected_volume)
void testHex8TrueCentroid()
void testQuad4AspectRatio()
CPPUNIT_TEST(testQuad4Invertible)
CPPUNIT_TEST(testTri3AspectRatio)
CPPUNIT_TEST(testC0PolyhedronCube)
std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > construct_elem(const std::vector< Point > &pts, ElemType elem_type)
CPPUNIT_TEST(testQuad4MinMaxAngle)
CPPUNIT_TEST(testEdge4Invertible)
void testC0PolyhedronCube()
CPPUNIT_TEST(testQuad4TrueCentroid)
void testElemVolume(const Elem *elem, Real expected_volume)
CPPUNIT_TEST(testPyramid5TrueCentroid)
CPPUNIT_TEST(testHex8Size)
CPPUNIT_TEST(testQuad4SkewAngle)
void testC0PolyhedronHexagonalPrism()
void setUp()
Definition volume_test.C:64
void testQuad4Invertible()
LIBMESH_CPPUNIT_TEST_SUITE(VolumeTest)
CPPUNIT_TEST(testQuad4Warpage)
void testTet4DihedralAngle()
CPPUNIT_TEST(testTri3TrueCentroid)
void testQuad4Warpage()
void testC0PolygonPentagon()
CPPUNIT_TEST(testHex20PLevelTrueCentroid)
CPPUNIT_TEST_SUITE_END()
Elem * buildC0Polyhedron(std::vector< std::shared_ptr< Polygon > > sides, MeshBase &mesh)
CPPUNIT_TEST(testTwistedVolume)
void testC0PolygonQuad()
void testEdge3Invertible()
CPPUNIT_TEST(testTet4Jacobian)
void testTri3AspectRatio()
void testQuad4Size()
void testC0PolygonHexagon()
void tearDown()
Definition volume_test.C:68
void testTri3TrueCentroid()
Definition volume_test.C:72
void testC0PolyhedronMethods(MeshBase &mesh, bool has_midnode)
CPPUNIT_TEST(testQuad4Condition)
void testEdge4Invertible()
CPPUNIT_TEST(testPrism6TrueCentroid)
void testTet4Jacobian()
void testQuad4Taper()
CPPUNIT_TEST(testQuad4Size)
CPPUNIT_TEST(testQuad4AspectRatio)
CPPUNIT_TEST(testEdge3Invertible)
CPPUNIT_TEST(testHex8SkewAngle)
void testPyramid5TrueCentroid()
void testPrism6TrueCentroid()
void testQuad4MinMaxAngle()
void testC0PolygonSquare()
CPPUNIT_TEST(testHex8Condition)
CPPUNIT_TEST(testQuad4Taper)
void testQuad4Jacobian()
void testQuad4SkewAngle()
CPPUNIT_TEST(testHex8TrueCentroid)
void testHex8Condition()
void testHex8SkewAngle()
CPPUNIT_TEST(testEdge3Volume)
CPPUNIT_TEST(testC0PolygonPentagon)
void testQuad4Condition()
void testTwistedVolume()
CPPUNIT_TEST(testC0PolyhedronHexagonalPrism)
bool test_elem_invertible(const std::vector< Point > &pts, ElemType elem_type)
CPPUNIT_TEST(testC0PolygonQuad)
CPPUNIT_TEST(testTet4DihedralAngle)
CPPUNIT_TEST(testQuad4Jacobian)
void testHex8Size()
void testC0PolygonMethods(MeshBase &mesh, unsigned int n_sides)
CPPUNIT_TEST(testC0PolygonHexagon)
void testEdge3Volume()
void testQuad4TrueCentroid()
Definition volume_test.C:85
CPPUNIT_TEST(testC0PolygonSquare)
The C0Polyhedron is an element in 3D with an arbitrary (but fixed) number of polygonal first-order (C...
dof_id_type & set_id()
Definition dof_object.h:841
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const =0
virtual unsigned int n_vertices() const =0
const Point & point(const unsigned int i) const
Definition elem.h:2583
virtual unsigned int local_side_node(unsigned int side, unsigned int side_node) const =0
virtual Point true_centroid() const
Definition elem.C:515
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
virtual unsigned int n_nodes() const =0
virtual bool is_flipped() const =0
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:382
virtual std::vector< unsigned int > nodes_on_side(const unsigned int) const =0
virtual bool is_face(const unsigned int i) const =0
virtual bool has_invertible_map(Real tol=TOLERANCE *TOLERANCE) const
Definition elem.C:3016
virtual std::vector< unsigned int > nodes_on_edge(const unsigned int) const =0
virtual void flip(BoundaryInfo *boundary_info)=0
Flips the element (by swapping node and neighbor pointers) to have a mapping Jacobian of opposite sig...
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 ...
virtual unsigned int n_sub_elem() const =0
virtual bool is_edge(const unsigned int i) const =0
virtual unsigned int n_faces() const =0
virtual unsigned int opposite_side(const unsigned int s) const
Definition elem.C:3959
virtual Real volume() const
Definition elem.C:3865
virtual bool is_vertex(const unsigned int i) const =0
unsigned int get_node_index(const Node *node_ptr) const
Definition elem.h:2675
virtual ElemType type() const =0
virtual bool is_node_on_edge(const unsigned int n, const unsigned int e) const =0
virtual unsigned int n_edges() const =0
virtual unsigned int n_sides() const =0
void set_p_level(const unsigned int p)
Sets the value of the p-refinement level for the element.
virtual std::unique_ptr< Elem > build_side_ptr(const unsigned int i)=0
A specific instantiation of the FEBase class.
Definition fe.h:128
This is the MeshBase class.
Definition mesh_base.h:81
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
virtual const Node * node_ptr(const dof_id_type i) const =0
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
virtual dof_id_type n_nodes() 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.
virtual const Elem * elem_ptr(const dof_id_type i) const =0
virtual Node * add_node(Node *n)=0
Add Node n to the end of the vertex array.
virtual const Elem * query_elem_ptr(const dof_id_type i) const =0
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
A Node is like a Point, but with more information.
Definition node.h:55
static std::unique_ptr< Node > build(const Node &n)
Definition node.h:315
const Parallel::Communicator & comm() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:41
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
This class defines a tensor in LIBMESH_DIM dimensional Real or Complex space.
bool absolute_fuzzy_equals(const TypeVector< T > &rhs, Real tol=TOLERANCE) const
Communicator * TestCommWorld
MeshBase & mesh
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 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.
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.
void distort(MeshBase &mesh, const Real factor, const bool perturb_boundary=false)
Randomly perturb the nodal locations.
const Elem & get(const ElemType type_in)
std::string enum_to_string(const T e)
The libMesh namespace provides an interface to certain functionality in the library.
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
ElemType
Defines an enum for geometric element types.
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
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
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 Real pi
.
Definition libmesh.h:292
static constexpr Real TOLERANCE
uint8_t dof_id_type
Definition id_types.h:67
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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
CPPUNIT_TEST_SUITE_REGISTRATION(VolumeTest)