libMesh
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 
15 using namespace libMesh;
16 
17 class NodalNeighborsTest : public CppUnit::TestCase
18 {
36 public:
37  LIBMESH_CPPUNIT_TEST_SUITE( NodalNeighborsTest );
38 
39  CPPUNIT_TEST( testEdge2 );
40  CPPUNIT_TEST( testEdge3 );
41  CPPUNIT_TEST( testEdge4 );
42  CPPUNIT_TEST( testOrientation );
43 
44  CPPUNIT_TEST( testTri3 );
45  CPPUNIT_TEST( testTri6 );
46  CPPUNIT_TEST( testTri7 );
47 
48  CPPUNIT_TEST( testQuad4 );
49  CPPUNIT_TEST( testQuad8 );
50  CPPUNIT_TEST( testQuad9 );
51 
52  CPPUNIT_TEST( testTet4 );
53  CPPUNIT_TEST( testTet10 );
54  CPPUNIT_TEST( testTet14 );
55 
56  CPPUNIT_TEST( testPyramid5 );
57  CPPUNIT_TEST( testPyramid13 );
58  CPPUNIT_TEST( testPyramid14 );
59  CPPUNIT_TEST( testPyramid18 );
60 
61  CPPUNIT_TEST( testPrism6 );
62  CPPUNIT_TEST( testPrism18 );
63  CPPUNIT_TEST( testPrism20 );
64  CPPUNIT_TEST( testPrism21 );
65 
66  CPPUNIT_TEST( testHex8 );
67  CPPUNIT_TEST( testHex20 );
68  CPPUNIT_TEST( testHex27 );
69 
70  CPPUNIT_TEST_SUITE_END();
71 
72 private:
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 
236  inline static const Map elem_type_to_neighbor_map = build_elem_type_to_neighbor_map();
237 
238 protected:
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 
331 public:
332  void setUp() {}
333 
334  void tearDown() {}
335 
336  void testEdge2()
337  {
338  LOG_UNIT_TEST;
339  do_test(EDGE2);
340  }
341 
342 
343  void testEdge3()
344  {
345  LOG_UNIT_TEST;
346  do_test(EDGE3);
347  }
348 
349 
350  void testEdge4()
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 
374  void testQuad4()
375  {
376  LOG_UNIT_TEST;
377  do_test(QUAD4);
378  }
379 
380  void testQuad8()
381  {
382  LOG_UNIT_TEST;
383  do_test(QUAD8);
384  }
385 
386  void testQuad9()
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 
398  void testTet10()
399  {
400  LOG_UNIT_TEST;
401  do_test(TET10);
402  }
403 
404  void testTet14()
405  {
406  LOG_UNIT_TEST;
407  do_test(TET14);
408  }
409 
411  {
412  LOG_UNIT_TEST;
413  do_test(PYRAMID5);
414  }
415 
417  {
418  LOG_UNIT_TEST;
419  do_test(PYRAMID13);
420  }
421 
423  {
424  LOG_UNIT_TEST;
425  do_test(PYRAMID14);
426  }
427 
429  {
430  LOG_UNIT_TEST;
431  do_test(PYRAMID18);
432  }
433 
434  void testPrism6()
435  {
436  LOG_UNIT_TEST;
437  do_test(PRISM6);
438  }
439 
440  void testPrism18()
441  {
442  LOG_UNIT_TEST;
443  do_test(PRISM18);
444  }
445 
446  void testPrism20()
447  {
448  LOG_UNIT_TEST;
449  do_test(PRISM20);
450  }
451 
452  void testPrism21()
453  {
454  LOG_UNIT_TEST;
455  do_test(PRISM21);
456  }
457 
458  void testHex8()
459  {
460  LOG_UNIT_TEST;
461  do_test(HEX8);
462  }
463 
464  void testHex20()
465  {
466  LOG_UNIT_TEST;
467  do_test(HEX20);
468  }
469 
470  void testHex27()
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 
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:992
ElemType
Defines an enum for geometric element types.
The ReplicatedMesh class is derived from the MeshBase class, and is used to store identical copies of...
virtual Node *& set_node(const unsigned int i)
Definition: elem.h:2559
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:171
unsigned int dim
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly ecreated (or read) mesh for use.
Definition: mesh_base.C:759
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
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...
Definition: mesh_tools.C:1043
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_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
The libMesh namespace provides an interface to certain functionality in the library.
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.
void do_test(ElemType elem_type)
static Map build_elem_type_to_neighbor_map()
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&#39;s ...
Definition: elem.C:556
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.
CPPUNIT_TEST_SUITE_REGISTRATION(NodalNeighborsTest)
unsigned int local_node(const dof_id_type i) const
Definition: elem.h:2488
virtual const Node * node_ptr(const dof_id_type i) const =0
std::map< ElemType, std::map< dof_id_type, std::set< dof_id_type > >> Map
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
const Elem & get(const ElemType type_in)
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.