libMesh
mesh_netgen_interface.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2023 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 #include "libmesh/libmesh_config.h"
19 #ifdef LIBMESH_HAVE_NETGEN
20 
21 
22 // C++ includes
23 #include <sstream>
24 
25 // Local includes
26 #include "libmesh/mesh_netgen_interface.h"
27 
28 #include "libmesh/boundary_info.h"
29 #include "libmesh/cell_tet4.h"
30 #include "libmesh/face_tri3.h"
31 #include "libmesh/libmesh_logging.h"
32 #include "libmesh/mesh_communication.h"
33 #include "libmesh/threads.h"
34 #include "libmesh/unstructured_mesh.h"
35 #include "libmesh/utility.h" // libmesh_map_find
36 
37 namespace nglib {
38 #include "netgen/nglib/nglib.h"
39 }
40 
41 namespace {
42 
43 // RAII for exception safety
44 class WrappedNgMesh
45 {
46 public:
47  WrappedNgMesh() {
48  _ngmesh = nglib::Ng_NewMesh();
49  }
50 
51  ~WrappedNgMesh() {
52  nglib::Ng_DeleteMesh(_ngmesh);
53  }
54 
55  void clear() {
56  nglib::Ng_DeleteMesh(_ngmesh);
57  _ngmesh = nglib::Ng_NewMesh();
58  }
59 
60  operator nglib::Ng_Mesh* () {
61  return _ngmesh;
62  }
63 
64 private:
65  nglib::Ng_Mesh * _ngmesh;
66 };
67 
68 }
69 
70 namespace libMesh
71 {
72 
73 //----------------------------------------------------------------------
74 // NetGenMeshInterface class members
77  _serializer(mesh)
78 {
79 }
80 
81 
82 
84 {
85  using namespace nglib;
86 
87  LOG_SCOPE("triangulate()", "NetGenMeshInterface");
88 
89  // We're hoping to do volume_to_surface_mesh in parallel at least,
90  // but then we'll need to serialize any hole meshes to rank 0 so it
91  // can use them in serial.
92 
93  const BoundingBox mesh_bb =
95 
96  std::vector<MeshSerializer> hole_serializers;
97  if (_holes)
98  for (std::unique_ptr<UnstructuredMesh> & hole : *_holes)
99  {
100  const BoundingBox hole_bb =
102 
103  libmesh_error_msg_if
104  (!mesh_bb.contains(hole_bb),
105  "Found hole with bounding box " << hole_bb <<
106  "\nextending outside of mesh bounding box " << mesh_bb);
107 
108  hole_serializers.emplace_back
109  (*hole, /* need_serial */ true,
110  /* serial_only_needed_on_proc_0 */ true);
111  }
112 
113  // This should probably only be done on rank 0, but the API is
114  // designed with the hope that we'll parallelize it eventually
115  auto integrity = this->improve_hull_integrity();
116  this->process_hull_integrity_result(integrity);
117 
118  // If we're not rank 0, we're just going to wait for rank 0 to call
119  // Netgen, then receive its data afterward, we're not going to hope
120  // that Netgen does the exact same thing on every processor.
121  if (this->_mesh.processor_id() != 0)
122  {
123  // We don't need our holes anymore. Delete their serializers
124  // first to avoid dereferencing dangling pointers.
125  hole_serializers.clear();
126  if (_holes)
127  _holes->clear();
128 
129  // Receive the mesh data rank 0 will send later, then fix it up
130  // together
132 
133  // If we got an empty mesh here then our tetrahedralization
134  // failed.
135  libmesh_error_msg_if (!this->_mesh.n_elem(),
136  "NetGen failed to generate any tetrahedra");
137 
138  this->_mesh.prepare_for_use();
139  return;
140  }
141 
142  Ng_Meshing_Parameters params;
143 
144  Ng_SetNumThreads(cast_int<int>(libMesh::n_threads()));
145 
146  // Override any default parameters we might need to, to avoid
147  // inserting nodes we don't want.
148  params.uselocalh = false;
149  params.minh = 0;
150  params.elementsperedge = 1;
151  params.elementspercurve = 1;
152  params.closeedgeenable = false;
153  params.closeedgefact = 0;
154  params.minedgelenenable = false;
155  params.minedgelen = 0;
156 
157  // Try to get a no-extra-nodes mesh if we're asked to, or try to
158  // translate our desired volume into NetGen terms otherwise.
159  //
160  // Spoiler alert: all we can do is try; NetGen uses a marching front
161  // algorithm that can insert extra nodes despite all my best
162  // efforts.
163  if (_desired_volume == 0) // shorthand for "no refinement"
164  {
165  params.maxh = std::numeric_limits<double>::max();
166  params.fineness = 0; // "coarse" in the docs
167  params.grading = 1; // "aggressive local grading" to avoid smoothing??
168 
169  // Turning off optimization steps avoids another opportunity for
170  // Netgen to try to add more nodes.
171  params.optsteps_3d = 0;
172  }
173  else
174  params.maxh = double(std::pow(_desired_volume, 1./3.));
175 
176  // Keep track of how NetGen copies of nodes map back to our original
177  // nodes, so we can connect new elements to nodes correctly.
178  std::unordered_map<int, dof_id_type> ng_to_libmesh_id;
179 
180  auto handle_ng_result = [](Ng_Result result) {
181  static const std::vector<std::string> result_types =
182  {"Netgen error", "Netgen success", "Netgen surface input error",
183  "Netgen volume failure", "Netgen STL input error",
184  "Netgen surface failure", "Netgen file not found"};
185 
186  if (result+1 >= 0 &&
187  std::size_t(result+1) < result_types.size())
188  libmesh_error_msg_if
189  (result, "Ng_GenerateVolumeMesh failed: " <<
190  result_types[result+1]);
191  else
192  libmesh_error_msg
193  ("Ng_GenerateVolumeMesh failed with an unknown error code");
194  };
195 
196  // Keep track of what boundary ids we want to assign to each new
197  // triangle. We'll give the outer boundary BC 0, and give holes ids
198  // starting from 1.
199  // We key on sorted tuples of node ids to identify a side.
200  std::unordered_map<std::array<dof_id_type,3>,
201  boundary_id_type, libMesh::hash> side_boundary_id;
202 
203  auto insert_id = []
204  (std::array<dof_id_type,3> & array,
205  dof_id_type n_id)
206  {
207  libmesh_assert_less(n_id, DofObject::invalid_id);
208  unsigned int i=0;
209  while (array[i] < n_id)
210  ++i;
211  while (i < 3)
212  std::swap(array[i++], n_id);
213  };
214 
215  WrappedNgMesh ngmesh;
216 
217  // Create surface mesh in the WrappedNgMesh
218  {
219  // NetGen appears to use ONE-BASED numbering for its nodes, and
220  // since it doesn't return an id when adding nodes we'll have to
221  // track the numbering ourselves.
222  int ng_id = 1;
223 
224  auto create_surface_component =
225  [this, &ng_id, &ng_to_libmesh_id, &ngmesh, &side_boundary_id, &insert_id]
226  (UnstructuredMesh & srcmesh, bool hole_mesh,
227  boundary_id_type bcid)
228  {
229  LOG_SCOPE("create_surface_component()", "NetGenMeshInterface");
230 
231  // Keep track of what nodes we've already added to the Netgen
232  // mesh vs what nodes we need to add. We'll keep track by id,
233  // not by point location. I don't know if Netgen can handle
234  // multiple nodes with the same point location, but if they can
235  // it's not going to be *us* who breaks that feature.
236  std::unordered_map<dof_id_type, int> libmesh_to_ng_id;
237 
238  // Keep track of what nodes we've already added to the main
239  // mesh from a hole mesh.
240  std::unordered_map<dof_id_type, dof_id_type> hole_to_main_mesh_id;
241 
242  // Use a separate array for passing points to NetGen, just in case
243  // we're not using double-precision ourselves.
244  std::array<double, 3> point_val;
245 
246  // And an array for element vertices
247  std::array<int, 3> elem_nodes;
248 
249  for (const auto * elem : srcmesh.element_ptr_range())
250  {
251  // If someone has triangles we can't triangulate, we have a
252  // problem
253  if (elem->type() == TRI6 ||
254  elem->type() == TRI7)
255  libmesh_not_implemented_msg
256  ("Netgen tetrahedralization currently only supports TRI3 boundaries");
257 
258  // If someone has non-triangles, let's just ignore them.
259  if (elem->type() != TRI3)
260  continue;
261 
262  std::array<dof_id_type,3> sorted_ids =
265 
266  for (int ni : make_range(3))
267  {
268  // Just using the "invert_trigs" option in NetGen params
269  // doesn't work for me, so we'll have to have properly
270  // oriented the tris earlier.
271  auto & elem_node = hole_mesh ? elem_nodes[2-ni] : elem_nodes[ni];
272 
273  const Node & n = elem->node_ref(ni);
274  auto n_id = n.id();
275  if (hole_mesh)
276  {
277  if (auto it = hole_to_main_mesh_id.find(n_id);
278  it != hole_to_main_mesh_id.end())
279  {
280  n_id = it->second;
281  }
282  else
283  {
284  Node * n_new = this->_mesh.add_point(n);
285  const dof_id_type n_new_id = n_new->id();
286  hole_to_main_mesh_id.emplace(n_id, n_new_id);
287  n_id = n_new_id;
288  }
289  }
290 
291  if (auto it = libmesh_to_ng_id.find(n_id);
292  it != libmesh_to_ng_id.end())
293  {
294  const int existing_ng_id = it->second;
295  elem_node = existing_ng_id;
296  }
297  else
298  {
299  for (auto i : make_range(3))
300  point_val[i] = double(n(i));
301 
302  Ng_AddPoint(ngmesh, point_val.data());
303 
304  ng_to_libmesh_id[ng_id] = n_id;
305  libmesh_to_ng_id[n_id] = ng_id;
306  elem_node = ng_id;
307  ++ng_id;
308  }
309 
310  insert_id(sorted_ids, n_id);
311  }
312 
313  side_boundary_id[sorted_ids] = bcid;
314 
315  Ng_AddSurfaceElement(ngmesh, NG_TRIG, elem_nodes.data());
316  }
317  };
318 
319  // Number the outer boundary 0, and the holes starting from 1
320  boundary_id_type bcid = 0;
321 
322  create_surface_component(this->_mesh, false, bcid);
323 
324  if (_holes)
325  for (const std::unique_ptr<UnstructuredMesh> & h : *_holes)
326  create_surface_component(*h, true, ++bcid);
327  }
328 
329  {
330  LOG_SCOPE("Ng_GenerateVolumeMesh()", "NetGenMeshInterface");
331 
332  auto result = Ng_GenerateVolumeMesh(ngmesh, &params);
333  handle_ng_result(result);
334  }
335 
336  const int n_elem = Ng_GetNE(ngmesh);
337 
338  // If Netgen fails us, we're likely to get n_elem <= 0. This is a
339  // common enough failure from bad setups that I want to make sure
340  // it's thrown in parallel so as to not desynchronize any unit tests
341  // that trigger it. So we'll broadcast the empty mesh to indicate
342  // the problem and enable throwing exceptions in parallel.
343  if (n_elem <= 0)
344  {
345  this->_mesh.clear();
347  libmesh_error_msg ("NetGen failed to generate any tetrahedra");
348  }
349 
350  const dof_id_type n_points = Ng_GetNP(ngmesh);
351  const dof_id_type old_nodes = this->_mesh.n_nodes();
352 
353  // Netgen may have generated new interior nodes
354  if (n_points != old_nodes)
355  {
356  std::array<double, 3> point_val;
357 
358  // We should only be getting new nodes if we asked for them
359  if (!_desired_volume)
360  {
361  std::cout <<
362  "NetGen output " << n_points <<
363  " points when we gave it " <<
364  old_nodes << " and disabled refinement\n" <<
365  "If new interior points are acceptable in your mesh, please set\n" <<
366  "a non-zero desired_volume to indicate that. If new interior\n" <<
367  "points are not acceptable in your mesh, you may need a different\n" <<
368  "(non-advancing-front?) mesh generator." << std::endl;
369  libmesh_error();
370  }
371  else
372  for (auto i : make_range(old_nodes, n_points))
373  {
374  // i+1 since ng uses ONE-BASED numbering
375  Ng_GetPoint (ngmesh, i+1, point_val.data());
376  const Point p(point_val[0], point_val[1], point_val[2]);
377  Node * n_new = this->_mesh.add_point(p);
378  const dof_id_type n_new_id = n_new->id();
379  ng_to_libmesh_id[i+1] = n_new_id;
380  }
381  }
382 
383  for (auto * elem : this->_mesh.element_ptr_range())
384  this->_mesh.delete_elem(elem);
385 
386  BoundaryInfo * bi = & this->_mesh.get_boundary_info();
387 
388  for (auto i : make_range(n_elem))
389  {
390  // Enough data to return even a Tet10 without a segfault if nglib
391  // went nuts
392  int ngnodes[11];
393 
394  // i+1 since we must be 1-based with these ids too...
395  Ng_Volume_Element_Type ngtype =
396  Ng_GetVolumeElement(ngmesh, i+1, ngnodes);
397 
398  // But really nglib shouldn't go nuts
399  libmesh_assert(ngtype == NG_TET);
400  libmesh_ignore(ngtype);
401 
402  auto elem = this->_mesh.add_elem(Elem::build_with_id(TET4, i));
403  for (auto n : make_range(4))
404  {
405  const dof_id_type node_id =
406  libmesh_map_find(ng_to_libmesh_id, ngnodes[n]);
407  elem->set_node(n, this->_mesh.node_ptr(node_id));
408  }
409 
410  // NetGen and we disagree about node numbering orientation
411  elem->orient(bi);
412 
413  for (auto s : make_range(4))
414  {
415  std::array<dof_id_type,3> sorted_ids =
418 
419  std::vector<unsigned int> nos = elem->nodes_on_side(s);
420  for (auto n : nos)
421  insert_id(sorted_ids, elem->node_id(n));
422 
423  if (auto it = side_boundary_id.find(sorted_ids);
424  it != side_boundary_id.end())
425  bi->add_side(elem, s, it->second);
426  }
427  }
428 
429  // We don't need our holes anymore. Delete their serializers
430  // first to avoid dereferencing dangling pointers.
431  hole_serializers.clear();
432  if (_holes)
433  _holes->clear();
434 
435  // Send our data to other ranks, then fix it up together
437  this->_mesh.prepare_for_use();
438 }
439 
440 
441 
442 } // namespace libMesh
443 
444 
445 #endif // #ifdef LIBMESH_HAVE_NETGEN
NetGenMeshInterface(UnstructuredMesh &mesh)
Constructor.
std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > _holes
A pointer to a vector of meshes each defining a hole.
A Node is like a Point, but with more information.
Definition: node.h:52
unsigned int n_threads()
Definition: libmesh_base.h:109
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).
Definition: mesh_tools.C:1004
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
MeshBase & mesh
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_...
virtual void triangulate() override
Method invokes NetGen library to compute a tetrahedralization.
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
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.
void libmesh_ignore(const Args &...)
T pow(const T &x)
Definition: utility.h:296
int8_t boundary_id_type
Definition: id_types.h:51
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
This is the MeshCommunication class.
dof_id_type id() const
Definition: dof_object.h:819
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:473
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.
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
libmesh_assert(ctx)
static BoundingBox volume_to_surface_mesh(UnstructuredMesh &mesh)
Remove volume elements from the given mesh, after converting their outer boundary faces to surface el...
Definition: libmesh.C:81
virtual void clear()
Deletes all the element and node data that is currently stored.
Definition: mesh_base.C:1029
Defines a Cartesian bounding box by the two corner extremum.
Definition: bounding_box.h:40
Real _desired_volume
The desired volume for the elements in the resulting mesh.
static std::unique_ptr< Elem > build_with_id(const ElemType type, dof_id_type id)
Calls the build() method above with a nullptr parent, and additionally sets the newly-created Elem&#39;s ...
Definition: elem.C:556
void broadcast(MeshBase &) const
Finds all the processors that may contain elements that neighbor my elements.
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
Add side side of element number elem with boundary id id to the boundary information data structure...
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
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...
bool contains(const BoundingBox &) const
Definition: bounding_box.h:209
virtual dof_id_type n_elem() const =0
virtual const Node * node_ptr(const dof_id_type i) const =0
processor_id_type processor_id() const
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
virtual dof_id_type n_nodes() const =0
Class MeshTetInterface provides an abstract interface for tetrahedralization of meshes by subclasses...
uint8_t dof_id_type
Definition: id_types.h:67