libMesh
Loading...
Searching...
No Matches
nodal_neighbors.C
Go to the documentation of this file.
1#include <libmesh/libmesh.h>
2#include <libmesh/node.h>
3#include <libmesh/mesh_generation.h>
4#include <libmesh/mesh_tools.h>
5#include <libmesh/replicated_mesh.h>
6#include <libmesh/elem.h>
7#include <libmesh/utility.h>
8#include <libmesh/reference_elem.h>
9#include <unordered_map>
10
11#include "test_comm.h"
12#include "libmesh_cppunit.h"
13
14
15using namespace libMesh;
16
17class NodalNeighborsTest : public CppUnit::TestCase
18{
36public:
38
43
47
51
55
60
65
69
71
72private:
73
74 // Hard-coded neighbor relationships for each element type
75 using Map = std::map<ElemType, std::map<dof_id_type, std::set<dof_id_type>>>;
76
78 {
79 Map m;
80 //m.reserve(10);
81
82 m[EDGE2][0].insert(1);
83 m[EDGE2][1].insert(0);
84
85 m[EDGE3][0].insert(2);
86 m[EDGE3][1].insert(2);
87 m[EDGE3][2].insert({0, 1});
88
89 m[EDGE4][0].insert(2);
90 m[EDGE4][1].insert(3);
91 m[EDGE4][2].insert({0, 3});
92 m[EDGE4][3].insert({2, 1});
93
94 m[TRI3][0].insert({1, 2});
95 m[TRI3][1].insert({0, 2});
96 m[TRI3][2].insert({1, 0});
97
98 m[TRI6][0].insert({3, 5});
99 m[TRI6][1].insert({3, 4});
100 m[TRI6][2].insert({5, 4});
101 m[TRI6][3].insert({0, 1});
102 m[TRI6][4].insert({1, 2});
103 m[TRI6][5].insert({2, 0});
104
105 m[TRI7] = m[TRI6];
106 // Node 6 of TRI7 is a face node, has no neighbors because it is not on an edge
107
108 m[QUAD4][0].insert({1, 3});
109 m[QUAD4][1].insert({0, 2});
110 m[QUAD4][2].insert({1, 3});
111 m[QUAD4][3].insert({0, 2});
112
113 m[QUAD8][0].insert({4, 7});
114 m[QUAD8][1].insert({4, 5});
115 m[QUAD8][2].insert({5, 6});
116 m[QUAD8][3].insert({6, 7});
117 m[QUAD8][4].insert({0, 1});
118 m[QUAD8][5].insert({1, 2});
119 m[QUAD8][6].insert({2, 3});
120 m[QUAD8][7].insert({0, 3});
121
122 m[QUAD9] = m[QUAD8];
123 // Node 8 of QUAD9 is a face node, has no neighbors because it is not on an edge
124
125 m[TET4][0].insert({1, 2, 3});
126 m[TET4][1].insert({0, 2, 3});
127 m[TET4][2].insert({0, 1, 3});
128 m[TET4][3].insert({0, 1, 2});
129
130 m[TET10][0].insert({4, 6, 7});
131 m[TET10][1].insert({4, 5, 8});
132 m[TET10][2].insert({5, 6, 9});
133 m[TET10][3].insert({7, 8, 9});
134 m[TET10][4].insert({0, 1});
135 m[TET10][5].insert({1, 2});
136 m[TET10][6].insert({0, 2});
137 m[TET10][7].insert({0, 3});
138 m[TET10][8].insert({1, 3});
139 m[TET10][9].insert({2, 3});
140
141 m[TET14] = m[TET10];
142 // Nodes 10-13 are face nodes and have no neighbors
143
144 m[PYRAMID5][0].insert({1, 3, 4});
145 m[PYRAMID5][1].insert({0, 2, 4});
146 m[PYRAMID5][2].insert({1, 3, 4});
147 m[PYRAMID5][3].insert({0, 2, 4});
148 m[PYRAMID5][4].insert({0, 1, 2, 3});
149
150 m[PYRAMID13][0].insert({5, 8, 9});
151 m[PYRAMID13][1].insert({5, 6, 10});
152 m[PYRAMID13][2].insert({6, 7, 11});
153 m[PYRAMID13][3].insert({7, 8, 12});
154 m[PYRAMID13][4].insert({9, 10, 11, 12});
155 m[PYRAMID13][5].insert({0, 1});
156 m[PYRAMID13][6].insert({1, 2});
157 m[PYRAMID13][7].insert({2, 3});
158 m[PYRAMID13][8].insert({0, 3});
159 m[PYRAMID13][9].insert({0, 4});
160 m[PYRAMID13][10].insert({1, 4});
161 m[PYRAMID13][11].insert({2, 4});
162 m[PYRAMID13][12].insert({3, 4});
163
164 m[PYRAMID14] = m[PYRAMID13];
165 // Node 13 is a face node and has no neighbors
166
167 m[PYRAMID18] = m[PYRAMID14];
168 // Nodes 14-17 are face nodes and have no neighbors
169
170 m[PRISM6][0].insert({1, 2, 3});
171 m[PRISM6][1].insert({0, 2, 4});
172 m[PRISM6][2].insert({0, 1, 5});
173 m[PRISM6][3].insert({0, 4, 5});
174 m[PRISM6][4].insert({1, 3, 5});
175 m[PRISM6][5].insert({2, 3, 4});
176
177 m[PRISM18][0].insert({6, 8, 9});
178 m[PRISM18][1].insert({6, 7, 10});
179 m[PRISM18][2].insert({7, 8, 11});
180 m[PRISM18][3].insert({9, 12, 14});
181 m[PRISM18][4].insert({10, 12, 13});
182 m[PRISM18][5].insert({11, 13, 14});
183 m[PRISM18][6].insert({0, 1});
184 m[PRISM18][7].insert({1, 2});
185 m[PRISM18][8].insert({0, 2});
186 m[PRISM18][9].insert({0, 3});
187 m[PRISM18][10].insert({1, 4});
188 m[PRISM18][11].insert({2, 5});
189 m[PRISM18][12].insert({3, 4});
190 m[PRISM18][13].insert({4, 5});
191 m[PRISM18][14].insert({3, 5});
192 // Nodes 15-17 are face nodes and have no neighbors
193
194 m[PRISM20] = m[PRISM18];
195 // Nodes 18-19 are face nodes and have no neighbors
196
197 m[PRISM21] = m[PRISM20];
198 // Node 20 is an interior node and has no neighbors
199
200 m[HEX8][0].insert({1, 3, 4});
201 m[HEX8][1].insert({0, 2, 5});
202 m[HEX8][2].insert({1, 3, 6});
203 m[HEX8][3].insert({0, 2, 7});
204 m[HEX8][4].insert({0, 5, 7});
205 m[HEX8][5].insert({1, 4, 6});
206 m[HEX8][6].insert({2, 5, 7});
207 m[HEX8][7].insert({3, 4, 6});
208
209 m[HEX20][0].insert({8, 11, 12});
210 m[HEX20][1].insert({8, 9, 13});
211 m[HEX20][2].insert({9, 10, 14});
212 m[HEX20][3].insert({10, 11, 15});
213 m[HEX20][4].insert({12, 16, 19});
214 m[HEX20][5].insert({13, 16, 17});
215 m[HEX20][6].insert({14, 17, 18});
216 m[HEX20][7].insert({15, 18, 19});
217 m[HEX20][8].insert({0, 1});
218 m[HEX20][9].insert({1, 2});
219 m[HEX20][10].insert({2, 3});
220 m[HEX20][11].insert({0, 3});
221 m[HEX20][12].insert({0, 4});
222 m[HEX20][13].insert({1, 5});
223 m[HEX20][14].insert({2, 6});
224 m[HEX20][15].insert({3, 7});
225 m[HEX20][16].insert({4, 5});
226 m[HEX20][17].insert({5, 6});
227 m[HEX20][18].insert({6, 7});
228 m[HEX20][19].insert({4, 7});
229
230 m[HEX27] = m[HEX20];
231 // Nodes 20-26 are face nodes and have no neighbors
232
233 return m;
234 };
235
237
238protected:
239
240 // Builds a 1D mesh with the specified ElemType and number of elements
241 void do_test(ElemType elem_type)
242 {
243 const auto * ref_elem = &(ReferenceElem::get(elem_type));
244 const auto dim = ref_elem->dim();
245
247
248 unsigned int n_elems_per_side = 4 / Elem::type_to_default_order_map[elem_type];
249
250 switch (dim)
251 {
252 case 1:
253 MeshTools::Generation::build_line(mesh, n_elems_per_side, 0., 1., elem_type);
254 break;
255 case 2:
257 mesh, n_elems_per_side, n_elems_per_side, 0., 1., 0., 1., elem_type);
258 break;
259
260 case 3:
262 n_elems_per_side,
263 n_elems_per_side,
264 n_elems_per_side,
265 0.,
266 1.,
267 0.,
268 1.,
269 0.,
270 1.,
271 elem_type);
272 break;
273
274 default:
275 libmesh_error_msg("Unsupported dimension " << dim);
276 }
277
278 const auto & neighbor_map = libmesh_map_find(elem_type_to_neighbor_map, elem_type);
279
280 // find_nodal_neighbors() needs a data structure which is prepared by another function
281 std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
282 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
283
284 // Loop over the nodes and call find_nodal_neighbors()
285 std::vector<const Node*> neighbor_nodes;
286
287 std::set<dof_id_type> node_ids_checked;
288 for (const auto * elem : mesh.element_ptr_range())
289 {
290 for (const auto & node : elem->node_ref_range())
291 {
292 // Have we already checked this node?
293 if (node_ids_checked.find(node.id()) != node_ids_checked.end())
294 continue;
295
296 MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbor_nodes);
297
298 for (const auto * neigh : neighbor_nodes)
299 {
300 // Find the common elements between node and neigh
301 const auto & elems_containing_node = libmesh_map_find(nodes_to_elem_map, node.id());
302 const auto & elems_containing_neigh = libmesh_map_find(nodes_to_elem_map, neigh->id());
303 const Elem * common_elem = nullptr;
304 for (const auto * neigh_elem : elems_containing_neigh)
305 {
306 if ((std::find(elems_containing_node.begin(), elems_containing_node.end(), neigh_elem) !=
307 elems_containing_node.end()))
308 common_elem = neigh_elem;
309 else
310 continue;
311
312 // Which local elem node numbers are node and neighbor?
313 unsigned node_id_local = common_elem->local_node(node.id());
314 unsigned neigh_id_local = common_elem->local_node(neigh->id());
315
316 // For a given element type, we know the hard-coded neighbor relationship
317 // between local node ids. Make sure the neigbor from find_nodal_neighbors
318 // was intended to be a neighbor. Note that this test does not currently check
319 // for neighbors that find_nodal_neighbors may not have found.
320 const auto & correct_neighbor_ids = libmesh_map_find(neighbor_map, node_id_local);
321 CPPUNIT_ASSERT(correct_neighbor_ids.find(neigh_id_local) != correct_neighbor_ids.end());
322
323 }
324 }
325
326 node_ids_checked.insert(node.id());
327 }
328 }
329 }
330
331public:
332 void setUp() {}
333
334 void tearDown() {}
335
337 {
338 LOG_UNIT_TEST;
339 do_test(EDGE2);
340 }
341
342
344 {
345 LOG_UNIT_TEST;
346 do_test(EDGE3);
347 }
348
349
351 {
352 LOG_UNIT_TEST;
353 do_test(EDGE4);
354 }
355
356 void testTri3()
357 {
358 LOG_UNIT_TEST;
359 do_test(TRI3);
360 }
361
362 void testTri6()
363 {
364 LOG_UNIT_TEST;
365 do_test(TRI6);
366 }
367
368 void testTri7()
369 {
370 LOG_UNIT_TEST;
371 do_test(TRI7);
372 }
373
375 {
376 LOG_UNIT_TEST;
377 do_test(QUAD4);
378 }
379
381 {
382 LOG_UNIT_TEST;
383 do_test(QUAD8);
384 }
385
387 {
388 LOG_UNIT_TEST;
389 do_test(QUAD9);
390 }
391
392 void testTet4()
393 {
394 LOG_UNIT_TEST;
395 do_test(TET4);
396 }
397
399 {
400 LOG_UNIT_TEST;
401 do_test(TET10);
402 }
403
405 {
406 LOG_UNIT_TEST;
407 do_test(TET14);
408 }
409
411 {
412 LOG_UNIT_TEST;
414 }
415
417 {
418 LOG_UNIT_TEST;
420 }
421
423 {
424 LOG_UNIT_TEST;
426 }
427
429 {
430 LOG_UNIT_TEST;
432 }
433
435 {
436 LOG_UNIT_TEST;
438 }
439
441 {
442 LOG_UNIT_TEST;
444 }
445
447 {
448 LOG_UNIT_TEST;
450 }
451
453 {
454 LOG_UNIT_TEST;
456 }
457
458 void testHex8()
459 {
460 LOG_UNIT_TEST;
461 do_test(HEX8);
462 }
463
465 {
466 LOG_UNIT_TEST;
467 do_test(HEX20);
468 }
469
471 {
472 LOG_UNIT_TEST;
473 do_test(HEX27);
474 }
475
477 {
478 LOG_UNIT_TEST;
479
481
482 // The (1-based) node numbering and element orientation (represented
483 // by arrows) for this mesh:
484 // 1 -> 3 -> 4 -> 7 -> 8 <- 6 <- 5 <- 2
485 // So the two elements that meet at node 8 have opposite orientation
486 // and were not detected as neighbors using the original (before the
487 // addition of this test) find_neighbors() algorithm.
488
489 // These nodes are copied from an exo file where we originally
490 // noticed the problem, but otherwise are not significant to the
491 // test. Note: the actual ids are 0-based but we are using a
492 // 1-based connectivity array below which is copied from the exo
493 // file.
494 mesh.add_point(Point(1.68, -1.695, 8.298), /*id=*/0);
495 mesh.add_point(Point(5.55, -1.695, 8.298), /*id=*/1);
496 mesh.add_point(Point(1.68, -0.175, 8.298), /*id=*/2);
497 mesh.add_point(Point(1.68, -0.175, 9.643), /*id=*/3);
498 mesh.add_point(Point(5.55, -0.175, 8.298), /*id=*/4);
499 mesh.add_point(Point(5.55, -0.175, 9.643), /*id=*/5);
500 mesh.add_point(Point(1.68, -0.075, 9.643), /*id=*/6);
501 mesh.add_point(Point(5.55, -0.075, 9.643), /*id=*/7);
502
503 // 1-based connectivity array (2 nodes per Elem) copied directly
504 // from exo file. We will convert these to 0-based ids when they
505 // are used.
506 std::vector<unsigned int> conn =
507 {
508 7, 8,
509 1, 3,
510 2, 5,
511 3, 4,
512 5, 6,
513 4, 7,
514 6, 8
515 };
516
517 // Add 7 EDGE2 elements and assign connectivity
518 for (unsigned int e=0; e<7; ++e)
519 {
521 elem->set_node(0, mesh.node_ptr(conn[2*e] - 1)); // convert to 0-based index
522 elem->set_node(1, mesh.node_ptr(conn[2*e + 1] - 1)); // convert to 0-based index
523 }
524
525 // Find neighbors, etc.
527
528 for (const auto & elem : mesh.element_ptr_range())
529 {
530 auto elem_id = elem->id();
531
532 // Elems 1, 2 should have no neighbor on side 0
533 if (elem_id == 1 || elem_id == 2)
534 {
535 CPPUNIT_ASSERT(elem->neighbor_ptr(0) == nullptr);
536 CPPUNIT_ASSERT(elem->neighbor_ptr(1) != nullptr);
537 }
538 // Otherwise, elem should have neighbor on both sides
539 else
540 {
541 CPPUNIT_ASSERT(elem->neighbor_ptr(0) != nullptr);
542 CPPUNIT_ASSERT(elem->neighbor_ptr(1) != nullptr);
543 }
544
545 // Debugging
546 // libMesh::out << "elem " << elem_id << std::endl;
547 // for (auto side_idx : make_range(elem->n_sides()))
548 // {
549 // if (elem->neighbor_ptr(side_idx))
550 // libMesh::out << "Neighbor on side " << side_idx << std::endl;
551 // else
552 // libMesh::out << "No neighbor on side " << side_idx << std::endl;
553 // }
554 }
555 }
556};
557
558
unsigned int dim
CPPUNIT_TEST(testTet4)
CPPUNIT_TEST(testHex27)
CPPUNIT_TEST(testPyramid18)
CPPUNIT_TEST(testQuad9)
CPPUNIT_TEST(testHex8)
static Map build_elem_type_to_neighbor_map()
CPPUNIT_TEST(testQuad4)
CPPUNIT_TEST(testPrism18)
CPPUNIT_TEST(testTri6)
std::map< ElemType, std::map< dof_id_type, std::set< dof_id_type > > > Map
CPPUNIT_TEST(testOrientation)
CPPUNIT_TEST(testPyramid14)
CPPUNIT_TEST(testTet14)
CPPUNIT_TEST(testTri3)
CPPUNIT_TEST(testTet10)
CPPUNIT_TEST(testPyramid5)
CPPUNIT_TEST(testQuad8)
CPPUNIT_TEST(testHex20)
CPPUNIT_TEST(testPyramid13)
CPPUNIT_TEST(testPrism20)
CPPUNIT_TEST(testEdge2)
CPPUNIT_TEST(testPrism21)
CPPUNIT_TEST(testPrism6)
CPPUNIT_TEST(testEdge3)
void do_test(ElemType elem_type)
CPPUNIT_TEST(testEdge4)
LIBMESH_CPPUNIT_TEST_SUITE(NodalNeighborsTest)
The goal of this test is to ensure that MeshTools::find_nodal_neighbors() works as expected.
static const Map elem_type_to_neighbor_map
CPPUNIT_TEST(testTri7)
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual Node *& set_node(const unsigned int i)
Definition elem.h:2567
static std::unique_ptr< Elem > build_with_id(const ElemType type, dof_id_type id)
Calls the build() method above with a nullptr parent, and additionally sets the newly-created Elem's ...
Definition elem.C:556
unsigned int local_node(const dof_id_type i) const
Definition elem.h:2496
static const Order type_to_default_order_map[INVALID_ELEM]
This array maps the integer representation of the ElemType enum to the default approximation order of...
Definition elem.h:990
virtual const Node * node_ptr(const dof_id_type i) const =0
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition mesh_base.C:824
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
Communicator * TestCommWorld
MeshBase & mesh
void build_square(UnstructuredMesh &mesh, const unsigned int nx, const unsigned int ny, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 2D meshes.
void build_line(UnstructuredMesh &mesh, const unsigned int nx, const Real xmin=0., const Real xmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
A specialized build_cube() for 1D meshes.
void build_cube(UnstructuredMesh &mesh, const unsigned int nx=0, const unsigned int ny=0, const unsigned int nz=0, const Real xmin=0., const Real xmax=1., const Real ymin=0., const Real ymax=1., const Real zmin=0., const Real zmax=1., const ElemType type=INVALID_ELEM, const bool gauss_lobatto_grid=false)
Builds a (elements) cube.
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type > > &nodes_to_elem_map)
After calling this function the input vector nodes_to_elem_map will contain the node to element conne...
Definition mesh_tools.C:456
void find_nodal_neighbors(const MeshBase &mesh, const Node &n, const std::vector< std::vector< const Elem * > > &nodes_to_elem_map, std::vector< const Node * > &neighbors)
Given a mesh and a node in the mesh, the vector will be filled with every node directly attached to t...
const Elem & get(const ElemType type_in)
The libMesh namespace provides an interface to certain functionality in the library.
ElemType
Defines an enum for geometric element types.
CPPUNIT_TEST_SUITE_REGISTRATION(NodalNeighborsTest)