libMesh
Loading...
Searching...
No Matches
mesh_stitch.C
Go to the documentation of this file.
1// libmesh includes
2#include <libmesh/boundary_info.h>
3#include <libmesh/distributed_mesh.h>
4#include <libmesh/elem.h>
5#include <libmesh/mesh.h>
6#include <libmesh/mesh_generation.h>
7#include <libmesh/mesh_modification.h>
8#include <libmesh/parallel_implementation.h>
9#include <libmesh/node.h>
10#include <libmesh/replicated_mesh.h>
11#include <libmesh/utility.h>
12
13// cppunit includes
14#include "test_comm.h"
15#include "libmesh_cppunit.h"
16
17#include <algorithm>
18#include <regex>
19
20using namespace libMesh;
21
22
23class MeshStitchTest : public CppUnit::TestCase {
24public:
26
27#if LIBMESH_DIM > 2
35#ifdef LIBMESH_HAVE_EXODUS_API
37#endif
38#endif // LIBMESH_DIM > 2
39
41
42private:
43
44public:
45 void setUp()
46 {}
47
48 void tearDown()
49 {}
50
52 const boundary_id_type boundary_id_offset,
53 const std::string & boundary_name_prefix)
54 {
55 BoundaryInfo & boundary_info = mesh.get_boundary_info();
56 const auto mesh_boundary_ids = boundary_info.get_global_boundary_ids();
57 for (auto rit = mesh_boundary_ids.rbegin(); rit != mesh_boundary_ids.rend(); ++rit)
58 {
59 const auto old_sideset_name = boundary_info.sideset_name(*rit);
60 const auto old_nodeset_name = boundary_info.nodeset_name(*rit);
61
62 MeshTools::Modification::change_boundary_id(mesh, *rit, *rit + boundary_id_offset);
63
64 boundary_info.sideset_name(*rit + boundary_id_offset) =
65 boundary_name_prefix + old_sideset_name;
66 boundary_info.nodeset_name(*rit + boundary_id_offset) =
67 boundary_name_prefix + old_nodeset_name;
68 }
69 }
70
71
72 template <typename MeshType>
74 {
75 LOG_UNIT_TEST;
76
77 MeshType mesh0(*TestCommWorld), mesh1(*TestCommWorld);
78
79 int ps = 2;
80 MeshTools::Generation::build_cube(mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX8);
81 MeshTools::Generation::build_cube(mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX8);
82
83 // rename and shift boundaries
84 renameAndShift(mesh0, 0, "zero_");
85 renameAndShift(mesh1, 6, "one_");
86
87 mesh0.stitch_meshes(mesh1, 2, 10, TOLERANCE, true, false, false, false);
88
89 CPPUNIT_ASSERT_EQUAL(mesh0.n_elem(), static_cast<dof_id_type>(16));
90 CPPUNIT_ASSERT_EQUAL(mesh0.n_nodes(), static_cast<dof_id_type>(45));
91
92 const BoundaryInfo & bi = mesh0.get_boundary_info();
93 std::set<boundary_id_type> sbi = bi.get_side_boundary_ids();
94 TestCommWorld->set_union(sbi);
95
96 typename std::decay<decltype(sbi.size())>::type expected_size = 10;
97 CPPUNIT_ASSERT_EQUAL(expected_size, sbi.size());
98
99 std::set<boundary_id_type> nbi = bi.get_node_boundary_ids();
100 TestCommWorld->set_union(nbi);
101 CPPUNIT_ASSERT_EQUAL(expected_size, nbi.size());
102
103 // We expect that the "zero_right" and "one_left" boundaries have
104 // disappeared after being stitched together.
105 std::set<std::string> expected_names = {{"zero_left",
106 "zero_top",
107 "zero_front",
108 "zero_back",
109 "zero_bottom",
110 "one_right",
111 "one_top",
112 "one_front",
113 "one_back",
114 "one_bottom"}};
115 std::set<std::string> ss_names;
116 for (const auto & pr : bi.get_sideset_name_map())
117 ss_names.insert(pr.second);
118 CPPUNIT_ASSERT(ss_names == expected_names);
119
120 std::set<std::string> ns_names;
121 for (const auto & pr : bi.get_nodeset_name_map())
122 ns_names.insert(pr.second);
123 CPPUNIT_ASSERT(ns_names == expected_names);
124 }
125
126
128 {
129 testBoundaryInfo<ReplicatedMesh>();
130 }
131
132
134 {
135 testBoundaryInfo<DistributedMesh>();
136 }
137
138
139 template <typename MeshType>
141 {
142 LOG_UNIT_TEST;
143
144 // Generate four meshes to be stitched together
145 MeshType mesh0(*TestCommWorld),
146 mesh1(*TestCommWorld),
147 mesh2(*TestCommWorld),
148 mesh3(*TestCommWorld);
149
150 // Give the meshes different extra integers to make sure those
151 // merge. Reuse names between nodes and elements to make sure
152 // those don't mix. Add some integers before and others after
153 // generation to test flexibility there.
154
155 std::vector<std::string> names2 {"bar", "baz"};
156 mesh2.add_elem_integers(names2);
157
158 std::vector<std::string> names3 {"bar", "foo"};
159 mesh3.add_elem_integers(names3);
160
161 int ps = 2;
162 MeshTools::Generation::build_cube (mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX27);
163 MeshTools::Generation::build_cube (mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX27);
164 MeshTools::Generation::build_cube (mesh2, ps, ps, ps, -1, 0, -1, 0, 0, 1, HEX27);
165 MeshTools::Generation::build_cube (mesh3, ps, ps, ps, 0, 1, -1, 0, 0, 1, HEX27);
166
167 struct trivially_copyable_pair // std::pair triggers -Wclass-memaccess
168 {
169 dof_id_type first, second;
170 };
171
172 mesh0.add_node_integer("baz");
173 unsigned int foo1e_idx = mesh1.add_elem_integer("foo");
174 mesh2.template add_elem_datum<trivially_copyable_pair>("qux");
175 unsigned int qux2n_idx = mesh2.template add_node_datum<trivially_copyable_pair>("qux");
176 mesh3.add_node_integers(names3);
177
178 for (const auto & elem : mesh1.element_ptr_range())
179 elem->set_extra_integer(foo1e_idx, 2);
180
181 for (const auto & node : mesh2.node_ptr_range())
182 node->template set_extra_datum<trivially_copyable_pair>
183 (qux2n_idx, {3, 4});
184
185 // We stitch the meshes in a hierarchical way.
186 mesh0.stitch_meshes(mesh1, 2, 4, TOLERANCE, true, /*verbose*/false, false, false);
187 mesh2.stitch_meshes(mesh3, 2, 4, TOLERANCE, true, /*verbose*/false, false, false);
188 mesh0.stitch_meshes(mesh2, 1, 3, TOLERANCE, true, /*verbose*/false, false, false);
189
190 CPPUNIT_ASSERT_EQUAL(mesh0.n_elem(), static_cast<dof_id_type>(32));
191 CPPUNIT_ASSERT_EQUAL(mesh0.n_nodes(), static_cast<dof_id_type>(405));
192 CPPUNIT_ASSERT_EQUAL(mesh0.n_elem_integers(), 5u); // that pair counts 2x
193 CPPUNIT_ASSERT_EQUAL(mesh0.n_node_integers(), 5u);
194 std::vector<std::string> all_names {"foo", "bar", "baz", "qux"};
195 std::vector<unsigned int> node_name_indices {4, 3, 0, 1};
196 for (unsigned int i=0; i != 4; ++i)
197 {
198 CPPUNIT_ASSERT(mesh0.has_elem_integer(all_names[i]));
199 CPPUNIT_ASSERT_EQUAL(mesh0.get_elem_integer_index(all_names[i]), i);
200 CPPUNIT_ASSERT(mesh0.has_node_integer(all_names[i]));
201 CPPUNIT_ASSERT_EQUAL(mesh0.get_node_integer_index(all_names[i]), node_name_indices[i]);
202 }
203
204 unsigned int foo0e_idx = mesh0.get_elem_integer_index("foo");
205 for (const auto & elem : mesh0.element_ptr_range())
206 {
207 CPPUNIT_ASSERT_EQUAL(elem->n_extra_integers(), 5u);
208 const Point c = elem->vertex_average();
209 if (c(0) > 0 && c(1) > 0) // this came from mesh1
210 CPPUNIT_ASSERT_EQUAL(elem->get_extra_integer(foo0e_idx), static_cast<dof_id_type>(2));
211 else
212 CPPUNIT_ASSERT_EQUAL(elem->get_extra_integer(foo0e_idx), DofObject::invalid_id);
213 }
214
215 unsigned int qux0n_idx = mesh0.get_node_integer_index("qux");
216 for (const auto & node : mesh0.node_ptr_range())
217 {
218 CPPUNIT_ASSERT_EQUAL(node->n_extra_integers(), 5u);
219 trivially_copyable_pair datum =
220 node->template get_extra_datum<trivially_copyable_pair>(qux0n_idx);
221 if ((*node)(0) <= 0 && (*node)(1) < 0) // this came from mesh2
222 {
223 CPPUNIT_ASSERT_EQUAL(datum.first, static_cast<dof_id_type>(3));
224 CPPUNIT_ASSERT_EQUAL(datum.second, static_cast<dof_id_type>(4));
225 }
226 else
227 {
228 CPPUNIT_ASSERT_EQUAL(datum.first, DofObject::invalid_id);
229 CPPUNIT_ASSERT_EQUAL(datum.second, DofObject::invalid_id);
230 }
231 }
232 }
233
235 {
236 testMeshStitch<ReplicatedMesh>();
237 }
238
240 {
241 testMeshStitch<DistributedMesh>();
242 }
243
244 template <typename MeshType>
245 void testMeshStitchElemsets (unsigned int ps)
246 {
247 LOG_UNIT_TEST;
248
249 // Generate meshes to be stitched together. We are going to clone
250 // these so work with unique_ptrs directly.
251 auto mesh0 = std::make_unique<MeshType>(*TestCommWorld);
252
253 // If the user tries to stitch meshes with overlapping codes, we
254 // allow this as long as the codes refer to the same underlying
255 // set ids.
256
257 // Build a mesh on the unit cube
258 MeshTools::Generation::build_cube (*mesh0, ps, ps, ps,
259 /*xmin=*/0., /*xmax=*/1.,
260 /*ymin=*/0., /*ymax=*/1.,
261 /*zmin=*/0., /*zmax=*/1.,
262 HEX27);
263
264 // Make a copy
265 auto mesh1 = mesh0->clone();
266
267 // Shift copy one unit to the right
268 MeshTools::Modification::translate(*mesh1, /*x-dir*/1.0);
269
270 // For both meshes:
271 // .) Put odd-numbered Elems in elmset 1
272 // .) Put even-numbered Elems in elemset 2
273 // We use the trivial encoding: elemset id == elemset code for simplicity
274 auto place_elems = [](MeshBase & mesh)
275 {
276 unsigned int elemset_index =
277 mesh.add_elem_integer("elemset_code", /*allocate_data=*/true);
278
279 mesh.add_elemset_code(/*code=*/1, /*set_ids*/{1});
280 mesh.add_elemset_code(/*code=*/2, /*set_ids*/{2});
281
282 for (const auto & elem : mesh.element_ptr_range())
283 {
284 if (elem->id() % 2) // id odd
285 elem->set_extra_integer(elemset_index, 1);
286 else // id even
287 elem->set_extra_integer(elemset_index, 2);
288 }
289 };
290
291 place_elems(*mesh0);
292 place_elems(*mesh1);
293
294 // Before stitching, change the elemset codes on mesh1 so they
295 // don't overlap with the codes on mesh0.
296 mesh1->change_elemset_code(/*old*/1, /*new*/3); // 1 -> 3
297 mesh1->change_elemset_code(/*old*/2, /*new*/4); // 2 -> 4
298
299 // Before stitching, change the elemset ids on mesh1 so they
300 // don't overlap with the elemset ids on mesh0.
301 mesh1->change_elemset_id(/*old*/1, /*new*/100); // 1 -> 100
302 mesh1->change_elemset_id(/*old*/2, /*new*/200); // 2 -> 200
303
304 // Stitch the meshes together at the indicated boundary ids
305 mesh0->stitch_meshes(dynamic_cast<UnstructuredMesh &>(*mesh1),
306 /*this boundary=*/2,
307 /*other boundary=*/4,
308 TOLERANCE,
309 /*clear_stitched_boundary_ids=*/true,
310 /*verbose=*/false,
311 /*use_binary_search=*/false,
312 /*enforce_all_nodes_match_on_boundaries=*/false);
313
314 // Number of elements in each mesh pre-stitch
315 dof_id_type n_elem_prestitch = Utility::pow<3>(ps);
316
317 // mesh0 should contain 2 * ps**3 total elements after stitching
318 CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(2 * n_elem_prestitch), mesh0->n_elem());
319
320 // Check that the stitched mesh still stores "elemset_code" in the
321 // same index (0) as it was before the meshes were stitched.
322 unsigned int elemset_index = mesh0->get_elem_integer_index("elemset_code");
323 CPPUNIT_ASSERT_EQUAL(0u, elemset_index);
324
325 // Check that the stitched mesh has merged elemset codes and ids as expected
326 MeshBase::elemset_type id_set_to_fill;
327 const elemset_id_type code_to_type[] = {0,1,2,100,200};
328 for (dof_id_type elemset_code=1; elemset_code<5; ++elemset_code)
329 {
330 mesh0->get_elemsets(elemset_code, id_set_to_fill);
331
332 // Assert one elemset id in each set, and that set contains the correct id
333 CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), id_set_to_fill.size());
334 CPPUNIT_ASSERT(id_set_to_fill.count(code_to_type[elemset_code]));
335 }
336
337 bool ps_odd = ps % 2;
338
339 for (const auto & elem : mesh0->element_ptr_range())
340 {
341 dof_id_type elemset_code = elem->get_extra_integer(elemset_index);
342 bool elem_id_odd = elem->id() % 2;
343
344 // Debugging
345 // libMesh::out << "Elem " << elem->id() << " in stitched mesh has elemset_code = " << elemset_code << std::endl;
346
347 // Verify that the stitched mesh elemset codes match their pre-stitched values
348 if (elem->id() < n_elem_prestitch) // lower half id
349 {
350 if (elem_id_odd)
351 CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(1), elemset_code);
352 else
353 CPPUNIT_ASSERT_EQUAL(static_cast<dof_id_type>(2), elemset_code);
354 }
355 else // upper half id
356 {
357 // i.) If ps == odd, then n_elem_prestitch == odd, and even mesh1
358 // elem ids will become odd, and odd mesh1 elem ids will become
359 // even..
360 // ii.) If ps == even, then n_elem_prestitch == even, and even mesh1
361 // elem ids will remain even, odd mesh1 elem ids will remain odd.
362 if (elem_id_odd)
363 CPPUNIT_ASSERT_EQUAL(ps_odd ? static_cast<dof_id_type>(4) : static_cast<dof_id_type>(3), elemset_code);
364 else
365 CPPUNIT_ASSERT_EQUAL(ps_odd ? static_cast<dof_id_type>(3) : static_cast<dof_id_type>(4), elemset_code);
366 }
367 }
368 }
369
371 {
372 testMeshStitchElemsets<ReplicatedMesh>(/*ps=*/2);
373 testMeshStitchElemsets<ReplicatedMesh>(/*ps=*/3);
374 }
375
376
378 {
379 LOG_UNIT_TEST;
380
381 Mesh mesh0(*TestCommWorld), mesh1(*TestCommWorld);
382
383 int ps = 2;
384 MeshTools::Generation::build_cube(mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX8);
385 MeshTools::Generation::build_cube(mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX8);
386
387 // rename and shift boundaries
388 renameAndShift(mesh0, 0, "zero_");
389 renameAndShift(mesh1, 6, "one_");
390
391 // Create "auto" generated subdomain ids
392 for (const auto & elem : mesh0.element_ptr_range())
393 elem->subdomain_id() = 123;
394
395 for (const auto & elem : mesh1.element_ptr_range())
396 elem->subdomain_id() = 456;
397
398 // Resolve them to the same name
399 mesh0.set_subdomain_name(123, "OneTwoThree");
400 mesh1.set_subdomain_name(456, "OneTwoThree"); // silly autogen
401
402 mesh0.stitch_meshes(mesh1, 2, 10, TOLERANCE, true, false, false,
403 false, false, /* remap_subdomain_ids = */ true);
404
405 CPPUNIT_ASSERT_EQUAL(mesh0.n_elem(), static_cast<dof_id_type>(16));
406 CPPUNIT_ASSERT_EQUAL(mesh0.n_nodes(), static_cast<dof_id_type>(45));
407
408 // Ensure they still map to the same name but now with the same id
409 for (const auto & elem : mesh0.element_ptr_range())
410 CPPUNIT_ASSERT_EQUAL(elem->subdomain_id(), subdomain_id_type(123));
411 }
412
413
415 {
416 LOG_UNIT_TEST;
417
418 Mesh mesh0(*TestCommWorld), mesh1(*TestCommWorld);
419
420 int ps = 2;
421 MeshTools::Generation::build_cube(mesh0, ps, ps, ps, -1, 0, 0, 1, 0, 1, HEX8);
422 MeshTools::Generation::build_cube(mesh1, ps, ps, ps, 0, 1, 0, 1, 0, 1, HEX8);
423
424 // rename and shift boundaries
425 renameAndShift(mesh0, 0, "zero_");
426 renameAndShift(mesh1, 6, "one_");
427
428 // Create matching subdomain ids
429 for (const auto & elem : mesh0.element_ptr_range())
430 elem->subdomain_id() = 123;
431
432 for (const auto & elem : mesh1.element_ptr_range())
433 elem->subdomain_id() = 123;
434
435 // Create a conflict when only one is named
436 mesh1.set_subdomain_name(123, "OneTwoThree");
437
438#ifdef LIBMESH_ENABLE_EXCEPTIONS
439 bool threw_error = false;
440 try
441 {
442 mesh0.stitch_meshes(mesh1, 2, 10, TOLERANCE, true, false, false,
443 false, false, /* remap_subdomain_ids = */ true);
444 }
445 catch (libMesh::LogicError & e)
446 {
447 std::regex msg_regex("safely stitch with a mesh");
448 CPPUNIT_ASSERT(std::regex_search(e.what(), msg_regex));
449 threw_error = true;
450 }
451
452 CPPUNIT_ASSERT(threw_error);
453#endif // LIBMESH_ENABLE_EXCEPTIONS
454 }
455
456#ifdef LIBMESH_HAVE_EXODUS_API
458 {
459 LOG_UNIT_TEST;
460
461 Mesh mesh1(*TestCommWorld);
462 mesh1.read("meshes/component_1.exo");
463 Mesh mesh2(*TestCommWorld);
464 mesh2.read("meshes/component_2.exo");
465
466 auto merged_nodes = mesh1.stitch_meshes(
467 mesh2,
468 /*this_mesh_boundary_id=*/5601,
469 /*other_mesh_boundary_id=*/5600,
470 /*tol=*/0.01,
471 /*clear_stitched_boundary_ids=*/ false,
472 /*verbose=*/false,
473 /*use_binary_search=*/false,
474 /*enforce_all_nodes_match_on_boundaries=*/true,
475 /*merge_boundary_nodes_all_or_nothing=*/true);
476
477 CPPUNIT_ASSERT_EQUAL(merged_nodes, static_cast<std::size_t>(36));
478 }
479#endif
480
481};
482
CPPUNIT_TEST(testDistributedBoundaryInfo)
void testMeshStitch()
LIBMESH_CPPUNIT_TEST_SUITE(MeshStitchTest)
CPPUNIT_TEST(testRemappingStitch)
void testMeshStitchElemsets(unsigned int ps)
CPPUNIT_TEST(testReplicatedMeshStitchElemsets)
void testNodeElemStitch()
void testReplicatedBoundaryInfo()
void testDistributedMeshStitch()
CPPUNIT_TEST(testReplicatedBoundaryInfo)
CPPUNIT_TEST(testNodeElemStitch)
void renameAndShift(UnstructuredMesh &mesh, const boundary_id_type boundary_id_offset, const std::string &boundary_name_prefix)
Definition mesh_stitch.C:51
void testReplicatedMeshStitch()
void testReplicatedMeshStitchElemsets()
void testBoundaryInfo()
Definition mesh_stitch.C:73
CPPUNIT_TEST(testDistributedMeshStitch)
CPPUNIT_TEST(testAmbiguousRemappingStitch)
void testRemappingStitch()
CPPUNIT_TEST(testReplicatedMeshStitch)
void testDistributedBoundaryInfo()
void testAmbiguousRemappingStitch()
The BoundaryInfo class contains information relevant to boundary conditions including storing faces,...
std::string & sideset_name(boundary_id_type id)
const std::set< boundary_id_type > & get_global_boundary_ids() const
std::string & nodeset_name(boundary_id_type id)
const std::set< boundary_id_type > & get_node_boundary_ids() const
const std::set< boundary_id_type > & get_side_boundary_ids() const
const std::map< boundary_id_type, std::string > & get_nodeset_name_map() const
const std::map< boundary_id_type, std::string > & get_sideset_name_map() const
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
A class to represent the internal "this should never happen" errors, to be thrown by "libmesh_error()...
This is the MeshBase class.
Definition mesh_base.h:81
void set_subdomain_name(subdomain_id_type id, const std::string &name, bool synchronous=false)
Sets the name for the provided id.
Definition mesh_base.C:1914
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
unsigned int add_elem_integer(std::string name, bool allocate_data=true, dof_id_type default_value=DofObject::invalid_id)
Register an integer datum (of type dof_id_type) to be added to each element in the mesh.
Definition mesh_base.C:623
void add_elemset_code(dof_id_type code, MeshBase::elemset_type id_set)
Tabulate a user-defined "code" for elements which belong to the element sets specified in id_set.
Definition mesh_base.C:456
std::set< elemset_id_type > elemset_type
Typedef for the "set" container used to store elemset ids.
Definition mesh_base.h:466
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition mesh.h:51
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
The UnstructuredMesh class is derived from the MeshBase class.
std::size_t stitch_meshes(const MeshBase &other_mesh, boundary_id_type this_mesh_boundary, boundary_id_type other_mesh_boundary, Real tol=TOLERANCE, bool clear_stitched_boundary_ids=false, bool verbose=true, bool use_binary_search=true, bool enforce_all_nodes_match_on_boundaries=false, bool merge_boundary_nodes_all_or_nothing=false, bool remap_subdomain_ids=false, bool prepare_after_stitching=true)
Stitch other_mesh to this mesh so that this mesh is the union of the two meshes.
virtual void read(const std::string &name, void *mesh_data=nullptr, bool skip_renumber_nodes_and_elements=false, bool skip_find_neighbors=false, bool skip_detect_interior_parents=false) override
Reads the file specified by name.
Communicator * TestCommWorld
MeshBase & mesh
CPPUNIT_TEST_SUITE_REGISTRATION(MeshStitchTest)
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 change_boundary_id(MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
Finds any boundary ids that are currently old_id, changes them to new_id.
void translate(MeshBase &mesh, const Real xt=0., const Real yt=0., const Real zt=0.)
Translates the mesh.
The libMesh namespace provides an interface to certain functionality in the library.
int8_t boundary_id_type
Definition id_types.h:51
TestClass subdomain_id_type
Based on the 4-byte comment warning above, this probably doesn't work with exodusII at all....
Definition id_types.h:43
static constexpr Real TOLERANCE
uint8_t dof_id_type
Definition id_types.h:67