LCOV - code coverage report
Current view: top level - src/mesh - triangulator_interface.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4510 (71b108) with base 165cb7 Lines: 219 290 75.5 %
Date: 2026-07-31 22:57:43 Functions: 12 21 57.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2026 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             : // We'd like reproduceability here even when different FP rounding can
      22             : // lead to different triangle splitting decisions
      23             : #include "libmesh/enforce_ieee754.h"
      24             : 
      25             : // libmesh includes
      26             : #include "libmesh/mesh_triangle_interface.h"
      27             : #include "libmesh/unstructured_mesh.h"
      28             : #include "libmesh/face_tri3.h"
      29             : #include "libmesh/face_tri6.h"
      30             : #include "libmesh/mesh_generation.h"
      31             : #include "libmesh/mesh_smoother_laplace.h"
      32             : #include "libmesh/boundary_info.h"
      33             : #include "libmesh/mesh_triangle_holes.h"
      34             : #include "libmesh/mesh_triangle_wrapper.h"
      35             : #include "libmesh/enum_elem_type.h"
      36             : #include "libmesh/enum_order.h"
      37             : #include "libmesh/enum_to_string.h"
      38             : #include "libmesh/utility.h"
      39             : 
      40             : #include "libmesh/meshfree_interpolation.h"
      41             : 
      42             : // C/C++ includes
      43             : #include <limits>
      44             : #include <sstream>
      45             : 
      46             : 
      47             : namespace libMesh
      48             : {
      49             : //
      50             : // Function definitions for the AutoAreaFunction class
      51             : //
      52             : 
      53             : // Constructor
      54           0 : AutoAreaFunction::AutoAreaFunction (const Parallel::Communicator &comm,
      55             :                                     const unsigned int num_nearest_pts,
      56             :                                     const unsigned int power,
      57             :                                     const Real background_value,
      58           0 :                                     const Real  background_eff_dist):
      59           0 :   _comm(comm),
      60           0 :   _num_nearest_pts(num_nearest_pts),
      61           0 :   _power(power),
      62           0 :   _background_value(background_value),
      63           0 :   _background_eff_dist(background_eff_dist),
      64           0 :   _auto_area_mfi(std::make_unique<InverseDistanceInterpolation<3>>(_comm, _num_nearest_pts, _power, _background_value, _background_eff_dist))
      65             : {
      66           0 :   this->_initialized = false;
      67           0 :   this->_is_time_dependent = false;
      68           0 : }
      69             : 
      70             : // Destructor
      71           0 : AutoAreaFunction::~AutoAreaFunction () = default;
      72             : 
      73           0 : void AutoAreaFunction::init_mfi (const std::vector<Point> & input_pts,
      74             :                                  const std::vector<Real> & input_vals)
      75             : {
      76           0 :   std::vector<std::string> field_vars{"f"};
      77           0 :   _auto_area_mfi->set_field_variables(field_vars);
      78           0 :   _auto_area_mfi->get_source_points() = input_pts;
      79             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
      80             :   std::vector<Number> input_complex_vals;
      81           0 :   for (const auto & input_val : input_vals)
      82           0 :     input_complex_vals.push_back(Complex (input_val, 0.0));
      83           0 :   _auto_area_mfi->get_source_vals() = input_complex_vals;
      84             : #else
      85           0 :   _auto_area_mfi->get_source_vals() = input_vals;
      86             : #endif
      87           0 :   _auto_area_mfi->prepare_for_use();
      88           0 :   this->_initialized = true;
      89           0 : }
      90             : 
      91           0 : Real AutoAreaFunction::operator() (const Point & p,
      92             :                                    const Real /*time*/)
      93             : {
      94           0 :   libmesh_assert(this->_initialized);
      95             : 
      96           0 :   std::vector<Point> target_pts;
      97           0 :   std::vector<Number> target_vals;
      98             : 
      99           0 :   target_pts.push_back(p);
     100           0 :   target_vals.resize(1);
     101             : 
     102           0 :   _auto_area_mfi->interpolate_field_data(_auto_area_mfi->field_variables(), target_pts, target_vals);
     103             : 
     104           0 :   return libmesh_real(target_vals.front());
     105             : }
     106             : 
     107             : //
     108             : // Function definitions for the TriangulatorInterface class
     109             : //
     110             : 
     111             : // Constructor
     112        2119 : TriangulatorInterface::TriangulatorInterface(UnstructuredMesh & mesh)
     113        1943 :   : _mesh(mesh),
     114        1943 :     _holes(nullptr),
     115        1943 :     _markers(nullptr),
     116        1943 :     _regions(nullptr),
     117        1943 :     _elem_type(TRI3),
     118        1943 :     _desired_area(0.1),
     119        1943 :     _minimum_angle(20.0),
     120        1943 :     _triangulation_type(GENERATE_CONVEX_HULL),
     121        1943 :     _insert_extra_points(false),
     122        1943 :     _smooth_after_generating(true),
     123        1943 :     _quiet(true),
     124        1943 :     _fixup_tri7_center_nodes(false),
     125        2295 :     _auto_area_function(nullptr)
     126        2119 : {}
     127             : 
     128             : 
     129         221 : void TriangulatorInterface::set_interpolate_boundary_points (int n_points)
     130             : {
     131             :   // Maybe we'll reserve a meaning for negatives later?
     132          10 :   libmesh_assert(n_points >= 0);
     133             : 
     134         221 :   _interpolate_boundary_points = n_points;
     135             : 
     136             :   // backwards compatibility - someone (including us) might want to
     137             :   // query this via the old API.
     138         221 :   _insert_extra_points = n_points;
     139         221 : }
     140             : 
     141             : 
     142             : 
     143        2279 : int TriangulatorInterface::get_interpolate_boundary_points () const
     144             : {
     145             :   // backwards compatibility - someone might have turned this off via
     146             :   // the old API
     147        2279 :   if (!_insert_extra_points)
     148         292 :     return 0;
     149             : 
     150         221 :   return _interpolate_boundary_points;
     151             : }
     152             : 
     153             : 
     154             : 
     155        2492 : void TriangulatorInterface::elems_to_segments()
     156             : {
     157             :   // Don't try to override manually specified segments
     158        2492 :   if (!this->segments.empty())
     159           4 :     return;
     160             : 
     161             :   // If we have edges, they should form the polyline with the ordering
     162             :   // we want.  Let's turn them into segments for later use, because
     163             :   // we're going to delete the original elements to replace with our
     164             :   // triangulation.
     165        2417 :   if (_mesh.n_elem())
     166             :     {
     167             :       // Mapping from points to node ids, to back those out from
     168             :       // MeshedHole results later
     169          56 :       std::map<Point, dof_id_type> point_id_map;
     170             : 
     171        7393 :       for (Node * node : _mesh.node_ptr_range())
     172             :         {
     173             :           // We're not going to support overlapping nodes on the boundary
     174        5835 :           libmesh_error_msg_if
     175             :             (point_id_map.count(*node),
     176             :              "TriangulatorInterface does not support overlapping nodes found at "
     177             :              << static_cast<Point&>(*node));
     178             : 
     179        5835 :           point_id_map.emplace(*node, node->id());
     180         737 :         }
     181             : 
     182             :       // We don't support directly generating Tri6, so for
     183             :       // compatibility with future stitching we need to be working
     184             :       // with first-order elements.  Let's get rid of any non-vertex
     185             :       // nodes we just added.
     186        8802 :       for (Elem * elem : _mesh.element_ptr_range())
     187        5480 :         for (auto n : make_range(elem->n_vertices(), elem->n_nodes()))
     188         849 :           point_id_map.erase(elem->point(n));
     189             : 
     190             :       // We'll steal the ordering calculation from
     191             :       // the MeshedHole code
     192        1608 :       const TriangulatorInterface::MeshedHole mh { _mesh, this->_bdy_ids };
     193             : 
     194             :       // If we've specified only a subset of the mesh as our outer
     195             :       // boundary, then we may have nodes that don't actually fall
     196             :       // inside that boundary.  Triangulator code doesn't like Steiner
     197             :       // points that aren't inside the triangulation domain, so we
     198             :       // need to get rid of them.
     199             :       //
     200             :       // Also, if we're using Edge3 elements to define our outer
     201             :       // boundary, we're only dealing with their 2 end nodes and we'll
     202             :       // need to get rid of their central nodes.
     203          44 :       std::unordered_set<Node *> nodes_to_delete;
     204             : 
     205        6864 :       for (Elem * elem : _mesh.element_ptr_range())
     206        4699 :         for (auto n : make_range(elem->n_vertices(), elem->n_nodes()))
     207        2312 :           nodes_to_delete.insert(elem->node_ptr(n));
     208             : 
     209         580 :       if (!this->_bdy_ids.empty())
     210             :         {
     211        4048 :           for (auto & node : _mesh.node_ptr_range())
     212        1953 :             if (!mh.contains(*node))
     213         204 :               nodes_to_delete.insert(node);
     214             :         }
     215             : 
     216             :       // And now we're done with elements.  Delete them lest they have
     217             :       // dangling pointers to nodes we'll be deleting.
     218         580 :       _mesh.clear_elems();
     219             : 
     220             :       // Make segments from boundary nodes; also make sure we don't
     221             :       // delete them.
     222         580 :       const std::size_t np = mh.n_points();
     223        2829 :       for (auto i : make_range(np))
     224             :         {
     225        2249 :           const Point pt = mh.point(i);
     226        2249 :           const dof_id_type id0 = libmesh_map_find(point_id_map, pt);
     227        2249 :           nodes_to_delete.erase(_mesh.node_ptr(id0));
     228        2249 :           const Point next_pt = mh.point((np+i+1)%np);
     229        2249 :           const dof_id_type id1 = libmesh_map_find(point_id_map, next_pt);
     230        2249 :           this->segments.emplace_back(id0, id1);
     231        3614 :           for (auto m : make_range(mh.n_midpoints()))
     232             :           {
     233        1365 :             this->segment_midpoints.emplace_back(mh.midpoint(m, i));
     234        1365 :             this->segment_midpoints_keys.emplace_back(pt);
     235             :           }
     236             :         }
     237             : 
     238        3030 :       for (Node * node : nodes_to_delete)
     239        2450 :         _mesh.delete_node(node);
     240             : 
     241         580 :       if (this->_verify_hole_boundaries && _holes)
     242           0 :         this->verify_holes(mh);
     243         536 :     }
     244             : }
     245             : 
     246             : 
     247             : 
     248        2279 : void TriangulatorInterface::nodes_to_segments(dof_id_type max_node_id)
     249             : {
     250             :   // Don't try to override manually specified segments, or try to add
     251             :   // segments if we're doing a convex hull
     252        2279 :   if (!this->segments.empty() || _triangulation_type != PSLG)
     253         256 :     return;
     254             : 
     255        1210 :   for (auto node_it = _mesh.nodes_begin(),
     256        1210 :        node_end = _mesh.nodes_end();
     257        5828 :        node_it != node_end;)
     258             :     {
     259        4664 :       Node * node = *node_it;
     260             : 
     261             :       // If we're out of boundary nodes, the rest are going to be
     262             :       // Steiner points or hole points
     263        4664 :       if (node->id() >= max_node_id)
     264           0 :         break;
     265             : 
     266        4476 :       ++node_it;
     267             : 
     268        9140 :       Node * next_node = (node_it == node_end) ?
     269        1306 :         *_mesh.nodes_begin() : *node_it;
     270             : 
     271        4664 :       this->segments.emplace_back(node->id(), next_node->id());
     272             :     }
     273             : 
     274        1164 :   if (this->_verify_hole_boundaries && _holes)
     275             :     {
     276          48 :       std::vector<Point> outer_pts;
     277        3255 :       for (auto segment : this->segments)
     278        2604 :         outer_pts.push_back(_mesh.point(segment.first));
     279             : 
     280         699 :       ArbitraryHole ah(outer_pts);
     281         651 :       this->verify_holes(ah);
     282         603 :     }
     283             : }
     284             : 
     285             : 
     286             : 
     287        2279 : void TriangulatorInterface::insert_any_extra_boundary_points()
     288             : {
     289             :   // If the initial PSLG is really simple, e.g. an L-shaped domain or
     290             :   // a square/rectangle, the resulting triangulation may be very
     291             :   // "structured" looking.  Sometimes this is a problem if your
     292             :   // intention is to work with an "unstructured" looking grid.  We can
     293             :   // attempt to work around this limitation by inserting midpoints
     294             :   // into the original PSLG.  Inserting additional points into a
     295             :   // set of points meant to be a convex hull usually makes less sense.
     296             : 
     297        2279 :   const int n_interpolated = this->get_interpolate_boundary_points();
     298        2279 :   if ((_triangulation_type==PSLG) && n_interpolated)
     299             :     {
     300             :       // If we were lucky enough to start with contiguous node ids,
     301             :       // let's keep them that way.
     302         221 :       dof_id_type nn = _mesh.max_node_id();
     303             : 
     304             :       std::vector<std::pair<unsigned int, unsigned int>> old_segments =
     305         231 :         std::move(this->segments);
     306             : 
     307             :       // We expect to have converted any elems and/or nodes into
     308             :       // segments by now.
     309          10 :       libmesh_assert(!old_segments.empty());
     310             : 
     311          10 :       this->segments.clear();
     312             : 
     313             :       // Insert a new point on each segment at evenly spaced locations
     314             :       // between existing boundary points.
     315             :       // np=index into new points vector
     316             :       // n =index into original points vector
     317        1105 :       for (auto old_segment : old_segments)
     318             :         {
     319         884 :           Node * begin_node = _mesh.node_ptr(old_segment.first);
     320         884 :           Node * end_node = _mesh.node_ptr(old_segment.second);
     321         884 :           dof_id_type current_id = begin_node->id();
     322        2920 :           for (auto i : make_range(n_interpolated))
     323             :             {
     324             :               // new points are equispaced along the original segments
     325             :               const Point new_point =
     326        2036 :                 ((n_interpolated-i) * *(Point *)(begin_node) +
     327        2036 :                  (i+1) * *(Point *)(end_node)) /
     328        2116 :                 (n_interpolated + 1);
     329        2036 :               Node * next_node = _mesh.add_point(new_point, nn++);
     330        1876 :               this->segments.emplace_back(current_id,
     331        2036 :                                           next_node->id());
     332        2036 :               current_id = next_node->id();
     333             :             }
     334         804 :           this->segments.emplace_back(current_id,
     335         884 :                                       end_node->id());
     336             :         }
     337             :     }
     338        2279 : }
     339             : 
     340             : 
     341        1783 : void TriangulatorInterface::increase_triangle_order()
     342             : {
     343        1783 :   switch (_elem_type)
     344             :     {
     345          42 :     case TRI3:
     346             :     // Nothing to do if we're not requested to increase order
     347        1491 :       return;
     348         221 :     case TRI6:
     349         221 :       _mesh.all_second_order();
     350          10 :       break;
     351          71 :     case TRI7:
     352          71 :       _mesh.all_complete_order();
     353           2 :       break;
     354           0 :     default:
     355           0 :       libmesh_not_implemented();
     356             :     }
     357             : 
     358             :   // If we have any midpoint location data, we'll want to look it up
     359             :   // by point.  all_midpoints[{p, m}] will be the mth midpoint
     360             :   // location following after point p (when traversing a triangle
     361             :   // counter-clockwise)
     362          14 :   std::map<std::pair<Point, unsigned int>, Point> all_midpoints;
     363             :   unsigned int n_midpoints =
     364         316 :     this->segment_midpoints.size() / this->segments.size();
     365          12 :   libmesh_assert_equal_to(this->segments.size() * n_midpoints,
     366             :                           this->segment_midpoints.size());
     367         509 :   for (auto m : make_range(n_midpoints))
     368        1014 :     for (auto i : make_range(this->segments.size()))
     369             :       {
     370         797 :         const Point & p = segment_midpoints_keys[i*n_midpoints+m];
     371         864 :         all_midpoints[{p,m}] =
     372          60 :           this->segment_midpoints[i*n_midpoints+m];
     373             :       }
     374             : 
     375         292 :   if (_holes)
     376         150 :     for (const Hole * hole : *_holes)
     377             :       {
     378          75 :         if (!hole->n_midpoints())
     379           0 :           continue;
     380          75 :         if (!n_midpoints)
     381          75 :           n_midpoints = hole->n_midpoints();
     382           0 :         else if (hole->n_midpoints() != n_midpoints)
     383           0 :           libmesh_not_implemented_msg
     384             :             ("Differing boundary midpoint counts " <<
     385             :              hole->n_midpoints() << " and " << n_midpoints);
     386             : 
     387             :         // Our inner holes are expected to have points in
     388             :         // counter-clockwise order, which is backwards from how we
     389             :         // want to traverse them when iterating in counter-clockwise
     390             :         // order over a triangle, so we'll need to reverse our maps
     391             :         // carefully here.
     392          75 :         const auto n_hole_points = hole->n_points();
     393           4 :         libmesh_assert(n_hole_points);
     394         150 :         for (auto m : make_range(n_midpoints))
     395             :           {
     396         600 :             for (auto i : make_range(n_hole_points-1))
     397             :               {
     398         525 :                 const Point & p = hole->point(i+1);
     399         525 :                 all_midpoints[{p,m}] = hole->midpoint(n_midpoints-m-1, i);
     400             :               }
     401          75 :             const Point & p = hole->point(0);
     402          77 :             all_midpoints[{p,m}] = hole->midpoint(n_midpoints-m-1, n_hole_points-1);
     403             :           }
     404             :       }
     405             : 
     406             :   // The n_midpoints > 1 case is for future proofing, but in the
     407             :   // present we have EDGE4 and no TRI10 yet.
     408         292 :   if (n_midpoints > 1)
     409           0 :     libmesh_not_implemented_msg
     410             :       ("Cannot construct triangles with more than 1 midpoint per edge");
     411             : 
     412         292 :   if (!n_midpoints)
     413           0 :     return;
     414             : 
     415        3036 :   for (Elem * elem : _mesh.element_ptr_range())
     416             :     {
     417             :       // This should only be called right after we've finished
     418             :       // converting a triangulation to higher order
     419          62 :       libmesh_assert_equal_to(elem->n_vertices(), 3);
     420          62 :       libmesh_assert_not_equal_to(elem->default_order(), FIRST);
     421             : 
     422        5052 :       for (auto n : make_range(3))
     423             :         {
     424             :           // Only hole/outer boundary segments need adjusted midpoints
     425        3975 :           if (elem->neighbor_ptr(n))
     426        1984 :             continue;
     427             : 
     428         156 :           const Point & p = elem->point(n);
     429             : 
     430        1697 :           if (const auto it = all_midpoints.find({p,0});
     431          78 :               it != all_midpoints.end())
     432        1459 :             elem->point(n+3) = it->second;
     433             :         }
     434         268 :     }
     435             : 
     436             :   // Moving boundary mid-edge nodes can displace the TRI7 interior node
     437             :   // and tangle the element map.  Repositioning the interior node is
     438             :   // opt-in (off by default); the validity check always runs.
     439         292 :   if (_elem_type == TRI7 && _fixup_tri7_center_nodes)
     440          71 :     this->fixup_tri7_center_nodes();
     441             : 
     442         292 :   this->verify_quadratic_elements();
     443             : }
     444             : 
     445             : 
     446          71 : void TriangulatorInterface::fixup_tri7_center_nodes()
     447             : {
     448           2 :   libmesh_assert_equal_to(_elem_type, TRI7);
     449             : 
     450             :   // Place the interior node at the image of the reference centroid
     451             :   // (xi, eta) = (1/3, 1/3) under the curved Tri6 map, using the Tri6
     452             :   // shape function values there as weights: -1/9 on the vertices and
     453             :   // 4/9 on the mid-edges.  This reduces to the straight-edge centroid
     454             :   // when no boundary midpoint has moved.
     455             :   static const Real wv = -Real(1)/9;
     456             :   static const Real wm =  Real(4)/9;
     457             : 
     458         416 :   for (Elem * elem : _mesh.element_ptr_range())
     459             :     {
     460           4 :       libmesh_assert_equal_to(elem->n_vertices(), 3);
     461           4 :       libmesh_assert_equal_to(elem->n_nodes(), 7u);
     462             : 
     463         142 :       elem->point(6) = wv * (elem->point(0) +
     464           4 :                              elem->point(1) +
     465           8 :                              elem->point(2)) +
     466           0 :                        wm * (elem->point(3) +
     467           8 :                              elem->point(4) +
     468          16 :                              elem->point(5));
     469          67 :     }
     470          71 : }
     471             : 
     472             : 
     473         292 : void TriangulatorInterface::verify_quadratic_elements()
     474             : {
     475         292 :   if (_elem_type != TRI6 && _elem_type != TRI7)
     476           0 :     return;
     477             : 
     478             :   // Once fixup_tri7_center_nodes() has placed node 6, the TRI6 and TRI7
     479             :   // mappings coincide and this Tri6 formula serves both.
     480             :   static const Real xi_samples[7]  = {Real(0),   Real(1),   Real(0),
     481             :                                       Real(1)/2, Real(1)/2, Real(0),
     482             :                                       Real(1)/3};
     483             :   static const Real eta_samples[7] = {Real(0),   Real(0),   Real(1),
     484             :                                       Real(0),   Real(1)/2, Real(1)/2,
     485             :                                       Real(1)/3};
     486             : 
     487        2909 :   for (Elem * elem : _mesh.element_ptr_range())
     488             :     {
     489          62 :       libmesh_assert_equal_to(elem->n_vertices(), 3);
     490          62 :       libmesh_assert_greater_equal(elem->n_nodes(), 6u);
     491             : 
     492         124 :       const Point & x0 = elem->point(0);
     493          62 :       const Point & x1 = elem->point(1);
     494          62 :       const Point & x2 = elem->point(2);
     495          62 :       const Point & x3 = elem->point(3);
     496          62 :       const Point & x4 = elem->point(4);
     497          62 :       const Point & x5 = elem->point(5);
     498             : 
     499             :       // Tri6 mapping derivative coefficients (see Tri6::volume()):
     500             :       // dx/dxi = xi*a1 + eta*b1 + c1, dx/deta = xi*b1 + eta*b2 + c2.
     501          62 :       const Point a1 =  4*x0 + 4*x1 - 8*x3;
     502          62 :       const Point b1 =  4*x0 - 4*x3 + 4*x4 - 4*x5;
     503          62 :       const Point c1 = -3*x0 - 1*x1 + 4*x3;
     504          62 :       const Point b2 =  4*x0 + 4*x2 - 8*x5;
     505          62 :       const Point c2 = -3*x0 - 1*x2 + 4*x5;
     506             : 
     507             :       // Scale the tolerance by the straight-edge triangle area, which
     508             :       // is strictly positive for the valid TRI3 poly2tri input.
     509        1263 :       const Real ref_area = 0.5 * cross_norm(x1 - x0, x2 - x0);
     510        1263 :       const Real jac_tol = TOLERANCE * ref_area;
     511             : 
     512          62 :       Real min_jac = std::numeric_limits<Real>::max();
     513          62 :       unsigned int worst_sample = 0;
     514       10104 :       for (unsigned int s = 0; s != 7; ++s)
     515             :         {
     516        8841 :           const Real xi  = xi_samples[s];
     517        8841 :           const Real eta = eta_samples[s];
     518         434 :           const Point dxi  = xi*a1 + eta*b1 + c1;
     519         434 :           const Point deta = xi*b1 + eta*b2 + c2;
     520             :           // z-component of the cross product; the elements are planar.
     521        8841 :           const Real jac = dxi(0)*deta(1) - dxi(1)*deta(0);
     522        8841 :           if (jac < min_jac)
     523             :             {
     524         100 :               min_jac = jac;
     525         100 :               worst_sample = s;
     526             :             }
     527             :         }
     528             : 
     529        1263 :       if (min_jac > jac_tol)
     530          60 :         continue;
     531             : 
     532             :       // Build a diagnostic naming every snapped boundary side on this
     533             :       // element so the user can immediately see which curved-boundary
     534             :       // input caused the tangle.
     535          75 :       std::ostringstream sides;
     536         284 :       for (unsigned int n = 0; n != 3; ++n)
     537         219 :         if (!elem->neighbor_ptr(n))
     538             :           {
     539             :             const Point straight =
     540         213 :               0.5 * (elem->point(n) + elem->point((n+1) % 3));
     541         207 :             sides << " (boundary side " << n
     542         207 :                   << ": straight midpoint " << straight
     543         219 :                   << ", snapped midpoint " << elem->point(n+3) << ")";
     544             :           }
     545             : 
     546         485 :       libmesh_error_msg(
     547             :         "TriangulatorInterface: snapping a boundary midpoint produced a "
     548             :         "tangled quadratic triangle (element " << elem->id()
     549             :         << ", non-positive Jacobian " << min_jac
     550             :         << " at reference sample (" << xi_samples[worst_sample] << ", "
     551             :         << eta_samples[worst_sample] << "); reference triangle area "
     552             :         << ref_area << ")." << sides.str()
     553             :         << " Refine the boundary discretization so that recorded "
     554             :         "midpoints lie closer to their straight-line midpoints, "
     555             :         "then retry.");
     556         335 :     }
     557             : }
     558             : 
     559             : 
     560         651 : void TriangulatorInterface::verify_holes(const Hole & outer_bdy)
     561             : {
     562        1870 :   for (const Hole * hole : *_holes)
     563             :     {
     564        7550 :       for (const Hole * hole2 : *_holes)
     565             :         {
     566        6331 :           if (hole == hole2)
     567        1179 :             continue;
     568             : 
     569       25560 :           for (auto i : make_range(hole2->n_points()))
     570       20448 :             if (hole->contains(hole2->point(i)))
     571           0 :               libmesh_error_msg
     572             :                 ("Found point " << hole2->point(i) <<
     573             :                  " on one hole boundary and another's interior");
     574             :         }
     575             : 
     576        6695 :       for (auto i : make_range(hole->n_points()))
     577        5476 :         if (!outer_bdy.contains(hole->point(i)))
     578           0 :           libmesh_error_msg
     579             :             ("Found point " << hole->point(i) <<
     580             :              " on hole boundary but outside outer boundary");
     581             :     }
     582         651 : }
     583             : 
     584             : 
     585         504 : unsigned int TriangulatorInterface::total_hole_points()
     586             : {
     587             :   // If the holes vector is non-nullptr (and non-empty) we need to determine
     588             :   // the number of additional points which the holes will add to the
     589             :   // triangulation.
     590             :   // Note that the number of points is always equal to the number of segments
     591             :   // that form the holes.
     592         252 :   unsigned int n_hole_points = 0;
     593             : 
     594         504 :   if (_holes)
     595          40 :     for (const auto & hole : *_holes)
     596             :     {
     597          24 :       n_hole_points += hole->n_points();
     598             :       // A hole at least has one enclosure.
     599             :       // Points on enclosures are ordered so that we can add segments implicitly.
     600             :       // Elements in segment_indices() indicates the starting points of all enclosures.
     601             :       // The last element in segment_indices() is the number of total points.
     602          12 :       libmesh_assert_greater(hole->segment_indices().size(), 1);
     603          12 :       libmesh_assert_equal_to(hole->segment_indices().back(), hole->n_points());
     604             :     }
     605             : 
     606         504 :   return n_hole_points;
     607             : }
     608             : 
     609           0 : void TriangulatorInterface::set_auto_area_function(const Parallel::Communicator &comm,
     610             :                                                    const unsigned int num_nearest_pts,
     611             :                                                    const unsigned int power,
     612             :                                                    const Real background_value,
     613             :                                                    const Real  background_eff_dist)
     614             : {
     615           0 :    _auto_area_function = std::make_unique<AutoAreaFunction>(comm, num_nearest_pts, power, background_value, background_eff_dist);
     616           0 : }
     617             : 
     618           0 : FunctionBase<Real> * TriangulatorInterface::get_auto_area_function()
     619             : {
     620           0 :   if (!_auto_area_function->initialized())
     621             :   {
     622             :     // Points and target element sizes for the interpolation
     623           0 :     std::vector<Point> function_points;
     624           0 :     std::vector<Real> function_sizes;
     625           0 :     calculate_auto_desired_area_samples(function_points, function_sizes);
     626           0 :     _auto_area_function->init_mfi(function_points, function_sizes);
     627             :   }
     628           0 :   return _auto_area_function.get();
     629             : }
     630             : 
     631           0 : void TriangulatorInterface::calculate_auto_desired_area_samples(std::vector<Point> & function_points,
     632             :                                                                 std::vector<Real> & function_sizes,
     633             :                                                                 const Real & area_factor)
     634             : {
     635             :   // Get the hole mesh of the outer boundary
     636             :   // Holes should already be attached if applicable when this function is called
     637           0 :   const TriangulatorInterface::MeshedHole bdry_mh { _mesh, this->_bdy_ids };
     638             :   // Collect all the centroid points of the outer boundary segments
     639             :   // and the corresponding element sizes
     640           0 :   for (unsigned int i = 0; i < bdry_mh.n_points(); i++)
     641             :   {
     642           0 :     function_points.push_back((bdry_mh.point(i) + bdry_mh.point((i + 1) % bdry_mh.n_points())) /
     643           0 :                               Real(2.0));
     644           0 :     function_sizes.push_back(
     645           0 :         (bdry_mh.point(i) - bdry_mh.point((i + 1) % bdry_mh.n_points())).norm());
     646             :   }
     647             :   // If holes are present, do the same for the hole boundaries
     648           0 :   if(_holes)
     649           0 :     for (const Hole * hole : *_holes)
     650             :     {
     651           0 :       for (unsigned int i = 0; i < hole->n_points(); i++)
     652             :       {
     653           0 :         function_points.push_back(
     654           0 :             (hole->point(i) + hole->point((i + 1) % hole->n_points())) / Real(2.0));
     655           0 :         function_sizes.push_back(
     656           0 :             (hole->point(i) - hole->point((i + 1) % hole->n_points())).norm());
     657             :       }
     658             :     }
     659             : 
     660           0 :   std::for_each(
     661           0 :       function_sizes.begin(), function_sizes.end(), [&area_factor](Real & a) { a = a * a * area_factor * std::sqrt(3.0) / 4.0; });
     662             : 
     663           0 : }
     664             : } // namespace libMesh
     665             : 
     666             : 
     667             : // Unnecessary at end of file, *except* maybe we'll do a unity build
     668             : // someday, and in the meantime test coverage is nice.
     669             : #include "libmesh/restore_ieee754.h"

Generated by: LCOV version 1.14