LCOV - code coverage report
Current view: top level - src/meshgenerators - XYZDelaunayGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #31761 (28487c) with base 701993 Lines: 257 266 96.6 %
Date: 2025-11-11 13:51:07 Functions: 5 5 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "XYZDelaunayGenerator.h"
      11             : 
      12             : #include "CastUniquePointer.h"
      13             : #include "MooseMeshUtils.h"
      14             : #include "MooseUtils.h"
      15             : #include "MooseMeshElementConversionUtils.h"
      16             : 
      17             : #include "libmesh/elem.h"
      18             : #include "libmesh/int_range.h"
      19             : #include "libmesh/mesh_modification.h"
      20             : #include "libmesh/mesh_netgen_interface.h"
      21             : #include "libmesh/mesh_serializer.h"
      22             : #include "libmesh/parsed_function.h"
      23             : #include "libmesh/replicated_mesh.h"
      24             : 
      25             : registerMooseObject("MooseApp", XYZDelaunayGenerator);
      26             : 
      27             : namespace std
      28             : {
      29             : template <>
      30             : struct hash<std::tuple<libMesh::Point, libMesh::Point, libMesh::Point>>
      31             : {
      32       17640 :   std::size_t operator()(const std::tuple<libMesh::Point, libMesh::Point, libMesh::Point> & p) const
      33             :   {
      34       17640 :     std::size_t seed = 0;
      35       17640 :     libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<0>(p)));
      36       17640 :     libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<1>(p)));
      37       17640 :     libMesh::boostcopy::hash_combine(seed, std::hash<libMesh::Point>()(std::get<2>(p)));
      38       17640 :     return seed;
      39             :   }
      40             : };
      41             : 
      42             : } // namespace std
      43             : 
      44             : InputParameters
      45       14761 : XYZDelaunayGenerator::validParams()
      46             : {
      47       14761 :   InputParameters params = MeshGenerator::validParams();
      48             : 
      49       59044 :   MooseEnum algorithm("BINARY EXHAUSTIVE", "BINARY");
      50             : 
      51       59044 :   params.addRequiredParam<MeshGeneratorName>(
      52             :       "boundary",
      53             :       "The input MeshGenerator defining the output outer boundary. The input mesh (the output mesh "
      54             :       "of the input mesh generator) can either "
      55             :       "include 3D volume elements or 2D surface elements.");
      56             : 
      57       59044 :   params.addParam<SubdomainName>("output_subdomain_name",
      58             :                                  "Subdomain name to set on new triangles.");
      59             : 
      60       59044 :   params.addParam<BoundaryName>(
      61             :       "output_boundary",
      62             :       "Boundary name to set on new outer boundary.  Default ID: 0 if no hole meshes are stitched; "
      63             :       "or maximum boundary ID of all the stitched hole meshes + 1.");
      64       59044 :   params.addParam<std::vector<BoundaryName>>(
      65             :       "hole_boundaries",
      66             :       "Boundary names to set on holes.  Default IDs are numbered up from 1 if no hole meshes are "
      67             :       "stitched; or from maximum boundary ID of all the stitched hole meshes + 2.");
      68             : 
      69       44283 :   params.addParam<bool>("smooth_triangulation",
      70       29522 :                         false,
      71             :                         "Whether to do Laplacian mesh smoothing on the generated triangles.");
      72       59044 :   params.addParam<std::vector<MeshGeneratorName>>(
      73             :       "holes",
      74             :       {},
      75             :       "The MeshGenerators that create meshes defining the holes. A hole mesh must contain either "
      76             :       "3D volume "
      77             :       "elements where the external surface of the mesh works as the closed manifold that defines "
      78             :       "the hole, or 2D surface elements that form the closed manifold that defines the hole.");
      79       44283 :   params.addParam<std::vector<bool>>(
      80       29522 :       "stitch_holes", std::vector<bool>(), "Whether to stitch to the mesh defining each hole.");
      81       44283 :   params.addParam<bool>("convert_holes_for_stitching",
      82       29522 :                         false,
      83             :                         "Whether to convert the 3D hole meshes with non-TRI3 surface sides into "
      84             :                         "a compatible form.");
      85             : 
      86       59044 :   MooseEnum conversion_method("ALL SURFACE", "ALL");
      87       59044 :   params.addParam<MooseEnum>(
      88             :       "conversion_method",
      89             :       conversion_method,
      90             :       "The method to convert 3D hole meshes into compatible meshes. Options are "
      91             :       "ALL: convert all elements into TET4; SURFACE: convert only the surface elements "
      92             :       "that are stitched to the generated Delaunay mesh.");
      93             : 
      94       44283 :   params.addParam<bool>(
      95             :       "combined_stitching",
      96       29522 :       false,
      97             :       "Whether to stitch all holes in one combined stitching step. This is efficient if a great "
      98             :       "number of holes are to be stitched. But it is hard to debug if problems arise.");
      99             : 
     100       88566 :   params.addRangeCheckedParam<Real>(
     101             :       "desired_volume",
     102             :       0,
     103             :       "desired_volume>=0",
     104             :       "Desired (maximum) tetrahedral volume, or 0 to skip uniform refinement");
     105             : 
     106       59044 :   params.addParam<MooseEnum>(
     107             :       "algorithm",
     108             :       algorithm,
     109             :       "Control the use of binary search for the nodes of the stitched surfaces.");
     110       44283 :   params.addParam<bool>(
     111       29522 :       "verbose_stitching", false, "Whether mesh hole stitching should have verbose output.");
     112             : 
     113       14761 :   params.addClassDescription(
     114             :       "Creates tetrahedral 3D meshes within boundaries defined by input meshes.");
     115             : 
     116       29522 :   return params;
     117       14761 : }
     118             : 
     119         251 : XYZDelaunayGenerator::XYZDelaunayGenerator(const InputParameters & parameters)
     120             :   : MeshGenerator(parameters),
     121         251 :     _bdy_ptr(getMesh("boundary")),
     122         502 :     _desired_volume(getParam<Real>("desired_volume")),
     123         251 :     _output_subdomain_id(0),
     124         502 :     _smooth_tri(getParam<bool>("smooth_triangulation")),
     125         502 :     _hole_ptrs(getMeshes("holes")),
     126         502 :     _stitch_holes(getParam<std::vector<bool>>("stitch_holes")),
     127         502 :     _convert_holes_for_stitching(getParam<bool>("convert_holes_for_stitching")),
     128         251 :     _conversion_method(parameters.get<MooseEnum>("conversion_method")),
     129         251 :     _combined_stitching(parameters.get<bool>("combined_stitching")),
     130         251 :     _algorithm(parameters.get<MooseEnum>("algorithm")),
     131         502 :     _verbose_stitching(parameters.get<bool>("verbose_stitching"))
     132             : {
     133         251 :   if (!_stitch_holes.empty() && _stitch_holes.size() != _hole_ptrs.size())
     134           8 :     paramError("stitch_holes", "Need one stitch_holes entry per hole, if specified.");
     135             : 
     136         741 :   if (isParamValid("hole_boundaries"))
     137             :   {
     138          74 :     auto & hole_boundaries = getParam<std::vector<BoundaryName>>("hole_boundaries");
     139          37 :     if (hole_boundaries.size() != _hole_ptrs.size())
     140           8 :       paramError("hole_boundaries", "Need one hole_boundaries entry per hole, if specified.");
     141             :   }
     142             : 
     143         729 :   if (isParamSetByUser("conversion_method") && !_convert_holes_for_stitching)
     144           0 :     paramError(
     145             :         "conversion_method",
     146             :         "This parameter is only applicable when convert_holes_for_stitching is set to true.");
     147         243 : }
     148             : 
     149             : std::unique_ptr<MeshBase>
     150         225 : XYZDelaunayGenerator::generate()
     151             : {
     152             : #ifdef LIBMESH_HAVE_NETGEN
     153             :   // Put the boundary mesh in a local pointer
     154             :   std::unique_ptr<UnstructuredMesh> mesh =
     155         225 :       dynamic_pointer_cast<UnstructuredMesh>(std::move(_bdy_ptr));
     156             : 
     157             :   // The libMesh Netgen removes all the sideset info
     158             :   // But it keeps some nodeset info for the retained nodes
     159             :   // We need to clear these nodeset info as they could overlap with upcoming boundary info
     160         225 :   mesh->get_boundary_info().clear_boundary_node_ids();
     161             : 
     162             :   // Get ready to triangulate its boundary
     163         225 :   libMesh::NetGenMeshInterface ngint(*mesh);
     164             : 
     165         225 :   ngint.smooth_after_generating() = _smooth_tri;
     166             : 
     167         225 :   ngint.desired_volume() = _desired_volume;
     168             : 
     169             :   // The hole meshes will be used for hole boundary identification and optionally for stitching.
     170             :   // if a hole mesh contains 3D volume elements but has non-TRI3 surface side elements, it cannot be
     171             :   // used directly for stitching. But it can be converted into an all-TET4 mesh to support hole
     172             :   // boundary identification
     173         497 :   for (const auto hole_i : index_range(_hole_ptrs))
     174             :   {
     175         288 :     UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
     176         288 :     libMesh::MeshSerializer serial_hole(hole_mesh);
     177             :     // Check the dimension of the hole mesh
     178             :     // We do not need to worry about element order here as libMesh checks it
     179         288 :     std::set<ElemType> hole_elem_types;
     180         288 :     std::set<unsigned short> hole_elem_dims;
     181         288 :     std::vector<std::pair<dof_id_type, unsigned int>> hole_elem_external_sides;
     182        9088 :     for (auto elem : hole_mesh.element_ptr_range())
     183             :     {
     184        8800 :       hole_elem_dims.emplace(elem->dim());
     185             : 
     186             :       // For a 3D element, we need to check the surface side element type instead of the element
     187             :       // type. It is also a good opportunity to define the external boundary.
     188        8800 :       if (elem->dim() == 3)
     189       44184 :         for (auto s : make_range(elem->n_sides()))
     190             :         {
     191             :           // Note that the entire external boundary is used for defining the hole at this time
     192       35604 :           if (!elem->neighbor_ptr(s))
     193             :           {
     194        5380 :             hole_elem_types.emplace(elem->side_ptr(s)->type());
     195        5380 :             hole_elem_external_sides.emplace_back(elem->id(), s);
     196             :           }
     197             :         }
     198             :       // For a non-3D element, we just need to record the element type
     199             :       else
     200         220 :         hole_elem_types.emplace(elem->type());
     201         288 :     }
     202         288 :     if (hole_elem_dims.size() != 1 || *hole_elem_dims.begin() < 2)
     203          16 :       paramError(
     204             :           "holes",
     205             :           "All elements in a hole mesh must have the same dimension that is either 2D or 3D.");
     206         280 :     else if (*hole_elem_dims.begin() == 3)
     207             :     {
     208             :       // For 3D meshes, if there are non-TRI3 surface side elements
     209             :       // (1) if no stitching is needed, we can just convert the whole mesh into TET to facilitate
     210             :       // boundary identification (2) if stitching is needed, we can still convert and stitch, but
     211             :       // that would modify the input hole mesh
     212         254 :       if (*hole_elem_types.begin() != ElemType::TRI3 || hole_elem_types.size() > 1)
     213             :       {
     214          70 :         if (_stitch_holes.size() && _stitch_holes[hole_i] && !_convert_holes_for_stitching)
     215           8 :           paramError("holes",
     216             :                      "3D hole meshes with non-TRI3 surface elements cannot be stitched without "
     217             :                      "converting them to TET4. Consider setting convert_holes_for_stitching=true.");
     218          66 :         else if (_stitch_holes.size() && _stitch_holes[hole_i] && _conversion_method == "SURFACE")
     219             :         {
     220             :           // Create a transition layer with triangle sides on the external boundary of the hole
     221          11 :           BoundaryID temp_ext_bid = MooseMeshUtils::getNextFreeBoundaryID(hole_mesh);
     222         605 :           for (const auto & hees : hole_elem_external_sides)
     223         594 :             hole_mesh.get_boundary_info().add_side(hees.first, hees.second, temp_ext_bid);
     224          11 :           MooseMeshElementConversionUtils::transitionLayerGenerator(
     225          22 :               hole_mesh, std::vector<BoundaryName>({std::to_string(temp_ext_bid)}), 1, false);
     226          11 :           hole_mesh.get_boundary_info().remove_id(temp_ext_bid);
     227             :           // MeshSerializer's destructor calls delete_remote_elements()
     228             :           // For replicated meshes, we need to refresh the neighbor information
     229          11 :           if (!hole_mesh.is_replicated())
     230           2 :             hole_mesh.prepare_for_use();
     231             :           else
     232           9 :             hole_mesh.find_neighbors();
     233             :         }
     234             :         else
     235          55 :           MeshTools::Modification::all_tri(**_hole_ptrs[hole_i]);
     236             :       }
     237             :     }
     238             :     else // if (*hole_elem_dims.begin() == 2)
     239             :     {
     240             :       // There is no point to stitch a 2D hole mesh, so we throw an error if this is attempted
     241          26 :       if (_stitch_holes.size() && _stitch_holes[hole_i])
     242           4 :         paramError("holes",
     243           4 :                    "the hole mesh with index " + std::to_string(hole_i) +
     244             :                        " is a 2D mesh, for which stitching onto a 3D mesh does not make sense.");
     245             :     }
     246         272 :   }
     247             : 
     248             :   std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> ngholes =
     249         209 :       std::make_unique<std::vector<std::unique_ptr<UnstructuredMesh>>>();
     250             : 
     251             :   // The libMesh Netgen interface will modify hole meshes in-place, so
     252             :   // we make copies to pass in.
     253         481 :   for (std::unique_ptr<MeshBase> * hole_ptr : _hole_ptrs)
     254             :   {
     255             :     // How did we never add a ReplicatedMesh(MeshBase&) constructor in
     256             :     // libMesh?
     257         272 :     const UnstructuredMesh & hole = dynamic_cast<UnstructuredMesh &>(**hole_ptr);
     258         272 :     ngholes->push_back(std::make_unique<ReplicatedMesh>(hole));
     259             :   }
     260             : 
     261         209 :   if (!_hole_ptrs.empty())
     262         169 :     ngint.attach_hole_list(std::move(ngholes));
     263             : 
     264         209 :   ngint.triangulate();
     265             : 
     266         627 :   if (isParamValid("output_subdomain_name"))
     267             :   {
     268         114 :     auto output_subdomain_name = getParam<SubdomainName>("output_subdomain_name");
     269          57 :     _output_subdomain_id = MooseMeshUtils::getSubdomainID(output_subdomain_name, *mesh);
     270             : 
     271          57 :     if (_output_subdomain_id == Elem::invalid_subdomain_id)
     272             :     {
     273             :       // We'll probably need to make a new ID, then
     274          57 :       _output_subdomain_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
     275             : 
     276             :       // But check the hole meshes for our output subdomain name too
     277         153 :       for (auto & hole_ptr : _hole_ptrs)
     278             :       {
     279          96 :         auto possible_sbdid = MooseMeshUtils::getSubdomainID(output_subdomain_name, **hole_ptr);
     280             :         // Huh, it was in one of them
     281          96 :         if (possible_sbdid != Elem::invalid_subdomain_id)
     282             :         {
     283           0 :           _output_subdomain_id = possible_sbdid;
     284           0 :           break;
     285             :         }
     286          96 :         _output_subdomain_id =
     287          96 :             std::max(_output_subdomain_id, MooseMeshUtils::getNextFreeSubdomainID(**hole_ptr));
     288             :       }
     289             : 
     290          57 :       mesh->subdomain_name(_output_subdomain_id) = output_subdomain_name;
     291             :     }
     292          57 :   }
     293             : 
     294         209 :   if (_smooth_tri || _output_subdomain_id)
     295       30721 :     for (auto elem : mesh->element_ptr_range())
     296             :     {
     297       30631 :       elem->subdomain_id() = _output_subdomain_id;
     298             : 
     299             :       // I do not trust Laplacian mesh smoothing not to invert
     300             :       // elements near reentrant corners.  Eventually we'll add better
     301             :       // smoothing options, but even those might have failure cases.
     302             :       // Better to always do extra tests here than to ever let users
     303             :       // try to run on a degenerate mesh.
     304       30631 :       if (elem->is_flipped())
     305             :       {
     306           0 :         if (_smooth_tri)
     307           0 :           mooseError("Inverted element found in triangulation.\n"
     308             :                      "Laplacian smoothing can create these at reentrant corners; disable it?");
     309             :         else
     310           0 :           mooseError("Unexplained inverted element found in triangulation.\n");
     311             :       }
     312          90 :     }
     313             : 
     314         209 :   const bool use_binary_search = (_algorithm == "BINARY");
     315             : 
     316             :   // The hole meshes are specified by the user, so they could have any
     317             :   // BCID or no BCID or any combination of BCIDs on their outer
     318             :   // boundary, so we'll have to set our own BCID to use for stitching
     319             :   // there.  We'll need to check all the holes for used BCIDs, if we
     320             :   // want to pick a new ID on hole N that doesn't conflict with any
     321             :   // IDs on hole M < N (or with the IDs on the new triangulation)
     322             : 
     323             :   // The Netgen generated mesh does not have hole and output boundary ids,
     324             :   // which is different from its 2D counterpart.
     325             :   // So, we need to assign boundary ids to the hole boundaries and output boundary
     326             :   // By default, we assign the hole boundary ids to be 1,2,..., N
     327             :   // and the output boundary id to be 0
     328             :   // We will need a different set of boundary ids if we are stitching holes
     329             : 
     330             :   // end_bcid is one more than the maximum of default output boundary id and hole boundary ids
     331         209 :   const boundary_id_type end_bcid = _hole_ptrs.size() + 1;
     332             : 
     333             :   // For the hole meshes that need to be stitched, we would like to make sure the hole boundary ids
     334             :   // and output boundary id are not conflicting with the existing boundary ids of the hole meshes to
     335             :   // be stitched.
     336         209 :   BoundaryID free_boundary_id = 0;
     337             :   // We need to get a free boundary id that is larger than the existing boundary ids in any hole
     338             :   // meshes
     339         209 :   if (_stitch_holes.size())
     340             :   {
     341         309 :     for (auto hole_i : index_range(_hole_ptrs))
     342             :     {
     343         184 :       if (_stitch_holes[hole_i])
     344             :       {
     345         151 :         free_boundary_id =
     346         151 :             std::max(free_boundary_id, MooseMeshUtils::getNextFreeBoundaryID(**_hole_ptrs[hole_i]));
     347         151 :         (*_hole_ptrs[hole_i])->comm().max(free_boundary_id);
     348             :       }
     349             :     }
     350             :   }
     351             :   // new_hole_bcid is used to ensure the boundary ids used for stitching have no conflicts
     352             :   // If we shift the default hole boundary ids and output boundary id by free_boundary_id, we need
     353             :   // to have new_hole_bcid that is larger than any existing boundary ids in the hole meshes and the
     354             :   // Netgen generated mesh
     355         209 :   boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
     356             : 
     357         418 :   auto hole_boundaries = isParamValid("hole_boundaries")
     358         209 :                              ? getParam<std::vector<BoundaryName>>("hole_boundaries")
     359         275 :                              : std::vector<BoundaryName>();
     360             :   const BoundaryName output_boundary =
     361         484 :       isParamValid("output_boundary") ? getParam<BoundaryName>("output_boundary") : BoundaryName();
     362             : 
     363             :   // if outer boundary id is not specified by a numeric output_boundary, we just assign
     364             :   // free_boundary_id to the output boundary
     365             :   const std::vector<BoundaryID> output_boundary_id =
     366         418 :       isParamValid("output_boundary")
     367          33 :           ? (MooseUtils::isDigits(output_boundary)
     368          33 :                  ? std::vector<BoundaryID>(
     369           0 :                        1, MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(output_boundary))
     370             :                  : std::vector<BoundaryID>(1, free_boundary_id))
     371         275 :           : std::vector<BoundaryID>();
     372             :   // Similarly, set the hole boundary ids one by one
     373         209 :   std::vector<BoundaryID> hole_boundary_ids;
     374         627 :   if (isParamValid("hole_boundaries"))
     375          99 :     for (auto h : index_range(hole_boundaries))
     376          66 :       hole_boundary_ids.push_back(
     377          66 :           MooseUtils::isDigits(hole_boundaries[h])
     378          22 :               ? MooseMeshUtils::getIDFromName<BoundaryName, BoundaryID>(hole_boundaries[h])
     379          44 :               : h + 1 + free_boundary_id);
     380             : 
     381             :   // if large ids are used in the hole boundaries or the output boundary,
     382             :   // we need to make sure the new_hole_bcid is larger than them
     383         209 :   if (hole_boundary_ids.size())
     384          99 :     for (auto h : index_range(_hole_ptrs))
     385          66 :       new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
     386         209 :   if (output_boundary_id.size())
     387          33 :     new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
     388             : 
     389         209 :   bool doing_stitching = false;
     390             : 
     391         481 :   for (auto hole_i : index_range(_hole_ptrs))
     392             :   {
     393         272 :     const MeshBase & hole_mesh = **_hole_ptrs[hole_i];
     394         272 :     auto & hole_boundary_info = hole_mesh.get_boundary_info();
     395         272 :     const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
     396             : 
     397         272 :     if (!local_hole_bcids.empty())
     398         250 :       new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
     399         272 :     hole_mesh.comm().max(new_hole_bcid);
     400             : 
     401         272 :     if (_stitch_holes.size() && _stitch_holes[hole_i])
     402         151 :       doing_stitching = true;
     403             :   }
     404             :   // Now we can define boundaries to assist with stitching
     405         209 :   const boundary_id_type inner_bcid = new_hole_bcid + 1;
     406             : 
     407             :   // libMesh mesh stitching still requires a serialized mesh, and it's
     408             :   // cheaper to do that once than to do it once-per-hole
     409         209 :   libMesh::MeshSerializer serial(*mesh, doing_stitching);
     410             : 
     411             :   // We'll be looking for any sides that match between hole meshes and
     412             :   // the newly triangulated mesh, to apply bcids accordingly.  We
     413             :   // can't key on Elem::key() here, because that depends on node ids
     414             :   // that differ from mesh to mesh.  We can't use centroids here,
     415             :   // because that depends on rounding error in order of operations
     416             :   // that can differ from mesh to mesh.  The node locations themselves
     417             :   // should always match up exactly, though, so let's use (sorted!)
     418             :   // tuples of those to map from nodes to elements and side numbers.
     419             :   // Also added a Boolean to check if the face is already used
     420             :   std::unordered_map<std::tuple<Point, Point, Point>,
     421             :                      std::pair<std::pair<Elem *, unsigned int>, bool>>
     422         209 :       mesh_faces;
     423             : 
     424       17640 :   auto sorted_point_tuple = [](Elem & elem, unsigned int side)
     425             :   {
     426       17640 :     std::vector<unsigned int> nodes_on_side = elem.nodes_on_side(side);
     427             :     libmesh_assert_equal_to(nodes_on_side.size(), 3);
     428       17640 :     std::vector<Point> p(3);
     429       70560 :     for (auto i : index_range(p))
     430       52920 :       p[i] = elem.point(nodes_on_side[i]);
     431       17640 :     if (p[0] < p[1])
     432             :     {
     433        8873 :       if (p[1] < p[2])
     434        2556 :         return std::make_tuple(p[0], p[1], p[2]);
     435        6317 :       else if (p[0] < p[2])
     436        3426 :         return std::make_tuple(p[0], p[2], p[1]);
     437             :       else
     438        2891 :         return std::make_tuple(p[2], p[0], p[1]);
     439             :     }
     440             :     else
     441             :     {
     442        8767 :       if (p[0] < p[2])
     443        2789 :         return std::make_tuple(p[1], p[0], p[2]);
     444        5978 :       else if (p[1] < p[2])
     445        3373 :         return std::make_tuple(p[1], p[2], p[0]);
     446             :       else
     447        2605 :         return std::make_tuple(p[2], p[1], p[0]);
     448             :     }
     449       17640 :   };
     450             : 
     451         209 :   if (!_hole_ptrs.empty())
     452       37887 :     for (auto elem : mesh->element_ptr_range())
     453      188590 :       for (auto s : make_range(elem->n_sides()))
     454      150872 :         if (!elem->neighbor_ptr(s))
     455             :         {
     456       10848 :           auto points = sorted_point_tuple(*elem, s);
     457             :           libmesh_assert(!mesh_faces.count(points));
     458       10848 :           mesh_faces.emplace(points, std::make_pair(std::make_pair(elem, s), false));
     459         169 :         }
     460             : 
     461         209 :   auto & mesh_boundary_info = mesh->get_boundary_info();
     462             : 
     463             :   // Define a reference map variable for subdomain map
     464         209 :   auto & main_subdomain_map = mesh->set_subdomain_name_map();
     465         481 :   for (auto hole_i : index_range(_hole_ptrs))
     466             :   {
     467         272 :     UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
     468         272 :     auto & hole_boundary_info = hole_mesh.get_boundary_info();
     469             : 
     470             :     // Our algorithm here requires a serialized Mesh.  To avoid
     471             :     // redundant serialization and deserialization (libMesh
     472             :     // MeshedHole and stitch_meshes still also require
     473             :     // serialization) we'll do the serialization up front.
     474         272 :     libMesh::MeshSerializer serial_hole(hole_mesh);
     475             : 
     476             :     // We'll look for any sides that match between the hole mesh and
     477             :     // the newly triangulated mesh, and apply bcids accordingly.
     478       12705 :     for (auto elem : hole_mesh.element_ptr_range())
     479       63309 :       for (auto s : make_range(elem->n_sides()))
     480       50876 :         if (!elem->neighbor_ptr(s))
     481             :         {
     482        6792 :           auto points = sorted_point_tuple(*elem, s);
     483        6792 :           auto it = mesh_faces.find(points);
     484             : 
     485             :           // I'd love to assert that we don't have any missing
     486             :           // matches, but our holes might themselves have holes
     487        6792 :           if (it != mesh_faces.end())
     488             :           {
     489        6792 :             auto [main_elem, main_side] = it->second.first;
     490        6792 :             if (_stitch_holes.size() && _stitch_holes[hole_i])
     491             :             {
     492        3888 :               hole_boundary_info.add_side(elem, s, new_hole_bcid);
     493        3888 :               mesh_boundary_info.add_side(main_elem, main_side, inner_bcid);
     494             :             }
     495             :             // We would like to take this opportunity to identify the hole boundary
     496             :             // We set the boundary id to hole_i + 1 at this stage (the default)
     497        6792 :             mesh_boundary_info.add_side(main_elem, main_side, hole_i + 1);
     498        6792 :             it->second.second = true;
     499             :           }
     500         272 :         }
     501         272 :   }
     502             : 
     503             :   // Any sides that do not match the hole surfaces belong to the external boundary
     504             :   // We set the boundary id to 0 at this stage (the default)
     505       11057 :   for (auto & [points, elem_side] : mesh_faces)
     506       10848 :     if (!elem_side.second)
     507             :     {
     508        4056 :       auto [main_elem, main_side] = elem_side.first;
     509        4056 :       mesh_boundary_info.add_side(main_elem, main_side, 0);
     510             :     }
     511             : 
     512             :   // Now hole boundary ids are 1,2,..., N
     513             :   // Now external boundary id is 0
     514             :   // We will find the upper bound of the boundary ids of the mesh so that we would not overwrite
     515             :   // things during renumbering (because of the bcids used for stitching, this is different from
     516             :   // end_bcid)
     517         209 :   const auto temp_bcid_shift = MooseMeshUtils::getNextFreeBoundaryID(*mesh);
     518             :   // If we stitch holes, we want to make sure the netgen mesh has no conflicting boundary ids
     519             :   // with the hole meshes.
     520             :   // OR, if we assign custom boundary ids, we want to make sure no overwriting occurs.
     521             :   // So we shift all these boundary ids by temp_bcid_shift + free_boundary_id
     522             :   // before we assign the new boundary ids
     523         209 :   if (_stitch_holes.size() || output_boundary_id.size())
     524         147 :     libMesh::MeshTools::Modification::change_boundary_id(
     525         147 :         *mesh, 0, temp_bcid_shift + free_boundary_id);
     526         209 :   if (_stitch_holes.size() || hole_boundary_ids.size())
     527         375 :     for (auto hole_i : index_range(_hole_ptrs))
     528         228 :       libMesh::MeshTools::Modification::change_boundary_id(
     529         228 :           *mesh, hole_i + 1, hole_i + 1 + temp_bcid_shift + free_boundary_id);
     530             : 
     531             :   // Now we can reassign the boundary ids
     532             :   // If these ids are specified, we will use them
     533             :   // Otherwise, we will use the default ones
     534         209 :   if (output_boundary_id.size())
     535          33 :     libMesh::MeshTools::Modification::change_boundary_id(
     536          33 :         *mesh, temp_bcid_shift + free_boundary_id, output_boundary_id[0]);
     537             :   else
     538         176 :     libMesh::MeshTools::Modification::change_boundary_id(
     539         176 :         *mesh, temp_bcid_shift + free_boundary_id, free_boundary_id);
     540             : 
     541         209 :   if (hole_boundary_ids.size())
     542          99 :     for (auto hole_i : index_range(_hole_ptrs))
     543          66 :       libMesh::MeshTools::Modification::change_boundary_id(
     544          66 :           *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_boundary_ids[hole_i]);
     545             :   else
     546         382 :     for (auto hole_i : index_range(_hole_ptrs))
     547         206 :       libMesh::MeshTools::Modification::change_boundary_id(
     548         206 :           *mesh, hole_i + 1 + temp_bcid_shift + free_boundary_id, hole_i + 1 + free_boundary_id);
     549             : 
     550         481 :   for (auto hole_i : index_range(_hole_ptrs))
     551             :   {
     552         272 :     UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(**_hole_ptrs[hole_i]);
     553             : 
     554         272 :     if (_stitch_holes.size() && _stitch_holes[hole_i])
     555             :     {
     556             :       // Retrieve subdomain name map from the mesh to be stitched and insert it into the main
     557             :       // subdomain map
     558         151 :       const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
     559         151 :       main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
     560             : 
     561         151 :       if (_combined_stitching)
     562          22 :         MooseMeshUtils::copyIntoMesh(*this, *mesh, hole_mesh, false, false, _communicator);
     563             :       else
     564             :       {
     565         129 :         std::size_t n_nodes_stitched = mesh->stitch_meshes(hole_mesh,
     566             :                                                            inner_bcid,
     567             :                                                            new_hole_bcid,
     568             :                                                            TOLERANCE,
     569             :                                                            /*clear_stitched_bcids*/ true,
     570         129 :                                                            _verbose_stitching,
     571             :                                                            use_binary_search);
     572             : 
     573         129 :         if (!n_nodes_stitched)
     574           0 :           mooseError("Failed to stitch hole mesh ", hole_i, " to new tetrahedralization.");
     575             :       }
     576             :     }
     577             :   }
     578         209 :   if (doing_stitching && _combined_stitching)
     579             :   {
     580          11 :     std::size_t n_nodes_stitched = mesh->stitch_surfaces(inner_bcid,
     581             :                                                          new_hole_bcid,
     582             :                                                          TOLERANCE,
     583             :                                                          /*clear_stitched_bcids*/ true,
     584          11 :                                                          _verbose_stitching,
     585             :                                                          use_binary_search);
     586          11 :     if (!n_nodes_stitched)
     587           0 :       mooseError("Failed to stitch combined hole meshes to new tetrahedralization.");
     588             :   }
     589             : 
     590             :   // Add user-specified sideset names
     591         209 :   if (hole_boundary_ids.size())
     592          99 :     for (auto h : index_range(_hole_ptrs))
     593          66 :       mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
     594         209 :   if (output_boundary_id.size())
     595          33 :     mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = output_boundary;
     596             : 
     597             :   // Check if one SubdomainName is shared by more than one subdomain ids
     598         209 :   std::set<SubdomainName> main_subdomain_map_name_list;
     599         318 :   for (auto const & id_name_pair : main_subdomain_map)
     600         109 :     main_subdomain_map_name_list.emplace(id_name_pair.second);
     601         209 :   if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
     602           8 :     paramError("holes", "The hole meshes contain subdomain name maps with conflicts.");
     603             : 
     604             :   // We're done with the hole meshes now, and MeshGenerator doesn't
     605             :   // want them anymore either.
     606         469 :   for (auto & hole_ptr : _hole_ptrs)
     607         264 :     hole_ptr->reset();
     608             : 
     609         205 :   mesh->set_isnt_prepared();
     610         410 :   return mesh;
     611             : #else
     612             :   mooseError("Cannot use XYZDelaunayGenerator without NetGen-enabled libMesh.");
     613             :   return std::unique_ptr<MeshBase>();
     614             : #endif
     615         216 : }

Generated by: LCOV version 1.14