libMesh
Loading...
Searching...
No Matches
Functions
libMesh::ReferenceElem Namespace Reference

This namespace implements singleton reference elements for each fundamental element type supported by libMesh. More...

Functions

const Elem & get (const ElemType type_in)
 
std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > ideal_target (const ElemType type)
 

Detailed Description

This namespace implements singleton reference elements for each fundamental element type supported by libMesh.

Author
Benjamin S. Kirk
Date
2013

Namespace providing access to reference geometric element types.

Function Documentation

◆ get()

const Elem & libMesh::ReferenceElem::get ( const ElemType  type_in)
Returns
A constant reference to the reference element of the user-requested type.

Definition at line 258 of file reference_elem.C.

259{
260 ElemType base_type = type_in;
261
262 // For shell elements, use non shell type as the base type
263 if (type_in == TRISHELL3)
264 base_type = TRI3;
265
266 if (type_in == QUADSHELL4)
267 base_type = QUAD4;
268
269 if (type_in == QUADSHELL8)
270 base_type = QUAD8;
271
272 if (type_in == QUADSHELL9)
273 base_type = QUAD9;
274
275 init_ref_elem_table();
276
277 // Throw an error if the user asked for an ElemType that we don't
278 // have a reference element for.
279 libmesh_error_msg_if(ref_elem_map[base_type] == nullptr || type_in == INVALID_ELEM,
280 "No reference elem data available for ElemType " << type_in
281 << " = " << Utility::enum_to_string(type_in) << ".");
282
283 return *ref_elem_map[base_type];
284}
std::string enum_to_string(const T e)
ElemType
Defines an enum for geometric element types.

References libMesh::Utility::enum_to_string(), libMesh::INVALID_ELEM, libMesh::QUAD4, libMesh::QUAD8, libMesh::QUAD9, libMesh::QUADSHELL4, libMesh::QUADSHELL8, libMesh::QUADSHELL9, libMesh::TRI3, and libMesh::TRISHELL3.

Referenced by NodalNeighborsTest::do_test(), main(), libMesh::Elem::reference_elem(), SideVertexAverageNormalTest::testEdge2(), SideVertexAverageNormalTest::testEdge3(), VolumeTest::testEdge4Invertible(), SideVertexAverageNormalTest::testHexes(), WhichNodeAmITest::testHexes(), SideVertexAverageNormalTest::testPrisms(), WhichNodeAmITest::testPrisms(), VolumeTest::testPyramid5TrueCentroid(), SideVertexAverageNormalTest::testPyramids(), WhichNodeAmITest::testPyramids(), VolumeTest::testQuad4TrueCentroid(), SideVertexAverageNormalTest::testQuads(), SideVertexAverageNormalTest::testTets(), WhichNodeAmITest::testTets(), VolumeTest::testTri3TrueCentroid(), SideVertexAverageNormalTest::testTris(), and MeshSmootherTest::testVariationalSmoother().

◆ ideal_target()

std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > libMesh::ReferenceElem::ideal_target ( const ElemType  type)
Returns
A freshly built "ideal" (regular) element of the given type, i.e. the optimally-shaped element that a mesh optimizer targets: an equilateral triangle, regular tetrahedron, etc., sized to the volume of the reference element. For element types that have no distinct ideal shape (e.g. quads and hexes, whose reference element is already regular), this returns a copy of the reference element.

The returned Elem holds pointers into the returned Nodes, so the caller must keep the Node vector alive for at least as long as the Elem.

Definition at line 287 of file reference_elem.C.

