libMesh
triangulator_interface.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 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 
19 #include "libmesh/libmesh_config.h"
20 
21 // libmesh includes
22 #include "libmesh/mesh_triangle_interface.h"
23 #include "libmesh/unstructured_mesh.h"
24 #include "libmesh/face_tri3.h"
25 #include "libmesh/face_tri6.h"
26 #include "libmesh/mesh_generation.h"
27 #include "libmesh/mesh_smoother_laplace.h"
28 #include "libmesh/boundary_info.h"
29 #include "libmesh/mesh_triangle_holes.h"
30 #include "libmesh/mesh_triangle_wrapper.h"
31 #include "libmesh/enum_elem_type.h"
32 #include "libmesh/enum_order.h"
33 #include "libmesh/enum_to_string.h"
34 #include "libmesh/utility.h"
35 
36 #include "libmesh/meshfree_interpolation.h"
37 
38 // C/C++ includes
39 #include <sstream>
40 
41 
42 namespace libMesh
43 {
44 //
45 // Function definitions for the AutoAreaFunction class
46 //
47 
48 // Constructor
50  const unsigned int num_nearest_pts,
51  const unsigned int power,
52  const Real background_value,
53  const Real background_eff_dist):
54  _comm(comm),
55  _num_nearest_pts(num_nearest_pts),
56  _power(power),
57  _background_value(background_value),
58  _background_eff_dist(background_eff_dist),
59  _auto_area_mfi(std::make_unique<InverseDistanceInterpolation<3>>(_comm, _num_nearest_pts, _power, _background_value, _background_eff_dist))
60 {
61  this->_initialized = false;
62  this->_is_time_dependent = false;
63 }
64 
65 // Destructor
67 
68 void AutoAreaFunction::init_mfi (const std::vector<Point> & input_pts,
69  const std::vector<Real> & input_vals)
70 {
71  std::vector<std::string> field_vars{"f"};
72  _auto_area_mfi->set_field_variables(field_vars);
73  _auto_area_mfi->get_source_points() = input_pts;
74 #ifdef LIBMESH_USE_COMPLEX_NUMBERS
75  std::vector<Number> input_complex_vals;
76  for (const auto & input_val : input_vals)
77  input_complex_vals.push_back(Complex (input_val, 0.0));
78  _auto_area_mfi->get_source_vals() = input_complex_vals;
79 #else
80  _auto_area_mfi->get_source_vals() = input_vals;
81 #endif
82  _auto_area_mfi->prepare_for_use();
83  this->_initialized = true;
84 }
85 
87  const Real /*time*/)
88 {
90 
91  std::vector<Point> target_pts;
92  std::vector<Number> target_vals;
93 
94  target_pts.push_back(p);
95  target_vals.resize(1);
96 
97  _auto_area_mfi->interpolate_field_data(_auto_area_mfi->field_variables(), target_pts, target_vals);
98 
99  return libmesh_real(target_vals.front());
100 }
101 
102 //
103 // Function definitions for the TriangulatorInterface class
104 //
105 
106 // Constructor
108  : _mesh(mesh),
109  _holes(nullptr),
110  _markers(nullptr),
111  _regions(nullptr),
112  _elem_type(TRI3),
113  _desired_area(0.1),
114  _minimum_angle(20.0),
115  _triangulation_type(GENERATE_CONVEX_HULL),
116  _insert_extra_points(false),
117  _smooth_after_generating(true),
118  _quiet(true),
119  _auto_area_function(nullptr)
120 {}
121 
122 
124 {
125  // Maybe we'll reserve a meaning for negatives later?
126  libmesh_assert(n_points >= 0);
127 
128  _interpolate_boundary_points = n_points;
129 
130  // backwards compatibility - someone (including us) might want to
131  // query this via the old API.
132  _insert_extra_points = n_points;
133 }
134 
135 
136 
138 {
139  // backwards compatibility - someone might have turned this off via
140  // the old API
142  return 0;
143 
145 }
146 
147 
148 
150 {
151  // Don't try to override manually specified segments
152  if (!this->segments.empty())
153  return;
154 
155  // If we have edges, they should form the polyline with the ordering
156  // we want. Let's turn them into segments for later use, because
157  // we're going to delete the original elements to replace with our
158  // triangulation.
159  if (_mesh.n_elem())
160  {
161  // Mapping from points to node ids, to back those out from
162  // MeshedHole results later
163  std::map<Point, dof_id_type> point_id_map;
164 
165  for (Node * node : _mesh.node_ptr_range())
166  {
167  // We're not going to support overlapping nodes on the boundary
168  libmesh_error_msg_if
169  (point_id_map.count(*node),
170  "TriangulatorInterface does not support overlapping nodes found at "
171  << static_cast<Point&>(*node));
172 
173  point_id_map.emplace(*node, node->id());
174  }
175 
176  // We don't support directly generating Tri6, so for
177  // compatibility with future stitching we need to be working
178  // with first-order elements. Let's get rid of any non-vertex
179  // nodes we just added.
180  for (Elem * elem : _mesh.element_ptr_range())
181  for (auto n : make_range(elem->n_vertices(), elem->n_nodes()))
182  point_id_map.erase(elem->point(n));
183 
184  // We'll steal the ordering calculation from
185  // the MeshedHole code
187 
188  // If we've specified only a subset of the mesh as our outer
189  // boundary, then we may have nodes that don't actually fall
190  // inside that boundary. Triangulator code doesn't like Steiner
191  // points that aren't inside the triangulation domain, so we
192  // need to get rid of them.
193  //
194  // Also, if we're using Edge3 elements to define our outer
195  // boundary, we're only dealing with their 2 end nodes and we'll
196  // need to get rid of their central nodes.
197  std::unordered_set<Node *> nodes_to_delete;
198 
199  for (Elem * elem : _mesh.element_ptr_range())
200  for (auto n : make_range(elem->n_vertices(), elem->n_nodes()))
201  nodes_to_delete.insert(elem->node_ptr(n));
202 
203  if (!this->_bdy_ids.empty())
204  {
205  for (auto & node : _mesh.node_ptr_range())
206  if (!mh.contains(*node))
207  nodes_to_delete.insert(node);
208  }
209 
210  // And now we're done with elements. Delete them lest they have
211  // dangling pointers to nodes we'll be deleting.
212  _mesh.clear_elems();
213 
214  // Make segments from boundary nodes; also make sure we don't
215  // delete them.
216  const std::size_t np = mh.n_points();
217  for (auto i : make_range(np))
218  {
219  const Point pt = mh.point(i);
220  const dof_id_type id0 = libmesh_map_find(point_id_map, pt);
221  nodes_to_delete.erase(_mesh.node_ptr(id0));
222  const Point next_pt = mh.point((np+i+1)%np);
223  const dof_id_type id1 = libmesh_map_find(point_id_map, next_pt);
224  this->segments.emplace_back(id0, id1);
225  for (auto m : make_range(mh.n_midpoints()))
226  {
227  this->segment_midpoints.emplace_back(mh.midpoint(m, i));
228  this->segment_midpoints_keys.emplace_back(pt);
229  }
230  }
231 
232  for (Node * node : nodes_to_delete)
233  _mesh.delete_node(node);
234 
235  if (this->_verify_hole_boundaries && _holes)
236  this->verify_holes(mh);
237  }
238 }
239 
240 
241 
243 {
244  // Don't try to override manually specified segments, or try to add
245  // segments if we're doing a convex hull
246  if (!this->segments.empty() || _triangulation_type != PSLG)
247  return;
248 
249  for (auto node_it = _mesh.nodes_begin(),
250  node_end = _mesh.nodes_end();
251  node_it != node_end;)
252  {
253  Node * node = *node_it;
254 
255  // If we're out of boundary nodes, the rest are going to be
256  // Steiner points or hole points
257  if (node->id() >= max_node_id)
258  break;
259 
260  ++node_it;
261 
262  Node * next_node = (node_it == node_end) ?
263  *_mesh.nodes_begin() : *node_it;
264 
265  this->segments.emplace_back(node->id(), next_node->id());
266  }
267 
268  if (this->_verify_hole_boundaries && _holes)
269  {
270  std::vector<Point> outer_pts;
271  for (auto segment : this->segments)
272  outer_pts.push_back(_mesh.point(segment.first));
273 
274  ArbitraryHole ah(outer_pts);
275  this->verify_holes(ah);
276  }
277 }
278 
279 
280 
282 {
283  // If the initial PSLG is really simple, e.g. an L-shaped domain or
284  // a square/rectangle, the resulting triangulation may be very
285  // "structured" looking. Sometimes this is a problem if your
286  // intention is to work with an "unstructured" looking grid. We can
287  // attempt to work around this limitation by inserting midpoints
288  // into the original PSLG. Inserting additional points into a
289  // set of points meant to be a convex hull usually makes less sense.
290 
291  const int n_interpolated = this->get_interpolate_boundary_points();
292  if ((_triangulation_type==PSLG) && n_interpolated)
293  {
294  // If we were lucky enough to start with contiguous node ids,
295  // let's keep them that way.
297 
298  std::vector<std::pair<unsigned int, unsigned int>> old_segments =
299  std::move(this->segments);
300 
301  // We expect to have converted any elems and/or nodes into
302  // segments by now.
303  libmesh_assert(!old_segments.empty());
304 
305  this->segments.clear();
306 
307  // Insert a new point on each segment at evenly spaced locations
308  // between existing boundary points.
309  // np=index into new points vector
310  // n =index into original points vector
311  for (auto old_segment : old_segments)
312  {
313  Node * begin_node = _mesh.node_ptr(old_segment.first);
314  Node * end_node = _mesh.node_ptr(old_segment.second);
315  dof_id_type current_id = begin_node->id();
316  for (auto i : make_range(n_interpolated))
317  {
318  // new points are equispaced along the original segments
319  const Point new_point =
320  ((n_interpolated-i) * *(Point *)(begin_node) +
321  (i+1) * *(Point *)(end_node)) /
322  (n_interpolated + 1);
323  Node * next_node = _mesh.add_point(new_point, nn++);
324  this->segments.emplace_back(current_id,
325  next_node->id());
326  current_id = next_node->id();
327  }
328  this->segments.emplace_back(current_id,
329  end_node->id());
330  }
331  }
332 }
333 
334 
336 {
337  switch (_elem_type)
338  {
339  case TRI3:
340  // Nothing to do if we're not requested to increase order
341  return;
342  case TRI6:
344  break;
345  case TRI7:
347  break;
348  default:
349  libmesh_not_implemented();
350  }
351 
352  // If we have any midpoint location data, we'll want to look it up
353  // by point. all_midpoints[{p, m}] will be the mth midpoint
354  // location following after point p (when traversing a triangle
355  // counter-clockwise)
356  std::map<std::pair<Point, unsigned int>, Point> all_midpoints;
357  unsigned int n_midpoints =
358  this->segment_midpoints.size() / this->segments.size();
359  libmesh_assert_equal_to(this->segments.size() * n_midpoints,
360  this->segment_midpoints.size());
361  for (auto m : make_range(n_midpoints))
362  for (auto i : make_range(this->segments.size()))
363  {
364  const Point & p = segment_midpoints_keys[i*n_midpoints+m];
365  all_midpoints[{p,m}] =
366  this->segment_midpoints[i*n_midpoints+m];
367  }
368 
369  if (_holes)
370  for (const Hole * hole : *_holes)
371  {
372  if (!hole->n_midpoints())
373  continue;
374  if (!n_midpoints)
375  n_midpoints = hole->n_midpoints();
376  else if (hole->n_midpoints() != n_midpoints)
377  libmesh_not_implemented_msg
378  ("Differing boundary midpoint counts " <<
379  hole->n_midpoints() << " and " << n_midpoints);
380 
381  // Our inner holes are expected to have points in
382  // counter-clockwise order, which is backwards from how we
383  // want to traverse them when iterating in counter-clockwise
384  // order over a triangle, so we'll need to reverse our maps
385  // carefully here.
386  const auto n_hole_points = hole->n_points();
387  libmesh_assert(n_hole_points);
388  for (auto m : make_range(n_midpoints))
389  {
390  for (auto i : make_range(n_hole_points-1))
391  {
392  const Point & p = hole->point(i+1);
393  all_midpoints[{p,m}] = hole->midpoint(n_midpoints-m-1, i);
394  }
395  const Point & p = hole->point(0);
396  all_midpoints[{p,m}] = hole->midpoint(n_midpoints-m-1, n_hole_points-1);
397  }
398  }
399 
400  // The n_midpoints > 1 case is for future proofing, but in the
401  // present we have EDGE4 and no TRI10 yet.
402  if (n_midpoints > 1)
403  libmesh_not_implemented_msg
404  ("Cannot construct triangles with more than 1 midpoint per edge");
405 
406  if (!n_midpoints)
407  return;
408 
409  for (Elem * elem : _mesh.element_ptr_range())
410  {
411  // This should only be called right after we've finished
412  // converting a triangulation to higher order
413  libmesh_assert_equal_to(elem->n_vertices(), 3);
414  libmesh_assert_not_equal_to(elem->default_order(), FIRST);
415 
416  for (auto n : make_range(3))
417  {
418  // Only hole/outer boundary segments need adjusted midpoints
419  if (elem->neighbor_ptr(n))
420  continue;
421 
422  const Point & p = elem->point(n);
423 
424  if (const auto it = all_midpoints.find({p,0});
425  it != all_midpoints.end())
426  elem->point(n+3) = it->second;
427  }
428  }
429 }
430 
431 
433 {
434  for (const Hole * hole : *_holes)
435  {
436  for (const Hole * hole2 : *_holes)
437  {
438  if (hole == hole2)
439  continue;
440 
441  for (auto i : make_range(hole2->n_points()))
442  if (hole->contains(hole2->point(i)))
443  libmesh_error_msg
444  ("Found point " << hole2->point(i) <<
445  " on one hole boundary and another's interior");
446  }
447 
448  for (auto i : make_range(hole->n_points()))
449  if (!outer_bdy.contains(hole->point(i)))
450  libmesh_error_msg
451  ("Found point " << hole->point(i) <<
452  " on hole boundary but outside outer boundary");
453  }
454 }
455 
456 
458 {
459  // If the holes vector is non-nullptr (and non-empty) we need to determine
460  // the number of additional points which the holes will add to the
461  // triangulation.
462  // Note that the number of points is always equal to the number of segments
463  // that form the holes.
464  unsigned int n_hole_points = 0;
465 
466  if (_holes)
467  for (const auto & hole : *_holes)
468  {
469  n_hole_points += hole->n_points();
470  // A hole at least has one enclosure.
471  // Points on enclosures are ordered so that we can add segments implicitly.
472  // Elements in segment_indices() indicates the starting points of all enclosures.
473  // The last element in segment_indices() is the number of total points.
474  libmesh_assert_greater(hole->segment_indices().size(), 1);
475  libmesh_assert_equal_to(hole->segment_indices().back(), hole->n_points());
476  }
477 
478  return n_hole_points;
479 }
480 
482  const unsigned int num_nearest_pts,
483  const unsigned int power,
484  const Real background_value,
485  const Real background_eff_dist)
486 {
487  _auto_area_function = std::make_unique<AutoAreaFunction>(comm, num_nearest_pts, power, background_value, background_eff_dist);
488 }
489 
491 {
492  if (!_auto_area_function->initialized())
493  {
494  // Points and target element sizes for the interpolation
495  std::vector<Point> function_points;
496  std::vector<Real> function_sizes;
497  calculate_auto_desired_area_samples(function_points, function_sizes);
498  _auto_area_function->init_mfi(function_points, function_sizes);
499  }
500  return _auto_area_function.get();
501 }
502 
503 void TriangulatorInterface::calculate_auto_desired_area_samples(std::vector<Point> & function_points,
504  std::vector<Real> & function_sizes,
505  const Real & area_factor)
506 {
507  // Get the hole mesh of the outer boundary
508  // Holes should already be attached if applicable when this function is called
509  const TriangulatorInterface::MeshedHole bdry_mh { _mesh, this->_bdy_ids };
510  // Collect all the centroid points of the outer boundary segments
511  // and the corresponding element sizes
512  for (unsigned int i = 0; i < bdry_mh.n_points(); i++)
513  {
514  function_points.push_back((bdry_mh.point(i) + bdry_mh.point((i + 1) % bdry_mh.n_points())) /
515  Real(2.0));
516  function_sizes.push_back(
517  (bdry_mh.point(i) - bdry_mh.point((i + 1) % bdry_mh.n_points())).norm());
518  }
519  // If holes are present, do the same for the hole boundaries
520  if(_holes)
521  for (const Hole * hole : *_holes)
522  {
523  for (unsigned int i = 0; i < hole->n_points(); i++)
524  {
525  function_points.push_back(
526  (hole->point(i) + hole->point((i + 1) % hole->n_points())) / Real(2.0));
527  function_sizes.push_back(
528  (hole->point(i) - hole->point((i + 1) % hole->n_points())).norm());
529  }
530  }
531 
532  std::for_each(
533  function_sizes.begin(), function_sizes.end(), [&area_factor](Real & a) { a = a * a * area_factor * std::sqrt(3.0) / 4.0; });
534 
535 }
536 } // namespace libMesh
537 
T libmesh_real(T a)
FunctionBase< Real > * get_auto_area_function()
Get the auto area function.
bool _verify_hole_boundaries
Flag which tells if we want to check hole geometry.
void init_mfi(const std::vector< Point > &input_pts, const std::vector< Real > &input_vals)
Inverse distance interpolation.
bool _insert_extra_points
Flag which tells whether or not to insert additional nodes before triangulation.
A Node is like a Point, but with more information.
Definition: node.h:52
std::set< std::size_t > _bdy_ids
A set of ids to allow on the outer boundary loop.
void insert_any_extra_boundary_points()
Helper function to add extra points (midpoints of initial segments) to a PSLG triangulation.
An abstract class for defining a 2-dimensional hole.
virtual Real operator()(const Point &p, const Real) override
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
bool _initialized
When init() was called so that everything is ready for calls to operator() (...), then this bool is t...
void set_interpolate_boundary_points(int n_points)
Complicated setter, for compatibility with insert_extra_points()
int _interpolate_boundary_points
Flag which tells how many additional nodes should be inserted between each pair of original mesh poin...
UnstructuredMesh & _mesh
Reference to the mesh which is to be created by triangle.
The libMesh namespace provides an interface to certain functionality in the library.
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 elems_to_segments()
Helper function to create PSLG segments from our other boundary-defining options (1D mesh edges...
ElemType _elem_type
The type of elements to generate.
void calculate_auto_desired_area_samples(std::vector< Point > &function_points, std::vector< Real > &function_sizes, const Real &area_factor=1.5)
The external boundary and all hole boundaries are collected.
dof_id_type id() const
Definition: dof_object.h:828
The UnstructuredMesh class is derived from the MeshBase class.
const std::vector< Hole * > * _holes
A pointer to a vector of Hole*s.
libmesh_assert(ctx)
virtual void delete_node(Node *n)=0
Removes the Node n from the mesh.
std::unique_ptr< InverseDistanceInterpolation< 3 > > _auto_area_mfi
std::vector< Point > segment_midpoints_keys
When saving the midpoint location data, we need to save the corresponding segment information too...
void increase_triangle_order()
Helper function to upconvert Tri3 to any higher order triangle type if requested via _elem_type...
bool _is_time_dependent
Cache whether or not this function is actually time-dependent.
unsigned int total_hole_points()
Helper function to count points in and verify holes.
std::vector< Point > segment_midpoints
When constructing a second-order triangulation from a second-order boundary, we may do the triangulat...
TriangulatorInterface(UnstructuredMesh &mesh)
The constructor.
virtual void clear_elems()=0
Deletes all the element data that is currently stored.
Triangulate the interior of a Planar Straight Line Graph, which is defined implicitly by the order of...
std::complex< Real > Complex
bool contains(Point p) const
Return true iff p lies inside the hole.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< std::pair< unsigned int, unsigned int > > segments
When constructing a PSLG, if the node numbers do not define the desired boundary segments implicitly ...
void nodes_to_segments(dof_id_type max_node_id)
Helper function to create PSLG segments from our node ordering, up to the maximum node id...
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:1650
Another concrete instantiation of the hole, as general as ArbitraryHole, but based on an existing 1D ...
int get_interpolate_boundary_points() const
Complicated getter, for compatibility with insert_extra_points()
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:140
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:1645
std::unique_ptr< AutoAreaFunction > _auto_area_function
The auto area function based on the spacing of boundary points.
AutoAreaFunction(const Parallel::Communicator &comm, const unsigned int num_nearest_pts, const unsigned int power, const Real background_value, const Real background_eff_dist)
Another concrete instantiation of the hole, this one should be sufficiently general for most non-poly...
virtual const Point & point(const dof_id_type i) const =0
void verify_holes(const Hole &outer_bdy)
Helper function to check holes for intersections if requested.
virtual dof_id_type max_node_id() const =0
virtual dof_id_type n_elem() const =0
void set_auto_area_function(const Parallel::Communicator &comm, const unsigned int num_nearest_pts, const unsigned int power, const Real background_value, const Real background_eff_dist)
Generate an auto area function based on spacing of boundary points.
virtual const Node * node_ptr(const dof_id_type i) const =0
TriangulationType _triangulation_type
The type of triangulation to perform: choices are: convex hull PSLG.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
uint8_t dof_id_type
Definition: id_types.h:67