LCOV - code coverage report
Current view: top level - src/geom - elem_cutter.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 117 130 90.0 %
Date: 2025-08-19 19:27:09 Functions: 8 9 88.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : 
      19             : #include "libmesh/libmesh_config.h"
      20             : #if defined(LIBMESH_HAVE_TRIANGLE) && defined(LIBMESH_HAVE_TETGEN)
      21             : 
      22             : // Local includes
      23             : #include "libmesh/elem_cutter.h"
      24             : #include "libmesh/elem.h"
      25             : #include "libmesh/replicated_mesh.h"
      26             : #include "libmesh/mesh_triangle_interface.h"
      27             : #include "libmesh/mesh_tetgen_interface.h"
      28             : 
      29             : // C++ Includes
      30             : #include <memory>
      31             : 
      32             : namespace
      33             : {
      34             : unsigned int cut_cntr;
      35             : }
      36             : 
      37             : namespace libMesh
      38             : {
      39             : 
      40           8 : ElemCutter::ElemCutter() :
      41           4 :   _inside_mesh_2D(std::make_unique<ReplicatedMesh>(_comm_self,2)),
      42           4 :   _triangle_inside(std::make_unique<TriangleInterface>(*_inside_mesh_2D)),
      43           0 :   _outside_mesh_2D(std::make_unique<ReplicatedMesh>(_comm_self,2)),
      44           4 :   _triangle_outside(std::make_unique<TriangleInterface>(*_outside_mesh_2D)),
      45           0 :   _inside_mesh_3D(std::make_unique<ReplicatedMesh>(_comm_self,3)),
      46           4 :   _tetgen_inside(std::make_unique<TetGenMeshInterface>(*_inside_mesh_3D)),
      47           0 :   _outside_mesh_3D(std::make_unique<ReplicatedMesh>(_comm_self,3)),
      48          20 :   _tetgen_outside(std::make_unique<TetGenMeshInterface>(*_outside_mesh_3D))
      49             : {
      50           8 :   cut_cntr = 0;
      51           8 : }
      52             : 
      53             : 
      54             : 
      55             : ElemCutter::~ElemCutter() = default;
      56             : 
      57             : 
      58             : 
      59         770 : bool ElemCutter::is_inside (const Elem & libmesh_dbg_var(elem),
      60             :                             const std::vector<Real> & vertex_distance_func) const
      61             : {
      62         385 :   libmesh_assert_equal_to (elem.n_vertices(), vertex_distance_func.size());
      63             : 
      64        1236 :   for (const auto & val : vertex_distance_func)
      65        1236 :     if (val > 0.)
      66         385 :       return false;
      67             : 
      68             :   // if the distance function is nonpositive, we are outside
      69           0 :   return true;
      70             : }
      71             : 
      72             : 
      73             : 
      74         770 : bool ElemCutter::is_outside (const Elem & libmesh_dbg_var(elem),
      75             :                              const std::vector<Real> & vertex_distance_func) const
      76             : {
      77         385 :   libmesh_assert_equal_to (elem.n_vertices(), vertex_distance_func.size());
      78             : 
      79        1670 :   for (const auto & val : vertex_distance_func)
      80        1670 :     if (val < 0.)
      81         385 :       return false;
      82             : 
      83             :   // if the distance function is nonnegative, we are outside
      84           0 :   return true;
      85             : }
      86             : 
      87             : 
      88             : 
      89       38273 : bool ElemCutter::is_cut (const Elem & libmesh_dbg_var(elem),
      90             :                          const std::vector<Real> & vertex_distance_func) const
      91             : {
      92       19329 :   libmesh_assert_equal_to (elem.n_vertices(), vertex_distance_func.size());
      93             : 
      94             :   Real
      95       38273 :     vmin = vertex_distance_func.front(),
      96       38273 :     vmax = vmin;
      97             : 
      98      220173 :   for (const auto & val : vertex_distance_func)
      99             :     {
     100      181900 :       vmin = std::min (vmin, val);
     101      181900 :       vmax = std::max (vmax, val);
     102             :     }
     103             : 
     104             :   // if the distance function changes sign, we're cut.
     105       38273 :   return (vmin*vmax < 0.);
     106             : }
     107             : 
     108             : 
     109             : 
     110         770 : void ElemCutter::operator()(const Elem & elem,
     111             :                             const std::vector<Real> & vertex_distance_func)
     112             : 
     113             : {
     114         385 :   libmesh_assert_equal_to (vertex_distance_func.size(), elem.n_vertices());
     115             : 
     116         770 :   _inside_elem.clear();
     117         770 :   _outside_elem.clear();
     118             : 
     119             :   // check for quick return?
     120             :   {
     121             :     // completely outside?
     122         770 :     if (this->is_outside(elem, vertex_distance_func))
     123             :       {
     124             :         //std::cout << "element completely outside\n";
     125           0 :         _outside_elem.push_back(& elem);
     126           0 :         return;
     127             :       }
     128             : 
     129             :     // completely inside?
     130         770 :     else if (this->is_inside(elem, vertex_distance_func))
     131             :       {
     132             :         //std::cout << "element completely inside\n";
     133           0 :         _inside_elem.push_back(&elem);
     134           0 :         return;
     135             :       }
     136             : 
     137         385 :     libmesh_assert (this->is_cut (elem, vertex_distance_func));
     138             :   }
     139             : 
     140             :   // we now know we are in a cut element, find the intersecting points.
     141         770 :   this->find_intersection_points (elem, vertex_distance_func);
     142             : 
     143             :   // and then dispatch the proper method
     144         770 :   switch (elem.dim())
     145             :     {
     146           0 :     case 1: this->cut_1D(elem, vertex_distance_func); break;
     147         230 :     case 2: this->cut_2D(elem, vertex_distance_func); break;
     148         540 :     case 3: this->cut_3D(elem, vertex_distance_func); break;
     149           0 :     default: libmesh_error_msg("Invalid element dimension: " << elem.dim());
     150             :     }
     151             : }
     152             : 
     153             : 
     154             : 
     155         770 : void ElemCutter::find_intersection_points(const Elem & elem,
     156             :                                           const std::vector<Real> & vertex_distance_func)
     157             : {
     158         770 :   _intersection_pts.clear();
     159             : 
     160        5422 :   for (auto e : elem.edge_index_range())
     161             :     {
     162        6978 :       std::unique_ptr<const Elem> edge (elem.build_edge_ptr(e));
     163             : 
     164             :       // find the element nodes el0, el1 that map
     165             :       unsigned int
     166        4652 :         el0 = elem.get_node_index(edge->node_ptr(0)),
     167        4652 :         el1 = elem.get_node_index(edge->node_ptr(1));
     168             : 
     169        2326 :       libmesh_assert (elem.is_vertex(el0));
     170        2326 :       libmesh_assert (elem.is_vertex(el1));
     171        2326 :       libmesh_assert_less (el0, vertex_distance_func.size());
     172        2326 :       libmesh_assert_less (el1, vertex_distance_func.size());
     173             : 
     174             :       const Real
     175        4652 :         d0 = vertex_distance_func[el0],
     176        4652 :         d1 = vertex_distance_func[el1];
     177             : 
     178             :       // if this edge has a 0 crossing
     179        4652 :       if (d0*d1 < 0.)
     180             :         {
     181        1135 :           libmesh_assert_not_equal_to (d0, d1);
     182             : 
     183             :           // then find d_star in [0,1], the
     184             :           // distance from el0 to el1 where the 0 lives.
     185        2270 :           const Real d_star = d0 / (d0 - d1);
     186             : 
     187             : 
     188             :           // Prevent adding nodes trivially close to existing
     189             :           // nodes.
     190        1135 :           const Real endpoint_tol = 0.01;
     191             : 
     192        2270 :           if ( (d_star > endpoint_tol) &&
     193        1135 :                (d_star < (1.-endpoint_tol)) )
     194             :             {
     195        2270 :               const Point x_star = (edge->point(0)*(1-d_star) +
     196        3405 :                                     edge->point(1)*d_star);
     197             : 
     198             :               /*
     199             :               std::cout << "adding cut point (d_star, x_star) = "
     200             :                         << d_star << " , " << x_star << std::endl;
     201             :               */
     202             : 
     203        2270 :               _intersection_pts.push_back (x_star);
     204             :             }
     205             :         }
     206             :     }
     207         770 : }
     208             : 
     209             : 
     210             : 
     211             : 
     212           0 : void ElemCutter::cut_1D (const Elem & /*elem*/,
     213             :                          const std::vector<Real> &/*vertex_distance_func*/)
     214             : {
     215           0 :   libmesh_not_implemented();
     216             : }
     217             : 
     218             : 
     219             : 
     220         230 : void ElemCutter::cut_2D (const Elem & elem,
     221             :                          const std::vector<Real> & vertex_distance_func)
     222             : {
     223             : #ifndef LIBMESH_HAVE_TRIANGLE
     224             : 
     225             :   // current implementation requires triangle!
     226             :   libMesh::err << "ERROR: current libMesh ElemCutter 2D implementation requires\n"
     227             :                << "       the \"triangle\" library!\n"
     228             :                << std::endl;
     229             :   libmesh_not_implemented();
     230             : 
     231             : #else // OK, LIBMESH_HAVE_TRIANGLE
     232             : 
     233             :   // std::cout << "Inside cut face element!\n";
     234             : 
     235         115 :   libmesh_assert (_inside_mesh_2D.get()  != nullptr);
     236         115 :   libmesh_assert (_outside_mesh_2D.get() != nullptr);
     237             : 
     238         230 :   _inside_mesh_2D->clear();
     239         230 :   _outside_mesh_2D->clear();
     240             : 
     241         982 :   for (auto v : make_range(elem.n_vertices()))
     242             :     {
     243        1128 :       if (vertex_distance_func[v] >= 0.)
     244         579 :         _outside_mesh_2D->add_point (elem.point(v));
     245             : 
     246        1128 :       if (vertex_distance_func[v] <= 0.)
     247         555 :         _inside_mesh_2D->add_point (elem.point(v));
     248             :     }
     249             : 
     250         686 :   for (const auto & pt : _intersection_pts)
     251             :     {
     252         456 :       _inside_mesh_2D->add_point(pt);
     253         456 :       _outside_mesh_2D->add_point(pt);
     254             :     }
     255             : 
     256             : 
     257             :   // Customize the variables for the triangulation
     258             :   // we will be cutting reference cell, and want as few triangles
     259             :   // as possible, so jack this up larger than the area we will be
     260             :   // triangulating so we are governed only by accurately defining
     261             :   // the boundaries.
     262         230 :   _triangle_inside->desired_area()  = 100.;
     263         230 :   _triangle_outside->desired_area() = 100.;
     264             : 
     265             :   // allow for small angles
     266         230 :   _triangle_inside->minimum_angle()  = 5.;
     267         230 :   _triangle_outside->minimum_angle() = 5.;
     268             : 
     269             :   // Turn off Laplacian mesh smoothing after generation.
     270         230 :   _triangle_inside->smooth_after_generating()  = false;
     271         230 :   _triangle_outside->smooth_after_generating() = false;
     272             : 
     273             :   // Triangulate!
     274         230 :   _triangle_inside->triangulate();
     275         230 :   _triangle_outside->triangulate();
     276             : 
     277             :   // std::ostringstream name;
     278             : 
     279             :   // name << "cut_face_"
     280             :   //  << cut_cntr++
     281             :   //  << ".dat";
     282             :   // _inside_mesh_2D->write  ("in_"  + name.str());
     283             :   // _outside_mesh_2D->write ("out_" + name.str());
     284             : 
     285             :   // finally, add the elements to our lists.
     286         230 :   _inside_elem.clear();
     287         230 :   _outside_elem.clear();
     288             : 
     289         721 :   for (const auto & el : _inside_mesh_2D->element_ptr_range())
     290         376 :     _inside_elem.push_back (el);
     291             : 
     292         749 :   for (const auto & el : _outside_mesh_2D->element_ptr_range())
     293         404 :     _outside_elem.push_back (el);
     294             : 
     295             : #endif
     296         230 : }
     297             : 
     298             : 
     299             : 
     300         540 : void ElemCutter::cut_3D (const Elem & elem,
     301             :                          const std::vector<Real> & vertex_distance_func)
     302             : {
     303             : #ifndef LIBMESH_HAVE_TETGEN
     304             : 
     305             :   // current implementation requires tetgen!
     306             :   libMesh::err << "ERROR: current libMesh ElemCutter 3D implementation requires\n"
     307             :                << "       the \"tetgen\" library!\n"
     308             :                << std::endl;
     309             :   libmesh_not_implemented();
     310             : 
     311             : #else // OK, LIBMESH_HAVE_TETGEN
     312             : 
     313             :   // std::cout << "Inside cut cell element!\n";
     314             : 
     315         270 :   libmesh_assert (_inside_mesh_3D.get()  != nullptr);
     316         270 :   libmesh_assert (_outside_mesh_3D.get() != nullptr);
     317             : 
     318         540 :   _inside_mesh_3D->clear();
     319         540 :   _outside_mesh_3D->clear();
     320             : 
     321        3140 :   for (auto v : make_range(elem.n_vertices()))
     322             :     {
     323        3900 :       if (vertex_distance_func[v] >= 0.)
     324        2139 :         _outside_mesh_3D->add_point (elem.point(v));
     325             : 
     326        3900 :       if (vertex_distance_func[v] <= 0.)
     327        1791 :         _inside_mesh_3D->add_point (elem.point(v));
     328             :     }
     329             : 
     330        2354 :   for (const auto & pt : _intersection_pts)
     331             :     {
     332        1814 :       _inside_mesh_3D->add_point(pt);
     333        1814 :       _outside_mesh_3D->add_point(pt);
     334             :     }
     335             : 
     336             : 
     337             :   // Triangulate!
     338         540 :   _tetgen_inside->triangulate_pointset();
     339             :   //_inside_mesh_3D->print_info();
     340         540 :   _tetgen_outside->triangulate_pointset();
     341             :   //_outside_mesh_3D->print_info();
     342             : 
     343             : 
     344             :   // (below generates some horribly expensive meshes,
     345             :   //  but seems immune to the 0 volume problem).
     346             :   // _tetgen_inside->pointset_convexhull();
     347             :   // _inside_mesh_3D->find_neighbors();
     348             :   // _inside_mesh_3D->print_info();
     349             :   // _tetgen_inside->triangulate_conformingDelaunayMesh (1.e3, 100.);
     350             :   // _inside_mesh_3D->print_info();
     351             : 
     352             :   // _tetgen_outside->pointset_convexhull();
     353             :   // _outside_mesh_3D->find_neighbors();
     354             :   // _outside_mesh_3D->print_info();
     355             :   // _tetgen_outside->triangulate_conformingDelaunayMesh (1.e3, 100.);
     356             :   // _outside_mesh_3D->print_info();
     357             : 
     358        1080 :   std::ostringstream name;
     359             : 
     360         270 :   name << "cut_cell_"
     361         540 :        << cut_cntr++
     362         540 :        << ".dat";
     363         810 :   _inside_mesh_3D->write  ("in_"  + name.str());
     364         810 :   _outside_mesh_3D->write ("out_" + name.str());
     365             : 
     366             :   // finally, add the elements to our lists.
     367         540 :   _inside_elem.clear();
     368         540 :   _outside_elem.clear();
     369             : 
     370        2366 :   for (const auto & el : _inside_mesh_3D->element_ptr_range())
     371        1556 :     if (el->volume() > std::numeric_limits<Real>::epsilon())
     372        1480 :       _inside_elem.push_back (el);
     373             : 
     374        2728 :   for (const auto & el : _outside_mesh_3D->element_ptr_range())
     375        1918 :     if (el->volume() > std::numeric_limits<Real>::epsilon())
     376        1770 :       _outside_elem.push_back (el);
     377             : 
     378             : #endif
     379         540 : }
     380             : 
     381             : 
     382             : 
     383             : } // namespace libMesh
     384             : 
     385             : #endif // LIBMESH_HAVE_TRIANGLE && LIBMESH_HAVE_TETGEN

Generated by: LCOV version 1.14