libMesh
Loading...
Searching...
No Matches
Public Member Functions | Protected Types | Protected Member Functions | Static Protected Member Functions | Protected Attributes | List of all members
libMesh::NetGenMeshInterface Class Reference

Class NetGenMeshInterface provides an interface for tetrahedralization of meshes using the NetGen library. More...

#include <mesh_netgen_interface.h>

Inheritance diagram for libMesh::NetGenMeshInterface:
[legend]

Public Member Functions

 NetGenMeshInterface (UnstructuredMesh &mesh)
 Constructor.
 
virtual ~NetGenMeshInterface () override=default
 Empty destructor.
 
virtual void triangulate () override
 Method invokes NetGen library to compute a tetrahedralization.
 
Realdesired_volume ()
 Sets and/or gets the desired tetrahedron volume.
 
bool & smooth_after_generating ()
 Sets/gets flag which tells whether to do two steps of Laplace mesh smoothing after generating the grid.
 
ElemTypeelem_type ()
 Sets and/or gets the desired element type.
 
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 set_verbosity (unsigned int v)
 Sets a verbosity level, defaulting to 0 (print nothing), to be set as high as 100 (print everything).
 

Protected Types

enum  SurfaceIntegrity {
  NON_TRI3 = 1 , MISSING_NEIGHBOR = 2 , EMPTY_MESH = 3 , MISSING_BACKLINK = 4 ,
  BAD_NEIGHBOR_NODES = 5 , NON_ORIENTED = 6 , BAD_NEIGHBOR_LINKS = 7 , DEGENERATE_ELEMENT = 8 ,
  DEGENERATE_MESH = 9
}
 Enumeration of possible surface mesh integrity issues. More...
 

Protected Member Functions

