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

Generated by: LCOV version 1.14