288{
289 // Build target element
290 auto target_elem = Elem::build(type);
291
292 // Volume of reference element
293 const auto ref_vol = target_elem->reference_elem()->volume();
294
295 // Update the nodes of the target element, depending on type
296 const Real sqrt_2 = std::sqrt(Real(2));
297 const Real sqrt_3 = std::sqrt(Real(3));
298 std::vector<std::unique_ptr<Node>> owned_nodes;
299
300 const auto type_str = Utility::enum_to_string(type);
301
302 // Elems deriving from Tri
303 if (type_str.compare(0, 3, "TRI") == 0)
304 {
305
306 // The target element will be an equilateral triangle with area equal to
307 // the area of the reference element.
308
309 // Equilateral triangle side length preserving area of the reference element
310 const auto side_length = std::sqrt(4. / sqrt_3 * ref_vol);
311
312 // Define the nodal locations of the vertices
313 const auto & s = side_length;
314 // x y node_id
315 owned_nodes.emplace_back(Node::build(Point(0., 0.), 0));
316 owned_nodes.emplace_back(Node::build(Point(s, 0.), 1));
317 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s), 2));
318
319 switch (type)
320 {
321 case TRI3:
322 case TRISHELL3: {
323 // Nothing to do here, vertices already added above
324 break;
325 }
326
327 case TRI6:
328 case TRI7: {
329 // Define the midpoint nodes of the equilateral triangle
330 // x y node_id
331 owned_nodes.emplace_back(Node::build(Point(0.50 * s, 0.00), 3));
332 owned_nodes.emplace_back(Node::build(Point(0.75 * s, 0.25 * sqrt_3 * s), 4));
333 owned_nodes.emplace_back(Node::build(Point(0.25 * s, 0.25 * sqrt_3 * s), 5));
334
335 if (type == TRI7)
336 {
337 // Define the interior "bubble" node at the centroid
338 const auto & on = owned_nodes;
339 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 6));
340 }
341
342 break;
343 }
344
345 default:
346 libmesh_error_msg("Unsupported triangular element: " << type_str);
347 break;
348 }
349 } // if Tri
350
351 // Elems deriving from Prism
352 else if (type_str.compare(0, 5, "PRISM") == 0)
353 {
354
355 // The target element will be a prism with an equilateral triangular
356 // base with volume equal to the volume of the reference element.
357
358 // For an equilateral triangular base with side length s, the
359 // base area is s^2 * sqrt(3) / 4.
360 // The prism height that will result in equal face areas is
361 // s * sqrt(3) / 4. We choose s such that the target element has
362 // the same volume as the reference element:
363 // v = (s^2 * sqrt(3) / 4) * (s * sqrt(3) / 4) = 3 * s^3 / 4
364 // --> s = (16 * v / 3)^(1/3)
365 // I have no particular motivation for imposing equal face areas,
366 // so this can be updated if a more `optimal` target prism is
367 // identified.
368
369 // Side length that preserves the volume of the reference element
370 const auto side_length = std::cbrt(16. * ref_vol / 3.);
371 // Prism height with the property that all faces have equal area
372 const auto target_height = 0.25 * side_length * sqrt_3;
373
374 const auto & s = side_length;
375 const auto & h = target_height;
376 // x y z node_id
377 owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
378 owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
379 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
380 owned_nodes.emplace_back(Node::build(Point(0., 0., h), 3));
381 owned_nodes.emplace_back(Node::build(Point(s, 0., h), 4));
382 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, h), 5));
383
384 if (type == PRISM15 || type == PRISM18 || type == PRISM20 || type == PRISM21)
385 {
386 // Define the edge midpoint nodes of the prism
387 const auto & on = owned_nodes;
388 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 6));
389 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 7));
390 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 8));
391 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 9));
392 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[4]) / 2.), 10));
393 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[5]) / 2.), 11));
394 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4]) / 2.), 12));
395 owned_nodes.emplace_back(Node::build(Point((*on[4] + *on[5]) / 2.), 13));
396 owned_nodes.emplace_back(Node::build(Point((*on[5] + *on[3]) / 2.), 14));
397
398 if (type == PRISM18 || type == PRISM20 || type == PRISM21)
399 {
400 // Define the rectangular face midpoint nodes of the prism
401 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3] + *on[4]) / 4.), 15));
402 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[4] + *on[5]) / 4.), 16));
403 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3] + *on[5]) / 4.), 17));
404
405 if (type == PRISM20 || type == PRISM21)
406 {
407 // Define the triangular face midpoint nodes of the prism
408 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 18));
409 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4] + *on[5]) / 3.), 19));
410
411 if (type == PRISM21)
412 // Define the interior point of the prism
413 owned_nodes.emplace_back(Node::build(Point((*on[9] + *on[10] + *on[11]) / 3.), 20));
414
415 }
416 }
417 }
418
419 else if (type != PRISM6)
420 libmesh_error_msg("Unsupported prism element: " << type_str);
421
422 } // if Prism
423
424 // Elems deriving from Pyramid
425 else if (type_str.compare(0, 7, "PYRAMID") == 0)
426 {
427
428 // The target element is a pyramid with an square base and
429 // equilateral triangular sides with volume equal to the volume of the
430 // reference element.
431
432 // A pyramid with square base sidelength s and equilateral triangular
433 // sides has height h = s / sqrt(2).
434 // The volume is v = s^2 h / 3 = s^3 / ( 3 sqrt(2)).
435 // Solving for s: s = (3 sqrt(2) v)^(1/3), where v is the volume of the
436 // non-optimal reference element.
437
438 // Side length that preserves the volume of the reference element
439 const auto side_length = std::cbrt(3. * sqrt_2 * ref_vol);
440 // Pyramid height with the property that all faces are equilateral triangles
441 const auto target_height = side_length / sqrt_2;
442
443 const auto & s = side_length;
444 const auto & h = target_height;
445
446 // x y z node_id
447 owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
448 owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
449 owned_nodes.emplace_back(Node::build(Point(s, s, 0.), 2));
450 owned_nodes.emplace_back(Node::build(Point(0., s, 0.), 3));
451 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * s, h), 4));
452
453 if (type == PYRAMID13 || type == PYRAMID14 || type == PYRAMID18)
454 {
455 const auto & on = owned_nodes;
456 // Define the edge midpoint nodes of the pyramid
457
458 // Base node to base node midpoint nodes
459 owned_nodes.emplace_back(Node::build((*on[0] + *on[1]) / 2., 5));
460 owned_nodes.emplace_back(Node::build((*on[1] + *on[2]) / 2., 6));
461 owned_nodes.emplace_back(Node::build((*on[2] + *on[3]) / 2., 7));
462 owned_nodes.emplace_back(Node::build((*on[3] + *on[0]) / 2., 8));
463
464 // Base node to apex node midpoint nodes
465 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[4]) / 2.), 9));
466 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[4]) / 2.), 10));
467 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[4]) / 2.), 11));
468 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4]) / 2.), 12));
469
470 if (type == PYRAMID14 || type == PYRAMID18)
471 {
472 // Define the square face midpoint node of the pyramid
473 owned_nodes.emplace_back(
474 Node::build(Point((*on[0] + *on[1] + *on[2] + *on[3]) / 4.), 13));
475
476 if (type == PYRAMID18)
477 {
478 // Define the triangular face nodes
479 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[4]) / 3.), 14));
480 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[4]) / 3.), 15));
481 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3] + *on[4]) / 3.), 16));
482 owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[0] + *on[4]) / 3.), 17));
483 }
484 }
485 }
486
487 else if (type != PYRAMID5)
488 libmesh_error_msg("Unsupported pyramid element: " << type_str);
489
490 } // if Pyramid
491
492 // Elems deriving from Tet
493 else if (type_str.compare(0, 3, "TET") == 0)
494 {
495
496 // The ideal target element is a a regular tet with equilateral
497 // triangles for all faces, with volume equal to the volume of the
498 // reference element.
499
500 // The volume of a tet is given by v = b * h / 3, where b is the area of
501 // the base face and h is the height of the apex node. The area of an
502 // equilateral triangle with side length s is b = sqrt(3) s^2 / 4.
503 // For all faces to have side length s, the height of the apex node is
504 // h = sqrt(2/3) * s. Then the volume is v = sqrt(2) * s^3 / 12.
505 // Solving for s, the side length that will preserve the volume of the
506 // reference element is s = (6 * sqrt(2) * v)^(1/3), where v is the volume
507 // of the non-optimal reference element (i.e., a right tet).
508
509 // Side length that preserves the volume of the reference element
510 const auto side_length = std::cbrt(6. * sqrt_2 * ref_vol);
511 // tet height with the property that all faces are equilateral triangles
512 const auto target_height = sqrt_2 / sqrt_3 * side_length;
513
514 const auto & s = side_length;
515 const auto & h = target_height;
516
517 // For regular tet
518 // x y z node_id
519 owned_nodes.emplace_back(Node::build(Point(0., 0., 0.), 0));
520 owned_nodes.emplace_back(Node::build(Point(s, 0., 0.), 1));
521 owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
522 owned_nodes.emplace_back(Node::build(Point(0.5 * s, sqrt_3 / 6. * s, h), 3));
523
524 if (type == TET10 || type == TET14)
525 {
526 const auto & on = owned_nodes;
527 // Define the edge midpoint nodes of the tet
528
529 // Base node to base node midpoint nodes
530 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 4));
531 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 5));
532 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 6));
533 // Base node to apex node midpoint nodes
534 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 7));
535 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[3]) / 2.), 8));
536 owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3]) / 2.), 9));
537
538 if (type == TET14)
539 {
540 // Define the face midpoint nodes of the tet
541 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 10));
542 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3]) / 3.), 11));
543 owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[3]) / 3.), 12));
544 owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3]) / 3.), 13));
545 }
546 }
547
548 else if (type != TET4)
549 libmesh_error_msg("Unsupported tet element: " << type_str);
550
551 } // if Tet
552
553 // Set the target_elem equal to the reference elem
554 else
555 for (const auto & node : target_elem->reference_elem()->node_ref_range())
556 owned_nodes.emplace_back(Node::build(node, node.id()));
557
558 // Set nodes of target element
559 for (const auto & node_ptr : owned_nodes)
560 target_elem->set_node(node_ptr->id(), node_ptr.get());
561
562 libmesh_assert(relative_fuzzy_equals(target_elem->volume(), ref_vol, TOLERANCE));
563
564 return std::make_pair(std::move(target_elem), std::move(owned_nodes));
565}
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:382
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
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:41
const Elem & get(const ElemType type_in)
bool relative_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
Function to check whether two variables are equal within a relative tolerance.
libmesh_assert(ctx)
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

References libMesh::Elem::build(), libMesh::Node::build(), libMesh::Utility::enum_to_string(), libMesh::libmesh_assert(), libMesh::PRISM15, libMesh::PRISM18, libMesh::PRISM20, libMesh::PRISM21, libMesh::PRISM6, libMesh::PYRAMID13, libMesh::PYRAMID14, libMesh::PYRAMID18, libMesh::PYRAMID5, libMesh::Real, libMesh::relative_fuzzy_equals(), libMesh::TET10, libMesh::TET14, libMesh::TET4, libMesh::TOLERANCE, libMesh::TRI3, libMesh::TRI6, libMesh::TRI7, and libMesh::TRISHELL3.

Referenced by libMesh::VariationalSmootherSystem::prepare_for_smoothing(), and libMesh::Elem::quality().