LCOV - code coverage report
Current view: top level - src/geom - cell_c0polyhedron.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4476 (4beb67) with base a68cc6 Lines: 410 426 96.2 %
Date: 2026-06-03 20:22:46 Functions: 35 38 92.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             : // Local includes
      19             : #include "libmesh/cell_c0polyhedron.h"
      20             : 
      21             : #include "libmesh/enum_order.h"
      22             : #include "libmesh/face_polygon.h"
      23             : #include "libmesh/mesh_tools.h"
      24             : #include "libmesh/replicated_mesh.h"
      25             : #include "libmesh/tensor_value.h"
      26             : #include "libmesh/node.h"
      27             : 
      28             : // C++ headers
      29             : #include <numeric> // std::iota
      30             : 
      31             : namespace libMesh
      32             : {
      33             : 
      34             : // ------------------------------------------------------------
      35             : // C0Polyhedron class member functions
      36             : 
      37        6121 : C0Polyhedron::C0Polyhedron
      38        6121 :   (const std::vector<std::shared_ptr<Polygon>> & sides, std::unique_ptr<Node> & mid_elem_node, Elem * p) :
      39             :   Polyhedron(sides, p),
      40        6121 :   _has_mid_elem_node(false)
      41             : {
      42        6121 :   this->retriangulate();
      43             : 
      44             :   // Grab the last node
      45        6121 :   if (_has_mid_elem_node)
      46        2059 :     mid_elem_node.reset(this->_nodelinks_data.back());
      47        6121 : }
      48             : 
      49             : 
      50             : 
      51       11546 : C0Polyhedron::~C0Polyhedron() = default;
      52             : 
      53             : 
      54             : 
      55          15 : std::unique_ptr<Elem> C0Polyhedron::disconnected_clone() const
      56             : {
      57          19 :   auto sides = this->side_clones();
      58             : 
      59          15 :   std::unique_ptr<Node> mid_elem_node;
      60          15 :   std::unique_ptr<Elem> returnval = std::make_unique<C0Polyhedron>(sides, mid_elem_node);
      61             :   // Swap out the new mid elem node with the previous one
      62          15 :   if (mid_elem_node)
      63             :   {
      64           0 :     libmesh_assert(returnval->n_nodes() == this->n_nodes());
      65           0 :     returnval->set_node(returnval->n_nodes() - 1, this->_nodelinks_data.back());
      66             :   }
      67             : 
      68          15 :   returnval->set_id() = this->id();
      69             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
      70          15 :   if (this->valid_unique_id())
      71           2 :     returnval->set_unique_id(this->unique_id());
      72             : #endif
      73             : 
      74          15 :   const auto n_elem_ints = this->n_extra_integers();
      75          15 :   returnval->add_extra_integers(n_elem_ints);
      76          15 :   for (unsigned int i = 0; i != n_elem_ints; ++i)
      77           0 :     returnval->set_extra_integer(i, this->get_extra_integer(i));
      78             : 
      79          13 :   returnval->inherit_data_from(*this);
      80             : 
      81          17 :   return returnval;
      82          11 : }
      83             : 
      84             : 
      85             : 
      86             : unsigned int
      87    12737170 : C0Polyhedron::n_vertices() const
      88             : {
      89    13798968 :   return this->_nodelinks_data.size() - _has_mid_elem_node;
      90             : }
      91             : 
      92             : 
      93             : 
      94       20286 : bool C0Polyhedron::is_vertex(const unsigned int i) const
      95             : {
      96        2252 :   libmesh_assert_less (i, this->n_nodes());
      97             : 
      98       20286 :   if (i < this->n_vertices())
      99        2144 :     return true;
     100             :   else
     101         810 :     return false;
     102             : }
     103             : 
     104             : 
     105             : 
     106        4260 : bool C0Polyhedron::is_edge(const unsigned int libmesh_dbg_var(i)) const
     107             : {
     108         120 :   libmesh_assert_less (i, this->n_nodes());
     109             : 
     110        4260 :   return false;
     111             : }
     112             : 
     113             : 
     114             : 
     115        4260 : bool C0Polyhedron::is_face(const unsigned int libmesh_dbg_var(i)) const
     116             : {
     117         120 :   libmesh_assert_less (i, this->n_nodes());
     118             : 
     119        4260 :   return false;
     120             : }
     121             : 
     122             : 
     123             : 
     124        5124 : bool C0Polyhedron::is_node_on_side(const unsigned int n,
     125             :                                    const unsigned int s) const
     126             : {
     127         192 :   libmesh_assert_less (n, this->n_nodes());
     128         192 :   libmesh_assert_less (s, this->n_sides());
     129             : 
     130             :   const std::vector<unsigned int> & node_map =
     131        5124 :     std::get<2>(_sidelinks_data[s]);
     132             : 
     133        5124 :   const auto it = std::find(node_map.begin(), node_map.end(), n);
     134             : 
     135        5316 :   return (it != node_map.end());
     136             : }
     137             : 
     138             : std::vector<unsigned int>
     139       16270 : C0Polyhedron::nodes_on_side(const unsigned int s) const
     140             : {
     141         502 :   libmesh_assert_less(s, this->n_sides());
     142       16772 :   return std::get<2>(_sidelinks_data[s]);
     143             : }
     144             : 
     145             : std::vector<unsigned int>
     146         684 : C0Polyhedron::nodes_on_edge(const unsigned int e) const
     147             : {
     148         684 :   auto [s, se] = _edge_lookup[e];
     149         684 :   const Polygon & face = *std::get<0>(_sidelinks_data[s]);
     150             :   const std::vector<unsigned int> & node_map =
     151          57 :     std::get<2>(_sidelinks_data[s]);
     152             :   std::vector<unsigned int> nodes_on_edge =
     153         684 :     face.nodes_on_side(se);
     154        2052 :   for (auto i : index_range(nodes_on_edge))
     155        1482 :     nodes_on_edge[i] = node_map[nodes_on_edge[i]];
     156         741 :   return nodes_on_edge;
     157             : }
     158             : 
     159             : 
     160             : 
     161         288 : bool C0Polyhedron::is_node_on_edge(const unsigned int n,
     162             :                                    const unsigned int e) const
     163             : {
     164          24 :   libmesh_assert_less(e, _edge_lookup.size());
     165         288 :   auto [s, se] = _edge_lookup[e];
     166             : 
     167         288 :   const Polygon & face = *std::get<0>(_sidelinks_data[s]);
     168             :   const std::vector<unsigned int> & node_map =
     169          24 :     std::get<2>(_sidelinks_data[s]);
     170             :   std::vector<unsigned int> nodes_on_edge =
     171         312 :     face.nodes_on_side(se);
     172         432 :   for (auto noe : nodes_on_edge)
     173         468 :     if (node_map[noe] == n)
     174          24 :       return true;
     175             : 
     176           0 :   return false;
     177             : }
     178             : 
     179             : 
     180             : 
     181             : std::vector<unsigned int>
     182         480 : C0Polyhedron::edges_adjacent_to_node(const unsigned int n) const
     183             : {
     184         480 :   if (n == n_nodes() - 1)
     185          55 :     return {};
     186         420 :   return Polyhedron::edges_adjacent_to_node(n);
     187             : }
     188             : 
     189             : 
     190             : 
     191           0 : void C0Polyhedron::connectivity(const unsigned int /*sf*/,
     192             :                                 const IOPackage /*iop*/,
     193             :                                 std::vector<dof_id_type> & /*conn*/) const
     194             : {
     195           0 :   libmesh_not_implemented();
     196             : }
     197             : 
     198             : 
     199             : 
     200         296 : Real C0Polyhedron::volume () const
     201             : {
     202             :   // This specialization is ... probably good for general non-Lagrange
     203             :   // mappings, since we require our polyhedral faces to be planar, but
     204             :   // I'd rather be slow than wrong.
     205         296 :   if (this->mapping_type() != LAGRANGE_MAP)
     206           0 :     return this->Elem::volume();
     207             : 
     208             :   // We use a triangulation to calculate here.
     209             : 
     210           9 :   Real six_vol = 0;
     211        3906 :   for (const auto & subtet : this->_triangulation)
     212             :     {
     213        3610 :       const Point p0 = this->point(subtet[0]);
     214        3610 :       const Point p1 = this->point(subtet[1]);
     215        3610 :       const Point p2 = this->point(subtet[2]);
     216        3715 :       const Point p3 = this->point(subtet[3]);
     217             : 
     218         105 :       const Point v01 = p1 - p0;
     219         105 :       const Point v02 = p2 - p0;
     220         105 :       const Point v03 = p3 - p0;
     221             : 
     222        3610 :       six_vol += triple_product(v01, v02, v03);
     223             :     }
     224             : 
     225         296 :   return six_vol * (1./6.);
     226             : }
     227             : 
     228             : 
     229             : 
     230          24 : Point C0Polyhedron::true_centroid () const
     231             : {
     232             :   // This specialization is good for Lagrange mappings only
     233          24 :   if (this->mapping_type() != LAGRANGE_MAP)
     234           0 :     return this->Elem::true_centroid();
     235             : 
     236           2 :   Real six_vol = 0;
     237           2 :   Point six_vol_weighted_centroid;
     238         144 :   for (const auto & subtet : this->_triangulation)
     239             :     {
     240         120 :       const Point p0 = this->point(subtet[0]);
     241         120 :       const Point p1 = this->point(subtet[1]);
     242         120 :       const Point p2 = this->point(subtet[2]);
     243         130 :       const Point p3 = this->point(subtet[3]);
     244             : 
     245          10 :       const Point v01 = p1 - p0;
     246          10 :       const Point v02 = p2 - p0;
     247          10 :       const Point v03 = p3 - p0;
     248             : 
     249         110 :       const Real six_tet_vol = triple_product(v01, v02, v03);
     250             : 
     251          10 :       const Point tet_centroid = (p0 + p1 + p2 + p3) * 0.25;
     252             : 
     253         120 :       six_vol += six_tet_vol;
     254             : 
     255          10 :       six_vol_weighted_centroid += six_tet_vol * tet_centroid;
     256             :     }
     257             : 
     258           2 :   return six_vol_weighted_centroid / six_vol;
     259             : }
     260             : 
     261             : 
     262             : 
     263             : std::pair<unsigned short int, unsigned short int>
     264           0 : C0Polyhedron::second_order_child_vertex (const unsigned int /*n*/) const
     265             : {
     266           0 :   libmesh_not_implemented();
     267             :   return std::pair<unsigned short int, unsigned short int> (0,0);
     268             : }
     269             : 
     270             : 
     271             : 
     272         216 : ElemType C0Polyhedron::side_type (const unsigned int libmesh_dbg_var(s)) const
     273             : {
     274          18 :   libmesh_assert_less (s, this->n_sides());
     275         216 :   return C0POLYGON;
     276             : }
     277             : 
     278             : 
     279             : 
     280             : Point
     281         852 : C0Polyhedron::side_vertex_average_normal(const unsigned int s) const
     282             : {
     283          24 :   libmesh_assert_less (s, this->n_sides());
     284          24 :   libmesh_assert_equal_to(this->mapping_type(), LAGRANGE_MAP);
     285         852 :   const auto poly_side_ptr = this->side_ptr(s);
     286         852 :   const auto n_side_edges = poly_side_ptr->n_sides();
     287             : 
     288             :   // At the side vertex average, things simplify a bit
     289             :   // We get the side "plane" normal at all vertices, then average them
     290          24 :   Point normal;
     291          48 :   Point current_edge = poly_side_ptr->point(1) - poly_side_ptr->point(0);
     292         852 :   for (auto i : make_range(n_side_edges))
     293             :   {
     294         852 :     const Point next_edge = poly_side_ptr->point((i + 2) % n_side_edges) -
     295         876 :                             poly_side_ptr->point((i + 1) % n_side_edges);
     296         828 :     const Point normal_at_vertex = current_edge.cross(next_edge);
     297          24 :     normal += normal_at_vertex;
     298             :     // Note: the sides are planar, we don't need to test them all
     299         852 :     if (normal.norm_sq() > TOLERANCE)
     300          24 :       break;
     301           0 :     current_edge = next_edge;
     302             :   }
     303         852 :   bool outward_normal = std::get<1>(_sidelinks_data[s]);
     304        1708 :   return (outward_normal ? 1. : -1.) * normal.unit();
     305         804 : }
     306             : 
     307             : 
     308             : std::array<int, 4>
     309        1917 : C0Polyhedron::subelement_sides_to_poly_sides(unsigned int tri_i) const
     310             : {
     311          54 :   std::array<int, 4> sides = {invalid_int, invalid_int, invalid_int, invalid_int};
     312          54 :   libmesh_assert(tri_i < this->n_subelements());
     313        1917 :   const auto & tet_nodes = _triangulation[tri_i];
     314       16401 :   for (const auto poly_side : make_range(n_sides()))
     315             :   {
     316       14892 :     const auto sd_nodes = nodes_on_side(poly_side);
     317       14892 :     bool zero_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[0]) != sd_nodes.end();
     318       14892 :     bool one_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[1]) != sd_nodes.end();
     319       14892 :     bool two_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[2]) != sd_nodes.end();
     320       14892 :     bool three_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[3]) != sd_nodes.end();
     321             : 
     322             :     // Take advantage of the fact that the poly side can only contain one tet side
     323             :     // Note: we could optimize by replacing the booleans with the searches above
     324       14484 :     if (zero_in)
     325             :     {
     326        5751 :       if (one_in)
     327             :       {
     328        2485 :         if (two_in)
     329        1491 :           sides[0] = poly_side;
     330         994 :         else if (three_in)
     331         355 :           sides[1] = poly_side;
     332             :       }
     333        3266 :       else if (two_in && three_in)
     334         355 :         sides[3] = poly_side;
     335             :     }
     336        8733 :     else if (three_in && two_in && one_in)
     337         355 :       sides[2] = poly_side;
     338             :   }
     339        1917 :   return sides;
     340             : }
     341             : 
     342             : 
     343        6121 : void C0Polyhedron::retriangulate()
     344             : {
     345         174 :   this->_triangulation.clear();
     346             : 
     347             :   // We should already have a triangulation for each side.  We'll turn
     348             :   // that into a triangulation of the entire polyhedral surface
     349             :   // (stored as a mesh, so we can use `find_neighbors()`, then turn
     350             :   // that into a tetrahedralization by shrinking the volume one tet at
     351             :   // a time, which should work fine for convex polyhedra.
     352             : 
     353         348 :   Parallel::Communicator comm_self; // the default communicator is 1 rank
     354        6469 :   ReplicatedMesh surface(comm_self);
     355             : 
     356             :   // poly_node_to_local_id[poly_node] is the local id of
     357             :   // poly_node in the Polyhedron, which is also the global id of the
     358             :   // corresponding node in the surface mesh.
     359         348 :   std::unordered_map<Node *, unsigned int> poly_node_to_id;
     360             : 
     361       43131 :   for (unsigned int s : make_range(this->n_sides()))
     362             :     {
     363       37010 :       const auto & [side, inward_normal, node_map] = this->_sidelinks_data[s];
     364             : 
     365      111598 :       for (auto t : make_range(side->n_subtriangles()))
     366             :         {
     367       76708 :           Elem * tri = surface.add_elem(Elem::build(TRI3));
     368             : 
     369       74588 :           const std::array<int, 3> subtri = side->subtriangle(t);
     370             : 
     371      298352 :           for (int i : make_range(3))
     372             :             {
     373      223764 :               const int side_id = subtri[i];
     374      223764 :               const Node * poly_node = side->node_ptr(side_id);
     375             : 
     376        6360 :               libmesh_assert_less(side_id, node_map.size());
     377      223764 :               const unsigned int local_id = node_map[side_id];
     378             : 
     379      223764 :               Node * surf_node = surface.query_node_ptr(local_id);
     380      223764 :               if (surf_node)
     381        4952 :                 libmesh_assert_equal_to(*(const Point*)poly_node,
     382             :                                         *(const Point*)surf_node);
     383             :               else
     384       49536 :                 surf_node = surface.add_point(*poly_node, local_id);
     385             : 
     386             :               // Get a consistent orientation for surface triangles,
     387             :               // facing their zeta directions outward
     388      223764 :               const int tri_node = inward_normal ? i : 2-i;
     389      223764 :               tri->set_node(tri_node, surf_node);
     390             :             }
     391             :         }
     392             :     }
     393             : 
     394         174 :   surface.allow_renumbering(false);
     395        6121 :   surface.prepare_for_use();
     396             : 
     397             :   // We should have a watertight surface with consistent triangle
     398             :   // orientations.  That's expensive to check.
     399             : #ifdef DEBUG
     400       22582 :   auto verify_surface = [& surface] ()
     401             :     {
     402        8118 :       for (const Elem * elem : surface.element_ptr_range())
     403             :         {
     404       28928 :           for (auto s : make_range(3))
     405             :             {
     406       21696 :               const Elem * neigh = elem->neighbor_ptr(s);
     407       21696 :               libmesh_assert(neigh);
     408       21696 :               libmesh_assert_equal_to(neigh,
     409             :                                       surface.elem_ptr(neigh->id()));
     410       21696 :               const unsigned int ns = neigh->which_neighbor_am_i(elem);
     411       21696 :               libmesh_assert_less(ns, 3);
     412       21696 :               libmesh_assert_equal_to(elem->node_ptr(s),
     413             :                                       neigh->node_ptr((ns+1)%3));
     414       21696 :               libmesh_assert_equal_to(elem->node_ptr((s+1)%3),
     415             :                                       neigh->node_ptr(ns));
     416             :             }
     417             :         }
     418         886 :     };
     419             : 
     420         174 :   verify_surface();
     421             : #endif
     422             : 
     423             :   // First heuristic: try with no interior point
     424             :   // This might not succeed, not every surface triangulation gives a tetrahedralization
     425             :   // with no additional interior point
     426             :   // But if it succeeds, it uses less tetrahedra to cut the polyhedron
     427             : #ifdef LIBMESH_ENABLE_EXCEPTIONS
     428             :   try
     429             :   {
     430             :   // We'll have to edit this as we change the surface elements, but we
     431             :   // have a method to initialize it easily.
     432         522 :   std::vector<std::vector<dof_id_type>> nodes_to_elem_vec_map;
     433        6121 :   MeshTools::build_nodes_to_elem_map(surface, nodes_to_elem_vec_map);
     434             : 
     435             :   // We'll be inserting and deleting entries heavily, so we'll use
     436             :   // sets rather than vectors.  We want to get the same results in
     437             :   // parallel, so we'll use element ids rather than Elem pointers
     438         464 :   std::vector<std::set<dof_id_type>> nodes_to_elem_map;
     439       55657 :   for (auto i : index_range(nodes_to_elem_vec_map))
     440             :     nodes_to_elem_map.emplace_back
     441       99665 :       (nodes_to_elem_vec_map[i].begin(),
     442       53818 :        nodes_to_elem_vec_map[i].end());
     443             : 
     444             :   // Now start meshing the volume enclosed by surface, one tet at a
     445             :   // time, with a greedy heuristic: find the vertex node with the most
     446             :   // acutely convex (solid) angle and strip it out as tetrahedra.
     447             : 
     448             :   // We'll want a vector of surrounding nodes for multiple uses,
     449             :   // sometimes with a similarly-sorted vector of surrounding elements
     450             :   // to go with it.
     451             :   auto surroundings_of =
     452       80275 :     [&nodes_to_elem_map, & surface]
     453             :     (const Node & node,
     454      101879 :      std::vector<Elem *> * surrounding_elems)
     455             :     {
     456             :       const std::set<dof_id_type> & elems_by_node =
     457        6045 :         nodes_to_elem_map[node.id()];
     458             : 
     459       85111 :       const unsigned int n_surrounding = elems_by_node.size();
     460        2418 :       libmesh_assert_greater_equal(n_surrounding, 3);
     461             : 
     462       85111 :       if (surrounding_elems)
     463             :         {
     464         886 :           libmesh_assert(surrounding_elems->empty());
     465       31173 :           surrounding_elems->resize(n_surrounding);
     466             :         }
     467             : 
     468       85111 :       std::vector<Node *> surrounding_nodes(n_surrounding);
     469             : 
     470       85111 :       Elem * elem = surface.elem_ptr(*elems_by_node.begin());
     471      420002 :       for (auto i : make_range(n_surrounding))
     472             :         {
     473      325377 :           const unsigned int n = elem->get_node_index(&node);
     474        9514 :           libmesh_assert_not_equal_to(n, invalid_uint);
     475      334891 :           Node * next_node = elem->node_ptr((n+1)%3);
     476      334891 :           surrounding_nodes[i] = next_node;
     477      334891 :           if (surrounding_elems)
     478      100703 :             (*surrounding_elems)[i] = elem;
     479      334891 :           elem = elem->neighbor_ptr((n+2)%3);
     480        9514 :           libmesh_assert(elem);
     481        9514 :           libmesh_assert_equal_to(elem, surface.elem_ptr(elem->id()));
     482             : 
     483             :           // We should have a manifold here, but verifying that is
     484             :           // expensive
     485             : #ifdef DEBUG
     486        9514 :           libmesh_assert_equal_to
     487             :             (std::count(surrounding_nodes.begin(),
     488             :                         surrounding_nodes.end(), next_node),
     489             :              1);
     490             : #endif
     491             :         }
     492             : 
     493             :       // We should have finished a loop
     494        2418 :       libmesh_assert_equal_to
     495             :         (elem, surface.elem_ptr(*elems_by_node.begin()));
     496             : 
     497       87529 :       return surrounding_nodes;
     498        6121 :     };
     499             : 
     500       53938 :   auto geometry_at = [&surroundings_of](const Node & node)
     501             :     {
     502             :       const std::vector<Node *> surrounding_nodes =
     503       55470 :         surroundings_of(node, nullptr);
     504             : 
     505             :       // Now sum up solid angles from tetrahedra created from the
     506             :       // trivial triangulation of the surrounding nodes loop
     507        1532 :       Real total_solid_angle = 0;
     508             :       const int n_surrounding =
     509        3064 :         cast_int<int>(surrounding_nodes.size());
     510             : 
     511      183032 :       for (auto n : make_range(n_surrounding-2))
     512             :         {
     513             :           const Point
     514      132762 :             v01 = static_cast<Point>(*surrounding_nodes[n]) - node,
     515      132762 :             v02 = static_cast<Point>(*surrounding_nodes[n+1]) - node,
     516      132762 :             v03 = static_cast<Point>(*surrounding_nodes[n+2]) - node;
     517             : 
     518      129094 :           total_solid_angle += solid_angle(v01, v02, v03);
     519             :         }
     520             : 
     521       55470 :       return std::make_pair(n_surrounding, total_solid_angle);
     522        5947 :     };
     523             : 
     524             :   // We'll keep track of solid angles and node valences so we don't
     525             :   // waste time recomputing them when they haven't changed.  We need
     526             :   // to be able to search efficiently for the smallest angles of each
     527             :   // valence, but also search efficiently for a particular node to
     528             :   // remove and reinsert it when its connectivity changes.
     529             :   //
     530             :   // Since C++11 multimap has guaranteed that pairs with matching keys
     531             :   // are kept in insertion order, so we can use Node * for values even
     532             :   // in parallel.
     533             :   typedef std::multimap<std::pair<int, Real>, Node*> node_map_type;
     534         348 :   node_map_type nodes_by_geometry;
     535         232 :   std::map<Node *, node_map_type::iterator> node_index;
     536             : 
     537       61604 :   for (auto node : surface.node_ptr_range())
     538       49536 :     node_index[node] =
     539      104845 :       nodes_by_geometry.emplace(geometry_at(*node), node);
     540             : 
     541             :   // In 3D, this will require nested loops: an outer loop to remove
     542             :   // each vertex, and an inner loop to remove multiple tetrahedra in
     543             :   // cases where the vertex has more than 3 neighboring triangles.
     544             : 
     545             :   // We'll be done when there are only three "unremoved" nodes left,
     546             :   // so they don't actually enclose any volume.
     547       35235 :   for (auto i : make_range(nodes_by_geometry.size()-3))
     548             :     {
     549         886 :       auto geometry_it = nodes_by_geometry.begin();
     550       31173 :       auto geometry_key = geometry_it->first;
     551       31173 :       auto [valence, angle] = geometry_key;
     552       31173 :       Node * node = geometry_it->second;
     553         886 :       libmesh_ignore(i);
     554             : 
     555             :       // If our lowest-valence nodes are all points of non-convexity,
     556             :       // skip to a higher valence.
     557       31173 :       while (angle > 2*pi-TOLERANCE)
     558             :         {
     559             :           geometry_it =
     560             :             nodes_by_geometry.upper_bound
     561           0 :               (std::make_pair(valence, Real(100)));
     562           0 :           libmesh_assert(geometry_it != nodes_by_geometry.end());
     563             : 
     564           0 :           std::tie(geometry_key, node) = *geometry_it;
     565           0 :           std::tie(valence, angle) = geometry_key;
     566             :         }
     567             : 
     568        1772 :       std::vector<Elem *> surrounding_elems;
     569             :       std::vector<Node *> surrounding_nodes =
     570       32059 :         surroundings_of(*node, &surrounding_elems);
     571             : 
     572        1772 :       const std::size_t n_surrounding = surrounding_nodes.size();
     573             : 
     574             :       // As we separate surrounding nodes from our center node, we'll
     575             :       // be marking them as nullptr; we still need to be able to find
     576             :       // predecessor and successor nodes in order afterward.
     577             :       auto find_valid_nodes_around =
     578      162762 :         [n_surrounding, & surrounding_nodes]
     579      369794 :         (unsigned int j)
     580             :       {
     581      172546 :         unsigned int jnext = (j+1)%n_surrounding;
     582      186636 :         while (!surrounding_nodes[jnext])
     583        8946 :           jnext = (jnext+1)%n_surrounding;
     584             : 
     585      172546 :         unsigned int jprev = (j+n_surrounding-1)%n_surrounding;
     586      190578 :         while (!surrounding_nodes[jprev])
     587       12780 :           jprev = (jprev+n_surrounding-1)%n_surrounding;
     588             : 
     589      177438 :         return std::make_pair(jprev, jnext);
     590       31173 :       };
     591             : 
     592             :       // We may have too many surrounding nodes to handle with
     593             :       // just one tet.  In that case we'll keep a cache of the
     594             :       // element qualities that we'd get by making a tet with the
     595             :       // edge from the center node to each surrounding node, so we
     596             :       // can build the best tets first.
     597             :       //
     598             :       // In the case where we just have 3 nodes, we'll just pretend
     599             :       // they all have the same positive quality, so we can still
     600             :       // search this vector.
     601       34060 :       std::vector<Real> local_tet_quality(n_surrounding, 1);
     602             : 
     603             :       // From our center node with N surrounding nodes we can make N-2
     604             :       // tetrahedra.  The first N-3 each replace two surface tets with
     605             :       // two new surface tets.
     606             :       //
     607             :       // My first idea was to greedily pick nodes with the smallest
     608             :       // local (solid) angles to get the best quality.  This works in
     609             :       // 2D, but such a node can give a pancake tet in 3D.
     610             :       //
     611             :       // My second idea was to greedily pick nodes with the highest
     612             :       // prospective tet quality.  This works for the first tets, but
     613             :       // can leave a pancake tet behind.
     614             :       //
     615             :       // My third idea is to try to fix the lowest quality tets first,
     616             :       // by picking cases where they have higher quality neighbors,
     617             :       // and creating those neighbors so as to change them.
     618             : 
     619             :       auto find_new_tet_nodes =
     620       33555 :         [& local_tet_quality, & find_valid_nodes_around]
     621       63849 :         ()
     622             :       {
     623        1010 :         unsigned int jbest = 0;
     624       35575 :         auto [jminus, jplus] = find_valid_nodes_around(jbest);
     625       35575 :         Real qneighbest = std::min(local_tet_quality[jminus],
     626       36719 :                                    local_tet_quality[jplus]);
     627       35823 :         for (auto j : make_range(std::size_t(1),
     628      117797 :                                  local_tet_quality.size()))
     629             :           {
     630             :             // We don't want to build a bad tet
     631       82222 :             if (local_tet_quality[j] <= 0)
     632        4830 :               continue;
     633             : 
     634       74984 :             std::tie(jminus, jplus) = find_valid_nodes_around(j);
     635       74984 :             Real qneighj = std::min(local_tet_quality[jminus],
     636       77112 :                                     local_tet_quality[jplus]);
     637             : 
     638             :             // We don't want to build a tet that can't fix a neighbor
     639             :             // if we can build one that can.
     640       74984 :             if (qneighbest <= 0 &&
     641             :                 qneighj > 0)
     642        4278 :               continue;
     643             : 
     644             :             // We want to try for the best possible fix.
     645       72586 :             if ((local_tet_quality[j] - qneighj) >
     646       72586 :                 (local_tet_quality[jbest] - qneighj))
     647             :               {
     648         240 :                 jbest = j;
     649         240 :                 qneighbest = qneighj;
     650             :               }
     651             :           }
     652             : 
     653       36585 :         libmesh_error_msg_if
     654             :           (local_tet_quality[jbest] <= 0,
     655             :            "Cannot build non-singular non-inverted tet");
     656             : 
     657       35575 :         std::tie(jminus, jplus) = find_valid_nodes_around(jbest);
     658             : 
     659       36585 :         return std::make_tuple(jbest, jminus, jplus);
     660       31173 :       };
     661             : 
     662       31173 :       if (n_surrounding > 3)
     663             :         {
     664             :           // We'll be searching local_tet_quality even after tet
     665             :           // extraction disconnects us from some nodes; when we do we
     666             :           // don't want to get one.
     667         124 :           constexpr Real far_node = -1e6;
     668             : 
     669             :           // Vectors from the center node to each of its surrounding
     670             :           // nodes are helpful for calculating prospective tet
     671             :           // quality.
     672        4526 :           std::vector<Point> v0s(n_surrounding);
     673       22010 :           for (auto j : make_range(n_surrounding))
     674       19096 :             v0s[j] = *(Point *)surrounding_nodes[j] - *node;
     675             : 
     676             :           // Find the tet quality we'd potentially get from each
     677             :           // possible choice of tet
     678             :           auto local_tet_quality_of =
     679       24924 :             [& surrounding_nodes, & v0s, & find_valid_nodes_around]
     680       65472 :             (unsigned int j)
     681             :           {
     682       26412 :             auto [jminus, jplus] = find_valid_nodes_around(j);
     683             : 
     684             :             // Anything proportional to the ratio of volume to
     685             :             // total-edge-length-cubed should peak for perfect tets
     686             :             // but hit 0 for pancakes and slivers.
     687             : 
     688             :             const Real total_len =
     689       30132 :               v0s[j].norm() + v0s[jminus].norm() + v0s[jplus].norm() +
     690       24924 :               (*(Point *)surrounding_nodes[jplus] -
     691       53568 :                *(Point *)surrounding_nodes[j]).norm() +
     692       24924 :               (*(Point *)surrounding_nodes[j] -
     693       27900 :                *(Point *)surrounding_nodes[jminus]).norm() +
     694       24924 :               (*(Point *)surrounding_nodes[jminus] -
     695       28644 :                *(Point *)surrounding_nodes[jplus]).norm();
     696             : 
     697             :             // Orientation here is tricky.  Think of the triple
     698             :             // product as (v1 cross v2) dot v3, with right hand rule.
     699             :             const Real six_vol =
     700       26412 :               triple_product(v0s[jminus], v0s[jplus], v0s[j]);
     701             : 
     702       26412 :             return six_vol / (total_len * total_len * total_len);
     703        4402 :           };
     704             : 
     705       22010 :           for (auto j : make_range(n_surrounding))
     706       17608 :             local_tet_quality[j] = local_tet_quality_of(j);
     707             : 
     708             :           // If we have N surrounding nodes, we can make N tets and
     709             :           // that'll bring us back to the 3-surrounding-node case to
     710             :           // finish.
     711        8804 :           for (auto t : make_range(n_surrounding-3))
     712             :             {
     713         124 :               libmesh_ignore(t);
     714             : 
     715        4402 :               auto [jbest, jminus, jplus] = find_new_tet_nodes();
     716             : 
     717             :               // Turn these four nodes into a tet
     718        4402 :               Node * nbest  = surrounding_nodes[jbest],
     719        4402 :                    * nminus = surrounding_nodes[jminus],
     720        4402 :                    * nplus  = surrounding_nodes[jplus];
     721        4402 :               this->add_tet(nminus->id(), nbest->id(), nplus->id(),
     722         248 :                             node->id());
     723             : 
     724             :               // Replace the old two triangles around these nodes with the
     725             :               // proper two new triangles.
     726        4402 :               Elem * oldtri1 = surrounding_elems[jminus],
     727        4402 :                    * oldtri2 = surrounding_elems[jbest],
     728        4402 :                    * newtri1 = surface.add_elem(Elem::build(TRI3)),
     729        4526 :                    * newtri2 = surface.add_elem(Elem::build(TRI3));
     730             : 
     731        4278 :               const unsigned int c1 = oldtri1->get_node_index(node),
     732        4278 :                                  c2 = oldtri2->get_node_index(node);
     733             : 
     734        4402 :               newtri1->set_node(0, node);
     735        4402 :               newtri1->set_node(1, nminus);
     736        4402 :               newtri1->set_node(2, nplus);
     737             : 
     738        4402 :               surrounding_elems[jminus] = newtri1;
     739             : 
     740         248 :               Elem * neigh10 = oldtri1->neighbor_ptr(c1);
     741        4402 :               Elem * neigh12 = oldtri2->neighbor_ptr((c2+2)%3);
     742         248 :               newtri1->set_neighbor(0, neigh10);
     743        4402 :               neigh10->set_neighbor(neigh10->which_neighbor_am_i(oldtri1), newtri1);
     744         248 :               newtri1->set_neighbor(1, newtri2);
     745         124 :               newtri1->set_neighbor(2, neigh12);
     746        4402 :               neigh12->set_neighbor(neigh12->which_neighbor_am_i(oldtri2), newtri1);
     747             : 
     748        4402 :               newtri2->set_node(0, nplus);
     749        4402 :               newtri2->set_node(1, nminus);
     750        4402 :               newtri2->set_node(2, nbest);
     751             : 
     752        4402 :               Elem * neigh21 = oldtri1->neighbor_ptr((c1+1)%3);
     753        4402 :               Elem * neigh22 = oldtri2->neighbor_ptr((c2+1)%3);
     754         248 :               newtri2->set_neighbor(0, newtri1);
     755         124 :               newtri2->set_neighbor(1, neigh21);
     756        4402 :               neigh21->set_neighbor(neigh21->which_neighbor_am_i(oldtri1), newtri2);
     757         248 :               newtri2->set_neighbor(2, neigh22);
     758        4402 :               neigh22->set_neighbor(neigh22->which_neighbor_am_i(oldtri2), newtri2);
     759             : 
     760       17608 :               for (unsigned int p : make_range(3))
     761             :                 {
     762       13950 :                   nodes_to_elem_map[oldtri1->node_id(p)].erase(oldtri1->id());
     763       13950 :                   nodes_to_elem_map[oldtri2->node_id(p)].erase(oldtri2->id());
     764       13950 :                   nodes_to_elem_map[newtri1->node_id(p)].insert(newtri1->id());
     765       13950 :                   nodes_to_elem_map[newtri2->node_id(p)].insert(newtri2->id());
     766             :                 }
     767             : 
     768             :               // We've finished replacing the old triangles.
     769        4402 :               surface.delete_elem(oldtri1);
     770        4402 :               surface.delete_elem(oldtri2);
     771             : 
     772             :               // The solid angle for the far node should now stay
     773             :               // unchanged until we're out of this inner loop; let's
     774             :               // recalculate it here, and then we'll be done with it.
     775         248 :               Node * & nbestref = surrounding_nodes[jbest];
     776        4402 :               nodes_by_geometry.erase(node_index[nbestref]);
     777        4402 :               node_index[nbestref] =
     778        8556 :                 nodes_by_geometry.emplace(geometry_at(*nbestref), nbestref);
     779             : 
     780             :               // The far node is no longer sharing an edge with our center
     781             :               // node.  Make sure we don't use it again with the center
     782             :               // node.
     783        4402 :               local_tet_quality[jbest] = far_node;
     784        4402 :               nbestref = nullptr;
     785             : 
     786             :               // The potential tet qualities using the side nodes have
     787             :               // changed now that they're directly connected to each
     788             :               // other.
     789        4526 :               local_tet_quality[jminus] =
     790        4402 :                 local_tet_quality_of(jminus);
     791             : 
     792        4526 :               local_tet_quality[jplus] =
     793        4402 :                 local_tet_quality_of(jplus);
     794             :             }
     795             :         }
     796             : 
     797             :       // Now we should have just 3 surrounding nodes, with which to
     798             :       // make one tetrahedron.  Put them in a counterclockwise
     799             :       // (looking from outside) orientation, not the "best, clockwise,
     800             :       // counterclockwise" we get from the lambda.
     801       31173 :       auto [j2, j1, j3] = find_new_tet_nodes();
     802             : 
     803             :       // Turn these last four nodes into a tet
     804       31173 :       Node * n1 = surrounding_nodes[j1],
     805       31173 :            * n2 = surrounding_nodes[j2],
     806       31173 :            * n3 = surrounding_nodes[j3];
     807       31173 :       this->add_tet(n1->id(), n2->id(), n3->id(), node->id());
     808             : 
     809             :       // Replace the three surface triangles of that tet with the new
     810             :       // fourth triangle.
     811       29114 :       Elem * oldtri1 = surrounding_elems[j1],
     812       29114 :            * oldtri2 = surrounding_elems[j2],
     813       29114 :            * oldtri3 = surrounding_elems[j3],
     814       29942 :            * newtri = surface.add_elem(Elem::build(TRI3));
     815             : 
     816       28286 :       const unsigned int c1 = oldtri1->get_node_index(node),
     817       28286 :                          c2 = oldtri2->get_node_index(node),
     818       28286 :                          c3 = oldtri3->get_node_index(node);
     819             : 
     820       29114 :       newtri->set_node(0, n1);
     821       29114 :       newtri->set_node(1, n2);
     822       29114 :       newtri->set_node(2, n3);
     823       29114 :       Elem * neigh0 = oldtri1->neighbor_ptr((c1+1)%3);
     824        1656 :       newtri->set_neighbor(0, neigh0);
     825       29114 :       neigh0->set_neighbor(neigh0->which_neighbor_am_i(oldtri1), newtri);
     826       29114 :       Elem * neigh1 = oldtri2->neighbor_ptr((c2+1)%3);
     827        1656 :       newtri->set_neighbor(1, neigh1);
     828       29114 :       neigh1->set_neighbor(neigh1->which_neighbor_am_i(oldtri2), newtri);
     829       29114 :       Elem * neigh2 = oldtri3->neighbor_ptr((c3+1)%3);
     830        1656 :       newtri->set_neighbor(2, neigh2);
     831       29114 :       neigh2->set_neighbor(neigh2->which_neighbor_am_i(oldtri3), newtri);
     832             : 
     833      116456 :       for (unsigned int p : make_range(3))
     834             :         {
     835       92310 :           nodes_to_elem_map[oldtri1->node_id(p)].erase(oldtri1->id());
     836       92310 :           nodes_to_elem_map[oldtri2->node_id(p)].erase(oldtri2->id());
     837       92310 :           nodes_to_elem_map[oldtri3->node_id(p)].erase(oldtri3->id());
     838       94311 :           nodes_to_elem_map[newtri->node_id(p)].insert(newtri->id());
     839             :         }
     840             : 
     841             :       // We've finished replacing the old triangles.
     842       29114 :       surface.delete_elem(oldtri1);
     843       29114 :       surface.delete_elem(oldtri2);
     844       29114 :       surface.delete_elem(oldtri3);
     845             : 
     846             :       // We should have used up all our surrounding nodes now, and we
     847             :       // shouldn't have messed up our surface in the process, and our
     848             :       // center node should no longer be part of the surface.
     849             : #ifdef DEBUG
     850         828 :       surrounding_nodes[j1] = nullptr;
     851         828 :       surrounding_nodes[j2] = nullptr;
     852         828 :       surrounding_nodes[j3] = nullptr;
     853             : 
     854        3436 :       for (auto ltq : surrounding_nodes)
     855        2608 :         libmesh_assert(!ltq);
     856             : 
     857         828 :       if (surface.n_elem() > 3)
     858         712 :         verify_surface();
     859             : 
     860        6172 :       for (const Elem * elem : surface.element_ptr_range())
     861       21376 :         for (auto p : make_range(3))
     862       16032 :           libmesh_assert_not_equal_to
     863             :             (elem->node_ptr(p), node);
     864             : #endif
     865             : 
     866             :       // We've used up our center node, so it's not something we can
     867             :       // eliminate again.
     868       28286 :       nodes_by_geometry.erase(geometry_it);
     869             :     }
     870             :     // At this point our surface should just have two triangles left.
     871         116 :     libmesh_assert_equal_to(surface.n_elem(), 2);
     872        4062 :     _has_mid_elem_node = false;
     873        7716 :   }
     874             :   // Failed without an interior point.
     875             :   // Use a single vertex-average interior point and tetrahedralize around it
     876        2175 :   catch ( libMesh::LogicError & )
     877             : #endif
     878             :   {
     879             :     // Clear the triangulation we started building
     880          58 :     this->_triangulation.clear();
     881             : 
     882             :     // Get the vertex-average, no need to triangulate for this
     883        2059 :     const auto v_avg = this->vertex_average();
     884        2059 :     if (!_has_mid_elem_node)
     885             :     {
     886             :       // Create the mid element node. Add it to nodelinks
     887             :       // We'll attach a unique ptr to it later
     888        2059 :       Node * mid_elem_node = new Node(v_avg);
     889        2059 :       this->_nodelinks_data.push_back(mid_elem_node);
     890        2059 :       _has_mid_elem_node = true;
     891             :     }
     892             :     else
     893             :     {
     894             :       // We are triangulating for a second time, we have already added this mid-element node
     895           0 :       libmesh_assert(n_vertices() == n_nodes() - 1);
     896             :     }
     897             : 
     898             :     // Build the tetrahedralization with each of the triangles on each side
     899       14697 :     for (unsigned int s : make_range(this->n_sides()))
     900             :     {
     901       12638 :       const auto & [side, inward_normal, node_map] = this->_sidelinks_data[s];
     902             : 
     903       38482 :       for (auto t : make_range(side->n_subtriangles()))
     904             :       {
     905             :         // Get all the nodes
     906       25844 :         const auto & n1 = node_map[side->subtriangle(t)[0]];
     907       25844 :         const auto & n2 = node_map[side->subtriangle(t)[1]];
     908       25844 :         const auto & n3 = node_map[side->subtriangle(t)[2]];
     909             : 
     910             :         // The mid node is the last node in the _nodes array
     911       25844 :         if (!inward_normal)
     912        6106 :           this->add_tet((int)n1, (int)n2, (int)n3, this->n_nodes() - 1);
     913             :         else
     914       19738 :           this->add_tet((int)n1, (int)n3, (int)n2, this->n_nodes() - 1);
     915             :       }
     916             :     }
     917        1943 :   }
     918       11894 : }
     919             : 
     920             : 
     921       61419 : void C0Polyhedron::add_tet(int n1,
     922             :                            int n2,
     923             :                            int n3,
     924             :                            int n4)
     925             : {
     926             : #ifndef NDEBUG
     927        1738 :   const auto nn = this->n_nodes();
     928        1738 :   libmesh_assert_less(n1, nn);
     929        1738 :   libmesh_assert_less(n2, nn);
     930        1738 :   libmesh_assert_less(n3, nn);
     931        1738 :   libmesh_assert_less(n4, nn);
     932             : #endif
     933             : 
     934       63157 :   const Point v12 = this->point(n2) - this->point(n1);
     935       61419 :   const Point v13 = this->point(n3) - this->point(n1);
     936       61419 :   const Point v14 = this->point(n4) - this->point(n1);
     937       59681 :   const Real six_vol = triple_product(v12, v13, v14);
     938             :   // We need to error on this in optimized modes to fall back onto the
     939             :   // tetrahedralization with a mid node
     940       63478 :   libmesh_error_msg_if(six_vol <= 0, "Creating flat tet");
     941             : 
     942       59360 :   this->_triangulation.push_back({n1, n2, n3, n4});
     943       59360 : }
     944             : 
     945             : 
     946             : } // namespace libMesh

Generated by: LCOV version 1.14