LCOV - code coverage report
Current view: top level - src/geom - reference_elem.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4343 (6ea38c) with base 8c3130 Lines: 195 201 97.0 %
Date: 2026-10-13 21:47:33 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : 
      19             : 
      20             : // Local includes
      21             : #include "libmesh/node.h"
      22             : #include "libmesh/elem.h"
      23             : #include "libmesh/reference_elem.h"
      24             : #include "libmesh/libmesh_singleton.h"
      25             : #include "libmesh/threads.h"
      26             : #include "libmesh/enum_to_string.h"
      27             : #include "libmesh/enum_elem_type.h"
      28             : #include "libmesh/fuzzy_equals.h"
      29             : 
      30             : // C++ includes
      31             : #include <map>
      32             : #include <sstream>
      33             : #include <memory> // std::unique_ptr
      34             : #include <cmath>  // std::sqrt, std::cbrt
      35             : #include <utility> // std::pair
      36             : #include <vector>
      37             : 
      38             : 
      39             : //-----------------------------------------------
      40             : // anonymous namespace for implementation details
      41             : namespace
      42             : {
      43             : using namespace libMesh;
      44             : 
      45             : namespace ElemDataStrings
      46             : {
      47             : // GCC 5.2.0 warns about overlength strings in the auto-generated
      48             : // reference_elem.data file.
      49             : #pragma GCC diagnostic ignored "-Woverlength-strings"
      50             : #include "reference_elem.data"
      51             : #pragma GCC diagnostic warning "-Woverlength-strings"
      52             : }
      53             : 
      54             : typedef Threads::spin_mutex InitMutex;
      55             : 
      56             : // Mutex for thread safety.
      57             : InitMutex init_mtx;
      58             : 
      59             : // map from ElemType to reference element file system object name
      60             : typedef std::map<ElemType, const char *> FileMapType;
      61             : FileMapType ref_elem_file;
      62             : Elem * ref_elem_map[INVALID_ELEM];
      63             : 
      64             : 
      65             : 
      66          14 : class SingletonCache : public libMesh::Singleton
      67             : {
      68             : public:
      69         712 :   virtual ~SingletonCache() = default;
      70             : 
      71             :   std::vector<std::unique_ptr<Node>> node_list;
      72             :   std::vector<std::unique_ptr<Elem>> elem_list;
      73             : };
      74             : 
      75             : // From [0], regarding the lifetime of the singleton_cache variable:
      76             : //
      77             : //     "All variables at namespace level, including the anonymous
      78             : //     namespace and function local static variable have static
      79             : //     storage duration unless they are declared thread_local."
      80             : //
      81             : // Variables with static storage duration are destroyed at the end of
      82             : // the program execution. From [1],
      83             : //
      84             : //     "If it is a pointer to the data which is static ... then like all
      85             : //     other dynamically allocated data, it will only be destructed when
      86             : //     you delete it.  There are two frequent solutions:
      87             : //     * use a smart pointer, which has a destructor which deletes it, or
      88             : //     * don't delete it; in most cases, there's really no reason to call the
      89             : //       destructor, and if you happen to use the instance in the destructors
      90             : //       of other static objects, you'll run into an order of destruction
      91             : //       problem."
      92             : //
      93             : // I tried making the singleton_cache a std::unique_ptr, but this resulted
      94             : // in a segfault during program shutdown which appeared to come from the
      95             : // single_cache unique_ptr's destructor. I didn't investigate whether the
      96             : // issue was caused by a double deletion or what, but it appears that the
      97             : // first suggestion above may not be valid in general.
      98             : //
      99             : // [0]: https://stackoverflow.com/questions/24342393/how-anonymous-namespaces-avoids-making-global-static-variable
     100             : // [1]: https://stackoverflow.com/questions/6850009/c-deleting-static-data
     101             : SingletonCache * singleton_cache = nullptr;
     102             : 
     103             : 
     104             : 
     105        9075 : void read_ref_elem (const ElemType type_in,
     106             :                     std::istream & in)
     107             : {
     108         350 :   libmesh_assert (singleton_cache != nullptr);
     109             : 
     110         350 :   std::string dummy;
     111             :   unsigned int n_elem, n_nodes, elem_type_read, nn;
     112             :   double x, y, z;
     113             : 
     114        9075 :   in >> dummy;
     115        9075 :   in >> n_elem;  /**/ std::getline (in, dummy); libmesh_assert_equal_to (n_elem, 1);
     116        9075 :   in >> n_nodes; /**/ std::getline (in, dummy);
     117        9075 :   in >> dummy;   /**/ std::getline (in, dummy);
     118        9075 :   in >> dummy;   /**/ std::getline (in, dummy);
     119        9075 :   in >> dummy;   /**/ std::getline (in, dummy);
     120        9075 :   in >> dummy;   /**/ std::getline (in, dummy);
     121        9075 :   in >> n_elem;  /**/ std::getline (in, dummy); libmesh_assert_equal_to (n_elem, 1);
     122             : 
     123         350 :   in >> elem_type_read;
     124             : 
     125         350 :   libmesh_assert_less (elem_type_read, INVALID_ELEM);
     126         350 :   libmesh_assert_equal_to (elem_type_read, static_cast<unsigned int>(type_in));
     127         350 :   libmesh_assert_equal_to (n_nodes, Elem::type_to_n_nodes_map[elem_type_read]);
     128             : 
     129             :   // Construct elem of appropriate type, store in the elem_list
     130        9425 :   auto & uelem = singleton_cache->elem_list.emplace_back(Elem::build(type_in));
     131             : 
     132             :   // We are expecting an identity map, so assert it!
     133      103455 :   for (unsigned int n=0; n<n_nodes; n++)
     134             :     {
     135        3640 :       in >> nn;
     136        3640 :       libmesh_assert_equal_to (n,nn);
     137             :     }
     138             : 
     139      103455 :   for (unsigned int n=0; n<n_nodes; n++)
     140             :     {
     141        3640 :       in >> x >> y >> z;
     142             : 
     143             :       auto & new_node =
     144       98020 :         singleton_cache->node_list.emplace_back(Node::build(x,y,z,n));
     145             : 
     146       98020 :       uelem->set_node(n, new_node.get());
     147             :     }
     148             : 
     149             :   // it is entirely possible we ran out of file or encountered
     150             :   // another error.  If so, throw an error.
     151        9425 :   libmesh_error_msg_if(!in, "ERROR while creating element singleton!");
     152             : 
     153             :   // Also store a pointer to the newly created Elem in the ref_elem_map array.
     154        9075 :   ref_elem_map[type_in] = uelem.get();
     155        9075 : }
     156             : 
     157             : 
     158             : 
     159      425869 : void init_ref_elem_table()
     160             : {
     161             :   // outside mutex - if this pointer is set, we can trust it.
     162      425869 :   if (singleton_cache != nullptr)
     163       34580 :     return;
     164             : 
     165             :   // playing with fire here - lock before touching shared
     166             :   // data structures
     167          14 :   InitMutex::scoped_lock lock(init_mtx);
     168             : 
     169             :   // inside mutex - pointer may have changed while waiting
     170             :   // for the lock to acquire, check it again.
     171         363 :   if (singleton_cache != nullptr)
     172           0 :     return;
     173             : 
     174             :   // OK, if we get here we have the lock and we are not
     175             :   // initialized.  populate singleton. Note that we do not
     176             :   // use a smart pointer to manage the singleton_cache variable
     177             :   // since variables with static storage duration are destroyed
     178             :   // automatically at the end of program execution.
     179         377 :   singleton_cache = new SingletonCache;
     180             : 
     181             :   // initialize the reference file table
     182             :   {
     183          14 :     ref_elem_file.clear();
     184             : 
     185             :     // 0D elements
     186         363 :     ref_elem_file[NODEELEM] = ElemDataStrings::one_nodeelem;
     187             : 
     188             :     // 1D elements
     189         363 :     ref_elem_file[EDGE2]    = ElemDataStrings::one_edge;
     190         363 :     ref_elem_file[EDGE3]    = ElemDataStrings::one_edge3;
     191         363 :     ref_elem_file[EDGE4]    = ElemDataStrings::one_edge4;
     192             : 
     193             :     // 2D elements
     194         363 :     ref_elem_file[TRI3]     = ElemDataStrings::one_tri;
     195         363 :     ref_elem_file[TRI6]     = ElemDataStrings::one_tri6;
     196         363 :     ref_elem_file[TRI7]     = ElemDataStrings::one_tri7;
     197             : 
     198         363 :     ref_elem_file[QUAD4]    = ElemDataStrings::one_quad;
     199         363 :     ref_elem_file[QUAD8]    = ElemDataStrings::one_quad8;
     200         363 :     ref_elem_file[QUAD9]    = ElemDataStrings::one_quad9;
     201             : 
     202             :     // 3D elements
     203         363 :     ref_elem_file[HEX8]     = ElemDataStrings::one_hex;
     204         363 :     ref_elem_file[HEX20]    = ElemDataStrings::one_hex20;
     205         363 :     ref_elem_file[HEX27]    = ElemDataStrings::one_hex27;
     206             : 
     207         363 :     ref_elem_file[TET4]     = ElemDataStrings::one_tet;
     208         363 :     ref_elem_file[TET10]    = ElemDataStrings::one_tet10;
     209         363 :     ref_elem_file[TET14]    = ElemDataStrings::one_tet14;
     210             : 
     211         363 :     ref_elem_file[PRISM6]   = ElemDataStrings::one_prism;
     212         363 :     ref_elem_file[PRISM15]  = ElemDataStrings::one_prism15;
     213         363 :     ref_elem_file[PRISM18]  = ElemDataStrings::one_prism18;
     214         363 :     ref_elem_file[PRISM20]  = ElemDataStrings::one_prism20;
     215         363 :     ref_elem_file[PRISM21]  = ElemDataStrings::one_prism21;
     216             : 
     217         363 :     ref_elem_file[PYRAMID5] = ElemDataStrings::one_pyramid;
     218         363 :     ref_elem_file[PYRAMID13] = ElemDataStrings::one_pyramid13;
     219         363 :     ref_elem_file[PYRAMID14] = ElemDataStrings::one_pyramid14;
     220         363 :     ref_elem_file[PYRAMID18] = ElemDataStrings::one_pyramid18;
     221             : 
     222             :     // No entry here for C0POLYGON or C0POLYHEDRON - the reference
     223             :     // element there depends on the number of nodes and can't be
     224             :     // precomputed.
     225             :   }
     226             : 
     227             :   // Read'em
     228        9438 :   for (const auto & [elem_type, filename] : ref_elem_file)
     229             :     {
     230       10125 :       std::istringstream stream(filename);
     231        9075 :       read_ref_elem(elem_type, stream);
     232        8375 :     }
     233             : }
     234             : 
     235             : 
     236             : // no reason to do this at startup -
     237             : // data structures will get initialized *if*
     238             : // ReferenceElem::get() is ever called.
     239             : // // Class to setup singleton data
     240             : // class ReferenceElemSetup : public Singleton::Setup
     241             : // {
     242             : //   void setup ()
     243             : //   {
     244             : //     init_ref_elem_table();
     245             : //   }
     246             : // } reference_elem_setup;
     247             : 
     248             : } // anonymous namespace
     249             : 
     250             : 
     251             : 
     252             : //----------------------------------------------------------------------------
     253             : // external API Implementation
     254             : namespace libMesh
     255             : {
     256             : namespace ReferenceElem
     257             : {
     258      425869 : const Elem & get (const ElemType type_in)
     259             : {
     260       34594 :   ElemType base_type = type_in;
     261             : 
     262             :   // For shell elements, use non shell type as the base type
     263      425869 :   if (type_in == TRISHELL3)
     264          88 :     base_type = TRI3;
     265             : 
     266      424989 :   if (type_in == QUADSHELL4)
     267        3428 :     base_type = QUAD4;
     268             : 
     269      391589 :   if (type_in == QUADSHELL8)
     270        6756 :     base_type = QUAD8;
     271             : 
     272      358309 :   if (type_in == QUADSHELL9)
     273         100 :     base_type = QUAD9;
     274             : 
     275      425869 :   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      425869 :   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      425869 :   return *ref_elem_map[base_type];
     284             : }
     285             : 
     286             : std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
     287      218896 : ideal_target (const ElemType type)
     288             : {
     289             :   // Build target element
     290      236986 :   auto target_elem = Elem::build(type);
     291             : 
     292             :   // Volume of reference element
     293      218896 :   const auto ref_vol = target_elem->reference_elem()->volume();
     294             : 
     295             :   // Update the nodes of the target element, depending on type
     296       18090 :   const Real sqrt_2 = std::sqrt(Real(2));
     297       18090 :   const Real sqrt_3 = std::sqrt(Real(3));
     298       54270 :   std::vector<std::unique_ptr<Node>> owned_nodes;
     299             : 
     300      236986 :   const auto type_str = Utility::enum_to_string(type);
     301             : 
     302             :   // Elems deriving from Tri
     303      218896 :   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        4213 :       const auto side_length = std::sqrt(4. / sqrt_3 * ref_vol);
     311             : 
     312             :       // Define the nodal locations of the vertices
     313         332 :       const auto & s = side_length;
     314             :       //                                         x        y                  node_id
     315        4545 :       owned_nodes.emplace_back(Node::build(Point(0.,      0.),               0));
     316        4213 :       owned_nodes.emplace_back(Node::build(Point(s,       0.),               1));
     317        4545 :       owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s), 2));
     318             : 
     319        4213 :       switch (type)
     320             :         {
     321         170 :             case TRI3:
     322             :             case TRISHELL3: {
     323             :               // Nothing to do here, vertices already added above
     324         170 :               break;
     325             :             }
     326             : 
     327        1975 :             case TRI6:
     328             :             case TRI7: {
     329             :               // Define the midpoint nodes of the equilateral triangle
     330             :               //                                         x         y                   node_id
     331        1975 :               owned_nodes.emplace_back(Node::build(Point(0.50 * s, 0.00),              3));
     332        2137 :               owned_nodes.emplace_back(Node::build(Point(0.75 * s, 0.25 * sqrt_3 * s), 4));
     333        2137 :               owned_nodes.emplace_back(Node::build(Point(0.25 * s, 0.25 * sqrt_3 * s), 5));
     334             : 
     335        1975 :               if (type == TRI7)
     336             :                 {
     337             :                   // Define the interior "bubble" node at the centroid
     338          80 :                   const auto & on = owned_nodes;
     339        1120 :                   owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 6));
     340             :                 }
     341             : 
     342         162 :               break;
     343             :             }
     344             : 
     345           0 :           default:
     346           0 :             libmesh_error_msg("Unsupported triangular element: " << type_str);
     347             :             break;
     348             :         }
     349             :     } // if Tri
     350             : 
     351             :   // Elems deriving from Prism
     352      214683 :   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         336 :       const auto side_length = std::cbrt(16. * ref_vol / 3.);
     371             :       // Prism height with the property that all faces have equal area
     372         336 :       const auto target_height = 0.25 * side_length * sqrt_3;
     373             : 
     374          14 :       const auto & s = side_length;
     375          14 :       const auto & h = target_height;
     376             :       //                                         x        y                 z    node_id
     377         350 :       owned_nodes.emplace_back(Node::build(Point(0.,      0.,               0.), 0));
     378         336 :       owned_nodes.emplace_back(Node::build(Point(s,       0.,               0.), 1));
     379         364 :       owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
     380         350 :       owned_nodes.emplace_back(Node::build(Point(0.,      0.,               h),  3));
     381         350 :       owned_nodes.emplace_back(Node::build(Point(s,       0.,               h),  4));
     382         336 :       owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, h),  5));
     383             : 
     384         336 :       if (type == PRISM15 || type == PRISM18 || type == PRISM20 || type == PRISM21)
     385             :         {
     386             :           // Define the edge midpoint nodes of the prism
     387          10 :           const auto & on = owned_nodes;
     388         217 :           owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 6));
     389         217 :           owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 7));
     390         217 :           owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 8));
     391         217 :           owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 9));
     392         217 :           owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[4]) / 2.), 10));
     393         217 :           owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[5]) / 2.), 11));
     394         217 :           owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4]) / 2.), 12));
     395         217 :           owned_nodes.emplace_back(Node::build(Point((*on[4] + *on[5]) / 2.), 13));
     396         217 :           owned_nodes.emplace_back(Node::build(Point((*on[5] + *on[3]) / 2.), 14));
     397             : 
     398         207 :           if (type == PRISM18 || type == PRISM20 || type == PRISM21)
     399             :             {
     400             :               // Define the rectangular face midpoint nodes of the prism
     401         145 :               owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3] + *on[4]) / 4.), 15));
     402         145 :               owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[4] + *on[5]) / 4.), 16));
     403         145 :               owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3] + *on[5]) / 4.), 17));
     404             : 
     405         137 :               if (type == PRISM20 || type == PRISM21)
     406             :                 {
     407             :                   // Define the triangular face midpoint nodes of the prism
     408          72 :                   owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 18));
     409          72 :                   owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4] + *on[5]) / 3.), 19));
     410             : 
     411          66 :                   if (type == PRISM21)
     412             :                     // Define the interior point of the prism
     413          52 :                     owned_nodes.emplace_back(Node::build(Point((*on[9] + *on[10] + *on[11]) / 3.), 20));
     414             : 
     415             :                 }
     416          10 :             }
     417             :         }
     418             : 
     419         129 :       else if (type != PRISM6)
     420           0 :         libmesh_error_msg("Unsupported prism element: " << type_str);
     421             : 
     422             :     } // if Prism
     423             : 
     424             :   // Elems deriving from Pyramid
     425      214347 :   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         305 :       const auto side_length = std::cbrt(3. * sqrt_2 * ref_vol);
     440             :       // Pyramid height with the property that all faces are equilateral triangles
     441         305 :       const auto target_height = side_length / sqrt_2;
     442             : 
     443          10 :       const auto & s = side_length;
     444          10 :       const auto & h = target_height;
     445             : 
     446             :       //                                         x        y        z    node_id
     447         315 :       owned_nodes.emplace_back(Node::build(Point(0.,      0.,      0.), 0));
     448         315 :       owned_nodes.emplace_back(Node::build(Point(s,       0.,      0.), 1));
     449         315 :       owned_nodes.emplace_back(Node::build(Point(s,       s,       0.), 2));
     450         305 :       owned_nodes.emplace_back(Node::build(Point(0.,      s,       0.), 3));
     451         315 :       owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * s, h),  4));
     452             : 
     453         305 :       if (type == PYRAMID13 || type == PYRAMID14 || type == PYRAMID18)
     454             :         {
     455           8 :           const auto & on = owned_nodes;
     456             :           // Define the edge midpoint nodes of the pyramid
     457             : 
     458             :           // Base node to base node midpoint nodes
     459         242 :           owned_nodes.emplace_back(Node::build((*on[0] + *on[1]) / 2., 5));
     460         242 :           owned_nodes.emplace_back(Node::build((*on[1] + *on[2]) / 2., 6));
     461         242 :           owned_nodes.emplace_back(Node::build((*on[2] + *on[3]) / 2., 7));
     462         242 :           owned_nodes.emplace_back(Node::build((*on[3] + *on[0]) / 2., 8));
     463             : 
     464             :           // Base node to apex node midpoint nodes
     465         242 :           owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[4]) / 2.), 9));
     466         242 :           owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[4]) / 2.), 10));
     467         242 :           owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[4]) / 2.), 11));
     468         242 :           owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[4]) / 2.), 12));
     469             : 
     470         234 :           if (type == PYRAMID14 || type == PYRAMID18)
     471             :             {
     472             :               // Define the square face midpoint node of the pyramid
     473         151 :               owned_nodes.emplace_back(
     474         169 :                   Node::build(Point((*on[0] + *on[1] + *on[2] + *on[3]) / 4.), 13));
     475             : 
     476         163 :               if (type == PYRAMID18)
     477             :                 {
     478             :                   // Define the triangular face nodes
     479          96 :                   owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[4]) / 3.), 14));
     480          96 :                   owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[4]) / 3.), 15));
     481          96 :                   owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3] + *on[4]) / 3.), 16));
     482         100 :                   owned_nodes.emplace_back(Node::build(Point((*on[3] + *on[0] + *on[4]) / 3.), 17));
     483             :                 }
     484           8 :             }
     485             :         }
     486             : 
     487          71 :       else if (type != PYRAMID5)
     488           0 :         libmesh_error_msg("Unsupported pyramid element: " << type_str);
     489             : 
     490             :     } // if Pyramid
     491             : 
     492             :   // Elems deriving from Tet
     493      214042 :   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      193814 :       const auto side_length = std::cbrt(6. * sqrt_2 * ref_vol);
     511             :       // tet height with the property that all faces are equilateral triangles
     512      193814 :       const auto target_height = sqrt_2 / sqrt_3 * side_length;
     513             : 
     514       16136 :       const auto & s = side_length;
     515       16136 :       const auto & h = target_height;
     516             : 
     517             :       // For regular tet
     518             :       //                                         x        y                z     node_id
     519      209950 :       owned_nodes.emplace_back(Node::build(Point(0.,      0.,               0.), 0));
     520      193814 :       owned_nodes.emplace_back(Node::build(Point(s,       0.,               0.), 1));
     521      209950 :       owned_nodes.emplace_back(Node::build(Point(0.5 * s, 0.5 * sqrt_3 * s, 0.), 2));
     522      209950 :       owned_nodes.emplace_back(Node::build(Point(0.5 * s, sqrt_3 / 6. * s,  h),  3));
     523             : 
     524      193814 :       if (type == TET10 || type == TET14)
     525             :         {
     526       10758 :           const auto & on = owned_nodes;
     527             :           // Define the edge midpoint nodes of the tet
     528             : 
     529             :           // Base node to base node midpoint nodes
     530      139989 :           owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1]) / 2.), 4));
     531      139989 :           owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2]) / 2.), 5));
     532      139989 :           owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[0]) / 2.), 6));
     533             :           // Base node to apex node midpoint nodes
     534      139989 :           owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[3]) / 2.), 7));
     535      139989 :           owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[3]) / 2.), 8));
     536      139989 :           owned_nodes.emplace_back(Node::build(Point((*on[2] + *on[3]) / 2.), 9));
     537             : 
     538      129231 :           if (type == TET14)
     539             :             {
     540             :               // Define the face midpoint nodes of the tet
     541       70028 :               owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[2]) / 3.), 10));
     542       70028 :               owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[1] + *on[3]) / 3.), 11));
     543       70028 :               owned_nodes.emplace_back(Node::build(Point((*on[1] + *on[2] + *on[3]) / 3.), 12));
     544       75408 :               owned_nodes.emplace_back(Node::build(Point((*on[0] + *on[2] + *on[3]) / 3.), 13));
     545       10758 :             }
     546             :         }
     547             : 
     548       64583 :       else if (type != TET4)
     549           0 :         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      334669 :     for (const auto & node : target_elem->reference_elem()->node_ref_range())
     556      363615 :       owned_nodes.emplace_back(Node::build(node, node.id()));
     557             : 
     558             :   // Set nodes of target element
     559     2368891 :   for (const auto & node_ptr : owned_nodes)
     560     2327905 :     target_elem->set_node(node_ptr->id(), node_ptr.get());
     561             : 
     562       18090 :   libmesh_assert(relative_fuzzy_equals(target_elem->volume(), ref_vol, TOLERANCE));
     563             : 
     564      236986 :   return std::make_pair(std::move(target_elem), std::move(owned_nodes));
     565      182716 : }
     566             : } // namespace ReferenceElem
     567             : } // namespace libMesh

Generated by: LCOV version 1.14