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);
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( testNetGenHole );
54 
55 #ifdef LIBMESH_ENABLE_AMR
56  CPPUNIT_TEST( testNetGenSphereShell );
57 #endif
58 
59  // We'll get to more advanced features later
60  /*
61  CPPUNIT_TEST( testNetGenInterp );
62  CPPUNIT_TEST( testNetGenInterp2 );
63  CPPUNIT_TEST( testNetGenRefined );
64  CPPUNIT_TEST( testNetGenNonRefined );
65  CPPUNIT_TEST( testNetGenExtraRefined );
66  */
67 #endif
68 
69  // Still need to work out the basics here - non-convex domains,
70  // precise control of refinement, etc.
71 #ifdef LIBMESH_HAVE_TETGEN
72  /*
73  CPPUNIT_TEST( testTetGen );
74  CPPUNIT_TEST( testTetGenError );
75  CPPUNIT_TEST( testTetGenInterp );
76  CPPUNIT_TEST( testTetGenInterp2 );
77  */
78 #endif
79 
80  CPPUNIT_TEST_SUITE_END();
81 
82 public:
83  void setUp() {}
84 
85  void tearDown() {}
86 
87  void testExceptionBase(const char * re,
88  MeshBase & mesh,
89  MeshTetInterface & tetinterface,
90  dof_id_type expected_n_elem = DofObject::invalid_id,
91  dof_id_type expected_n_nodes = DofObject::invalid_id,
92  Real expected_volume = 0)
93  {
94 #ifdef LIBMESH_ENABLE_EXCEPTIONS
95  // We can't just CPPUNIT_ASSERT_THROW, because we want to make
96  // sure we were thrown from the right place with the right error
97  // message!
98  bool threw_desired_exception = false;
99  try {
100  this->testTetInterfaceBase(mesh, tetinterface, expected_n_elem,
101  expected_n_nodes, expected_volume);
102  }
103  catch (libMesh::LogicError & e) {
104  std::regex msg_regex(re);
105  CPPUNIT_ASSERT(std::regex_search(e.what(), msg_regex));
106  threw_desired_exception = true;
107  }
108  catch (CppUnit::Exception & e) {
109  throw e;
110  }
111  catch (...) {
112  CPPUNIT_ASSERT_MESSAGE("Unexpected exception type thrown", false);
113  }
114  CPPUNIT_ASSERT(threw_desired_exception);
115 #endif
116  }
117 
118 
120  MeshTetInterface & triangulator,
121  dof_id_type expected_n_elem = DofObject::invalid_id,
122  dof_id_type expected_n_nodes = DofObject::invalid_id,
123  Real expected_volume = 0)
124  {
125  triangulator.triangulate();
126 
127  if (expected_n_elem != DofObject::invalid_id)
128  CPPUNIT_ASSERT_EQUAL(mesh.n_elem(), expected_n_elem);
129 
130  if (expected_n_nodes != DofObject::invalid_id)
131  CPPUNIT_ASSERT_EQUAL(mesh.n_nodes(), expected_n_nodes);
132 
133  if (expected_volume != 0)
134  LIBMESH_ASSERT_FP_EQUAL(MeshTools::volume(mesh),
135  expected_volume,
137 
138  for (const auto & elem : mesh.element_ptr_range())
139  {
140  CPPUNIT_ASSERT_EQUAL(elem->type(), TET4);
141 
142  // Make sure we're not getting any inverted elements
143  CPPUNIT_ASSERT(!elem->is_flipped());
144  }
145  }
146 
147 
149  MeshTetInterface & triangulator)
150  {
151  std::unique_ptr<UnstructuredMesh> holemesh =
152  std::make_unique<Mesh>(*TestCommWorld);
153 
155  -2, 2, -2, 2, -2, 2);
156 
157  const Real hole_volume =
158  build_octahedron(*holemesh, false, -1, 1, -1, 1, -1, 1);
159 
160  auto holes =
161  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
162 
163  holes->push_back(std::move(holemesh));
164 
165  triangulator.attach_hole_list(std::move(holes));
166 
167  const Real expected_volume =
168  MeshTools::volume(mesh) - hole_volume;
169  this->testTetInterfaceBase(mesh, triangulator, 32, 14,
170  expected_volume);
171  }
172 
173 
174 #ifdef LIBMESH_ENABLE_AMR
176  MeshTetInterface & triangulator)
177  {
178  std::unique_ptr<UnstructuredMesh> holemesh =
179  std::make_unique<Mesh>(*TestCommWorld);
180 
181  MeshTools::Generation::build_sphere (*holemesh, 1, 2, TET4);
182 
184 
185  auto holes =
186  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
187 
188  holes->push_back(std::move(holemesh));
189 
190  triangulator.attach_hole_list(std::move(holes));
191 
192  // Netgen can't seem to triangulate this without inserting points,
193  // so let MeshNetgenInterface know that we're allowed to insert
194  // points
195  triangulator.desired_volume() = 1000;
196 
197  this->testTetInterfaceBase(mesh, triangulator);
198  }
199 #endif
200 
201 
203  MeshTetInterface & triangulator,
204  bool flip_tris = false)
205  {
206  // An asymmetric octahedron, so we hopefully have an unambiguous
207  // choice of shortest diagonal for a Delaunay algorithm to pick.
208  const Real expected_volume =
209  build_octahedron(mesh, flip_tris, -1, 1, -1, 1, -0.1, 0.1);
210 
211  this->testTetInterfaceBase(mesh, triangulator, /* n_elem = */ 4,
212  /* n_nodes = */ 6, expected_volume);
213  }
214 
215 
217  MeshTetInterface & triangulator,
218  bool flip_tris = false)
219  {
220  const Real expected_volume =
221  build_octahedron(mesh, flip_tris, -1, 1, -1, 1, -0.1, 0.1);
222 
223  // Remove one tri, breaking the mesh
224  for (auto elem : mesh.element_ptr_range())
225  {
226  Point center = elem->vertex_average();
227  if (center(0) > 0 &&
228  center(1) > 0 &&
229  center(2) > 0)
230  mesh.delete_elem(elem);
231  }
233 
234  this->testExceptionBase("element with a null neighbor", mesh, triangulator,
235  /* n_elem = */ 4, /* n_nodes = */ 6,
236  expected_volume);
237  }
238 
239 
241  MeshTetInterface & triangulator)
242  {
243  // An asymmetric octahedron, so we hopefully have an unambiguous
244  // choice of shortest diagonal for a Delaunay algorithm to pick.
245  mesh.add_point(Point(0,0,-0.1), 0);
246  mesh.add_point(Point(1,0,0), 1);
247  mesh.add_point(Point(0,1,0), 2);
248  mesh.add_point(Point(-1,0,0), 3);
249  mesh.add_point(Point(0,-1,0), 4);
250  mesh.add_point(Point(0,0,0.1), 5);
251 
252  auto add_tet = [&mesh](std::array<dof_id_type,4> nodes)
253  {
254  auto elem = mesh.add_elem(Elem::build(TET4));
255  elem->set_node(0, mesh.node_ptr(nodes[0]));
256  elem->set_node(1, mesh.node_ptr(nodes[1]));
257  elem->set_node(2, mesh.node_ptr(nodes[2]));
258  elem->set_node(3, mesh.node_ptr(nodes[3]));
259  };
260 
261  // Split along a different diagonal to start
262  add_tet({1,3,4,5});
263  add_tet({1,3,5,2});
264  add_tet({1,3,2,0});
265  add_tet({1,3,0,4});
266 
268 
269  const Real expected_volume = MeshTools::volume(mesh);
270 
271  this->testTetInterfaceBase(mesh, triangulator, /* n_elem = */ 4,
272  /* n_nodes = */ 6, expected_volume);
273  }
274 
275 
276 #ifdef LIBMESH_HAVE_TETGEN
277  void testTetGen()
278  {
279  LOG_UNIT_TEST;
280 
282  TetGenMeshInterface tet_tet(mesh);
283  testTrisToTets(mesh, tet_tet);
284  }
285 
286 
288  {
289  LOG_UNIT_TEST;
290 
292  TetGenMeshInterface tet_tet(mesh);
293  testTrisToTetsError(mesh, tet_tet);
294  }
295 
296 
297 
298  /*
299  void testTetGenInterp()
300  {
301  LOG_UNIT_TEST;
302 
303  Mesh mesh(*TestCommWorld);
304  TetGenMeshInterface tet_tet(mesh);
305  testTrisToTetsInterp(mesh, tet_tet, 1, 6);
306  }
307 
308 
309  void testTetGenInterp2()
310  {
311  LOG_UNIT_TEST;
312 
313  Mesh mesh(*TestCommWorld);
314  TetGenMeshInterface tet_tet(mesh);
315  testTrisToTetsInterp(mesh, tet_tet, 2, 10);
316  }
317  */
318 
319 #endif // LIBMESH_HAVE_TETGEN
320 
321 
322 #ifdef LIBMESH_HAVE_NETGEN
323  void testNetGen()
324  {
325  LOG_UNIT_TEST;
326 
328  NetGenMeshInterface net_tet(mesh);
329  testTrisToTets(mesh, net_tet);
330  }
331 
332 
334  {
335  LOG_UNIT_TEST;
336 
338  NetGenMeshInterface net_tet(mesh);
339  testTrisToTetsError(mesh, net_tet);
340  }
341 
342 
344  {
345  LOG_UNIT_TEST;
346 
348  NetGenMeshInterface net_tet(mesh);
349  testTetsToTets(mesh, net_tet);
350  }
351 
352 
354  {
355  LOG_UNIT_TEST;
356 
358  NetGenMeshInterface net_tet(mesh);
359  testTrisToTets(mesh, net_tet, true);
360  }
361 
362 
364  {
365  LOG_UNIT_TEST;
366 
368  NetGenMeshInterface net_tet(mesh);
369  testHole(mesh, net_tet);
370  }
371 
372 
373 #ifdef LIBMESH_ENABLE_AMR
375  {
376  LOG_UNIT_TEST;
377 
379  NetGenMeshInterface net_tet(mesh);
380  testSphereShell(mesh, net_tet);
381  }
382 #endif
383 
384 
385  /*
386  void testNetGenInterp()
387  {
388  LOG_UNIT_TEST;
389 
390  Mesh mesh(*TestCommWorld);
391  NetGenMeshInterface net_tet(mesh);
392  testTrisToTetsInterp(mesh, net_tet, 1, 6);
393  }
394 
395 
396  void testNetGenInterp2()
397  {
398  LOG_UNIT_TEST;
399 
400  Mesh mesh(*TestCommWorld);
401  NetGenMeshInterface net_tet(mesh);
402  testTrisToTetsInterp(mesh, net_tet, 2, 10);
403  }
404 
405 
406  void testNetGenRefinementBase
407  (UnstructuredMesh & mesh,
408  const std::vector<MeshTetInterface::Hole*> * holes,
409  Real expected_total_area,
410  dof_id_type n_original_elem,
411  Real desired_area = 0.1,
412  FunctionBase<Real> * area_func = nullptr)
413  {
414  NetGenMeshInterface triangulator(mesh);
415 
416  if (holes)
417  triangulator.attach_hole_list(holes);
418 
419  // Try to insert points!
420  triangulator.desired_area() = desired_area;
421  triangulator.set_desired_area_function(area_func);
422 
423  triangulator.triangulate();
424 
425  // If refinement should have increased our element count, check it
426  if (desired_area || area_func)
427  CPPUNIT_ASSERT_GREATER(n_original_elem, mesh.n_elem()); // n_elem+++
428  else
429  CPPUNIT_ASSERT_EQUAL(mesh.n_elem(), n_original_elem);
430 
431  Real area = 0;
432  for (const auto & elem : mesh.active_local_element_ptr_range())
433  {
434  CPPUNIT_ASSERT_EQUAL(elem->level(), 0u);
435  CPPUNIT_ASSERT_EQUAL(elem->type(), TRI3);
436 
437  const Real my_area = elem->volume();
438 
439  // my_area <= desired_area, wow this macro ordering hurts
440  if (desired_area != 0)
441  CPPUNIT_ASSERT_LESSEQUAL(desired_area, my_area);
442 
443  if (area_func != nullptr)
444  for (auto v : make_range(elem->n_vertices()))
445  {
446  const Real local_desired_area =
447  (*area_func)(elem->point(v));
448  CPPUNIT_ASSERT_LESSEQUAL(local_desired_area, my_area);
449  }
450 
451  area += my_area;
452  }
453 
454  mesh.comm().sum(area);
455 
456  LIBMESH_ASSERT_FP_EQUAL(area, expected_total_area, TOLERANCE*TOLERANCE);
457  }
458 
459  void testNetGenRefined()
460  {
461  LOG_UNIT_TEST;
462 
463  Mesh mesh(*TestCommWorld);
464  testTriangulatorTrapMesh(mesh);
465  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 15);
466  }
467 
468  void testNetGenNonRefined()
469  {
470  LOG_UNIT_TEST;
471 
472  Mesh mesh(*TestCommWorld);
473  testTriangulatorTrapMesh(mesh);
474  // Make sure we see 0 as "don't refine", not "infinitely refine"
475  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 2, 0);
476  }
477 
478 
479  void testNetGenExtraRefined()
480  {
481  LOG_UNIT_TEST;
482 
483  Mesh mesh(*TestCommWorld);
484  testTriangulatorTrapMesh(mesh);
485  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 150, 0.01);
486  }
487 */
488 
489 #endif // LIBMESH_HAVE_NETGEN
490 
491 };
492 
493 
Class TetGenMeshInterface provides an interface for tetrahedralization of meshes using the TetGen lib...
void build_sphere(UnstructuredMesh &mesh, const Real rad=1, const unsigned int nr=2, const ElemType type=INVALID_ELEM, const unsigned int n_smooth=2, const bool flat=true)
Meshes a spherical or mapped-spherical domain.
virtual Node *& set_node(const unsigned int i)
Definition: elem.h:2558
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:171
static constexpr Real TOLERANCE
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
void testNetGen()
CPPUNIT_TEST_SUITE_REGISTRATION(MeshTetTest)
MeshBase & mesh
void testTetGen()
The libMesh namespace provides an interface to certain functionality in the library.
void tearDown()
Definition: mesh_tet_test.C:85
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:75
void testSphereShell(UnstructuredMesh &mesh, MeshTetInterface &triangulator)
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()
void setUp()
Definition: mesh_tet_test.C:83
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:444
void testNetGenHole()
static const dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:482
void testTrisToTets(UnstructuredMesh &mesh, MeshTetInterface &triangulator, bool flip_tris=false)
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:985
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.
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()...
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 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:87
Class MeshTetInterface provides an abstract interface for tetrahedralization of meshes by subclasses...
uint8_t dof_id_type
Definition: id_types.h:67