libMesh
mesh_tet_test.C
Go to the documentation of this file.
1 #include <libmesh/boundary_info.h>
2 #include <libmesh/elem.h>
3 #include <libmesh/mesh.h>
4 #include <libmesh/mesh_generation.h>
5 #include <libmesh/mesh_netgen_interface.h>
6 #include <libmesh/mesh_tetgen_interface.h>
7 #include <libmesh/mesh_tet_interface.h>
8 #include <libmesh/mesh_tools.h>
9 #include <libmesh/parallel_implementation.h> // max()
10 
11 #include "test_comm.h"
12 #include "libmesh_cppunit.h"
13 
14 #include <algorithm>
15 #include <cmath>
16 #include <regex>
17 
18 
19 using namespace libMesh;
20 
21 namespace {
22 
23 Real build_octahedron (UnstructuredMesh & mesh, bool flip_tris,
24  Real xmin, Real xmax,
25  Real ymin, Real ymax,
26  Real zmin, Real zmax)
27 {
29  (mesh, xmin, xmax, ymin, ymax, zmin, zmax, flip_tris);
30 
31  // Octahedron volume
32  return (xmax-xmin)*(ymax-ymin)*(zmax-zmin)/6;
33 }
34 
35 }
36 
37 
38 class MeshTetTest : public CppUnit::TestCase
39 {
44 public:
45  LIBMESH_CPPUNIT_TEST_SUITE( MeshTetTest );
46 
47 #ifdef LIBMESH_HAVE_NETGEN
48  // The most basic test to start with
49  CPPUNIT_TEST( testNetGen );
50  CPPUNIT_TEST( testNetGenError );
51  CPPUNIT_TEST( testNetGenTets );
52  CPPUNIT_TEST( testNetGenFlippedTris );
53  CPPUNIT_TEST( testNetGenNonOriented );
54  CPPUNIT_TEST( testNetGenHole );
55 
56 #ifdef LIBMESH_ENABLE_AMR
57  CPPUNIT_TEST( testNetGenSphereShell );
58 #endif
59 
60  // We'll get to more advanced features later
61  /*
62  CPPUNIT_TEST( testNetGenInterp );
63  CPPUNIT_TEST( testNetGenInterp2 );
64  CPPUNIT_TEST( testNetGenRefined );
65  CPPUNIT_TEST( testNetGenNonRefined );
66  CPPUNIT_TEST( testNetGenExtraRefined );
67  */
68 #endif
69 
70  // Still need to work out the basics here - non-convex domains,
71  // precise control of refinement, etc.
72 #ifdef LIBMESH_HAVE_TETGEN
73  /*
74  CPPUNIT_TEST( testTetGen );
75  CPPUNIT_TEST( testTetGenError );
76  CPPUNIT_TEST( testTetGenInterp );
77  CPPUNIT_TEST( testTetGenInterp2 );
78  */
79 #endif
80 
81  CPPUNIT_TEST_SUITE_END();
82 
83 public:
84  void setUp() {}
85 
86  void tearDown() {}
87 
88  void testExceptionBase(const char * re,
89  MeshBase & mesh,
90  MeshTetInterface & tetinterface,
91  dof_id_type expected_n_elem = DofObject::invalid_id,
92  dof_id_type expected_n_nodes = DofObject::invalid_id,
93  Real expected_volume = 0)
94  {
95 #ifdef LIBMESH_ENABLE_EXCEPTIONS
96  // We can't just CPPUNIT_ASSERT_THROW, because we want to make
97  // sure we were thrown from the right place with the right error
98  // message!
99  bool threw_desired_exception = false;
100  try {
101  this->testTetInterfaceBase(mesh, tetinterface, expected_n_elem,
102  expected_n_nodes, expected_volume);
103  }
104  catch (libMesh::LogicError & e) {
105  std::regex msg_regex(re);
106  CPPUNIT_ASSERT(std::regex_search(e.what(), msg_regex));
107  threw_desired_exception = true;
108  }
109  catch (CppUnit::Exception & e) {
110  throw e;
111  }
112  catch (...) {
113  CPPUNIT_ASSERT_MESSAGE("Unexpected exception type thrown", false);
114  }
115  CPPUNIT_ASSERT(threw_desired_exception);
116 #endif
117  }
118 
119 
121  MeshTetInterface & triangulator,
122  dof_id_type expected_n_elem = DofObject::invalid_id,
123  dof_id_type expected_n_nodes = DofObject::invalid_id,
124  Real expected_volume = 0)
125  {
126  triangulator.triangulate();
127 
128  if (expected_n_elem != DofObject::invalid_id)
129  CPPUNIT_ASSERT_EQUAL(mesh.n_elem(), expected_n_elem);
130 
131  if (expected_n_nodes != DofObject::invalid_id)
132  CPPUNIT_ASSERT_EQUAL(mesh.n_nodes(), expected_n_nodes);
133 
134  if (expected_volume != 0)
135  LIBMESH_ASSERT_FP_EQUAL(MeshTools::volume(mesh),
136  expected_volume,
138 
139  for (const auto & elem : mesh.element_ptr_range())
140  {
141  CPPUNIT_ASSERT_EQUAL(elem->type(), TET4);
142 
143  // Make sure we're not getting any inverted elements
144  CPPUNIT_ASSERT(!elem->is_flipped());
145  }
146  }
147 
148 
150  {
152  for (const auto & elem : mesh.element_ptr_range())
153  {
154  for (auto s : elem->side_index_range())
155  {
156  auto neigh = elem->neighbor_ptr(s);
157 
158  if (neigh)
159  {
160  CPPUNIT_ASSERT_EQUAL(bi.n_boundary_ids(elem, s), 0u);
161  continue;
162  }
163 
164  CPPUNIT_ASSERT_EQUAL(bi.n_boundary_ids(elem, s), 1u);
165 
166  auto side = elem->side_ptr(s);
167  auto normal = (side->point(1) - side->point(0)).cross
168  (side->point(2) - side->point(0));
169  // Outer faces, in these tests
170  if (normal * side->vertex_average() > 0)
171  CPPUNIT_ASSERT(bi.has_boundary_id(elem, s, 0));
172  // Inner faces, with one-hole tests
173  else
174  CPPUNIT_ASSERT(bi.has_boundary_id(elem, s, 1));
175  }
176  }
177  }
178 
179 
181  MeshTetInterface & triangulator)
182  {
183  std::unique_ptr<UnstructuredMesh> holemesh =
184  std::make_unique<Mesh>(*TestCommWorld);
185 
187  -2, 2, -2, 2, -2, 2);
188 
189  const Real hole_volume =
190  build_octahedron(*holemesh, false, -1, 1, -1, 1, -1, 1);
191 
192  auto holes =
193  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
194 
195  holes->push_back(std::move(holemesh));
196 
197  triangulator.attach_hole_list(std::move(holes));
198 
199  const Real expected_volume =
200  MeshTools::volume(mesh) - hole_volume;
201  this->testTetInterfaceBase(mesh, triangulator, 32, 14,
202  expected_volume);
203  }
204 
205 
206 #ifdef LIBMESH_ENABLE_AMR
208  MeshTetInterface & triangulator)
209  {
210  std::unique_ptr<UnstructuredMesh> holemesh =
211  std::make_unique<Mesh>(*TestCommWorld);
212 
213  MeshTools::Generation::build_sphere (*holemesh, 1, 2, TET4);
214 
216 
217  auto holes =
218  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
219 
220  holes->push_back(std::move(holemesh));
221 
222  triangulator.attach_hole_list(std::move(holes));
223 
224  // Netgen can't seem to triangulate this without inserting points,
225  // so let MeshNetgenInterface know that we're allowed to insert
226  // points
227  triangulator.desired_volume() = 1000;
228 
229  this->testTetInterfaceBase(mesh, triangulator);
230  }
231 #endif
232 
233 
235  MeshTetInterface & triangulator,
236  bool flip_tris = false,
237  bool flip_some_tris = false)
238  {
239  // An asymmetric octahedron, so we hopefully have an unambiguous
240  // choice of shortest diagonal for a Delaunay algorithm to pick.
241  const Real expected_volume =
242  build_octahedron(mesh, flip_tris, -1, 1, -1, 1, -0.1, 0.1);
243 
244  // Flip a couple tri, breaking the mesh in a way we can fix
245  if (flip_some_tris)
246  for (auto elem : mesh.element_ptr_range())
247  {
248  Point center = elem->vertex_average();
249  if ((center(0) > 0 &&
250  center(1) > 0 &&
251  center(2) > 0) ||
252  (center(0) < 0 &&
253  center(1) < 0 &&
254  center(2) < 0))
255  elem->flip(&mesh.get_boundary_info());
256 
258  }
259 
260  this->testTetInterfaceBase(mesh, triangulator, /* n_elem = */ 4,
261  /* n_nodes = */ 6, expected_volume);
262  }
263 
264 
266  MeshTetInterface & triangulator,
267  bool flip_tris = false)
268  {
269  const Real expected_volume =
270  build_octahedron(mesh, flip_tris, -1, 1, -1, 1, -0.1, 0.1);
271 
272  // Remove one tri, breaking the mesh
273  for (auto elem : mesh.element_ptr_range())
274  {
275  Point center = elem->vertex_average();
276  if (center(0) > 0 &&
277  center(1) > 0 &&
278  center(2) > 0)
279  mesh.delete_elem(elem);
280  }
282 
283  this->testExceptionBase("element with a null neighbor", mesh, triangulator,
284  /* n_elem = */ 4, /* n_nodes = */ 6,
285  expected_volume);
286  }
287 
288 
290  MeshTetInterface & triangulator)
291  {
292  // An asymmetric octahedron, so we hopefully have an unambiguous
293  // choice of shortest diagonal for a Delaunay algorithm to pick.
294  mesh.add_point(Point(0,0,-0.1), 0);
295  mesh.add_point(Point(1,0,0), 1);
296  mesh.add_point(Point(0,1,0), 2);
297  mesh.add_point(Point(-1,0,0), 3);
298  mesh.add_point(Point(0,-1,0), 4);
299  mesh.add_point(Point(0,0,0.1), 5);
300 
301  auto add_tet = [&mesh](std::array<dof_id_type,4> nodes)
302  {
303  auto elem = mesh.add_elem(Elem::build(TET4));
304  elem->set_node(0, mesh.node_ptr(nodes[0]));
305  elem->set_node(1, mesh.node_ptr(nodes[1]));
306  elem->set_node(2, mesh.node_ptr(nodes[2]));
307  elem->set_node(3, mesh.node_ptr(nodes[3]));
308  };
309 
310  // Split along a different diagonal to start
311  add_tet({1,3,4,5});
312  add_tet({1,3,5,2});
313  add_tet({1,3,2,0});
314  add_tet({1,3,0,4});
315 
317 
318  const Real expected_volume = MeshTools::volume(mesh);
319 
320  this->testTetInterfaceBase(mesh, triangulator, /* n_elem = */ 4,
321  /* n_nodes = */ 6, expected_volume);
322  }
323 
324 
325 #ifdef LIBMESH_HAVE_TETGEN
326  void testTetGen()
327  {
328  LOG_UNIT_TEST;
329 
331  TetGenMeshInterface tet_tet(mesh);
332  testTrisToTets(mesh, tet_tet);
333  }
334 
335 
337  {
338  LOG_UNIT_TEST;
339 
341  TetGenMeshInterface tet_tet(mesh);
342  testTrisToTetsError(mesh, tet_tet);
343  }
344 
345 
346 
347  /*
348  void testTetGenInterp()
349  {
350  LOG_UNIT_TEST;
351 
352  Mesh mesh(*TestCommWorld);
353  TetGenMeshInterface tet_tet(mesh);
354  testTrisToTetsInterp(mesh, tet_tet, 1, 6);
355  }
356 
357 
358  void testTetGenInterp2()
359  {
360  LOG_UNIT_TEST;
361 
362  Mesh mesh(*TestCommWorld);
363  TetGenMeshInterface tet_tet(mesh);
364  testTrisToTetsInterp(mesh, tet_tet, 2, 10);
365  }
366  */
367 
368 #endif // LIBMESH_HAVE_TETGEN
369 
370 
371 #ifdef LIBMESH_HAVE_NETGEN
372  void testNetGen()
373  {
374  LOG_UNIT_TEST;
375 
377  NetGenMeshInterface net_tet(mesh);
378 
379  // This should give no output for our correctly-set-up inputs
380  net_tet.set_verbosity(50);
381 
382  testTrisToTets(mesh, net_tet);
383  testBcids(mesh);
384  }
385 
386 
388  {
389  LOG_UNIT_TEST;
390 
392  NetGenMeshInterface net_tet(mesh);
393  testTrisToTetsError(mesh, net_tet);
394  }
395 
396 
398  {
399  LOG_UNIT_TEST;
400 
402  NetGenMeshInterface net_tet(mesh);
403  testTetsToTets(mesh, net_tet);
404  testBcids(mesh);
405  }
406 
407 
409  {
410  LOG_UNIT_TEST;
411 
413  NetGenMeshInterface net_tet(mesh);
414  testTrisToTets(mesh, net_tet, true);
415  testBcids(mesh);
416  }
417 
418 
420  {
421  LOG_UNIT_TEST;
422 
424  NetGenMeshInterface net_tet(mesh);
425  testTrisToTets(mesh, net_tet, true, true);
426  testBcids(mesh);
427  }
428 
429 
431  {
432  LOG_UNIT_TEST;
433 
435  NetGenMeshInterface net_tet(mesh);
436  testHole(mesh, net_tet);
437  testBcids(mesh);
438  }
439 
440 
441 #ifdef LIBMESH_ENABLE_AMR
443  {
444  LOG_UNIT_TEST;
445 
447  NetGenMeshInterface net_tet(mesh);
448  testSphereShell(mesh, net_tet);
449  testBcids(mesh);
450  }
451 #endif
452 
453 
454  /*
455  void testNetGenInterp()
456  {
457  LOG_UNIT_TEST;
458 
459  Mesh mesh(*TestCommWorld);
460  NetGenMeshInterface net_tet(mesh);
461  testTrisToTetsInterp(mesh, net_tet, 1, 6);
462  }
463 
464 
465  void testNetGenInterp2()
466  {
467  LOG_UNIT_TEST;
468 
469  Mesh mesh(*TestCommWorld);
470  NetGenMeshInterface net_tet(mesh);
471  testTrisToTetsInterp(mesh, net_tet, 2, 10);
472  }
473 
474 
475  void testNetGenRefinementBase
476  (UnstructuredMesh & mesh,
477  const std::vector<MeshTetInterface::Hole*> * holes,
478  Real expected_total_area,
479  dof_id_type n_original_elem,
480  Real desired_area = 0.1,
481  FunctionBase<Real> * area_func = nullptr)
482  {
483  NetGenMeshInterface triangulator(mesh);
484 
485  if (holes)
486  triangulator.attach_hole_list(holes);
487 
488  // Try to insert points!
489  triangulator.desired_area() = desired_area;
490  triangulator.set_desired_area_function(area_func);
491 
492  triangulator.triangulate();
493 
494  // If refinement should have increased our element count, check it
495  if (desired_area || area_func)
496  CPPUNIT_ASSERT_GREATER(n_original_elem, mesh.n_elem()); // n_elem+++
497  else
498  CPPUNIT_ASSERT_EQUAL(mesh.n_elem(), n_original_elem);
499 
500  Real area = 0;
501  for (const auto & elem : mesh.active_local_element_ptr_range())
502  {
503  CPPUNIT_ASSERT_EQUAL(elem->level(), 0u);
504  CPPUNIT_ASSERT_EQUAL(elem->type(), TRI3);
505 
506  const Real my_area = elem->volume();
507 
508  // my_area <= desired_area, wow this macro ordering hurts
509  if (desired_area != 0)
510  CPPUNIT_ASSERT_LESSEQUAL(desired_area, my_area);
511 
512  if (area_func != nullptr)
513  for (auto v : make_range(elem->n_vertices()))
514  {
515  const Real local_desired_area =
516  (*area_func)(elem->point(v));
517  CPPUNIT_ASSERT_LESSEQUAL(local_desired_area, my_area);
518  }
519 
520  area += my_area;
521  }
522 
523  mesh.comm().sum(area);
524 
525  LIBMESH_ASSERT_FP_EQUAL(area, expected_total_area, TOLERANCE*TOLERANCE);
526  }
527 
528  void testNetGenRefined()
529  {
530  LOG_UNIT_TEST;
531 
532  Mesh mesh(*TestCommWorld);
533  testTriangulatorTrapMesh(mesh);
534  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 15);
535  }
536 
537  void testNetGenNonRefined()
538  {
539  LOG_UNIT_TEST;
540 
541  Mesh mesh(*TestCommWorld);
542  testTriangulatorTrapMesh(mesh);
543  // Make sure we see 0 as "don't refine", not "infinitely refine"
544  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 2, 0);
545  }
546 
547 
548  void testNetGenExtraRefined()
549  {
550  LOG_UNIT_TEST;
551 
552  Mesh mesh(*TestCommWorld);
553  testTriangulatorTrapMesh(mesh);
554  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 150, 0.01);
555  }
556 */
557 
558 #endif // LIBMESH_HAVE_NETGEN
559 
560 };
561 
562 
Class TetGenMeshInterface provides an interface for tetrahedralization of meshes using the TetGen lib...
void testBcids(UnstructuredMesh &mesh)
virtual Node *& set_node(const unsigned int i)
Definition: elem.h:2564
bool has_boundary_id(const Node *const node, const boundary_id_type id) const
Real & desired_volume()
Sets and/or gets the desired tetrahedron volume.
Class NetGenMeshInterface provides an interface for tetrahedralization of meshes using the NetGen lib...
void testTrisToTetsError(UnstructuredMesh &mesh, MeshTetInterface &triangulator, bool flip_tris=false)
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:218
static constexpr Real TOLERANCE
void set_verbosity(unsigned int v)
Sets a verbosity level, defaulting to 0 (print nothing), to be set as high as 100 (print everything)...
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
void testNetGen()
CPPUNIT_TEST_SUITE_REGISTRATION(MeshTetTest)
MeshBase & mesh
void testTetGen()
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
void tearDown()
Definition: mesh_tet_test.C:86
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.
This is the MeshBase class.
Definition: mesh_base.h:80
void testSphereShell(UnstructuredMesh &mesh, MeshTetInterface &triangulator)
std::size_t n_boundary_ids() const
void testTetInterfaceBase(MeshBase &mesh, MeshTetInterface &triangulator, dof_id_type expected_n_elem=DofObject::invalid_id, dof_id_type expected_n_nodes=DofObject::invalid_id, Real expected_volume=0)
void testTetsToTets(MeshBase &mesh, MeshTetInterface &triangulator)
void testNetGenSphereShell()
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
void testNetGenTets()
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:473
void setUp()
Definition: mesh_tet_test.C:84
The UnstructuredMesh class is derived from the MeshBase class.
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition: elem.C:442
void testNetGenHole()
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
void testNetGenFlippedTris()
Real volume(const MeshBase &mesh, unsigned int dim=libMesh::invalid_uint)
Find the total volume of a mesh (interpreting that as area for dim = 2, or total arc length for dim =...
Definition: mesh_tools.C:1020
void testNetGenError()
void surface_octahedron(UnstructuredMesh &mesh, Real xmin, Real xmax, Real ymin, Real ymax, Real zmin, Real zmax, bool flip_tris=false)
Meshes the surface of an octahedron with 8 Tri3 elements, with counter-clockwise (libMesh default) no...
void testTetGenError()
void attach_hole_list(std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh >>> holes)
Attaches a vector of Mesh pointers defining holes which will be meshed around.
void build_sphere(UnstructuredMesh &mesh, const Real radius=1, const unsigned int n_refinements=2, const ElemType type=INVALID_ELEM, const unsigned int n_smooth=2, const bool flat=true)
Meshes a spherical or mapped-spherical domain.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
A class to represent the internal "this should never happen" errors, to be thrown by "libmesh_error()...
void testNetGenNonOriented()
virtual void triangulate()=0
This is the main public interface for this function.
void testHole(UnstructuredMesh &mesh, MeshTetInterface &triangulator)
virtual dof_id_type n_elem() const =0
virtual const Node * node_ptr(const dof_id_type i) const =0
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition: mesh.h:50
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
void unset_is_prepared()
Tells this we have done some operation where we should no longer consider ourself prepared...
Definition: mesh_base.C:1063
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.
virtual dof_id_type n_nodes() const =0
void testExceptionBase(const char *re, MeshBase &mesh, MeshTetInterface &tetinterface, dof_id_type expected_n_elem=DofObject::invalid_id, dof_id_type expected_n_nodes=DofObject::invalid_id, Real expected_volume=0)
Definition: mesh_tet_test.C:88
Class MeshTetInterface provides an abstract interface for tetrahedralization of meshes by subclasses...
uint8_t dof_id_type
Definition: id_types.h:67
void testTrisToTets(UnstructuredMesh &mesh, MeshTetInterface &triangulator, bool flip_tris=false, bool flip_some_tris=false)