LCOV - code coverage report
Current view: top level - src/meshgenerators - XYDelaunayGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 909fe5 Lines: 274 287 95.5 %
Date: 2025-08-29 20:01:24 Functions: 4 4 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 "XYDelaunayGenerator.h"
      11             : 
      12             : #include "CastUniquePointer.h"
      13             : #include "MooseMeshUtils.h"
      14             : #include "MooseUtils.h"
      15             : 
      16             : #include "libmesh/elem.h"
      17             : #include "libmesh/enum_to_string.h"
      18             : #include "libmesh/int_range.h"
      19             : #include "libmesh/mesh_modification.h"
      20             : #include "libmesh/mesh_serializer.h"
      21             : #include "libmesh/mesh_triangle_holes.h"
      22             : #include "libmesh/parsed_function.h"
      23             : #include "libmesh/poly2tri_triangulator.h"
      24             : #include "libmesh/unstructured_mesh.h"
      25             : #include "DelimitedFileReader.h"
      26             : 
      27             : registerMooseObject("MooseApp", XYDelaunayGenerator);
      28             : 
      29             : InputParameters
      30       15059 : XYDelaunayGenerator::validParams()
      31             : {
      32       15059 :   InputParameters params = SurfaceDelaunayGeneratorBase::validParams();
      33             : 
      34       60236 :   MooseEnum algorithm("BINARY EXHAUSTIVE", "BINARY");
      35       60236 :   MooseEnum tri_elem_type("TRI3 TRI6 TRI7 DEFAULT", "DEFAULT");
      36             : 
      37       60236 :   params.addRequiredParam<MeshGeneratorName>(
      38             :       "boundary",
      39             :       "The input MeshGenerator defining the output outer boundary and required Steiner points.");
      40       60236 :   params.addParam<std::vector<BoundaryName>>(
      41             :       "input_boundary_names", "2D-input-mesh boundaries defining the output mesh outer boundary");
      42       60236 :   params.addParam<std::vector<SubdomainName>>(
      43             :       "input_subdomain_names", "1D-input-mesh subdomains defining the output mesh outer boundary");
      44       45177 :   params.addParam<unsigned int>("add_nodes_per_boundary_segment",
      45       30118 :                                 0,
      46             :                                 "How many more nodes to add in each outer boundary segment.");
      47       45177 :   params.addParam<bool>(
      48       30118 :       "refine_boundary", true, "Whether to allow automatically refining the outer boundary.");
      49             : 
      50       60236 :   params.addParam<SubdomainName>("output_subdomain_name",
      51             :                                  "Subdomain name to set on new triangles.");
      52       60236 :   params.addParam<SubdomainID>("output_subdomain_id", "Subdomain id to set on new triangles.");
      53             : 
      54       60236 :   params.addParam<BoundaryName>(
      55             :       "output_boundary",
      56             :       "Boundary name to set on new outer boundary.  Default ID: 0 if no hole meshes are stitched; "
      57             :       "or maximum boundary ID of all the stitched hole meshes + 1.");
      58       60236 :   params.addParam<std::vector<BoundaryName>>(
      59             :       "hole_boundaries",
      60             :       "Boundary names to set on holes.  Default IDs are numbered up from 1 if no hole meshes are "
      61             :       "stitched; or from maximum boundary ID of all the stitched hole meshes + 2.");
      62             : 
      63       45177 :   params.addParam<bool>(
      64             :       "verify_holes",
      65       30118 :       true,
      66             :       "Verify holes do not intersect boundary or each other.  Asymptotically costly.");
      67             : 
      68       45177 :   params.addParam<bool>("smooth_triangulation",
      69       30118 :                         false,
      70             :                         "Whether to do Laplacian mesh smoothing on the generated triangles.");
      71       45177 :   params.addParam<std::vector<MeshGeneratorName>>(
      72       30118 :       "holes", std::vector<MeshGeneratorName>(), "The MeshGenerators that define mesh holes.");
      73       45177 :   params.addParam<std::vector<bool>>(
      74       30118 :       "stitch_holes", std::vector<bool>(), "Whether to stitch to the mesh defining each hole.");
      75       45177 :   params.addParam<std::vector<bool>>("refine_holes",
      76       30118 :                                      std::vector<bool>(),
      77             :                                      "Whether to allow automatically refining each hole boundary.");
      78       90354 :   params.addRangeCheckedParam<Real>(
      79             :       "desired_area",
      80             :       0,
      81             :       "desired_area>=0",
      82             :       "Desired (maximum) triangle area, or 0 to skip uniform refinement");
      83       45177 :   params.addParam<std::string>(
      84             :       "desired_area_func",
      85       30118 :       std::string(),
      86             :       "Desired area as a function of x,y; omit to skip non-uniform refinement");
      87             : 
      88       75295 :   params.addParam<MooseEnum>(
      89             :       "algorithm",
      90             :       algorithm,
      91             :       "Control the use of binary search for the nodes of the stitched surfaces.");
      92       60236 :   params.addParam<MooseEnum>(
      93             :       "tri_element_type", tri_elem_type, "Type of the triangular elements to be generated.");
      94       45177 :   params.addParam<bool>(
      95       30118 :       "verbose_stitching", false, "Whether mesh stitching should have verbose output.");
      96       60236 :   params.addParam<std::vector<Point>>("interior_points",
      97             :                                       {},
      98             :                                       "Interior node locations, if no smoothing is used. Any point "
      99             :                                       "outside the surface will not be meshed.");
     100       60236 :   params.addParam<std::vector<FileName>>(
     101             :       "interior_point_files", {}, "Text file(s) with the interior points, one per line");
     102       30118 :   params.addClassDescription("Triangulates meshes within boundaries defined by input meshes.");
     103             : 
     104       60236 :   params.addParamNamesToGroup(
     105             :       "use_auto_area_func auto_area_func_default_size auto_area_func_default_size_dist",
     106             :       "Automatic triangle meshing area control");
     107       45177 :   params.addParamNamesToGroup("interior_points interior_point_files",
     108             :                               "Mandatory mesh interior nodes");
     109             : 
     110       30118 :   return params;
     111       15059 : }
     112             : 
     113         403 : XYDelaunayGenerator::XYDelaunayGenerator(const InputParameters & parameters)
     114             :   : SurfaceDelaunayGeneratorBase(parameters),
     115         403 :     _bdy_ptr(getMesh("boundary")),
     116         806 :     _add_nodes_per_boundary_segment(getParam<unsigned int>("add_nodes_per_boundary_segment")),
     117         806 :     _refine_bdy(getParam<bool>("refine_boundary")),
     118         403 :     _output_subdomain_id(0),
     119         806 :     _smooth_tri(getParam<bool>("smooth_triangulation")),
     120         806 :     _verify_holes(getParam<bool>("verify_holes")),
     121         806 :     _hole_ptrs(getMeshes("holes")),
     122         806 :     _stitch_holes(getParam<std::vector<bool>>("stitch_holes")),
     123         806 :     _refine_holes(getParam<std::vector<bool>>("refine_holes")),
     124         806 :     _desired_area(getParam<Real>("desired_area")),
     125         806 :     _desired_area_func(getParam<std::string>("desired_area_func")),
     126         403 :     _algorithm(parameters.get<MooseEnum>("algorithm")),
     127         403 :     _tri_elem_type(parameters.get<MooseEnum>("tri_element_type")),
     128         403 :     _verbose_stitching(parameters.get<bool>("verbose_stitching")),
     129        1209 :     _interior_points(getParam<std::vector<Point>>("interior_points"))
     130             : {
     131         344 :   if ((_desired_area > 0.0 && !_desired_area_func.empty()) ||
     132        1146 :       (_desired_area > 0.0 && _use_auto_area_func) ||
     133         399 :       (!_desired_area_func.empty() && _use_auto_area_func))
     134           8 :     paramError("desired_area_func",
     135             :                "Only one of the three methods ('desired_area', 'desired_area_func', and "
     136             :                "'_use_auto_area_func') to set element area limit should be used.");
     137             : 
     138         399 :   if (!_use_auto_area_func)
     139        1164 :     if (isParamSetByUser("auto_area_func_default_size") ||
     140        1552 :         isParamSetByUser("auto_area_func_default_size_dist") ||
     141        2716 :         isParamSetByUser("auto_area_function_num_points") ||
     142        1552 :         isParamSetByUser("auto_area_function_power"))
     143           8 :       paramError("use_auto_area_func",
     144             :                  "If this parameter is set to false, the following parameters should not be set: "
     145             :                  "'auto_area_func_default_size', 'auto_area_func_default_size_dist', "
     146             :                  "'auto_area_function_num_points', 'auto_area_function_power'.");
     147             : 
     148         395 :   if (!_stitch_holes.empty() && _stitch_holes.size() != _hole_ptrs.size())
     149           0 :     paramError("stitch_holes", "Need one stitch_holes entry per hole, if specified.");
     150             : 
     151         572 :   for (auto hole_i : index_range(_stitch_holes))
     152         177 :     if (_stitch_holes[hole_i] && (hole_i >= _refine_holes.size() || _refine_holes[hole_i]))
     153           0 :       paramError("refine_holes", "Disable auto refine of any hole boundary to be stitched.");
     154             : 
     155        1185 :   if (isParamValid("hole_boundaries"))
     156             :   {
     157          92 :     auto & hole_boundaries = getParam<std::vector<BoundaryName>>("hole_boundaries");
     158          46 :     if (hole_boundaries.size() != _hole_ptrs.size())
     159           0 :       paramError("hole_boundaries", "Need one hole_boundaries entry per hole, if specified.");
     160             :   }
     161             :   // Copied from MultiApp.C
     162         790 :   const auto & positions_files = getParam<std::vector<FileName>>("interior_point_files");
     163         410 :   for (const auto p_file_it : index_range(positions_files))
     164             :   {
     165          15 :     const std::string positions_file = positions_files[p_file_it];
     166          15 :     MooseUtils::DelimitedFileReader file(positions_file, &_communicator);
     167          15 :     file.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
     168          15 :     file.read();
     169             : 
     170          15 :     const std::vector<Point> & data = file.getDataAsPoints();
     171          75 :     for (const auto & d : data)
     172          60 :       _interior_points.push_back(d);
     173          15 :   }
     174             :   bool has_duplicates =
     175         395 :       std::any_of(_interior_points.begin(),
     176             :                   _interior_points.end(),
     177         125 :                   [&](const Point & p)
     178         125 :                   { return std::count(_interior_points.begin(), _interior_points.end(), p) > 1; });
     179         395 :   if (has_duplicates)
     180           8 :     paramError("interior_points", "Duplicate points were found in the provided interior points.");
     181         391 : }
     182             : 
     183             : std::unique_ptr<MeshBase>
     184         375 : XYDelaunayGenerator::generate()
     185             : {
     186             :   // Put the boundary mesh in a local pointer
     187             :   std::unique_ptr<UnstructuredMesh> mesh =
     188         375 :       dynamic_pointer_cast<UnstructuredMesh>(std::move(_bdy_ptr));
     189             : 
     190             :   // Get ready to triangulate the line segments we extract from it
     191         375 :   libMesh::Poly2TriTriangulator poly2tri(*mesh);
     192         375 :   poly2tri.triangulation_type() = libMesh::TriangulatorInterface::PSLG;
     193             : 
     194             :   // If we're using a user-requested subset of boundaries on that
     195             :   // mesh, get their ids.
     196         375 :   std::set<std::size_t> bdy_ids;
     197             : 
     198        1125 :   if (isParamValid("input_boundary_names"))
     199             :   {
     200          33 :     if (isParamValid("input_subdomain_names"))
     201           0 :       paramError(
     202             :           "input_subdomain_names",
     203             :           "input_boundary_names and input_subdomain_names cannot both specify an outer boundary.");
     204             : 
     205          22 :     const auto & boundary_names = getParam<std::vector<BoundaryName>>("input_boundary_names");
     206          22 :     for (const auto & name : boundary_names)
     207             :     {
     208          11 :       auto bcid = MooseMeshUtils::getBoundaryID(name, *mesh);
     209          11 :       if (bcid == BoundaryInfo::invalid_id)
     210           0 :         paramError("input_boundary_names", name, " is not a boundary name in the input mesh");
     211             : 
     212          11 :       bdy_ids.insert(bcid);
     213             :     }
     214             :   }
     215             : 
     216        1125 :   if (isParamValid("input_subdomain_names"))
     217             :   {
     218          22 :     const auto & subdomain_names = getParam<std::vector<SubdomainName>>("input_subdomain_names");
     219             : 
     220          11 :     const auto subdomain_ids = MooseMeshUtils::getSubdomainIDs(*mesh, subdomain_names);
     221             : 
     222             :     // Check that the requested subdomains exist in the mesh
     223          11 :     std::set<SubdomainID> subdomains;
     224          11 :     mesh->subdomain_ids(subdomains);
     225             : 
     226          22 :     for (auto i : index_range(subdomain_ids))
     227             :     {
     228          11 :       if (subdomain_ids[i] == Moose::INVALID_BLOCK_ID || !subdomains.count(subdomain_ids[i]))
     229           0 :         paramError(
     230           0 :             "input_subdomain_names", subdomain_names[i], " was not found in the boundary mesh");
     231             : 
     232          11 :       bdy_ids.insert(subdomain_ids[i]);
     233             :     }
     234          11 :   }
     235             : 
     236         375 :   if (!bdy_ids.empty())
     237          22 :     poly2tri.set_outer_boundary_ids(bdy_ids);
     238             : 
     239         375 :   poly2tri.set_interpolate_boundary_points(_add_nodes_per_boundary_segment);
     240         375 :   poly2tri.set_refine_boundary_allowed(_refine_bdy);
     241         375 :   poly2tri.set_verify_hole_boundaries(_verify_holes);
     242             : 
     243         375 :   poly2tri.desired_area() = _desired_area;
     244         375 :   poly2tri.minimum_angle() = 0; // Not yet supported
     245         375 :   poly2tri.smooth_after_generating() = _smooth_tri;
     246             : 
     247         375 :   std::vector<libMesh::TriangulatorInterface::MeshedHole> meshed_holes;
     248         750 :   std::vector<libMesh::TriangulatorInterface::Hole *> triangulator_hole_ptrs(_hole_ptrs.size());
     249         750 :   std::vector<std::unique_ptr<MeshBase>> hole_ptrs(_hole_ptrs.size());
     250             :   // This tells us the element orders of the hole meshes
     251             :   // For the boundary meshes, it can be access through poly2tri.segment_midpoints.
     252         375 :   std::vector<bool> holes_with_midpoints(_hole_ptrs.size());
     253         375 :   bool stitch_second_order_holes(false);
     254             : 
     255             :   // Make sure pointers here aren't invalidated by a resize
     256         375 :   meshed_holes.reserve(_hole_ptrs.size());
     257         689 :   for (auto hole_i : index_range(_hole_ptrs))
     258             :   {
     259         314 :     hole_ptrs[hole_i] = std::move(*_hole_ptrs[hole_i]);
     260         314 :     if (!hole_ptrs[hole_i]->is_prepared())
     261         189 :       hole_ptrs[hole_i]->prepare_for_use();
     262         314 :     meshed_holes.emplace_back(*hole_ptrs[hole_i]);
     263         314 :     holes_with_midpoints[hole_i] = meshed_holes.back().n_midpoints();
     264         314 :     stitch_second_order_holes = _stitch_holes.empty()
     265         491 :                                     ? false
     266         177 :                                     : ((holes_with_midpoints[hole_i] && _stitch_holes[hole_i]) ||
     267             :                                        stitch_second_order_holes);
     268         314 :     if (hole_i < _refine_holes.size())
     269         226 :       meshed_holes.back().set_refine_boundary_allowed(_refine_holes[hole_i]);
     270             : 
     271         314 :     triangulator_hole_ptrs[hole_i] = &meshed_holes.back();
     272             :   }
     273         375 :   if (stitch_second_order_holes && (_tri_elem_type == "TRI3" || _tri_elem_type == "DEFAULT"))
     274           8 :     paramError(
     275             :         "tri_element_type",
     276             :         "Cannot use first order elements with stitched quadratic element holes. Please try "
     277             :         "to specify a higher-order tri_element_type or reduce the order of the hole inputs.");
     278             : 
     279         371 :   if (!triangulator_hole_ptrs.empty())
     280         218 :     poly2tri.attach_hole_list(&triangulator_hole_ptrs);
     281             : 
     282         371 :   if (_desired_area_func != "")
     283             :   {
     284             :     // poly2tri will clone this so it's fine going out of scope
     285          11 :     libMesh::ParsedFunction<Real> area_func{_desired_area_func};
     286          11 :     poly2tri.set_desired_area_function(&area_func);
     287          11 :   }
     288         360 :   else if (_use_auto_area_func)
     289             :   {
     290          33 :     poly2tri.set_auto_area_function(
     291             :         this->comm(),
     292          11 :         _auto_area_function_num_points,
     293          11 :         _auto_area_function_power,
     294          11 :         _auto_area_func_default_size > 0.0 ? _auto_area_func_default_size : 0.0,
     295          11 :         _auto_area_func_default_size_dist > 0.0 ? _auto_area_func_default_size_dist : -1.0);
     296             :   }
     297             : 
     298         371 :   if (_tri_elem_type == "TRI6")
     299          22 :     poly2tri.elem_type() = libMesh::ElemType::TRI6;
     300         349 :   else if (_tri_elem_type == "TRI7")
     301          22 :     poly2tri.elem_type() = libMesh::ElemType::TRI7;
     302             :   // Add interior points before triangulating. Only points inside the boundaries
     303             :   // will be meshed.
     304         492 :   for (auto & point : _interior_points)
     305         121 :     mesh->add_point(point);
     306             : 
     307         371 :   poly2tri.triangulate();
     308             : 
     309        1089 :   if (isParamValid("output_subdomain_id"))
     310          33 :     _output_subdomain_id = getParam<SubdomainID>("output_subdomain_id");
     311             : 
     312        1089 :   if (isParamValid("output_subdomain_name"))
     313             :   {
     314         204 :     auto output_subdomain_name = getParam<SubdomainName>("output_subdomain_name");
     315         102 :     auto id = MooseMeshUtils::getSubdomainID(output_subdomain_name, *mesh);
     316             : 
     317         102 :     if (id == Elem::invalid_subdomain_id)
     318             :     {
     319         144 :       if (!isParamValid("output_subdomain_id"))
     320             :       {
     321             :         // We'll probably need to make a new ID, then
     322          37 :         _output_subdomain_id = MooseMeshUtils::getNextFreeSubdomainID(*mesh);
     323             : 
     324             :         // But check the hole meshes for our output subdomain name too
     325         111 :         for (auto & hole_ptr : hole_ptrs)
     326             :         {
     327          74 :           auto possible_sbdid = MooseMeshUtils::getSubdomainID(output_subdomain_name, *hole_ptr);
     328             :           // Huh, it was in one of them
     329          74 :           if (possible_sbdid != Elem::invalid_subdomain_id)
     330             :           {
     331           0 :             _output_subdomain_id = possible_sbdid;
     332           0 :             break;
     333             :           }
     334          74 :           _output_subdomain_id =
     335          74 :               std::max(_output_subdomain_id, MooseMeshUtils::getNextFreeSubdomainID(*hole_ptr));
     336             :         }
     337             :       }
     338             :     }
     339             :     else
     340             :     {
     341         162 :       if (isParamValid("output_subdomain_id"))
     342             :       {
     343           0 :         if (id != _output_subdomain_id)
     344           0 :           paramError("output_subdomain_name",
     345             :                      "name has been used by the input meshes and the corresponding id is not equal "
     346             :                      "to 'output_subdomain_id'");
     347             :       }
     348             :       else
     349          54 :         _output_subdomain_id = id;
     350             :     }
     351             :     // We do not want to set an empty subdomain name
     352         102 :     if (output_subdomain_name.size())
     353         102 :       mesh->subdomain_name(_output_subdomain_id) = output_subdomain_name;
     354         102 :   }
     355             : 
     356         363 :   if (_smooth_tri || _output_subdomain_id)
     357        8365 :     for (auto elem : mesh->element_ptr_range())
     358             :     {
     359             :       mooseAssert(elem->type() ==
     360             :                       (_tri_elem_type == "TRI6" ? TRI6 : (_tri_elem_type == "TRI7" ? TRI7 : TRI3)),
     361             :                   "Unexpected element type " << libMesh::Utility::enum_to_string(elem->type())
     362             :                                              << " found in triangulation");
     363             : 
     364        8241 :       elem->subdomain_id() = _output_subdomain_id;
     365             : 
     366             :       // I do not trust Laplacian mesh smoothing not to invert
     367             :       // elements near reentrant corners.  Eventually we'll add better
     368             :       // smoothing options, but even those might have failure cases.
     369             :       // Better to always do extra tests here than to ever let users
     370             :       // try to run on a degenerate mesh.
     371        8241 :       if (_smooth_tri)
     372             :       {
     373        2411 :         auto cross_prod = (elem->point(1) - elem->point(0)).cross(elem->point(2) - elem->point(0));
     374             : 
     375        2411 :         if (cross_prod(2) <= 0)
     376           0 :           mooseError("Inverted element found in triangulation.\n"
     377             :                      "Laplacian smoothing can create these at reentrant corners; disable it?");
     378             :       }
     379         124 :     }
     380             : 
     381         363 :   const bool use_binary_search = (_algorithm == "BINARY");
     382             : 
     383             :   // The hole meshes are specified by the user, so they could have any
     384             :   // BCID or no BCID or any combination of BCIDs on their outer
     385             :   // boundary, so we'll have to set our own BCID to use for stitching
     386             :   // there.  We'll need to check all the holes for used BCIDs, if we
     387             :   // want to pick a new ID on hole N that doesn't conflict with any
     388             :   // IDs on hole M < N (or with the IDs on the new triangulation)
     389             : 
     390             :   // The new triangulation by default assigns BCID i+1 to hole i ...
     391             :   // but we can't even use this for mesh stitching, because we can't
     392             :   // be sure it isn't also already in use on the hole's mesh and so we
     393             :   // won't be able to safely clear it afterwards.
     394         363 :   const boundary_id_type end_bcid = hole_ptrs.size() + 1;
     395             : 
     396             :   // For the hole meshes that need to be stitched, we would like to make sure the hole boundary ids
     397             :   // and output boundary id are not conflicting with the existing boundary ids of the hole meshes to
     398             :   // be stitched.
     399         363 :   BoundaryID free_boundary_id = 0;
     400         363 :   if (_stitch_holes.size())
     401             :   {
     402         309 :     for (auto hole_i : index_range(hole_ptrs))
     403             :     {
     404         173 :       if (_stitch_holes[hole_i])
     405             :       {
     406         129 :         free_boundary_id =
     407         129 :             std::max(free_boundary_id, MooseMeshUtils::getNextFreeBoundaryID(*hole_ptrs[hole_i]));
     408         129 :         hole_ptrs[hole_i]->comm().max(free_boundary_id);
     409             :       }
     410             :     }
     411         309 :     for (auto h : index_range(hole_ptrs))
     412         173 :       libMesh::MeshTools::Modification::change_boundary_id(*mesh, h + 1, h + 1 + free_boundary_id);
     413             :   }
     414         363 :   boundary_id_type new_hole_bcid = end_bcid + free_boundary_id;
     415             : 
     416             :   // We might be overriding the default bcid numbers.  We have to be
     417             :   // careful about how we renumber, though.  We pick unused temporary
     418             :   // numbers because e.g. "0->2, 2->0" is impossible to do
     419             :   // sequentially, but "0->N, 2->N+2, N->2, N+2->0" works.
     420         363 :   libMesh::MeshTools::Modification::change_boundary_id(
     421        1089 :       *mesh, 0, (isParamValid("output_boundary") ? end_bcid : 0) + free_boundary_id);
     422             : 
     423        1089 :   if (isParamValid("hole_boundaries"))
     424             :   {
     425          76 :     auto hole_boundaries = getParam<std::vector<BoundaryName>>("hole_boundaries");
     426          38 :     auto hole_boundary_ids = MooseMeshUtils::getBoundaryIDs(*mesh, hole_boundaries, true);
     427             : 
     428          87 :     for (auto h : index_range(hole_ptrs))
     429          49 :       libMesh::MeshTools::Modification::change_boundary_id(
     430          49 :           *mesh, h + 1 + free_boundary_id, h + 1 + free_boundary_id + end_bcid);
     431             : 
     432          87 :     for (auto h : index_range(hole_ptrs))
     433             :     {
     434          49 :       libMesh::MeshTools::Modification::change_boundary_id(
     435          49 :           *mesh, h + 1 + free_boundary_id + end_bcid, hole_boundary_ids[h]);
     436          49 :       mesh->get_boundary_info().sideset_name(hole_boundary_ids[h]) = hole_boundaries[h];
     437          49 :       new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(hole_boundary_ids[h] + 1));
     438             :     }
     439          38 :   }
     440             : 
     441        1089 :   if (isParamValid("output_boundary"))
     442             :   {
     443          22 :     const BoundaryName output_boundary = getParam<BoundaryName>("output_boundary");
     444             :     const std::vector<BoundaryID> output_boundary_id =
     445          33 :         MooseMeshUtils::getBoundaryIDs(*mesh, {output_boundary}, true);
     446             : 
     447          11 :     libMesh::MeshTools::Modification::change_boundary_id(
     448          11 :         *mesh, end_bcid + free_boundary_id, output_boundary_id[0]);
     449          11 :     mesh->get_boundary_info().sideset_name(output_boundary_id[0]) = output_boundary;
     450             : 
     451          11 :     new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(output_boundary_id[0] + 1));
     452          11 :   }
     453             : 
     454         363 :   bool doing_stitching = false;
     455             : 
     456         673 :   for (auto hole_i : index_range(hole_ptrs))
     457             :   {
     458         310 :     const MeshBase & hole_mesh = *hole_ptrs[hole_i];
     459         310 :     auto & hole_boundary_info = hole_mesh.get_boundary_info();
     460         310 :     const std::set<boundary_id_type> & local_hole_bcids = hole_boundary_info.get_boundary_ids();
     461             : 
     462         310 :     if (!local_hole_bcids.empty())
     463         200 :       new_hole_bcid = std::max(new_hole_bcid, boundary_id_type(*local_hole_bcids.rbegin() + 1));
     464         310 :     hole_mesh.comm().max(new_hole_bcid);
     465             : 
     466         310 :     if (hole_i < _stitch_holes.size() && _stitch_holes[hole_i])
     467         129 :       doing_stitching = true;
     468             :   }
     469             : 
     470         363 :   const boundary_id_type inner_bcid = new_hole_bcid + 1;
     471             : 
     472             :   // libMesh mesh stitching still requires a serialized mesh, and it's
     473             :   // cheaper to do that once than to do it once-per-hole
     474         363 :   libMesh::MeshSerializer serial(*mesh, doing_stitching);
     475             : 
     476             :   // Define a reference map variable for subdomain map
     477         363 :   auto & main_subdomain_map = mesh->set_subdomain_name_map();
     478         673 :   for (auto hole_i : index_range(hole_ptrs))
     479             :   {
     480         310 :     if (hole_i < _stitch_holes.size() && _stitch_holes[hole_i])
     481             :     {
     482         129 :       UnstructuredMesh & hole_mesh = dynamic_cast<UnstructuredMesh &>(*hole_ptrs[hole_i]);
     483             :       // increase hole mesh order if the triangulation mesh has higher order
     484         129 :       if (!holes_with_midpoints[hole_i])
     485             :       {
     486         107 :         if (_tri_elem_type == "TRI6")
     487          11 :           hole_mesh.all_second_order();
     488          96 :         else if (_tri_elem_type == "TRI7")
     489          11 :           hole_mesh.all_complete_order();
     490             :       }
     491         129 :       auto & hole_boundary_info = hole_mesh.get_boundary_info();
     492             : 
     493             :       // Our algorithm here requires a serialized Mesh.  To avoid
     494             :       // redundant serialization and deserialization (libMesh
     495             :       // MeshedHole and stitch_meshes still also require
     496             :       // serialization) we'll do the serialization up front.
     497         129 :       libMesh::MeshSerializer serial_hole(hole_mesh);
     498             : 
     499             :       // It would have been nicer for MeshedHole to add the BCID
     500             :       // itself, but we want MeshedHole to work with a const mesh.
     501             :       // We'll still use MeshedHole, for its code distinguishing
     502             :       // outer boundaries from inner boundaries on a
     503             :       // hole-with-holes.
     504         129 :       libMesh::TriangulatorInterface::MeshedHole mh{hole_mesh};
     505             : 
     506             :       // We have to translate from MeshedHole points to mesh
     507             :       // sides.
     508         129 :       std::unordered_map<Point, Point> next_hole_boundary_point;
     509         129 :       const int np = mh.n_points();
     510        1460 :       for (auto pi : make_range(1, np))
     511        1331 :         next_hole_boundary_point[mh.point(pi - 1)] = mh.point(pi);
     512         129 :       next_hole_boundary_point[mh.point(np - 1)] = mh.point(0);
     513             : 
     514             : #ifndef NDEBUG
     515             :       int found_hole_sides = 0;
     516             : #endif
     517        5448 :       for (auto elem : hole_mesh.element_ptr_range())
     518             :       {
     519        5319 :         if (elem->dim() != 2)
     520           0 :           mooseError("Non 2-D element found in hole; stitching is not supported.");
     521             : 
     522        5319 :         auto ns = elem->n_sides();
     523       22239 :         for (auto s : make_range(ns))
     524             :         {
     525       16920 :           auto it_s = next_hole_boundary_point.find(elem->point(s));
     526       16920 :           if (it_s != next_hole_boundary_point.end())
     527        3878 :             if (it_s->second == elem->point((s + 1) % ns))
     528             :             {
     529        1460 :               hole_boundary_info.add_side(elem, s, new_hole_bcid);
     530             : #ifndef NDEBUG
     531             :               ++found_hole_sides;
     532             : #endif
     533             :             }
     534             :         }
     535         129 :       }
     536             :       mooseAssert(found_hole_sides == np, "Failed to find full outer boundary of meshed hole");
     537             : 
     538         129 :       auto & mesh_boundary_info = mesh->get_boundary_info();
     539             : #ifndef NDEBUG
     540             :       int found_inner_sides = 0;
     541             : #endif
     542       12717 :       for (auto elem : mesh->element_ptr_range())
     543             :       {
     544       12588 :         auto ns = elem->n_sides();
     545       50388 :         for (auto s : make_range(ns))
     546             :         {
     547       37800 :           auto it_s = next_hole_boundary_point.find(elem->point((s + 1) % ns));
     548       37800 :           if (it_s != next_hole_boundary_point.end())
     549        5053 :             if (it_s->second == elem->point(s))
     550             :             {
     551        1460 :               mesh_boundary_info.add_side(elem, s, inner_bcid);
     552             : #ifndef NDEBUG
     553             :               ++found_inner_sides;
     554             : #endif
     555             :             }
     556             :         }
     557         129 :       }
     558             :       mooseAssert(found_inner_sides == np, "Failed to find full boundary around meshed hole");
     559             : 
     560             :       // Retrieve subdomain name map from the mesh to be stitched and insert it into the main
     561             :       // subdomain map
     562         129 :       const auto & increment_subdomain_map = hole_mesh.get_subdomain_name_map();
     563         129 :       main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
     564             : 
     565         129 :       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         129 :     }
     573             :   }
     574             :   // Check if one SubdomainName is shared by more than one subdomain ids
     575         363 :   std::set<SubdomainName> main_subdomain_map_name_list;
     576         528 :   for (auto const & id_name_pair : main_subdomain_map)
     577         165 :     main_subdomain_map_name_list.emplace(id_name_pair.second);
     578         363 :   if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
     579           8 :     paramError("holes", "The hole meshes contain subdomain name maps with conflicts.");
     580             : 
     581         359 :   mesh->set_isnt_prepared();
     582         718 :   return mesh;
     583         426 : }

Generated by: LCOV version 1.14