libMesh
Loading...
Searching...
No Matches
reference_elem.C
Go to the documentation of this file.
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
41namespace
42{
43using namespace libMesh;
44
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
54typedef Threads::spin_mutex InitMutex;
55
56// Mutex for thread safety.
57InitMutex init_mtx;
58
59// map from ElemType to reference element file system object name
60typedef std::map<ElemType, const char *> FileMapType;
61FileMapType ref_elem_file;
62Elem * ref_elem_map[INVALID_ELEM];
63
64
65
66class SingletonCache : public libMesh::Singleton
67{
68public:
69 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
101SingletonCache * singleton_cache = nullptr;
102
103
104
105void read_ref_elem (const ElemType type_in,
106 std::istream & in)
107{
108 libmesh_assert (singleton_cache != nullptr);
109
110 std::string dummy;
111 unsigned int n_elem, n_nodes, elem_type_read, nn;
112 double x, y, z;
113
114 in >> dummy;
115 in >> n_elem; std::getline (in, dummy); libmesh_assert_equal_to (n_elem, 1);
116 in >> n_nodes; std::getline (in, dummy);
117 in >> dummy; std::getline (in, dummy);
118 in >> dummy; std::getline (in, dummy);
119 in >> dummy; std::getline (in, dummy);
120 in >> dummy; std::getline (in, dummy);
121 in >> n_elem; std::getline (in, dummy); libmesh_assert_equal_to (n_elem, 1);
122
123 in >> elem_type_read;
124
125 libmesh_assert_less (elem_type_read, INVALID_ELEM);
126 libmesh_assert_equal_to (elem_type_read, static_cast<unsigned int>(type_in));
127 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 auto & uelem = singleton_cache->elem_list.emplace_back(Elem::build(type_in));
131
132 // We are expecting an identity map, so assert it!
133 for (unsigned int n=0; n<n_nodes; n++)
134 {
135 in >> nn;
136 libmesh_assert_equal_to (n,nn);
137 }
138
139 for (unsigned int n=0; n<n_nodes; n++)
140 {
141 in >> x >> y >> z;
142
143 auto & new_node =
144 singleton_cache->node_list.emplace_back(Node::build(x,y,z,n));
145
146 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 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 ref_elem_map[type_in] = uelem.get();
155}
156
157
158
159void init_ref_elem_table()
160{
161 // outside mutex - if this pointer is set, we can trust it.
162 if (singleton_cache != nullptr)
163 return;
164
165 // playing with fire here - lock before touching shared
166 // data structures
167 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 if (singleton_cache != nullptr)
172 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 singleton_cache = new SingletonCache;
180
181 // initialize the reference file table
182 {
183 ref_elem_file.clear();
184
185 // 0D elements
186 ref_elem_file[NODEELEM] = ElemDataStrings::one_nodeelem;
187
188 // 1D elements
189 ref_elem_file[EDGE2] = ElemDataStrings::one_edge;
190 ref_elem_file[EDGE3] = ElemDataStrings::one_edge3;
191 ref_elem_file[EDGE4] = ElemDataStrings::one_edge4;
192
193 // 2D elements
194 ref_elem_file[TRI3] = ElemDataStrings::one_tri;
195 ref_elem_file[TRI6] = ElemDataStrings::one_tri6;
196 ref_elem_file[TRI7] = ElemDataStrings::one_tri7;
197
198 ref_elem_file[QUAD4] = ElemDataStrings::one_quad;
199 ref_elem_file[QUAD8] = ElemDataStrings::one_quad8;
200 ref_elem_file[QUAD9] = ElemDataStrings::one_quad9;
201
202 // 3D elements
203 ref_elem_file[HEX8] = ElemDataStrings::one_hex;
204 ref_elem_file[HEX20] = ElemDataStrings::one_hex20;
205 ref_elem_file[HEX27] = ElemDataStrings::one_hex27;
206
207 ref_elem_file[TET4] = ElemDataStrings::one_tet;
208 ref_elem_file[TET10] = ElemDataStrings::one_tet10;
209 ref_elem_file[TET14] = ElemDataStrings::one_tet14;
210
211 ref_elem_file[PRISM6] = ElemDataStrings::one_prism;
212 ref_elem_file[PRISM15] = ElemDataStrings::one_prism15;
213 ref_elem_file[PRISM18] = ElemDataStrings::one_prism18;
214 ref_elem_file[PRISM20] = ElemDataStrings::one_prism20;
215 ref_elem_file[PRISM21] = ElemDataStrings::one_prism21;
216
217 ref_elem_file[PYRAMID5] = ElemDataStrings::one_pyramid;
218 ref_elem_file[PYRAMID13] = ElemDataStrings::one_pyramid13;
219 ref_elem_file[PYRAMID14] = ElemDataStrings::one_pyramid14;
220 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 for (const auto & [elem_type, filename] : ref_elem_file)
229 {
230 std::istringstream stream(filename);
231 read_ref_elem(elem_type, stream);
232 }
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
254namespace libMesh
255{
256namespace ReferenceElem
257{
258const Elem & get (const ElemType type_in)
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}
285
286std::pair<std::unique_ptr<Elem>, std::vector<std::unique_ptr<Node>>>
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}
566} // namespace ReferenceElem
567} // namespace libMesh
This is the base class from which all geometric element types are derived.
Definition elem.h:96
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:382
static constexpr unsigned int type_to_n_nodes_map[INVALID_ELEM]
This array maps the integer representation of the ElemType enum to the number of nodes in the element...
Definition elem.h:664
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
Base class for all library singleton objects.
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
const Elem & get(const ElemType type_in)
std::pair< std::unique_ptr< Elem >, std::vector< std::unique_ptr< Node > > > ideal_target(const ElemType type)
std::string enum_to_string(const T e)
The libMesh namespace provides an interface to certain functionality in the library.
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.
ElemType
Defines an enum for geometric element types.
libmesh_assert(ctx)
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const dof_id_type n_nodes
Definition tecplot_io.C:67