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:
55
56public:
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
145 }
146
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
961protected:
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
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)
void testC0PolyhedronHexagonalPrism()
void setUp()
Definition volume_test.C:57
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 testC0PolygonHexagon()
void tearDown()
Definition volume_test.C:61
void testTri3TrueCentroid()
Definition volume_test.C:65
void testC0PolyhedronMethods(MeshBase &mesh, bool has_midnode)
void testEdge4Invertible()
CPPUNIT_TEST(testPrism6TrueCentroid)
void testTet4Jacobian()
CPPUNIT_TEST(testQuad4AspectRatio)
CPPUNIT_TEST(testEdge3Invertible)
void testPyramid5TrueCentroid()
void testPrism6TrueCentroid()
void testQuad4MinMaxAngle()
void testC0PolygonSquare()
void testQuad4Jacobian()
CPPUNIT_TEST(testHex8TrueCentroid)
CPPUNIT_TEST(testEdge3Volume)
CPPUNIT_TEST(testC0PolygonPentagon)
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 testC0PolygonMethods(MeshBase &mesh, unsigned int n_sides)
CPPUNIT_TEST(testC0PolygonHexagon)
void testEdge3Volume()
void testQuad4TrueCentroid()
Definition volume_test.C:78
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:827
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:2462
virtual unsigned int local_side_node(unsigned int side, unsigned int side_node) const =0
virtual Point true_centroid() const
Definition elem.C:575
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:442
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:2914
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:3556
virtual Real volume() const
Definition elem.C:3462
virtual bool is_vertex(const unsigned int i) const =0
unsigned int get_node_index(const Node *node_ptr) const
Definition elem.h:2554
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:40
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)