std::set< SurfaceIntegritycheck_hull_integrity () const
 This function checks the integrity of the current set of elements in the Mesh to see if they comprise a topological manifold that (if it's also geometrically valid) would define valid boundary for a tetrahedralized volume.
 
std::set< SurfaceIntegrityimprove_hull_integrity ()
 This function checks the integrity of the current set of elements in the Mesh, and corrects what it can.
 
void process_hull_integrity_result (const std::set< SurfaceIntegrity > &result) const
 This function prints an informative message and throws an exception based on the output of the check_hull_integrity() function.
 
void delete_2D_hull_elements ()
 Delete original convex hull elements from the Mesh after performing a Delaunay tetrahedralization.
 
void increase_tet_order ()
 Converts all linear tet elements to the type requested by _elem_type, if that type differs from TET4.
 

Static Protected Member Functions

static BoundingBox volume_to_surface_mesh (UnstructuredMesh &mesh)
 Remove volume elements from the given mesh, after converting their outer boundary faces to surface elements.
 

Protected Attributes

MeshSerializer _serializer
 Tetgen only operates on serial meshes.
 
unsigned int _verbosity
 verbosity setting
 
Real _desired_volume
 The desired volume for the elements in the resulting mesh.
 
bool _smooth_after_generating
 Flag which tells whether we should smooth the mesh after it is generated.
 
ElemType _elem_type
 The exact type of tetrahedra we intend to construct.
 
UnstructuredMesh_mesh
 Local reference to the mesh we are working with.
 
std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > _holes
 A pointer to a vector of meshes each defining a hole.
 

Detailed Description

Class NetGenMeshInterface provides an interface for tetrahedralization of meshes using the NetGen library.

For information about TetGen cf. NetGen github repository.

Author
Roy H. Stogner
Date
2024

Definition at line 52 of file mesh_netgen_interface.h.

Member Enumeration Documentation

◆ SurfaceIntegrity

Enumeration of possible surface mesh integrity issues.

Enumerator
NON_TRI3 
MISSING_NEIGHBOR 
EMPTY_MESH 
MISSING_BACKLINK 
BAD_NEIGHBOR_NODES 
NON_ORIENTED 
BAD_NEIGHBOR_LINKS 
DEGENERATE_ELEMENT 
DEGENERATE_MESH 

Definition at line 124 of file mesh_tet_interface.h.

124 {
125 NON_TRI3 = 1, // a non-TRI3 element is found
126 MISSING_NEIGHBOR = 2, // an element with a nullptr-neighbor is found
127 EMPTY_MESH = 3, // the mesh is empty
128 MISSING_BACKLINK = 4, // an element neighbor isn't linked back to it
129 BAD_NEIGHBOR_NODES = 5, // an element neighbor isn't linked to expected nodes
130 NON_ORIENTED = 6, // an element neighbor has inconsistent orientation
131 BAD_NEIGHBOR_LINKS = 7, // an element neighbor has other inconsistent links
132 DEGENERATE_ELEMENT = 8, // an element has zero area
133 DEGENERATE_MESH = 9 // the mesh clearly bounds zero volume
134 };

Constructor & Destructor Documentation

◆ NetGenMeshInterface()

libMesh::NetGenMeshInterface::NetGenMeshInterface ( UnstructuredMesh mesh)
explicit

Constructor.

Takes a reference to the mesh containing the triangulated surface which is to be tetrahedralized.

Definition at line 78 of file mesh_netgen_interface.C.

78 :
81{
82}
MeshTetInterface(UnstructuredMesh &mesh)
Constructor.
MeshSerializer _serializer
Tetgen only operates on serial meshes.
MeshBase & mesh

◆ ~NetGenMeshInterface()

virtual libMesh::NetGenMeshInterface::~NetGenMeshInterface ( )
overridevirtualdefault

Empty destructor.

Member Function Documentation

◆ attach_hole_list()

void libMesh::MeshTetInterface::attach_hole_list ( std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > >  holes)
inherited

Attaches a vector of Mesh pointers defining holes which will be meshed around.

We use unique_ptr here because we expect that we may need to modify these meshes internally.

Definition at line 127 of file mesh_tet_interface.C.

129{
130 _holes = std::move(holes);
131}
std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > _holes
A pointer to a vector of meshes each defining a hole.

References libMesh::MeshTetInterface::_holes.

Referenced by MeshTetTest::testHole(), and MeshTetTest::testSphereShell().

◆ check_hull_integrity()

std::set< MeshTetInterface::SurfaceIntegrity > libMesh::MeshTetInterface::check_hull_integrity ( ) const
protectedinherited

This function checks the integrity of the current set of elements in the Mesh to see if they comprise a topological manifold that (if it's also geometrically valid) would define valid boundary for a tetrahedralized volume.

Named check_hull_integrity() for backward compatibility, but now accepts non-convex manifolds.

Returns
a set of enums describing problems found, or an empty set if no problems are found.

Definition at line 341 of file mesh_tet_interface.C.

342{
343 // Check for easy return: if the Mesh is empty (i.e. if
344 // somebody called triangulate_conformingDelaunayMesh on
345 // a Mesh with no elements, then hull integrity check must
346 // fail...
347 if (_mesh.n_elem() == 0)
348 return {EMPTY_MESH};
349
350 std::set<MeshTetInterface::SurfaceIntegrity> returnval;
351
353 const Point extents = bb.max() - bb.min();
354 if (extents(0) == 0 ||
355 extents(1) == 0 ||
356 extents(2) == 0)
357 returnval.insert(DEGENERATE_MESH);
358
359 // Figure a area to use for relative tolerances when detecting
360 // degenerate elements
361 const Real ref_area = std::abs(extents(0) * extents(1)) +
362 std::abs(extents(0) * extents(2)) +
363 std::abs(extents(1) * extents(2));
364
365 struct TriChecker {
366 std::set<MeshTetInterface::SurfaceIntegrity> my_returnval;
367 const Real my_ref_area;
368 const unsigned int my_verbosity;
369
370 TriChecker (Real ref_area, unsigned int verbosity) :
371 my_returnval(), my_ref_area(ref_area),
372 my_verbosity(verbosity) {}
373 TriChecker (TriChecker & other, Threads::split) :
374 my_returnval(), my_ref_area(other.my_ref_area),
375 my_verbosity(other.my_verbosity) {}
376
377 void operator()(const ConstElemRange & range) {
378
379 for (const Elem * elem : range)
380 {
381 // Check for proper element type
382 if (elem->type() != TRI3)
383 {
384 if (my_verbosity >= 50)
385 std::cerr << "Non-Tri3: " << elem->get_info() << std::endl;
386 my_returnval.insert(NON_TRI3);
387 }
388
389 // Make sure it's a decent element.
390 if (elem->volume() < my_ref_area * TOLERANCE * TOLERANCE)
391 {
392 if (my_verbosity >= 50)
393 std::cerr << "Degenerate element: " << elem->get_info() << std::endl;
394 my_returnval.insert(DEGENERATE_ELEMENT);
395 }
396
397 for (auto s : elem->side_index_range())
398 {
399 const Elem * const neigh = elem->neighbor_ptr(s);
400
401 if (neigh == nullptr)
402 {
403 if (my_verbosity >= 50)
404 std::cerr << "Element missing neighbor " << s << ": " << elem->get_info() << std::endl;
405 my_returnval.insert(MISSING_NEIGHBOR);
406 continue;
407 }
408
409 // Make sure our neighbor points back to us
410 const unsigned int nn = neigh->which_neighbor_am_i(elem);
411
412 if (nn >= 3)
413 {
414 if (my_verbosity >= 50)
415 std::cerr << "Element missing backlink " << s << ": " << elem->get_info() << std::endl;
416 my_returnval.insert(MISSING_BACKLINK);
417 continue;
418 }
419
420 // Our neighbor should have the same the edge nodes we do on
421 // the neighboring edgei
422 const Node * const n1 = elem->node_ptr(s);
423 const Node * const n2 = elem->node_ptr((s+1)%3);
424
425 const unsigned int i1 = neigh->local_node(n1->id());
426 const unsigned int i2 = neigh->local_node(n2->id());
427 if (i1 >= 3 || i2 >= 3)
428 {
429 if (my_verbosity >= 50)
430 std::cerr << "Element with bad neighbor " << s << " nodes: " << elem->get_info() << std::endl;
431 my_returnval.insert(BAD_NEIGHBOR_NODES);
432 continue;
433 }
434
435 // It should have those edge nodes in the opposite order
436 // (because they have the same orientation we do)
437 if ((i2 + 1)%3 != i1)
438 {
439 if (my_verbosity >= 50)
440 std::cerr << "Element orientation mismatch with neighbor " << s << ": " << elem->get_info() << std::endl;
441 my_returnval.insert(NON_ORIENTED);
442 continue;
443 }
444
445 // And it should have those edge nodes in the expected
446 // places relative to its neighbor link
447 if (i2 != nn)
448 {
449 if (my_verbosity >= 50)
450 std::cerr << "Element with bad links on neighbor " << s << ": " << elem->get_info() << std::endl;
451 my_returnval.insert(BAD_NEIGHBOR_LINKS);
452 continue;
453 }
454 }
455 }
456 }
457
458 void join(TriChecker & other) {
459 my_returnval.merge(other.my_returnval);
460 }
461 };
462
463 TriChecker checker (ref_area, this->_verbosity);
464
466 (this->_mesh.active_local_element_stored_range(), checker);
467
468 // Join problems found in threaded loop
469 returnval.merge(checker.my_returnval);
470
471 // Join problems found on other ranks
472 std::set<char> int_set;
473 std::transform
474 (returnval.begin(), returnval.end(),
475 std::inserter<std::set<char>>(int_set, int_set.end()),
476 [](SurfaceIntegrity i){return int(i);});
477 _mesh.comm().set_union(int_set);
478 std::transform
479 (int_set.begin(), int_set.end(),
480 std::inserter<std::set<SurfaceIntegrity>>(returnval, returnval.end()),
481 [](int i){return SurfaceIntegrity(i);});
482
483 return returnval;
484}
void set_union(T &data, const unsigned int root_id) const
Defines a Cartesian bounding box by the two corner extremum.
const Point & max() const
const Point & min() const
dof_id_type id() const
Definition dof_object.h:819
This is the base class from which all geometric element types are derived.
Definition elem.h:96
unsigned int which_neighbor_am_i(const Elem *e) const
This function tells you which neighbor e is.
Definition elem.h:2936
unsigned int local_node(const dof_id_type i) const
Definition elem.h:2496
std::string get_info() const
Prints relevant information about the element to a string.
Definition elem.C:2956
const Elem * neighbor_ptr(unsigned int i) const
Definition elem.h:2615
virtual dof_id_type n_elem() const =0
const ConstElemRange & active_local_element_stored_range() const
Definition mesh_base.C:1954
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.
unsigned int _verbosity
verbosity setting
SurfaceIntegrity
Enumeration of possible surface mesh integrity issues.
A Node is like a Point, but with more information.
Definition node.h:55
const Parallel::Communicator & comm() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
The StoredRange class defines a contiguous, divisible set of objects.
Dummy "splitting object" used to distinguish splitting constructors from copy constructors.
libMesh::BoundingBox create_bounding_box(const MeshBase &mesh)
Definition mesh_tools.C:566
void parallel_reduce(const Range &range, Body &body, unsigned int n_threads=libMesh::n_threads())
Execute the provided reduction operation in parallel on the specified range.
static constexpr Real TOLERANCE
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

References libMesh::MeshTetInterface::_mesh, libMesh::MeshTetInterface::_verbosity, libMesh::MeshBase::active_local_element_stored_range(), libMesh::ParallelObject::comm(), libMesh::MeshTools::create_bounding_box(), libMesh::MeshTetInterface::DEGENERATE_MESH, libMesh::MeshTetInterface::EMPTY_MESH, libMesh::Elem::get_info(), libMesh::DofObject::id(), libMesh::Elem::local_node(), libMesh::BoundingBox::max(), libMesh::BoundingBox::min(), libMesh::MeshBase::n_elem(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ptr(), libMesh::Threads::parallel_reduce(), libMesh::Real, libMesh::Parallel::Communicator::set_union(), libMesh::Elem::side_index_range(), libMesh::TOLERANCE, libMesh::TRI3, libMesh::Elem::type(), libMesh::Elem::volume(), and libMesh::Elem::which_neighbor_am_i().

Referenced by libMesh::MeshTetInterface::improve_hull_integrity().

◆ delete_2D_hull_elements()

void libMesh::MeshTetInterface::delete_2D_hull_elements ( )
protectedinherited

Delete original convex hull elements from the Mesh after performing a Delaunay tetrahedralization.

Definition at line 685 of file mesh_tet_interface.C.

686{
687 for (auto & elem : this->_mesh.element_ptr_range())
688 {
689 // Check for proper element type. Yes, we legally delete elements while
690 // iterating over them because no entries from the underlying container
691 // are actually erased.
692 if (elem->type() == TRI3)
693 _mesh.delete_elem(elem);
694 }
695
696 // We just removed any boundary info associated with hull element
697 // edges, so let's update the boundary id caches.
699}
void regenerate_id_sets()
Clears and regenerates the cached sets of ids.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.

References libMesh::MeshTetInterface::_mesh, libMesh::MeshBase::delete_elem(), libMesh::MeshBase::get_boundary_info(), libMesh::BoundaryInfo::regenerate_id_sets(), libMesh::TRI3, and libMesh::Elem::type().

Referenced by libMesh::TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole().

◆ desired_volume()

Real & libMesh::MeshTetInterface::desired_volume ( )
inlineinherited

Sets and/or gets the desired tetrahedron volume.

Set to zero to disable volume constraint.

Definition at line 68 of file mesh_tet_interface.h.

68{return _desired_volume;}
Real _desired_volume
The desired volume for the elements in the resulting mesh.

References libMesh::MeshTetInterface::_desired_volume.

Referenced by main(), and MeshTetTest::testSphereShell().

◆ elem_type()

ElemType & libMesh::MeshTetInterface::elem_type ( )
inlineinherited

Sets and/or gets the desired element type.

This should be a Tet type.

Definition at line 81 of file mesh_tet_interface.h.

81{return _elem_type;}
ElemType _elem_type
The exact type of tetrahedra we intend to construct.

References libMesh::MeshTetInterface::_elem_type.

Referenced by MeshTetTest::testNetGenQuadratic(), MeshTetTest::testNetGenQuadraticCurved(), MeshTetTest::testNetGenTet14(), and MeshTetTest::testNetGenTet14Curved().

◆ improve_hull_integrity()

std::set< MeshTetInterface::SurfaceIntegrity > libMesh::MeshTetInterface::improve_hull_integrity ( )
protectedinherited

This function checks the integrity of the current set of elements in the Mesh, and corrects what it can.

Returns
A set of SurfaceIntegrity codes from check_hull_integrity() if there are problems it can't fix, or an empty set otherwise.

Definition at line 488 of file mesh_tet_interface.C.

489{
490 // We don't really do anything parallel here, but we aspire to.
491 libmesh_parallel_only(this->_mesh.comm());
492
493 std::set<MeshTetInterface::SurfaceIntegrity> integrityproblems =
494 this->check_hull_integrity();
495
496 // If we have no problem, or a problem we can't fix, we're done.
497 if (integrityproblems.empty() ||
498 integrityproblems.count(NON_TRI3) ||
499 integrityproblems.count(EMPTY_MESH))
500 return integrityproblems;
501
502 // Possibly the user gave us an unprepared mesh with missing or bad
503 // neighbor links?
504 if (integrityproblems.count(MISSING_NEIGHBOR) ||
505 integrityproblems.count(MISSING_BACKLINK) ||
506 integrityproblems.count(BAD_NEIGHBOR_LINKS))
507 {
508 this->_mesh.find_neighbors();
509 integrityproblems = this->check_hull_integrity();
510 }
511
512 // If find_neighbors() doesn't fix these, I give up.
513 if (integrityproblems.count(MISSING_NEIGHBOR) ||
514 integrityproblems.count(MISSING_BACKLINK) ||
515 integrityproblems.count(BAD_NEIGHBOR_LINKS))
516 return integrityproblems;
517
518 // find_neighbors() might have fixed everything
519 if (integrityproblems.empty())
520 return integrityproblems;
521
522 // A non-oriented (but orientable!) surface is the only thing we
523 // shouldn't have fixed or given up on by now.
524 libmesh_assert_equal_to(integrityproblems.size(), 1);
525 libmesh_assert_equal_to(integrityproblems.count(NON_ORIENTED), 1);
526
527 // We need one known-good triangle to start from. We'll pick the
528 // most-negative-x normal among the triangles on the most-negative-x
529 // point.
530
531 // We'll just implement this in serial for now.
532 MeshSerializer mesh_serializer(this->_mesh);
533
534 // I don't see why we'd need boundary info here, but maybe we'll
535 // want to preserve edge/node conditions eventually?
536 BoundaryInfo & bi = this->_mesh.get_boundary_info();
537
538 const Node * lowest_point = (*this->_mesh.elements_begin())->node_ptr(0);
539
540 // Index by ids, not pointers, for consistency in parallel
541 std::unordered_set<dof_id_type> attached_elements;
542
543 for (Elem * elem : this->_mesh.element_ptr_range())
544 {
545 for (const Node & node : elem->node_ref_range())
546 {
547 if (node(0) < (*lowest_point)(0))
548 {
549 lowest_point = &node;
550 attached_elements.clear();
551 }
552 if (&node == lowest_point)
553 attached_elements.insert(elem->id());
554 }
555 }
556
557 Elem * best_elem = nullptr;
558 Real best_abs_normal_0 = 0;
559
560 for (dof_id_type id : attached_elements)
561 {
562 Elem * elem = this->_mesh.elem_ptr(id);
563 const Point e01 = elem->point(1) - elem->point(0);
564 const Point e02 = elem->point(2) - elem->point(0);
565 const Point normal = e01.cross(e02).unit();
566 const Real abs_normal_0 = std::abs(normal(0));
567
568 if (!best_elem || abs_normal_0 > best_abs_normal_0)
569 {
570 best_elem = elem;
571 best_abs_normal_0 = abs_normal_0;
572
573 // Make sure that element is actually a good one, by
574 // flipping it if it's not.
575 if (abs_normal_0 == normal(0))
576 elem->flip(&bi);
577 }
578 }
579
580 // Now flood-fill from that element to get a consistent orientation
581 // for the others.
582 std::unordered_set<dof_id_type> frontier_elements{best_elem->id()},
583 finished_elements{};
584
585 while (!frontier_elements.empty())
586 {
587 const dof_id_type elem_id = *frontier_elements.begin();
588 Elem & elem = this->_mesh.elem_ref(elem_id);
589 for (auto s : elem.side_index_range())
590 {
591 Elem * neigh = elem.neighbor_ptr(s);
592 libmesh_assert(neigh);
593 libmesh_assert_less(neigh->which_neighbor_am_i(&elem), 3);
594
595 const Node * const n1 = elem.node_ptr(s);
596 const Node * const n2 = elem.node_ptr((s+1)%3);
597 const unsigned int i1 = neigh->local_node(n1->id());
598 const unsigned int i2 = neigh->local_node(n2->id());
599 libmesh_assert_less(i1, 3);
600 libmesh_assert_less(i2, 3);
601
602 const dof_id_type neigh_id = neigh->id();
603
604 const bool frontier_neigh = frontier_elements.count(neigh_id);
605 const bool finished_neigh = finished_elements.count(neigh_id);
606
607 // Are we flipped?
608 if ((i2 + 1)%3 != i1)
609 {
610 // Are we a Moebius strip??? We give up.
611 if (frontier_neigh || finished_neigh)
612 return integrityproblems;
613
614 neigh->flip(&bi);
615 }
616
617 if (!frontier_neigh && !finished_neigh)
618 frontier_elements.insert(neigh_id);
619 }
620
621 finished_elements.insert(elem_id);
622 frontier_elements.erase(elem_id);
623 }
624
625 this->_mesh.find_neighbors();
626
627 libmesh_assert(this->check_hull_integrity().empty());
628
629 return {};
630}
The BoundaryInfo class contains information relevant to boundary conditions including storing faces,...
const Point & point(const unsigned int i) const
Definition elem.h:2462
virtual void flip(BoundaryInfo *boundary_info)=0
Flips the element (by swapping node and neighbor pointers) to have a mapping Jacobian of opposite sig...
const Node * node_ptr(const unsigned int i) const
Definition elem.h:2516
virtual const Elem * elem_ptr(const dof_id_type i) const =0
virtual const Elem & elem_ref(const dof_id_type i) const
Definition mesh_base.h:788
Temporarily serialize a DistributedMesh for non-distributed-mesh capable code paths.
std::set< SurfaceIntegrity > check_hull_integrity() const
This function checks the integrity of the current set of elements in the Mesh to see if they comprise...
TypeVector< typename CompareTypes< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const
virtual void find_neighbors(const bool reset_remote_elements=false, const bool reset_current_list=true, const bool assert_valid=true) override
Other functions from MeshBase requiring re-definition.
libmesh_assert(ctx)
uint8_t dof_id_type
Definition id_types.h:67

References libMesh::MeshTetInterface::_mesh, libMesh::MeshTetInterface::BAD_NEIGHBOR_LINKS, libMesh::MeshTetInterface::check_hull_integrity(), libMesh::ParallelObject::comm(), libMesh::TypeVector< T >::cross(), libMesh::MeshBase::elem_ptr(), libMesh::MeshBase::elem_ref(), libMesh::MeshTetInterface::EMPTY_MESH, libMesh::UnstructuredMesh::find_neighbors(), libMesh::Elem::flip(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::libmesh_assert(), libMesh::Elem::local_node(), libMesh::MeshTetInterface::MISSING_BACKLINK, libMesh::MeshTetInterface::MISSING_NEIGHBOR, libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ptr(), libMesh::Elem::node_ref_range(), libMesh::MeshTetInterface::NON_ORIENTED, libMesh::MeshTetInterface::NON_TRI3, libMesh::Elem::point(), libMesh::Real, libMesh::Elem::side_index_range(), and libMesh::Elem::which_neighbor_am_i().

Referenced by triangulate(), and libMesh::TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole().

◆ increase_tet_order()

void libMesh::MeshTetInterface::increase_tet_order ( )
protectedinherited

Converts all linear tet elements to the type requested by _elem_type, if that type differs from TET4.

Definition at line 703 of file mesh_tet_interface.C.

704{
705 switch (_elem_type)
706 {
707 case TET4:
708 return;
709 case TET10:
711 break;
712 case TET14:
714 break;
715 default:
716 libmesh_not_implemented();
717 }
718}
void all_second_order(const bool full_ordered=true)
Calls the range-based version of this function with a range consisting of all elements in the mesh.
Definition mesh_base.C:1803
virtual void all_complete_order()
Calls the range-based version of this function with a range consisting of all elements in the mesh.
Definition mesh_base.C:1808

References libMesh::MeshTetInterface::_elem_type, libMesh::MeshTetInterface::_mesh, libMesh::MeshBase::all_complete_order(), libMesh::MeshBase::all_second_order(), libMesh::TET10, libMesh::TET14, and libMesh::TET4.

Referenced by triangulate().

◆ process_hull_integrity_result()

void libMesh::MeshTetInterface::process_hull_integrity_result ( const std::set< SurfaceIntegrity > &  result) const
protectedinherited

This function prints an informative message and throws an exception based on the output of the check_hull_integrity() function.

It is a separate function so that you can check hull integrity without exiting or catching an exception if desired.

Definition at line 633 of file mesh_tet_interface.C.

635{
636 std::ostringstream err_msg;
637
638 if (result.empty()) // success
639 return;
640
641 err_msg << "Error! Conforming Delaunay mesh tetrahedralization requires a convex hull." << std::endl;
642
643 if (result.count(NON_TRI3))
644 {
645 err_msg << "At least one non-Tri3 element was found in the input boundary mesh. ";
646 err_msg << "Our constrained Delaunay tetrahedralization boundary must be a triangulation of Tri3 elements." << std::endl;
647 }
648 if (result.count(MISSING_NEIGHBOR))
649 {
650 err_msg << "At least one triangle without three neighbors was found in the input boundary mesh. ";
651 err_msg << "A constrained Delaunay tetrahedralization boundary must be a triangular manifold without boundary." << std::endl;
652 }
653 if (result.count(EMPTY_MESH))
654 {
655 err_msg << "The input boundary mesh was empty!" << std::endl;
656 err_msg << "Our constrained Delaunay tetrahedralization boundary must be a triangulation of Tri3 elements." << std::endl;
657 }
658 if (result.count(MISSING_BACKLINK))
659 {
660 err_msg << "At least one triangle neighbor without a return neighbor link was found in the input boundary mesh. ";
661 err_msg << "A constrained Delaunay tetrahedralization boundary must be a conforming and non-adaptively-refined mesh." << std::endl;
662 }
663 if (result.count(BAD_NEIGHBOR_NODES))
664 {
665 err_msg << "At least one triangle neighbor without expected node links was found in the input boundary mesh. ";
666 err_msg << "A constrained Delaunay tetrahedralization boundary must be a conforming and non-adaptively-refined mesh." << std::endl;
667 }
668 if (result.count(NON_ORIENTED))
669 {
670 err_msg << "At least one triangle neighbor with an inconsistent orientation was found in the input boundary mesh. ";
671 err_msg << "A constrained Delaunay tetrahedralization boundary must be an oriented Tri3 mesh." << std::endl;
672 }
673 if (result.count(BAD_NEIGHBOR_LINKS))
674 err_msg << "At least one triangle neighbor with inconsistent node and neighbor links was found in the input boundary mesh." << std::endl;
675 if (result.count(DEGENERATE_ELEMENT))
676 err_msg << "At least one input triangle is degenerate, with near-zero area relative to the manifold." << std::endl;
677 if (result.count(DEGENERATE_MESH))
678 err_msg << "The input mesh is degenerate, with zero thickness in at least one direction." << std::endl;
679
680 libmesh_error_msg(err_msg.str());
681}

References libMesh::MeshTetInterface::BAD_NEIGHBOR_LINKS, libMesh::MeshTetInterface::BAD_NEIGHBOR_NODES, libMesh::MeshTetInterface::DEGENERATE_ELEMENT, libMesh::MeshTetInterface::DEGENERATE_MESH, libMesh::MeshTetInterface::EMPTY_MESH, libMesh::MeshTetInterface::MISSING_BACKLINK, libMesh::MeshTetInterface::MISSING_NEIGHBOR, libMesh::MeshTetInterface::NON_ORIENTED, and libMesh::MeshTetInterface::NON_TRI3.

Referenced by triangulate(), and libMesh::TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole().

◆ set_verbosity()

void libMesh::MeshTetInterface::set_verbosity ( unsigned int  v)
inlineinherited

Sets a verbosity level, defaulting to 0 (print nothing), to be set as high as 100 (print everything).

For verbosity >= 50, print all detected surface mesh integrity issues as they're found. Subclasses may add other output at other verbosity levels.

Definition at line 214 of file mesh_tet_interface.h.

215{
216 this->_verbosity = v;
217}

References libMesh::MeshTetInterface::_verbosity.

Referenced by MeshTetTest::testNetGen().

◆ smooth_after_generating()

bool & libMesh::MeshTetInterface::smooth_after_generating ( )
inlineinherited

Sets/gets flag which tells whether to do two steps of Laplace mesh smoothing after generating the grid.

False by default (for compatibility with old TetGenMeshInterface behavior).

Definition at line 75 of file mesh_tet_interface.h.

bool _smooth_after_generating
Flag which tells whether we should smooth the mesh after it is generated.

References libMesh::MeshTetInterface::_smooth_after_generating.

◆ triangulate()

void libMesh::NetGenMeshInterface::triangulate ( )
overridevirtual

Method invokes NetGen library to compute a tetrahedralization.

Implements libMesh::MeshTetInterface.

Definition at line 86 of file mesh_netgen_interface.C.

87{
88 using namespace nglib;
89
90 LOG_SCOPE("triangulate()", "NetGenMeshInterface");
91
92 if (_elem_type != TET4 &&
93 _elem_type != TET10 &&
95 libmesh_not_implemented();
96
97 // We're hoping to do volume_to_surface_mesh in parallel at least,
98 // but then we'll need to serialize any hole meshes to rank 0 so it
99 // can use them in serial.
100
101 // If the user wants higher-order output, record midpoints from any
102 // quadratic boundary elements before stripping them to TRI3 for
103 // NetGen. We key by sorted position pairs (not node IDs) so that
104 // outer mesh and hole mesh node namespaces cannot conflict.
105 std::map<std::pair<Point,Point>, Point> edge_midpoints;
106
107 // TRI7 boundary faces additionally carry a face-centroid node (local index
108 // 6) that TET14 output reproduces (its faces are TRI7). Record those too,
109 // keyed by the sorted triple of the face's three corner positions so the
110 // key is independent of node id and rotation.
111 std::map<std::array<Point,3>, Point> face_centroids;
112
113 auto record_and_strip =
114 [&edge_midpoints, &face_centroids](UnstructuredMesh & m)
115 {
116 bool has_quadratic = false;
117 for (const auto & elem : m.element_ptr_range())
118 {
119 if (elem->type() != TRI6 && elem->type() != TRI7) continue;
120 has_quadratic = true;
121 // TRI6/TRI7: edge e runs node[e]→node[(e+1)%3], midpoint=node[e+3]
122 for (auto e : make_range(3u))
123 {
124 const Point & pa = elem->point(e);
125 const Point & pb = elem->point((e+1)%3);
126 auto key = pa < pb
127 ? std::make_pair(pa,pb) : std::make_pair(pb,pa);
128 edge_midpoints[key] = elem->point(e+3);
129 }
130 // TRI7: node 6 is the face-centroid node.
131 if (elem->type() == TRI7)
132 {
133 std::array<Point,3> corners =
134 {elem->point(0), elem->point(1), elem->point(2)};
135 std::sort(corners.begin(), corners.end());
136 face_centroids[corners] = elem->point(6);
137 }
138 }
139 if (has_quadratic)
140 m.all_first_order();
141 };
142
143 const BoundingBox mesh_bb =
145
146 if (_elem_type != TET4)
147 record_and_strip(this->_mesh);
148
149 std::vector<MeshSerializer> hole_serializers;
150 if (_holes)
151 for (std::unique_ptr<UnstructuredMesh> & hole : *_holes)
152 {
153 const BoundingBox hole_bb =
155
156 if (_elem_type != TET4)
157 record_and_strip(*hole);
158
159 libmesh_error_msg_if
160 (!mesh_bb.contains(hole_bb),
161 "Found hole with bounding box " << hole_bb <<
162 "\nextending outside of mesh bounding box " << mesh_bb);
163
164 hole_serializers.emplace_back
165 (*hole, /* need_serial */ true,
166 /* serial_only_needed_on_proc_0 */ true);
167 }
168
169 // Increasing the element order (all_second_order()/all_complete_order()
170 // via increase_tet_order()) performs collective MPI communication, so it
171 // must run on every rank in lockstep -- it cannot run on rank 0 alone
172 // while the other ranks wait in broadcast(). We therefore broadcast
173 // NetGen's TET4 result first, then increase the order and restore the
174 // curved-boundary midpoints identically on all ranks. This mirrors what
175 // the 2D interfaces do (see TriangulatorInterface::increase_triangle_order,
176 // which is likewise called on all ranks). edge_midpoints was built while
177 // the mesh was serialized on every rank, so it is identical everywhere and
178 // this post-broadcast fixup is deterministic.
179 auto increase_order_and_restore_midpoints =
180 [this, &edge_midpoints, &face_centroids]()
181 {
182 if (_elem_type == TET4)
183 return;
184
185 // find_neighbors() is needed before all_second_order() can place
186 // shared edge midpoints correctly.
187 this->_mesh.find_neighbors();
188
189 // Refresh the cached element dimensions. We just replaced the 2D
190 // TRI3 surface elements with 3D TET4 volume elements, but the mesh's
191 // cached dimension is still that of the original surface (2).
192 // all_second_order()/all_complete_order() derive the per-element
193 // unique_id reservation width from mesh_dimension(): a stale value of
194 // 2 reserves only 9-4=5 slots per element, too few for the 6 new edge
195 // nodes of a TET10, so adjacent elements' unique_id ranges overlap and
196 // collide. cache_elem_data() recomputes the dimension to 3 (reserving
197 // 27-8=19 slots) before the order increase runs.
198 this->_mesh.cache_elem_data();
199
200 this->increase_tet_order();
201
202 // Move auto-placed geometric midpoints to the recorded positions,
203 // preserving any curvature from the original quadratic boundary.
204 if (!edge_midpoints.empty() || !face_centroids.empty())
205 for (Elem * elem : _mesh.element_ptr_range())
206 for (auto s : elem->side_index_range())
207 {
208 if (elem->neighbor_ptr(s)) continue;
209
210 // build_side_ptr() returns a TRI6 (TET10) or TRI7 (TET14)
211 // whose node pointers reference the actual mesh nodes; point()
212 // assignments update mesh node coordinates in place.
213 auto side = elem->build_side_ptr(s);
214 for (auto e : make_range(3u))
215 {
216 const Point & pa = side->point(e);
217 const Point & pb = side->point((e+1)%3);
218 auto key = pa < pb
219 ? std::make_pair(pa,pb) : std::make_pair(pb,pa);
220 if (auto it = edge_midpoints.find(key);
221 it != edge_midpoints.end())
222 side->point(e+3) = it->second;
223 }
224
225 // TRI7 faces (TET14 output) also carry a face-centroid node
226 // at local index 6; restore its recorded curved position.
227 if (side->type() == TRI7)
228 {
229 std::array<Point,3> corners =
230 {side->point(0), side->point(1), side->point(2)};
231 std::sort(corners.begin(), corners.end());
232 if (auto it = face_centroids.find(corners);
233 it != face_centroids.end())
234 side->point(6) = it->second;
235 }
236 }
237 };
238
239 // This should probably only be done on rank 0, but the API is
240 // designed with the hope that we'll parallelize it eventually
241 auto integrity = this->improve_hull_integrity();
242 this->process_hull_integrity_result(integrity);
243
244 // If we're not rank 0, we're just going to wait for rank 0 to call
245 // Netgen, then receive its data afterward, we're not going to hope
246 // that Netgen does the exact same thing on every processor.
247 if (this->_mesh.processor_id() != 0)
248 {
249 // We don't need our holes anymore. Delete their serializers
250 // first to avoid dereferencing dangling pointers.
251 hole_serializers.clear();
252 if (_holes)
253 _holes->clear();
254
255 // Receive the TET4 mesh data rank 0 will send later.
256 MeshCommunication().broadcast(this->_mesh);
257
258 // If we got an empty mesh here then our tetrahedralization
259 // failed.
260 libmesh_error_msg_if (!this->_mesh.n_elem(),
261 "NetGen failed to generate any tetrahedra");
262
263 // Increase the element order collectively, in lockstep with rank 0.
264 increase_order_and_restore_midpoints();
265
266 this->_mesh.prepare_for_use();
267 return;
268 }
269
270 Ng_Meshing_Parameters params;
271
272 Ng_SetNumThreads(cast_int<int>(libMesh::n_threads()));
273
274 // Override any default parameters we might need to, to avoid
275 // inserting nodes we don't want.
276 params.uselocalh = false;
277 params.minh = 0;
278 params.elementsperedge = 1;
279 params.elementspercurve = 1;
280 params.closeedgeenable = false;
281 params.closeedgefact = 0;
282 params.minedgelenenable = false;
283 params.minedgelen = 0;
284
285 // Try to get a no-extra-nodes mesh if we're asked to, or try to
286 // translate our desired volume into NetGen terms otherwise.
287 //
288 // Spoiler alert: all we can do is try; NetGen uses a marching front
289 // algorithm that can insert extra nodes despite all my best
290 // efforts.
291 if (_desired_volume == 0) // shorthand for "no refinement"
292 {
293 params.maxh = std::numeric_limits<double>::max();
294 params.fineness = 0; // "coarse" in the docs
295 params.grading = 1; // "aggressive local grading" to avoid smoothing??
296
297 // Turning off optimization steps avoids another opportunity for
298 // Netgen to try to add more nodes.
299 params.optsteps_3d = 0;
300 }
301 else
302 params.maxh = double(std::pow(_desired_volume, 1./3.));
303
304 // Keep track of how NetGen copies of nodes map back to our original
305 // nodes, so we can connect new elements to nodes correctly.
306 std::unordered_map<int, dof_id_type> ng_to_libmesh_id;
307
308 auto handle_ng_result = [](Ng_Result result) {
309 static const std::vector<std::string> result_types =
310 {"Netgen error", "Netgen success", "Netgen surface input error",
311 "Netgen volume failure", "Netgen STL input error",
312 "Netgen surface failure", "Netgen file not found"};
313
314 if (result+1 >= 0 &&
315 std::size_t(result+1) < result_types.size())
316 libmesh_error_msg_if
317 (result, "Ng_GenerateVolumeMesh failed: " <<
318 result_types[result+1]);
319 else
320 libmesh_error_msg
321 ("Ng_GenerateVolumeMesh failed with an unknown error code");
322 };
323
324 // Keep track of what boundary ids we want to assign to each new
325 // triangle. We'll give the outer boundary BC 0, and give holes ids
326 // starting from 1.
327 // We key on sorted tuples of node ids to identify a side.
328 std::unordered_map<std::array<dof_id_type,3>,
329 boundary_id_type, libMesh::hash> side_boundary_id;
330
331 auto insert_id = []
332 (std::array<dof_id_type,3> & array,
333 dof_id_type n_id)
334 {
335 libmesh_assert_less(n_id, DofObject::invalid_id);
336 unsigned int i=0;
337 while (array[i] < n_id)
338 ++i;
339 while (i < 3)
340 std::swap(array[i++], n_id);
341 };
342
343 WrappedNgMesh ngmesh;
344
345 // Create surface mesh in the WrappedNgMesh
346 {
347 // NetGen appears to use ONE-BASED numbering for its nodes, and
348 // since it doesn't return an id when adding nodes we'll have to
349 // track the numbering ourselves.
350 int ng_id = 1;
351
352 auto create_surface_component =
353 [this, &ng_id, &ng_to_libmesh_id, &ngmesh, &side_boundary_id, &insert_id]
354 (UnstructuredMesh & srcmesh, bool hole_mesh,
355 boundary_id_type bcid)
356 {
357 LOG_SCOPE("create_surface_component()", "NetGenMeshInterface");
358
359 // Keep track of what nodes we've already added to the Netgen
360 // mesh vs what nodes we need to add. We'll keep track by id,
361 // not by point location. I don't know if Netgen can handle
362 // multiple nodes with the same point location, but if they can
363 // it's not going to be *us* who breaks that feature.
364 std::unordered_map<dof_id_type, int> libmesh_to_ng_id;
365
366 // Keep track of what nodes we've already added to the main
367 // mesh from a hole mesh.
368 std::unordered_map<dof_id_type, dof_id_type> hole_to_main_mesh_id;
369
370 // Use a separate array for passing points to NetGen, just in case
371 // we're not using double-precision ourselves.
372 std::array<double, 3> point_val;
373
374 // And an array for element vertices
375 std::array<int, 3> elem_nodes;
376
377 for (const auto * elem : srcmesh.element_ptr_range())
378 {
379 // If someone has triangles we can't triangulate, we have a
380 // problem
381 if (elem->type() == TRI6 ||
382 elem->type() == TRI7)
383 libmesh_not_implemented_msg
384 ("Netgen tetrahedralization currently only supports TRI3 boundaries");
385
386 // If someone has non-triangles, let's just ignore them.
387 if (elem->type() != TRI3)
388 continue;
389
390 std::array<dof_id_type,3> sorted_ids =
393
394 for (int ni : make_range(3))
395 {
396 // Just using the "invert_trigs" option in NetGen params
397 // doesn't work for me, so we'll have to have properly
398 // oriented the tris earlier.
399 auto & elem_node = hole_mesh ? elem_nodes[2-ni] : elem_nodes[ni];
400
401 const Node & n = elem->node_ref(ni);
402 auto n_id = n.id();
403 if (hole_mesh)
404 {
405 if (auto it = hole_to_main_mesh_id.find(n_id);
406 it != hole_to_main_mesh_id.end())
407 {
408 n_id = it->second;
409 }
410 else
411 {
412 Node * n_new = this->_mesh.add_point(n);
413 const dof_id_type n_new_id = n_new->id();
414 hole_to_main_mesh_id.emplace(n_id, n_new_id);
415 n_id = n_new_id;
416 }
417 }
418
419 if (auto it = libmesh_to_ng_id.find(n_id);
420 it != libmesh_to_ng_id.end())
421 {
422 const int existing_ng_id = it->second;
423 elem_node = existing_ng_id;
424 }
425 else
426 {
427 for (auto i : make_range(3))
428 point_val[i] = double(n(i));
429
430 Ng_AddPoint(ngmesh, point_val.data());
431
432 ng_to_libmesh_id[ng_id] = n_id;
433 libmesh_to_ng_id[n_id] = ng_id;
434 elem_node = ng_id;
435 ++ng_id;
436 }
437
438 insert_id(sorted_ids, n_id);
439 }
440
441 side_boundary_id[sorted_ids] = bcid;
442
443 Ng_AddSurfaceElement(ngmesh, NG_TRIG, elem_nodes.data());
444 }
445 };
446
447 // Number the outer boundary 0, and the holes starting from 1
448 boundary_id_type bcid = 0;
449
450 create_surface_component(this->_mesh, false, bcid);
451
452 if (_holes)
453 for (const std::unique_ptr<UnstructuredMesh> & h : *_holes)
454 create_surface_component(*h, true, ++bcid);
455 }
456
457 {
458 LOG_SCOPE("Ng_GenerateVolumeMesh()", "NetGenMeshInterface");
459
460 auto result = Ng_GenerateVolumeMesh(ngmesh, &params);
461 handle_ng_result(result);
462 }
463
464 const int n_elem = Ng_GetNE(ngmesh);
465
466 // If Netgen fails us, we're likely to get n_elem <= 0. This is a
467 // common enough failure from bad setups that I want to make sure
468 // it's thrown in parallel so as to not desynchronize any unit tests
469 // that trigger it. So we'll broadcast the empty mesh to indicate
470 // the problem and enable throwing exceptions in parallel.
471 if (n_elem <= 0)
472 {
473 this->_mesh.clear();
474 MeshCommunication().broadcast(this->_mesh);
475 libmesh_error_msg ("NetGen failed to generate any tetrahedra");
476 }
477
478 const dof_id_type n_points = Ng_GetNP(ngmesh);
479 const dof_id_type old_nodes = this->_mesh.n_nodes();
480
481 // Netgen may have generated new interior nodes
482 if (n_points != old_nodes)
483 {
484 std::array<double, 3> point_val;
485
486 // We should only be getting new nodes if we asked for them
487 if (!_desired_volume)
488 {
489 std::cout <<
490 "NetGen output " << n_points <<
491 " points when we gave it " <<
492 old_nodes << " and disabled refinement\n" <<
493 "If new interior points are acceptable in your mesh, please set\n" <<
494 "a non-zero desired_volume to indicate that. If new interior\n" <<
495 "points are not acceptable in your mesh, you may need a different\n" <<
496 "(non-advancing-front?) mesh generator." << std::endl;
497 libmesh_error();
498 }
499 else
500 for (auto i : make_range(old_nodes, n_points))
501 {
502 // i+1 since ng uses ONE-BASED numbering
503 Ng_GetPoint (ngmesh, i+1, point_val.data());
504 const Point p(point_val[0], point_val[1], point_val[2]);
505 Node * n_new = this->_mesh.add_point(p);
506 const dof_id_type n_new_id = n_new->id();
507 ng_to_libmesh_id[i+1] = n_new_id;
508 }
509 }
510
511 for (auto * elem : this->_mesh.element_ptr_range())
512 this->_mesh.delete_elem(elem);
513
514 BoundaryInfo * bi = & this->_mesh.get_boundary_info();
515
516 for (auto i : make_range(n_elem))
517 {
518 // Enough data to return even a Tet10 without a segfault if nglib
519 // went nuts
520 int ngnodes[11];
521
522 // i+1 since we must be 1-based with these ids too...
523 Ng_Volume_Element_Type ngtype =
524 Ng_GetVolumeElement(ngmesh, i+1, ngnodes);
525
526 // But really nglib shouldn't go nuts
527 libmesh_assert(ngtype == NG_TET);
528 libmesh_ignore(ngtype);
529
530 auto elem = this->_mesh.add_elem(Elem::build_with_id(TET4, i));
531 for (auto n : make_range(4))
532 {
533 const dof_id_type node_id =
534 libmesh_map_find(ng_to_libmesh_id, ngnodes[n]);
535 elem->set_node(n, this->_mesh.node_ptr(node_id));
536 }
537
538 // NetGen and we disagree about node numbering orientation
539 elem->orient(bi);
540
541 for (auto s : make_range(4))
542 {
543 std::array<dof_id_type,3> sorted_ids =
546
547 std::vector<unsigned int> nos = elem->nodes_on_side(s);
548 for (auto n : nos)
549 insert_id(sorted_ids, elem->node_id(n));
550
551 if (auto it = side_boundary_id.find(sorted_ids);
552 it != side_boundary_id.end())
553 bi->add_side(elem, s, it->second);
554 }
555 }
556
557 // We don't need our holes anymore. Delete their serializers
558 // first to avoid dereferencing dangling pointers.
559 hole_serializers.clear();
560 if (_holes)
561 _holes->clear();
562
563 // Send NetGen's TET4 result to the other ranks, then increase the element
564 // order collectively on all ranks together. increase_tet_order() performs
565 // collective communication, so it must not run on rank 0 alone -- doing so
566 // would deadlock against the other ranks waiting in broadcast() above.
567 MeshCommunication().broadcast(this->_mesh);
568 increase_order_and_restore_midpoints();
569 this->_mesh.prepare_for_use();
570}
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
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
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 dof_id_type n_nodes() const =0
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 void clear()
Deletes all the element and node data that is currently stored.
Definition mesh_base.C:1036
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
void process_hull_integrity_result(const std::set< SurfaceIntegrity > &result) const
This function prints an informative message and throws an exception based on the output of the check_...
std::set< SurfaceIntegrity > improve_hull_integrity()
This function checks the integrity of the current set of elements in the Mesh, and corrects what it c...
void increase_tet_order()
Converts all linear tet elements to the type requested by _elem_type, if that type differs from TET4.
static BoundingBox volume_to_surface_mesh(UnstructuredMesh &mesh)
Remove volume elements from the given mesh, after converting their outer boundary faces to surface el...
processor_id_type processor_id() const
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
int8_t boundary_id_type
Definition id_types.h:51
void libmesh_ignore(const Args &...)
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176
unsigned int n_threads()

References libMesh::MeshTetInterface::_desired_volume, libMesh::MeshTetInterface::_elem_type, libMesh::MeshTetInterface::_holes, libMesh::MeshTetInterface::_mesh, libMesh::MeshBase::add_elem(), libMesh::MeshBase::add_point(), libMesh::BoundaryInfo::add_side(), libMesh::MeshCommunication::broadcast(), libMesh::Elem::build_with_id(), libMesh::MeshBase::cache_elem_data(), libMesh::MeshBase::clear(), libMesh::BoundingBox::contains(), libMesh::MeshBase::delete_elem(), libMesh::UnstructuredMesh::find_neighbors(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::MeshTetInterface::improve_hull_integrity(), libMesh::MeshTetInterface::increase_tet_order(), libMesh::DofObject::invalid_id, libMesh::libmesh_assert(), libMesh::libmesh_ignore(), libMesh::make_range(), libMesh::MeshBase::n_elem(), libMesh::MeshBase::n_nodes(), libMesh::n_threads(), libMesh::MeshBase::node_ptr(), libMesh::MeshBase::prepare_for_use(), libMesh::MeshTetInterface::process_hull_integrity_result(), libMesh::ParallelObject::processor_id(), libMesh::TET10, libMesh::TET14, libMesh::TET4, libMesh::TRI3, libMesh::TRI6, libMesh::TRI7, and libMesh::MeshTetInterface::volume_to_surface_mesh().

Referenced by main(), MeshTetTest::testNetGenQuadratic(), MeshTetTest::testNetGenQuadraticCurved(), MeshTetTest::testNetGenTet14(), and MeshTetTest::testNetGenTet14Curved().

◆ volume_to_surface_mesh()

BoundingBox libMesh::MeshTetInterface::volume_to_surface_mesh ( UnstructuredMesh mesh)
staticprotectedinherited

Remove volume elements from the given mesh, after converting their outer boundary faces to surface elements.

Returns the bounding box of the mesh; this is useful for detecting misplaced holes later.

Definition at line 134 of file mesh_tet_interface.C.

135{
136 // If we've been handed an unprepared mesh then we need to be made
137 // aware of that and fix that; we're relying on neighbor pointers.
139
140 if (!mesh.is_prepared())
142
143 // We'll return a bounding box for use by subclasses in basic sanity checks.
144 BoundingBox surface_bb;
145
146 // First convert all volume boundaries to surface elements; this
147 // gives us a manifold bounding the mesh, though it may not be a
148 // connected manifold even if the volume mesh was connected.
149 {
150 // Make sure ids are in sync and valid on a DistributedMesh
151 const dof_id_type max_orig_id = mesh.max_elem_id();
152#ifdef LIBMESH_ENABLE_UNIQUE_ID
153 const unique_id_type max_unique_id = mesh.parallel_max_unique_id();
154#endif
155
156 // Change this if we add arbitrary polyhedra...
157 const dof_id_type max_sides = 6;
158
159 std::unordered_set<Elem *> elems_to_delete;
160
161 std::vector<std::unique_ptr<Elem>> elems_to_add;
162
163 // Convert all faces to surface elements
164 for (auto * elem : mesh.active_element_ptr_range())
165 {
166 libmesh_error_msg_if (elem->dim() < 2,
167 "Cannot use meshes with 0D or 1D elements to define a volume");
168
169 // If we've already got 2D elements then those are (part of)
170 // our surface.
171 if (elem->dim() == 2)
172 continue;
173
174 // 3D elements will be removed after we've extracted their
175 // surface faces.
176 elems_to_delete.insert(elem);
177
178 for (auto s : make_range(elem->n_sides()))
179 {
180 // If there's a neighbor on this side then there's not a
181 // boundary
182 if (elem->neighbor_ptr(s))
183 {
184 // We're not supporting AMR meshes here yet
185 if (elem->level() != elem->neighbor_ptr(s)->level())
186 libmesh_not_implemented_msg
187 ("Tetrahedralizaton of adapted meshes is not currently supported");
188 continue;
189 }
190
191 elems_to_add.push_back(elem->build_side_ptr(s));
192 Elem * side_elem = elems_to_add.back().get();
193
194 // Wipe the interior_parent before it can become a
195 // dangling pointer later
196 side_elem->set_interior_parent(nullptr);
197
198 // If the mesh is replicated then its automatic id
199 // setting is fine. If not, then we need unambiguous ids
200 // independent of element traversal.
201 if (!mesh.is_replicated())
202 {
203 side_elem->set_id(max_orig_id + max_sides*elem->id() + s);
204#ifdef LIBMESH_ENABLE_UNIQUE_ID
205 side_elem->set_unique_id(max_unique_id + max_sides*elem->id() + s);
206#endif
207 }
208 }
209 }
210
211 // If the mesh is replicated then its automatic neighbor finding
212 // is fine. If not, then we need to insert them ourselves, but
213 // it's easy because we can use the fact (from our implementation
214 // above) that our new elements have no parents or children, plus
215 // the fact (from the tiny fraction of homology I understand) that
216 // a manifold boundary is a manifold with no boundary.
217 //
218 // See UnstructuredMesh::find_neighbors() for more explanation of
219 // (a more complicated version of) the algorithm here.
220 if (!mesh.is_replicated())
221 {
222 typedef dof_id_type key_type;
223 typedef std::pair<Elem *, unsigned char> val_type;
224 typedef std::unordered_multimap<key_type, val_type> map_type;
225 map_type side_to_elem_map;
226
227 std::unique_ptr<Elem> my_side, their_side;
228
229 for (auto & elem : elems_to_add)
230 {
231 for (auto s : elem->side_index_range())
232 {
233 if (elem->neighbor_ptr(s))
234 continue;
235 const dof_id_type key = elem->low_order_key(s);
236 auto bounds = side_to_elem_map.equal_range(key);
237 if (bounds.first != bounds.second)
238 {
239 elem->side_ptr(my_side, s);
240 while (bounds.first != bounds.second)
241 {
242 Elem * potential_neighbor = bounds.first->second.first;
243 const unsigned int ns = bounds.first->second.second;
244 potential_neighbor->side_ptr(their_side, ns);
245 if (*my_side == *their_side)
246 {
247 elem->set_neighbor(s, potential_neighbor);
248 potential_neighbor->set_neighbor(ns, elem.get());
249 side_to_elem_map.erase (bounds.first);
250 break;
251 }
252 ++bounds.first;
253 }
254
255 if (!elem->neighbor_ptr(s))
256 side_to_elem_map.emplace
257 (key, std::make_pair(elem.get(), cast_int<unsigned char>(s)));
258 }
259 }
260 }
261
262 // At this point we *should* have a match for everything, so
263 // anything we don't have a match for is remote.
264 for (auto & elem : elems_to_add)
265 for (auto s : elem->side_index_range())
266 if (!elem->neighbor_ptr(s))
267 elem->set_neighbor(s, const_cast<RemoteElem*>(remote_elem));
268 }
269
270 // Remove volume and edge elements
271 for (Elem * elem : elems_to_delete)
272 mesh.delete_elem(elem);
273
274 // Add the new elements outside the loop so we don't risk
275 // invalidating iterators.
276 for (auto & elem : elems_to_add)
277 mesh.add_elem(std::move(elem));
278 }
279
280 // Fix up neighbor pointers, element counts, etc.
282
283 // We're making tets; we need to start with tris
285
286 // Partition surface into connected components. At this point I'm
287 // finally going to give up and serialize, because at least we got
288 // from 3D down to 2D first, and because I don't want to have to
289 // turn flood_component into a while loop with a parallel sync in
290 // the middle, and because we do have to serialize *eventually*
291 // anyways unless we get a parallel tetrahedralizer backend someday.
292 MeshSerializer mesh_serializer(mesh);
293
294 std::vector<std::unordered_set<Elem *>> components;
295 std::unordered_set<Elem *> in_component;
296
297 for (auto * elem : mesh.element_ptr_range())
298 if (!in_component.count(elem))
299 components.emplace_back(flood_component(in_component, elem));
300
301 const std::unordered_set<Elem *> * biggest_component = nullptr;
302 Real biggest_six_vol = 0;
303 for (const auto & component : components)
304 {
305 Real six_vol = six_times_signed_volume(component);
306 if (std::abs(six_vol) > std::abs(biggest_six_vol))
307 {
308 biggest_six_vol = six_vol;
309 biggest_component = &component;
310 }
311 }
312
313 if (!biggest_component)
314 libmesh_error_msg("No non-zero-volume component found among " <<
315 components.size() << " boundary components");
316
317 for (const auto & component : components)
318 if (&component != biggest_component)
319 {
320 for (Elem * elem: component)
321 mesh.delete_elem(elem);
322 }
323 else
324 {
325 for (Elem * elem: component)
326 {
327 if (biggest_six_vol < 0)
328 elem->flip(&mesh.get_boundary_info());
329
330 for (auto & node : elem->node_ref_range())
331 surface_bb.union_with(node);
332 }
333 }
334
336
337 return surface_bb;
338}
dof_id_type & set_id()
Definition dof_object.h:827
void set_unique_id(unique_id_type new_id)
Sets the unique_id for this DofObject.
Definition dof_object.h:848
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
void set_neighbor(const unsigned int i, Elem *n)
Assigns n as the neighbor.
Definition elem.h:2635
void set_interior_parent(Elem *p)
Sets the pointer to the element's interior_parent.
Definition elem.C:1222
bool is_prepared() const
Definition mesh_base.C:1064
virtual bool is_replicated() const
Definition mesh_base.h:379
virtual dof_id_type max_elem_id() const =0
virtual unique_id_type parallel_max_unique_id() const =0
In parallel meshes where a ghost element has neighbors which do not exist on the local processor,...
Definition remote_elem.h:61
void all_tri(MeshBase &mesh)
Subdivides any non-simplex elements in a Mesh to produce simplex (triangular in 2D,...
bool valid_is_prepared(const MeshBase &mesh)
A function for testing whether a mesh's cached is_prepared() setting is not a false positive.
uint8_t unique_id_type
Definition id_types.h:86
const RemoteElem * remote_elem
Definition remote_elem.C:57

References libMesh::MeshBase::add_elem(), libMesh::MeshTools::Modification::all_tri(), libMesh::Elem::build_side_ptr(), libMesh::MeshBase::delete_elem(), libMesh::Elem::dim(), libMesh::Elem::flip(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::MeshBase::is_prepared(), libMesh::MeshBase::is_replicated(), libMesh::Elem::level(), libMesh::libmesh_assert(), libMesh::Elem::low_order_key(), libMesh::make_range(), libMesh::MeshBase::max_elem_id(), mesh, libMesh::Elem::n_sides(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ref_range(), libMesh::MeshBase::parallel_max_unique_id(), libMesh::MeshBase::prepare_for_use(), libMesh::Real, libMesh::remote_elem, libMesh::DofObject::set_id(), libMesh::Elem::set_interior_parent(), libMesh::Elem::set_neighbor(), libMesh::DofObject::set_unique_id(), libMesh::Elem::side_index_range(), libMesh::Elem::side_ptr(), libMesh::BoundingBox::union_with(), and libMesh::MeshTools::valid_is_prepared().

Referenced by triangulate().

Member Data Documentation

◆ _desired_volume

Real libMesh::MeshTetInterface::_desired_volume
protectedinherited

The desired volume for the elements in the resulting mesh.

Unlimited (indicated by 0) by default

Definition at line 184 of file mesh_tet_interface.h.

Referenced by libMesh::MeshTetInterface::desired_volume(), triangulate(), and libMesh::TetGenMeshInterface::triangulate_pointset().

◆ _elem_type

ElemType libMesh::MeshTetInterface::_elem_type
protectedinherited

The exact type of tetrahedra we intend to construct.

Definition at line 195 of file mesh_tet_interface.h.

Referenced by libMesh::MeshTetInterface::elem_type(), libMesh::MeshTetInterface::increase_tet_order(), and triangulate().

◆ _holes

std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh> > > libMesh::MeshTetInterface::_holes
protectedinherited

A pointer to a vector of meshes each defining a hole.

If this is nullptr, there are no holes!

Definition at line 206 of file mesh_tet_interface.h.

Referenced by libMesh::MeshTetInterface::attach_hole_list(), and triangulate().

◆ _mesh

UnstructuredMesh& libMesh::MeshTetInterface::_mesh
protectedinherited

◆ _serializer

MeshSerializer libMesh::NetGenMeshInterface::_serializer
protected

Tetgen only operates on serial meshes.

Definition at line 78 of file mesh_netgen_interface.h.

◆ _smooth_after_generating

bool libMesh::MeshTetInterface::_smooth_after_generating
protectedinherited

◆ _verbosity

unsigned int libMesh::MeshTetInterface::_verbosity
protectedinherited

The documentation for this class was generated from the following files: