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  {
151  for (const auto & elem : mesh.element_ptr_range())
152  {
153  for (auto s : elem->side_index_range())
154  {
155  auto neigh = elem->neighbor_ptr(s);
156 
157  if (neigh)
158  {
159  CPPUNIT_ASSERT_EQUAL(bi.n_boundary_ids(elem, s), 0u);
160  continue;
161  }
162 
163  CPPUNIT_ASSERT_EQUAL(bi.n_boundary_ids(elem, s), 1u);
164 
165  auto side = elem->side_ptr(s);
166  auto normal = (side->point(1) - side->point(0)).cross
167  (side->point(2) - side->point(0));
168  // Outer faces, in these tests
169  if (normal * side->vertex_average() > 0)
170  CPPUNIT_ASSERT(bi.has_boundary_id(elem, s, 0));
171  // Inner faces, with one-hole tests
172  else
173  CPPUNIT_ASSERT(bi.has_boundary_id(elem, s, 1));
174  }
175  }
176  }
177 
178 
180  MeshTetInterface & triangulator)
181  {
182  std::unique_ptr<UnstructuredMesh> holemesh =
183  std::make_unique<Mesh>(*TestCommWorld);
184 
186  -2, 2, -2, 2, -2, 2);
187 
188  const Real hole_volume =
189  build_octahedron(*holemesh, false, -1, 1, -1, 1, -1, 1);
190 
191  auto holes =
192  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
193 
194  holes->push_back(std::move(holemesh));
195 
196  triangulator.attach_hole_list(std::move(holes));
197 
198  const Real expected_volume =
199  MeshTools::volume(mesh) - hole_volume;
200  this->testTetInterfaceBase(mesh, triangulator, 32, 14,
201  expected_volume);
202  }
203 
204 
205 #ifdef LIBMESH_ENABLE_AMR
207  MeshTetInterface & triangulator)
208  {
209  std::unique_ptr<UnstructuredMesh> holemesh =
210  std::make_unique<Mesh>(*TestCommWorld);
211 
212  MeshTools::Generation::build_sphere (*holemesh, 1, 2, TET4);
213 
215 
216  auto holes =
217  std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
218 
219  holes->push_back(std::move(holemesh));
220 
221  triangulator.attach_hole_list(std::move(holes));
222 
223  // Netgen can't seem to triangulate this without inserting points,
224  // so let MeshNetgenInterface know that we're allowed to insert
225  // points
226  triangulator.desired_volume() = 1000;
227 
228  this->testTetInterfaceBase(mesh, triangulator);
229  }
230 #endif
231 
232 
234  MeshTetInterface & triangulator,
235  bool flip_tris = false)
236  {
237  // An asymmetric octahedron, so we hopefully have an unambiguous
238  // choice of shortest diagonal for a Delaunay algorithm to pick.
239  const Real expected_volume =
240  build_octahedron(mesh, flip_tris, -1, 1, -1, 1, -0.1, 0.1);
241 
242  this->testTetInterfaceBase(mesh, triangulator, /* n_elem = */ 4,
243  /* n_nodes = */ 6, expected_volume);
244  }
245 
246 
248  MeshTetInterface & triangulator,
249  bool flip_tris = false)
250  {
251  const Real expected_volume =
252  build_octahedron(mesh, flip_tris, -1, 1, -1, 1, -0.1, 0.1);
253 
254  // Remove one tri, breaking the mesh
255  for (auto elem : mesh.element_ptr_range())
256  {
257  Point center = elem->vertex_average();
258  if (center(0) > 0 &&
259  center(1) > 0 &&
260  center(2) > 0)
261  mesh.delete_elem(elem);
262  }
264 
265  this->testExceptionBase("element with a null neighbor", mesh, triangulator,
266  /* n_elem = */ 4, /* n_nodes = */ 6,
267  expected_volume);
268  }
269 
270 
272  MeshTetInterface & triangulator)
273  {
274  // An asymmetric octahedron, so we hopefully have an unambiguous
275  // choice of shortest diagonal for a Delaunay algorithm to pick.
276  mesh.add_point(Point(0,0,-0.1), 0);
277  mesh.add_point(Point(1,0,0), 1);
278  mesh.add_point(Point(0,1,0), 2);
279  mesh.add_point(Point(-1,0,0), 3);
280  mesh.add_point(Point(0,-1,0), 4);
281  mesh.add_point(Point(0,0,0.1), 5);
282 
283  auto add_tet = [&mesh](std::array<dof_id_type,4> nodes)
284  {
285  auto elem = mesh.add_elem(Elem::build(TET4));
286  elem->set_node(0, mesh.node_ptr(nodes[0]));
287  elem->set_node(1, mesh.node_ptr(nodes[1]));
288  elem->set_node(2, mesh.node_ptr(nodes[2]));
289  elem->set_node(3, mesh.node_ptr(nodes[3]));
290  };
291 
292  // Split along a different diagonal to start
293  add_tet({1,3,4,5});
294  add_tet({1,3,5,2});
295  add_tet({1,3,2,0});
296  add_tet({1,3,0,4});
297 
299 
300  const Real expected_volume = MeshTools::volume(mesh);
301 
302  this->testTetInterfaceBase(mesh, triangulator, /* n_elem = */ 4,
303  /* n_nodes = */ 6, expected_volume);
304  }
305 
306 
307 #ifdef LIBMESH_HAVE_TETGEN
308  void testTetGen()
309  {
310  LOG_UNIT_TEST;
311 
313  TetGenMeshInterface tet_tet(mesh);
314  testTrisToTets(mesh, tet_tet);
315  }
316 
317 
319  {
320  LOG_UNIT_TEST;
321 
323  TetGenMeshInterface tet_tet(mesh);
324  testTrisToTetsError(mesh, tet_tet);
325  }
326 
327 
328 
329  /*
330  void testTetGenInterp()
331  {
332  LOG_UNIT_TEST;
333 
334  Mesh mesh(*TestCommWorld);
335  TetGenMeshInterface tet_tet(mesh);
336  testTrisToTetsInterp(mesh, tet_tet, 1, 6);
337  }
338 
339 
340  void testTetGenInterp2()
341  {
342  LOG_UNIT_TEST;
343 
344  Mesh mesh(*TestCommWorld);
345  TetGenMeshInterface tet_tet(mesh);
346  testTrisToTetsInterp(mesh, tet_tet, 2, 10);
347  }
348  */
349 
350 #endif // LIBMESH_HAVE_TETGEN
351 
352 
353 #ifdef LIBMESH_HAVE_NETGEN
354  void testNetGen()
355  {
356  LOG_UNIT_TEST;
357 
359  NetGenMeshInterface net_tet(mesh);
360  testTrisToTets(mesh, net_tet);
361  testBcids(mesh);
362  }
363 
364 
366  {
367  LOG_UNIT_TEST;
368 
370  NetGenMeshInterface net_tet(mesh);
371  testTrisToTetsError(mesh, net_tet);
372  }
373 
374 
376  {
377  LOG_UNIT_TEST;
378 
380  NetGenMeshInterface net_tet(mesh);
381  testTetsToTets(mesh, net_tet);
382  testBcids(mesh);
383  }
384 
385 
387  {
388  LOG_UNIT_TEST;
389 
391  NetGenMeshInterface net_tet(mesh);
392  testTrisToTets(mesh, net_tet, true);
393  testBcids(mesh);
394  }
395 
396 
398  {
399  LOG_UNIT_TEST;
400 
402  NetGenMeshInterface net_tet(mesh);
403  testHole(mesh, net_tet);
404  testBcids(mesh);
405  }
406 
407 
408 #ifdef LIBMESH_ENABLE_AMR
410  {
411  LOG_UNIT_TEST;
412 
414  NetGenMeshInterface net_tet(mesh);
415  testSphereShell(mesh, net_tet);
416  testBcids(mesh);
417  }
418 #endif
419 
420 
421  /*
422  void testNetGenInterp()
423  {
424  LOG_UNIT_TEST;
425 
426  Mesh mesh(*TestCommWorld);
427  NetGenMeshInterface net_tet(mesh);
428  testTrisToTetsInterp(mesh, net_tet, 1, 6);
429  }
430 
431 
432  void testNetGenInterp2()
433  {
434  LOG_UNIT_TEST;
435 
436  Mesh mesh(*TestCommWorld);
437  NetGenMeshInterface net_tet(mesh);
438  testTrisToTetsInterp(mesh, net_tet, 2, 10);
439  }
440 
441 
442  void testNetGenRefinementBase
443  (UnstructuredMesh & mesh,
444  const std::vector<MeshTetInterface::Hole*> * holes,
445  Real expected_total_area,
446  dof_id_type n_original_elem,
447  Real desired_area = 0.1,
448  FunctionBase<Real> * area_func = nullptr)
449  {
450  NetGenMeshInterface triangulator(mesh);
451 
452  if (holes)
453  triangulator.attach_hole_list(holes);
454 
455  // Try to insert points!
456  triangulator.desired_area() = desired_area;
457  triangulator.set_desired_area_function(area_func);
458 
459  triangulator.triangulate();
460 
461  // If refinement should have increased our element count, check it
462  if (desired_area || area_func)
463  CPPUNIT_ASSERT_GREATER(n_original_elem, mesh.n_elem()); // n_elem+++
464  else
465  CPPUNIT_ASSERT_EQUAL(mesh.n_elem(), n_original_elem);
466 
467  Real area = 0;
468  for (const auto & elem : mesh.active_local_element_ptr_range())
469  {
470  CPPUNIT_ASSERT_EQUAL(elem->level(), 0u);
471  CPPUNIT_ASSERT_EQUAL(elem->type(), TRI3);
472 
473  const Real my_area = elem->volume();
474 
475  // my_area <= desired_area, wow this macro ordering hurts
476  if (desired_area != 0)
477  CPPUNIT_ASSERT_LESSEQUAL(desired_area, my_area);
478 
479  if (area_func != nullptr)
480  for (auto v : make_range(elem->n_vertices()))
481  {
482  const Real local_desired_area =
483  (*area_func)(elem->point(v));
484  CPPUNIT_ASSERT_LESSEQUAL(local_desired_area, my_area);
485  }
486 
487  area += my_area;
488  }
489 
490  mesh.comm().sum(area);
491 
492  LIBMESH_ASSERT_FP_EQUAL(area, expected_total_area, TOLERANCE*TOLERANCE);
493  }
494 
495  void testNetGenRefined()
496  {
497  LOG_UNIT_TEST;
498 
499  Mesh mesh(*TestCommWorld);
500  testTriangulatorTrapMesh(mesh);
501  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 15);
502  }
503 
504  void testNetGenNonRefined()
505  {
506  LOG_UNIT_TEST;
507 
508  Mesh mesh(*TestCommWorld);
509  testTriangulatorTrapMesh(mesh);
510  // Make sure we see 0 as "don't refine", not "infinitely refine"
511  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 2, 0);
512  }
513 
514 
515  void testNetGenExtraRefined()
516  {
517  LOG_UNIT_TEST;
518 
519  Mesh mesh(*TestCommWorld);
520  testTriangulatorTrapMesh(mesh);
521  testPoly2TriRefinementBase(mesh, nullptr, 1.5, 150, 0.01);
522  }
523 */
524 
525 #endif // LIBMESH_HAVE_NETGEN
526 
527 };
528 
529 
Class TetGenMeshInterface provides an interface for tetrahedralization of meshes using the TetGen lib...
void testBcids(UnstructuredMesh &mesh)
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
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: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.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:165
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)
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()
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()
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
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