LCOV - code coverage report
Current view: top level - src/mesh - mesh_netgen_interface.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4232 (290bfc) with base 82cc40 Lines: 146 155 94.2 %
Date: 2025-08-27 15:46:53 Functions: 10 10 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14