LCOV - code coverage report
Current view: top level - src/meshgenerators - NekMeshGenerator.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: be601f Lines: 447 461 97.0 %
Date: 2025-07-15 20:50:38 Functions: 22 22 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /********************************************************************/
       2             : /*                  SOFTWARE COPYRIGHT NOTIFICATION                 */
       3             : /*                             Cardinal                             */
       4             : /*                                                                  */
       5             : /*                  (c) 2021 UChicago Argonne, LLC                  */
       6             : /*                        ALL RIGHTS RESERVED                       */
       7             : /*                                                                  */
       8             : /*                 Prepared by UChicago Argonne, LLC                */
       9             : /*               Under Contract No. DE-AC02-06CH11357               */
      10             : /*                With the U. S. Department of Energy               */
      11             : /*                                                                  */
      12             : /*             Prepared by Battelle Energy Alliance, LLC            */
      13             : /*               Under Contract No. DE-AC07-05ID14517               */
      14             : /*                With the U. S. Department of Energy               */
      15             : /*                                                                  */
      16             : /*                 See LICENSE for full restrictions                */
      17             : /********************************************************************/
      18             : 
      19             : #include "NekMeshGenerator.h"
      20             : #include "CastUniquePointer.h"
      21             : #include "UserErrorChecking.h"
      22             : #include "MooseMeshUtils.h"
      23             : #include "DelimitedFileReader.h"
      24             : #include "GeometryUtils.h"
      25             : 
      26             : #include "libmesh/mesh_tools.h"
      27             : #include "libmesh/cell_hex8.h"
      28             : #include "libmesh/cell_hex20.h"
      29             : #include "libmesh/cell_hex27.h"
      30             : #include "libmesh/face_quad4.h"
      31             : #include "libmesh/face_quad8.h"
      32             : #include "libmesh/face_quad9.h"
      33             : 
      34             : registerMooseObject("CardinalApp", NekMeshGenerator);
      35             : 
      36             : InputParameters
      37         894 : NekMeshGenerator::validParams()
      38             : {
      39         894 :   InputParameters params = MeshGenerator::validParams();
      40        1788 :   params.addRequiredParam<MeshGeneratorName>("input", "The mesh we want to modify");
      41             : 
      42        1788 :   MooseEnum geom("cylinder sphere");
      43        1788 :   params.addParam<MooseEnum>(
      44             :       "geometry_type", geom, "Geometry type to use for moving boundary nodes");
      45             : 
      46             :   // optional parameters if fitting sidesets to a circular surface
      47        1788 :   MooseEnum axis("x y z", "z");
      48        1788 :   params.addParam<MooseEnum>(
      49             :       "axis",
      50             :       axis,
      51             :       "If 'geometry_type = cylinder', the axis of the mesh about which to build "
      52             :       "the cylinder surface(s)");
      53        1788 :   params.addParam<std::vector<BoundaryName>>("boundary",
      54             :                                              "Boundary(s) to enforce the curved surface");
      55        1788 :   params.addParam<std::vector<Real>>("radius", "Radius(es) of the surfaces");
      56        1788 :   params.addParam<std::vector<std::vector<Real>>>(
      57             :       "origins",
      58             :       "Origin(s) about which to form the curved surfaces; "
      59             :       "if not specified, all values default to (0, 0, 0)");
      60        1788 :   params.addParam<std::vector<std::string>>("origins_files",
      61             :                                             "Origin(s) about which to form the curved surfaces, "
      62             :                                             "with a file of points provided for each boundary. If "
      63             :                                             "not specified, all values default to (0, 0, 0)");
      64        1788 :   params.addParam<std::vector<unsigned int>>(
      65             :       "layers",
      66             :       "Number of layers to sweep for each "
      67             :       "boundary when forming the curved surfaces; if not specified, all values default to 0");
      68             : 
      69             :   // optional parameters if fitting corners of a polygon to radius of curvature
      70        1788 :   params.addParam<bool>("curve_corners",
      71        1788 :                         false,
      72             :                         "If 'geometry_type = cylinder', whether to move elements to respect radius "
      73             :                         "of curvature of polygon corners");
      74        1788 :   params.addRangeCheckedParam<unsigned int>(
      75             :       "polygon_sides",
      76             :       "polygon_sides > 2",
      77             :       "If 'geometry_type = cylinder' and when curving corners, the number of sides of the polygon "
      78             :       "to use for identifying corners");
      79        1788 :   params.addRangeCheckedParam<Real>("polygon_size",
      80             :                                     "polygon_size > 0.0",
      81             :                                     "If 'geometry_type = cylinder' and when curving corners, the "
      82             :                                     "size of the polygon (measured as distance from center to a "
      83             :                                     "corner) to use for identifying corners");
      84        1788 :   params.addRangeCheckedParam<Real>("corner_radius",
      85             :                                     "corner_radius >= 0.0",
      86             :                                     "If 'geometry_type = cylinder' and when curving corners, the "
      87             :                                     "radius of curvature of the corners");
      88        1788 :   params.addParam<unsigned int>("polygon_layers",
      89        1788 :                                 0,
      90             :                                 "If 'geometry_type = cylinder' and when curving corners, the "
      91             :                                 "number of layers to sweep for each polygon corner");
      92        1788 :   params.addParam<std::vector<Real>>(
      93             :       "polygon_layer_smoothing",
      94             :       "If 'geometry_type = cylinder' and when curving corners, the multiplicative factor to apply "
      95             :       "to each boundary layer; if not "
      96             :       "specified, all values default to 1.0");
      97        1788 :   params.addParam<BoundaryName>(
      98             :       "polygon_boundary",
      99             :       "If 'geometry_type = cylinder', boundary to enforce radius of curvature for polygon corners");
     100        1788 :   params.addParam<std::vector<std::vector<Real>>>(
     101             :       "polygon_origins",
     102             :       "If 'geometry_type = cylinder', origin(s) about which to curve "
     103             :       "the polygon corners; if not specified, defaults to (0, 0, 0)");
     104        1788 :   params.addParam<Real>(
     105             :       "rotation_angle",
     106        1788 :       0,
     107             :       "If 'geometry_type = cylinder' and when curving corners, the rotation angle (degrees) "
     108             :       "needed to apply to the original mesh to get a polygon boundary with one side horizontal");
     109             : 
     110             :   // TODO: stop-gap solution until the MOOSE reactor module does a better job
     111             :   // of clearing out lingering sidesets used for stitching, but not for actual BCs/physics
     112        1788 :   params.addParam<std::vector<BoundaryName>>(
     113             :       "boundaries_to_rebuild",
     114             :       "Boundary(s) to retain from the original mesh in the new mesh; if not "
     115             :       "specified, all original boundaries are kept.");
     116             : 
     117        1788 :   params.addParam<bool>(
     118             :       "retain_original_elem_type",
     119        1788 :       false,
     120             :       "Whether to skip the conversion "
     121             :       "from QUAD9 to QUAD8, or from HEX27 to HEX20, to get into NekRS-compatible element type. "
     122             :       "This is primarily used to just allow MOOSE's AdvancedExtruderGenerator to extrude Quad9 "
     123             :       "elements.");
     124             : 
     125         894 :   params.addClassDescription(
     126             :       "Converts MOOSE meshes to element types needed for Nek (Quad8 or Hex20), "
     127             :       "while optionally preserving curved edges (which were faceted) in the original mesh.");
     128         894 :   return params;
     129         894 : }
     130             : 
     131         447 : NekMeshGenerator::NekMeshGenerator(const InputParameters & params)
     132             :   : MeshGenerator(params),
     133         447 :     _input(getMesh("input")),
     134         894 :     _geometry_type(getParam<MooseEnum>("geometry_type")),
     135         894 :     _axis(getParam<MooseEnum>("axis")),
     136         894 :     _curve_corners(getParam<bool>("curve_corners")),
     137         894 :     _rotation_angle(getParam<Real>("rotation_angle")),
     138         894 :     _retain_original_elem_type(getParam<bool>("retain_original_elem_type")),
     139        1341 :     _has_moving_boundary(isParamValid("boundary") || _curve_corners)
     140             : {
     141         447 :   if (_geometry_type == "sphere")
     142             :   {
     143         312 :     checkUnusedParam(params,
     144             :                      {"axis",
     145             :                       "curve_corners",
     146             :                       "polygon_sides",
     147             :                       "polygon_size",
     148             :                       "corner_radius",
     149             :                       "polygon_layers",
     150             :                       "polygon_layer_smoothing",
     151             :                       "polygon_boundary",
     152             :                       "polygon_origins",
     153             :                       "rotation_angle"},
     154             :                      "'geometry_type = sphere'");
     155             :   }
     156             :   else
     157             :   {
     158         423 :     if (_curve_corners)
     159         525 :       checkRequiredParam(params,
     160             :                          {"polygon_sides", "polygon_size", "polygon_boundary", "corner_radius"},
     161             :                          "'curve_corners' is true");
     162             :     else
     163        3828 :       checkUnusedParam(params,
     164             :                        {"polygon_sides",
     165             :                         "polygon_size",
     166             :                         "polygon_boundary",
     167             :                         "corner_radius",
     168             :                         "polygon_layers",
     169             :                         "rotation_angle",
     170             :                         "polygon_layer_smoothing",
     171             :                         "polygon_origins"},
     172             :                        "'curve_corners' is false");
     173             :   }
     174             : 
     175         894 :   if (isParamValid("boundary"))
     176         600 :     checkRequiredParam(params, "radius", "specifying a 'boundary'");
     177             : 
     178         894 :   if (isParamValid("radius"))
     179         600 :     checkRequiredParam(params, "boundary", "specifying a 'radius'");
     180             : 
     181        1341 :   if (isParamValid("radius") || _curve_corners)
     182         726 :     checkRequiredParam(params, "geometry_type", "specifying a 'radius' or 'curve_corners = true'");
     183             : 
     184        1188 :   if (!isParamValid("boundary") && !isParamValid("radius"))
     185        1029 :     checkUnusedParam(
     186             :         params, {"axis", "origins", "origins_files", "layers"}, "not setting a 'boundary'");
     187        1635 : }
     188             : 
     189             : Point
     190      320025 : NekMeshGenerator::projectPoint(const Point & origin, const Point & pt) const
     191             : {
     192             :   Point vec = pt - origin;
     193             : 
     194      320025 :   if (_geometry_type == "cylinder")
     195      314265 :     vec(_axis) = 0.0;
     196             : 
     197      320025 :   return vec;
     198             : }
     199             : 
     200             : Point
     201      115269 : NekMeshGenerator::adjustPointToCircle(const unsigned int & node_id,
     202             :                                       Elem * elem,
     203             :                                       const Real & radius,
     204             :                                       const Point & origin) const
     205             : {
     206      115269 :   auto & node = elem->node_ref(node_id);
     207      115269 :   const Point pt(node(0), node(1), node(2));
     208             : 
     209             :   // project point onto the circle plane and convert to unit vector
     210      115269 :   Point xy_plane = projectPoint(origin, pt);
     211             : 
     212             :   // if the point is exactly on the origin, we don't know in which direction to move
     213             :   // it so that it lands up on the circle
     214      115269 :   if (geom_utils::isPointZero(xy_plane))
     215           3 :     mooseError("Node ID ",
     216             :                node_id,
     217             :                " of element ",
     218           3 :                elem->id(),
     219             :                " is already on the origin (",
     220             :                pt(0),
     221             :                ", ",
     222             :                pt(1),
     223             :                ", ",
     224             :                pt(2),
     225             :                ").\n"
     226             :                "This node lacks the nonzero unit vector needed to move it.");
     227             : 
     228      115266 :   Point adjustment = xy_plane.unit() * (radius - xy_plane.norm());
     229             : 
     230             :   node = pt + adjustment;
     231      115266 :   return adjustment;
     232             : }
     233             : 
     234             : BoundaryID
     235         720 : NekMeshGenerator::getBoundaryID(const BoundaryName & name, const MeshBase & mesh) const
     236             : {
     237             :   auto & boundary_info = mesh.get_boundary_info();
     238         720 :   auto id = MooseMeshUtils::getBoundaryID(name, mesh);
     239             : 
     240             :   const auto & all_boundaries = boundary_info.get_boundary_ids();
     241         720 :   if (all_boundaries.find(id) == all_boundaries.end())
     242          15 :     mooseError("Boundary '", name, "' was not found in the mesh!");
     243             : 
     244         705 :   return id;
     245             : }
     246             : 
     247             : void
     248         126 : NekMeshGenerator::checkPointLength(const std::vector<std::vector<Real>> & points,
     249             :                                    std::string name) const
     250             : {
     251         306 :   for (const auto & o : points)
     252             :   {
     253         186 :     if (o.size() == 0)
     254           6 :       mooseError("Zero-length entry in '" + name +
     255             :                  "' detected! Please be sure that each "
     256           3 :                  "entry in '" +
     257             :                  name + "' has a length\ndivisible by 3 to represent (x, y, z) coordinates.");
     258             : 
     259         183 :     if (o.size() % 3 != 0)
     260           6 :       mooseError("When using multiple origins for one boundary, each entry in '" + name +
     261             :                  "' "
     262             :                  "must have a length\ndivisible by 3 to represent (x, y, z) coordinates!");
     263             :   }
     264         120 : }
     265             : 
     266             : void
     267       90513 : NekMeshGenerator::adjustMidPointNode(const unsigned int & node_id, Elem * elem) const
     268             : {
     269       90513 :   std::pair<unsigned int, unsigned int> p = pairedNodesAboutMidPoint(node_id);
     270             : 
     271       90513 :   auto & adjust = elem->node_ref(node_id);
     272       90513 :   const auto & primary = elem->node_ref(p.first);
     273       90513 :   const auto & secondary = elem->node_ref(p.second);
     274             : 
     275       90513 :   Point pt(0.5 * (primary(0) + secondary(0)),
     276       90513 :            0.5 * (primary(1) + secondary(1)),
     277       90513 :            0.5 * (primary(2) + secondary(2)));
     278             : 
     279             :   adjust = pt;
     280       90513 : }
     281             : 
     282             : bool
     283       45684 : NekMeshGenerator::isNearCorner(const Point & pt) const
     284             : {
     285             :   // point is a moving point by the corner if the distance from the corner is less
     286             :   // that the distance from the circle tangent to the corner
     287       45684 :   Real distance_to_closest_corner = geom_utils::minDistanceToPoints(pt, _polygon_corners, _axis);
     288             : 
     289       45684 :   return distance_to_closest_corner < _max_corner_distance;
     290             : }
     291             : 
     292             : unsigned int
     293      146232 : NekMeshGenerator::getNodeIndex(const Elem * elem, const Point & pt) const
     294             : {
     295     1388232 :   for (unsigned int i = 0; i < _n_start_nodes; ++i)
     296     1388232 :     if (pt.absolute_fuzzy_equals(elem->point(i)))
     297      146232 :       return i;
     298             : 
     299           0 :   mooseError("Failed to find any node on element ", elem->id(), " that matches ", pt);
     300             : }
     301             : 
     302             : Point
     303       17664 : NekMeshGenerator::getClosestOrigin(const unsigned int & index, const Point & pt) const
     304             : {
     305       17664 :   const auto & candidates = _origin[index];
     306             :   double distance = std::numeric_limits<double>::max();
     307             :   int origin_index;
     308             : 
     309       17664 :   int n_origins = candidates.size() / 3;
     310      222420 :   for (int i = 0; i < n_origins; ++i)
     311             :   {
     312      204756 :     Point origin(candidates[3 * i], candidates[3 * i + 1], candidates[3 * i + 2]);
     313             : 
     314             :     // get the distance to this origin
     315      204756 :     Point d = projectPoint(origin, pt);
     316      204756 :     Real current_distance = d.norm();
     317             : 
     318      204756 :     if (current_distance < distance)
     319             :     {
     320             :       distance = current_distance;
     321             :       origin_index = i;
     322             :     }
     323             :   }
     324             : 
     325       17664 :   Point closest(candidates[3 * origin_index],
     326       17664 :                 candidates[3 * origin_index + 1],
     327       17664 :                 candidates[3 * origin_index + 2]);
     328       17664 :   return closest;
     329             : }
     330             : 
     331             : void
     332       17670 : NekMeshGenerator::moveElem(Elem * elem,
     333             :                            const unsigned int & boundary_index,
     334             :                            const unsigned int & primary_face,
     335             :                            const std::vector<Real> & polygon_layer_smoothing)
     336             : {
     337       17670 :   bool is_corner_boundary = boundary_index >= _n_noncorner_boundaries;
     338             : 
     339       17670 :   const auto bl_elems = getBoundaryLayerElems(elem, _layers[boundary_index], primary_face);
     340             : 
     341             :   // use the element centroid for finding the closest origin
     342       17664 :   const Point centroid = elem->vertex_average();
     343       17664 :   Point pt = getClosestOrigin(boundary_index, centroid);
     344             : 
     345      163638 :   for (auto & face_node : _side_nodes_map[primary_face])
     346             :   {
     347      145977 :     const Node & n = elem->node_ref(face_node);
     348      145977 :     const Point corner_point(n(0), n(1), n(2));
     349      145977 :     if (is_corner_boundary && !isNearCorner(corner_point))
     350       30708 :       continue;
     351             : 
     352             :     // move the points on the primary face
     353      115269 :     Point adjustment = adjustPointToCircle(face_node, elem, _radius[boundary_index], pt);
     354             : 
     355             :     // move boundary layers of paired nodes, if present
     356             :     Elem * bl_elem = elem;
     357      115266 :     unsigned int start_node = face_node;
     358      115266 :     unsigned int start_face = primary_face;
     359      115266 :     unsigned int pair_node = pairedFaceNode(start_node, start_face);
     360             : 
     361      261498 :     for (unsigned int l = 0; l < _layers[boundary_index]; ++l)
     362             :     {
     363             :       auto & paired_node = bl_elem->node_ref(pair_node);
     364      146232 :       Real multiplier = is_corner_boundary ? polygon_layer_smoothing[l] : 1.0;
     365             : 
     366             :       paired_node += adjustment * multiplier;
     367             : 
     368             :       // if this is a corner node, we also need to adjust the mid-point node
     369      146232 :       if (isCornerNode(start_node))
     370       68544 :         adjustMidPointNode(midPointNodeIndex(start_face, start_node), bl_elem);
     371             : 
     372             :       // increment to the next boundary layer element
     373      146232 :       auto next_elem = bl_elems[l];
     374             :       bl_elem = const_cast<Elem *>(next_elem);
     375      146232 :       Point pt(paired_node(0), paired_node(1), paired_node(2));
     376      146232 :       start_node = getNodeIndex(bl_elem, pt);
     377      146232 :       pair_node = pairedFaceNode(start_node, start_face);
     378             :     }
     379             : 
     380             :     // even if there aren't boundary layers, we need to adjust the mid-point side node
     381             :     // of the first moved element
     382      115266 :     if (_layers[boundary_index] == 0)
     383       48954 :       if (isCornerNode(face_node))
     384       21969 :         adjustMidPointNode(midPointNodeIndex(primary_face, face_node), elem);
     385             :   }
     386       17661 : }
     387             : 
     388             : std::vector<Elem *>
     389       17670 : NekMeshGenerator::getBoundaryLayerElems(Elem * elem,
     390             :                                         const unsigned int & n_layers,
     391             :                                         const unsigned int & primary_face) const
     392             : {
     393             :   std::vector<Elem *> nested_elems;
     394             : 
     395       17670 :   Elem * bl_elem = elem;
     396       17670 :   unsigned int start_face = primary_face;
     397       17670 :   unsigned int pair_face = _across_face[start_face];
     398             : 
     399       38133 :   for (unsigned int l = 0; l < n_layers; ++l)
     400             :   {
     401             :     // increment to the next boundary layer element
     402       20469 :     auto next_elem = getNextLayerElem(*bl_elem, pair_face, start_face);
     403       20463 :     bl_elem = const_cast<Elem *>(next_elem);
     404       20463 :     pair_face = _across_face[start_face];
     405       20463 :     nested_elems.push_back(bl_elem);
     406             :   }
     407             : 
     408       17664 :   return nested_elems;
     409             : }
     410             : 
     411             : unsigned int
     412      261498 : NekMeshGenerator::pairedFaceNode(const unsigned int & node_id, const unsigned int & face_id) const
     413             : {
     414             :   unsigned int pair;
     415             : 
     416     1272891 :   for (const auto & p : _across_pair[face_id])
     417             :   {
     418     1272891 :     if (p.first == node_id)
     419             :     {
     420      261498 :       pair = p.second;
     421      261498 :       break;
     422             :     }
     423             :   }
     424             : 
     425      261498 :   return pair;
     426             : }
     427             : 
     428             : unsigned int
     429       90513 : NekMeshGenerator::midPointNodeIndex(const unsigned int & face_id,
     430             :                                     const unsigned int & face_node) const
     431             : {
     432       90513 :   const auto & primary_nodes = _corner_nodes[face_id];
     433       90513 :   auto it = std::find(primary_nodes.begin(), primary_nodes.end(), face_node);
     434       90513 :   return _side_ids[face_id][it - primary_nodes.begin()];
     435             : }
     436             : 
     437             : const Elem *
     438       20469 : NekMeshGenerator::getNextLayerElem(const Elem & elem,
     439             :                                    const unsigned int & touching_face,
     440             :                                    unsigned int & next_touching_face) const
     441             : 
     442             : {
     443             :   std::set<const Elem *> neighbor_set;
     444             : 
     445             :   // for the input element, get the node index on the mid-face, since for Hex27 that should
     446             :   // only uniquely touch one other element
     447       20469 :   auto face_node = getFaceNode(touching_face);
     448       20469 :   auto face_pt = elem.point(face_node);
     449             : 
     450       20469 :   elem.find_point_neighbors(face_pt, neighbor_set);
     451             : 
     452       20469 :   if (neighbor_set.size() != 2)
     453           6 :     mooseError(
     454             :         "Boundary layer sweeping requires finding exactly one neighbor element\n"
     455             :         "through the layer face! Found ",
     456           6 :         neighbor_set.size() - 1,
     457             :         " neighbors for element ",
     458           6 :         elem.id(),
     459             :         ", face ",
     460             :         touching_face,
     461             :         ".\n\nThis can happen if you have specified more 'layers' than are actually in your mesh.");
     462             : 
     463             :   // When in 3-D, this mesh generator currently assumes we're working on an extruded mesh,
     464             :   // so that the face pattern follows as we sweep through the boundary layers
     465       20463 :   next_touching_face = _across_face[touching_face];
     466             : 
     467             :   std::set<const Elem *>::iterator it = neighbor_set.begin();
     468       38739 :   for (std::size_t i = 0; i < neighbor_set.size(); ++i, it++)
     469             :   {
     470             :     // we restrict the size to 2, so just return the element that is NOT the input element
     471       38739 :     if ((*it)->id() != elem.id())
     472       20463 :       return *it;
     473             :   }
     474             : 
     475           0 :   mooseError("Failed to find a neighbor element across the boundary layer! Please check that\n"
     476             :              "the 'layers' are set to reasonable values.");
     477             : }
     478             : 
     479             : void
     480         384 : NekMeshGenerator::moveNodes(std::unique_ptr<MeshBase> & mesh,
     481             :                             std::vector<Real> & polygon_layer_smoothing)
     482             : {
     483             :   auto & boundary_info = mesh->get_boundary_info();
     484             : 
     485             :   // move the nodes on the surface of interest
     486      166620 :   for (auto & elem : mesh->element_ptr_range())
     487             :   {
     488             :     bool at_least_one_face_on_boundary = false;
     489             : 
     490      561828 :     for (unsigned short int s = 0; s < _n_sides; ++s)
     491             :     {
     492             :       // get the boundary IDs that this element face lie on
     493             :       std::vector<boundary_id_type> b;
     494      478902 :       boundary_info.boundary_ids(elem, s, b);
     495             : 
     496             :       // if there is no moving boundary, or no faces of this element are on any boundaries, we can
     497             :       // leave
     498      478902 :       if (!_has_moving_boundary || b.size() == 0)
     499      343080 :         continue;
     500             : 
     501             :       // is this face on any of the specified boundaries?
     502             :       std::vector<int> indices;
     503             : 
     504      282630 :       for (const auto & b_index : b)
     505             :       {
     506      146808 :         auto it = std::find(_moving_boundary.begin(), _moving_boundary.end(), b_index);
     507             : 
     508      146808 :         if (it != _moving_boundary.end())
     509       17685 :           indices.push_back(it - _moving_boundary.begin());
     510             :       }
     511             : 
     512             :       // if none of this element's faces are on the boundaries of interest, we can leave
     513      135822 :       if (indices.size() == 0)
     514             :         continue;
     515             : 
     516       17682 :       if (indices.size() > 1)
     517           3 :         mooseError("This mesh generator does not support elements with the same face "
     518             :                    "existing on multiple moving side sets!");
     519             : 
     520       17679 :       unsigned int index = indices[0];
     521             : 
     522       17679 :       if (at_least_one_face_on_boundary)
     523           9 :         mooseError("This mesh generator cannot be applied to elements that have more than "
     524             :                    "one face on the circular sideset!");
     525             : 
     526             :       at_least_one_face_on_boundary = true;
     527       17670 :       moveElem(elem, index, s, polygon_layer_smoothing);
     528             :     }
     529         363 :   }
     530         363 : }
     531             : 
     532             : unsigned int
     533       20469 : NekMeshGenerator::getFaceNode(const unsigned int & primary_face) const
     534             : {
     535       20469 :   return _side_nodes_map[primary_face][_n_start_nodes_per_side - 1];
     536             : }
     537             : 
     538             : void
     539         390 : NekMeshGenerator::checkElementType(std::unique_ptr<MeshBase> & mesh)
     540             : {
     541             :   // the type of the first element, should match the type of all other elements
     542         390 :   _etype = mesh->elem_ptr(0)->type();
     543             : 
     544      169986 :   for (const auto & elem : mesh->element_ptr_range())
     545             :   {
     546       84609 :     if (_etype != elem->type())
     547           0 :       mooseError(
     548             :           "This mesh generator can only be applied to meshes that contain a single element type!");
     549             : 
     550       84609 :     switch (elem->type())
     551             :     {
     552             :       case QUAD9:
     553             :         break;
     554             :       case HEX27:
     555             :         break;
     556           6 :       default:
     557           6 :         mooseError("This mesh generator can only be applied to meshes that contain QUAD9 or HEX27 "
     558             :                    "elements!");
     559             :     }
     560         384 :   }
     561         384 : }
     562             : 
     563             : void
     564         384 : NekMeshGenerator::initializeElemData(std::unique_ptr<MeshBase> & mesh)
     565             : {
     566         384 :   const ElemType etype = mesh->elem_ptr(0)->type();
     567         384 :   switch (etype)
     568             :   {
     569         144 :     case QUAD9:
     570             :     {
     571         144 :       _n_start_nodes = Quad9::num_nodes;
     572         144 :       _n_start_nodes_per_side = Quad9::nodes_per_side;
     573         144 :       _n_sides = Quad9::num_sides;
     574         144 :       _n_corner_nodes = Quad4::num_nodes;
     575             : 
     576         144 :       if (_retain_original_elem_type)
     577           0 :         _n_end_nodes = Quad9::num_nodes;
     578             :       else
     579         144 :         _n_end_nodes = Quad8::num_nodes;
     580             : 
     581         144 :       _side_nodes_map.resize(_n_sides);
     582         720 :       for (unsigned int i = 0; i < _n_sides; ++i)
     583        2304 :         for (unsigned int j = 0; j < _n_start_nodes_per_side; ++j)
     584        1728 :           _side_nodes_map[i].push_back(Quad9::side_nodes_map[i][j]);
     585             : 
     586         144 :       _face_nodes_map = _side_nodes_map;
     587             : 
     588             :       // for each face, the mid-side nodes to be adjusted
     589         144 :       _side_ids.push_back({7, 5});
     590         144 :       _side_ids.push_back({4, 6});
     591         144 :       _side_ids.push_back({5, 7});
     592         144 :       _side_ids.push_back({4, 6});
     593             : 
     594             :       // corner nodes for each face
     595         144 :       _corner_nodes.resize(Quad9::num_sides);
     596         720 :       for (unsigned int i = 0; i < Quad9::num_sides; ++i)
     597        1728 :         for (unsigned int j = 0; j < Quad4::nodes_per_side; ++j)
     598        1152 :           _corner_nodes[i].push_back(Quad9::side_nodes_map[i][j]);
     599             : 
     600         144 :       _across_pair.resize(Quad9::num_sides);
     601         144 :       _across_pair[0] = {{0, 3}, {4, 6}, {1, 2}};
     602         144 :       _across_pair[1] = {{1, 0}, {5, 7}, {2, 3}};
     603         144 :       _across_pair[2] = {{2, 1}, {6, 4}, {3, 0}};
     604         144 :       _across_pair[3] = {{3, 2}, {7, 5}, {0, 1}};
     605             : 
     606         144 :       _across_face.resize(Quad9::num_sides);
     607         144 :       _across_face[0] = 2;
     608         144 :       _across_face[1] = 3;
     609         144 :       _across_face[2] = 0;
     610         144 :       _across_face[3] = 1;
     611         144 :       break;
     612             :     }
     613         240 :     case HEX27:
     614             :     {
     615         240 :       _n_start_nodes = Hex27::num_nodes;
     616         240 :       _n_start_nodes_per_side = Hex27::nodes_per_side;
     617         240 :       _n_sides = Hex27::num_sides;
     618         240 :       _n_corner_nodes = Hex8::num_nodes;
     619             : 
     620         240 :       if (_retain_original_elem_type)
     621           0 :         _n_end_nodes = Hex27::num_nodes;
     622             :       else
     623         240 :         _n_end_nodes = Hex20::num_nodes;
     624             : 
     625         240 :       _side_nodes_map.resize(_n_sides);
     626        1680 :       for (unsigned int i = 0; i < _n_sides; ++i)
     627       14400 :         for (unsigned int j = 0; j < _n_start_nodes_per_side; ++j)
     628       12960 :           _side_nodes_map[i].push_back(Hex27::side_nodes_map[i][j]);
     629             : 
     630         240 :       _face_nodes_map.resize(Hex27::num_edges);
     631        3120 :       for (unsigned int i = 0; i < Hex27::num_edges; ++i)
     632       11520 :         for (unsigned int j = 0; j < Hex27::nodes_per_edge; ++j)
     633        8640 :           _face_nodes_map[i].push_back(Hex27::edge_nodes_map[i][j]);
     634             : 
     635             :       // for each face, the mid-side nodes to be adjusted
     636         240 :       _side_ids.push_back({12, 15, 14, 13});
     637         240 :       _side_ids.push_back({11, 9, 17, 19});
     638         240 :       _side_ids.push_back({8, 18, 10, 16});
     639         240 :       _side_ids.push_back({9, 11, 19, 17});
     640         240 :       _side_ids.push_back({10, 8, 16, 18});
     641         240 :       _side_ids.push_back({12, 13, 14, 15});
     642             : 
     643             :       // corner nodes for each face
     644         240 :       _corner_nodes.resize(Hex27::num_sides);
     645        1680 :       for (unsigned int i = 0; i < Hex27::num_sides; ++i)
     646        7200 :         for (unsigned int j = 0; j < Hex8::nodes_per_side; ++j)
     647        5760 :           _corner_nodes[i].push_back(Hex27::side_nodes_map[i][j]);
     648             : 
     649         240 :       _across_pair.resize(Hex27::num_sides);
     650             :       _across_pair[0] = {
     651         240 :           {0, 4}, {8, 16}, {1, 5}, {11, 19}, {20, 25}, {9, 17}, {3, 7}, {10, 18}, {2, 6}};
     652             :       _across_pair[1] = {
     653         240 :           {0, 3}, {8, 10}, {1, 2}, {12, 15}, {21, 23}, {13, 14}, {4, 7}, {16, 18}, {5, 6}};
     654             :       _across_pair[2] = {
     655         240 :           {1, 0}, {9, 11}, {2, 3}, {13, 12}, {22, 24}, {14, 15}, {5, 4}, {17, 19}, {6, 7}};
     656             :       _across_pair[3] = {
     657         240 :           {3, 0}, {10, 8}, {2, 1}, {15, 12}, {23, 21}, {14, 13}, {7, 4}, {18, 16}, {6, 5}};
     658             :       _across_pair[4] = {
     659         240 :           {0, 1}, {11, 9}, {3, 2}, {12, 13}, {24, 22}, {15, 14}, {4, 5}, {19, 17}, {7, 6}};
     660             :       _across_pair[5] = {
     661         240 :           {4, 0}, {16, 8}, {5, 1}, {19, 11}, {25, 20}, {17, 9}, {7, 3}, {18, 10}, {6, 2}};
     662             : 
     663         240 :       _across_face.resize(Hex27::num_sides);
     664         240 :       _across_face[0] = 5;
     665         240 :       _across_face[1] = 3;
     666         240 :       _across_face[2] = 4;
     667         240 :       _across_face[3] = 1;
     668         240 :       _across_face[4] = 2;
     669         240 :       _across_face[5] = 0;
     670         240 :       break;
     671             :     }
     672           0 :     default:
     673           0 :       mooseError("Unhandled element type in initializeElemData()!");
     674             :   }
     675         384 : }
     676             : 
     677             : bool
     678      195186 : NekMeshGenerator::isCornerNode(const unsigned int & node) const
     679             : {
     680      195186 :   return node < _n_corner_nodes;
     681             : }
     682             : 
     683             : std::pair<unsigned int, unsigned int>
     684       90513 : NekMeshGenerator::pairedNodesAboutMidPoint(const unsigned int & node_id) const
     685             : {
     686       90513 :   int index = node_id - _n_corner_nodes;
     687       90513 :   unsigned int p0 = _face_nodes_map[index][0];
     688       90513 :   unsigned int p1 = _face_nodes_map[index][1];
     689       90513 :   return {p0, p1};
     690             : }
     691             : 
     692             : std::unique_ptr<MeshBase>
     693         447 : NekMeshGenerator::generate()
     694             : {
     695         447 :   std::unique_ptr<MeshBase> mesh = std::move(_input);
     696             :   auto & boundary_info = mesh->get_boundary_info();
     697             :   const auto & original_boundaries = boundary_info.get_boundary_ids();
     698             : 
     699             :   // TODO: no real reason for this restriction, just didn't need it in the first pass
     700         447 :   if (!mesh->is_replicated())
     701           0 :     mooseError("This mesh generator does not yet support distributed mesh implementations!");
     702             : 
     703             :   // get the boundary movement information, and check for valid user specifications
     704         894 :   if (isParamValid("boundary"))
     705             :   {
     706         900 :     _radius = getParam<std::vector<Real>>("radius");
     707             : 
     708         672 :     for (const auto & r : _radius)
     709         375 :       if (r <= 0.0)
     710           3 :         mooseError("All entries in 'radius' must be non-zero and positive!");
     711             : 
     712         594 :     const auto & moving_names = getParam<std::vector<BoundaryName>>("boundary");
     713         663 :     for (const auto & name : moving_names)
     714         372 :       _moving_boundary.push_back(getBoundaryID(name, *mesh));
     715             : 
     716         291 :     if (_moving_boundary.size() != _radius.size())
     717           3 :       mooseError("'boundary' and 'radius' must be the same length!"
     718             :                  "\n 'boundary' length: ",
     719           3 :                  _moving_boundary.size(),
     720             :                  "\n 'radius' length: ",
     721           3 :                  _radius.size());
     722             : 
     723         798 :     if (isParamValid("origins") && isParamValid("origins_files"))
     724           3 :       mooseError("Cannot specify both 'origins' and 'origins_files'!");
     725             : 
     726         570 :     if (isParamValid("origins"))
     727             :     {
     728         324 :       _origin = getParam<std::vector<std::vector<Real>>>("origins");
     729             : 
     730         108 :       if (_moving_boundary.size() != _origin.size())
     731           3 :         mooseError("'boundary' and 'origins' must be the same length!"
     732             :                    "\n 'boundary' length: ",
     733           3 :                    _moving_boundary.size(),
     734             :                    "\n 'origins' length: ",
     735           3 :                    _origin.size());
     736             : 
     737         204 :       checkPointLength(_origin, "origins");
     738             :     }
     739         354 :     else if (isParamValid("origins_files"))
     740             :     {
     741          54 :       auto origin_filenames = getParam<std::vector<std::string>>("origins_files");
     742             : 
     743          18 :       if (_moving_boundary.size() != origin_filenames.size())
     744           3 :         mooseError("'boundary' and 'origins_files' must be the same length!"
     745             :                    "\n 'boundary' length: ",
     746           3 :                    _moving_boundary.size(),
     747             :                    "\n 'origins_files' length: ",
     748           3 :                    origin_filenames.size());
     749             : 
     750          15 :       _origin.resize(origin_filenames.size());
     751             : 
     752             :       int i = 0;
     753          27 :       for (const auto & f : origin_filenames)
     754             :       {
     755          15 :         MooseUtils::DelimitedFileReader file(f, &_communicator);
     756             :         file.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
     757          15 :         file.read();
     758             : 
     759          15 :         const std::vector<std::vector<double>> & data = file.getData();
     760             : 
     761          99 :         for (const auto & d : data)
     762             :         {
     763          87 :           if (d.size() != 3)
     764           3 :             mooseError("All entries in '",
     765             :                        f,
     766             :                        "' must contain exactly 3 entries to represent (x, y, z) coordinates!");
     767             : 
     768          84 :           _origin[i].push_back(d[0]);
     769          84 :           _origin[i].push_back(d[1]);
     770          84 :           _origin[i].push_back(d[2]);
     771             :         }
     772             : 
     773          12 :         i += 1;
     774          12 :       }
     775          12 :     }
     776             :     else
     777             :     {
     778             :       // set to the default value of (0, 0, 0)
     779         354 :       for (std::size_t i = 0; i < _radius.size(); ++i)
     780         390 :         _origin.push_back({0.0, 0.0, 0.0});
     781             :     }
     782             : 
     783         540 :     if (isParamValid("layers"))
     784             :     {
     785         180 :       _layers = getParam<std::vector<unsigned int>>("layers");
     786             : 
     787          60 :       if (_moving_boundary.size() != _layers.size())
     788           3 :         mooseError("'boundary' and 'layers' must be the same length!"
     789             :                    "\n 'boundary' length: ",
     790           3 :                    _moving_boundary.size(),
     791             :                    "\n 'layers' length: ",
     792           3 :                    _layers.size());
     793             :     }
     794             :     else
     795             :     {
     796             :       // set to the default values of 0
     797         480 :       for (std::size_t i = 0; i < _moving_boundary.size(); ++i)
     798         270 :         _layers.push_back(0.0);
     799             :     }
     800             :   }
     801             : 
     802             :   // get information related to moving corners
     803             :   std::vector<Real> polygon_layer_smoothing;
     804         414 :   _n_noncorner_boundaries = _moving_boundary.size();
     805             : 
     806         414 :   if (_curve_corners)
     807             :   {
     808         150 :     auto polygon_sides = getParam<unsigned int>("polygon_sides");
     809         150 :     auto polygon_size = getParam<Real>("polygon_size");
     810         150 :     auto corner_radius = getParam<Real>("corner_radius");
     811         150 :     auto polygon_layers = getParam<unsigned int>("polygon_layers");
     812             : 
     813             :     std::vector<std::vector<Real>> polygon_origin;
     814         150 :     if (isParamValid("polygon_origins"))
     815             :     {
     816          42 :       polygon_origin = getParam<std::vector<std::vector<Real>>>("polygon_origins");
     817          21 :       checkPointLength(polygon_origin, "polygon_origins");
     818             : 
     819             :       // Cannot specify a non-zero rotation when giving a different polygon origin,
     820             :       // because we simply rotate the point about either the x, y, or z 'axis'. This
     821             :       // is not a hard limit, just something we didn't initially put effort to support.
     822          21 :       if (std::abs(_rotation_angle) > 1e-8)
     823           3 :         mooseError(
     824             :             "Cannot specify a non-zero 'rotation_angle' when providing custom 'polygon_origins'!");
     825             :     }
     826             :     else
     827         108 :       polygon_origin.push_back({0.0, 0.0, 0.0});
     828             : 
     829          72 :     if (polygon_layers)
     830             :     {
     831          30 :       if (isParamValid("polygon_layer_smoothing"))
     832             :       {
     833          30 :         polygon_layer_smoothing = getParam<std::vector<Real>>("polygon_layer_smoothing");
     834          15 :         if (polygon_layers != polygon_layer_smoothing.size())
     835           3 :           mooseError("The length of 'polygon_layer_smoothing' must be equal to 'polygon_layers'!");
     836             : 
     837          42 :         for (auto & p : polygon_layer_smoothing)
     838          33 :           if (p <= 0.0)
     839           3 :             mooseError("Each entry in 'polygon_layer_smoothing' must be positive and non-zero!");
     840             :       }
     841             :       else
     842             :       {
     843           0 :         for (unsigned int i = 0; i < polygon_layers; ++i)
     844           0 :           polygon_layer_smoothing.push_back(1.0);
     845             :       }
     846             :     }
     847             :     else
     848         114 :       checkUnusedParam(parameters(), "polygon_layer_smoothing", "not setting 'polygon_layers'");
     849             : 
     850          66 :     Real polygon_angle = M_PI - (2.0 * M_PI / polygon_sides);
     851          66 :     Real max_circle_radius = polygon_size * std::cos(M_PI / polygon_sides);
     852             : 
     853          66 :     if (corner_radius > max_circle_radius)
     854           3 :       mooseError("Specified 'corner_radius' cannot fit within the specified polygon!\n"
     855             :                  "The maximum allowable radius of curvature is: ",
     856             :                  max_circle_radius);
     857             : 
     858         126 :     const auto & name = getParam<BoundaryName>("polygon_boundary");
     859          63 :     auto polygon_boundary = getBoundaryID(name, *mesh);
     860             : 
     861             :     // polygon boundary shouldn't already have been specified in the 'boundary'
     862          60 :     if (std::count(_moving_boundary.begin(), _moving_boundary.end(), polygon_boundary))
     863           3 :       mooseError("The 'polygon_boundary' cannot also be listed in the 'boundary'!");
     864             : 
     865          57 :     Real theta = M_PI / 2.0 - polygon_angle / 2.0;
     866          57 :     Real l = corner_radius / std::cos(theta);
     867          57 :     _max_corner_distance = l * std::sin(theta);
     868             : 
     869             :     // find origins of the cylinders for the corner fitting
     870             :     std::vector<Point> corner_origins;
     871         150 :     for (const auto & o : polygon_origin)
     872             :     {
     873          93 :       Point shift(o[0], o[1], o[2]);
     874          93 :       auto tmp1 = geom_utils::polygonCorners(polygon_sides, polygon_size, _axis);
     875         651 :       for (const auto & t : tmp1)
     876         558 :         _polygon_corners.push_back(t + shift);
     877             : 
     878          93 :       auto tmp2 = geom_utils::polygonCorners(polygon_sides, polygon_size - l, _axis);
     879         651 :       for (const auto & t : tmp2)
     880         558 :         corner_origins.push_back(t + shift);
     881             :     }
     882             :     // apply optional rotation
     883          57 :     Real rotation_angle_radians = _rotation_angle * M_PI / 180.0;
     884             :     Point axis(0.0, 0.0, 0.0);
     885          57 :     axis(_axis) = 1.0;
     886         615 :     for (auto & o : _polygon_corners)
     887         558 :       o = geom_utils::rotatePointAboutAxis(o, rotation_angle_radians, axis);
     888         615 :     for (auto & o : corner_origins)
     889         558 :       o = geom_utils::rotatePointAboutAxis(o, rotation_angle_radians, axis);
     890             : 
     891             :     std::vector<Real> flattened_corner_origins;
     892         615 :     for (const auto & o : corner_origins)
     893        2232 :       for (unsigned int i = 0; i < 3; ++i)
     894        1674 :         flattened_corner_origins.push_back(o(i));
     895             : 
     896             :     // We can treat the polygon corners simply as extra entries in the
     897             :     // boundary, origins, and radii vectors
     898          57 :     _moving_boundary.push_back(polygon_boundary);
     899          57 :     _radius.push_back(corner_radius);
     900          57 :     _origin.push_back(flattened_corner_origins);
     901         171 :     _layers.push_back(getParam<unsigned int>("polygon_layers"));
     902          57 :   }
     903             : 
     904             :   // get information on which boundaries to rebuild, and check for valid user specifications
     905         792 :   if (isParamValid("boundaries_to_rebuild"))
     906             :   {
     907         234 :     const auto & rebuild_names = getParam<std::vector<BoundaryName>>("boundaries_to_rebuild");
     908         396 :     for (const auto & name : rebuild_names)
     909         285 :       _boundaries_to_rebuild.insert(getBoundaryID(name, *mesh));
     910             :   }
     911             :   else
     912             :   {
     913             :     // by default, rebuild all the boundaries
     914        2112 :     for (const auto & b : original_boundaries)
     915        1833 :       _boundaries_to_rebuild.insert(b);
     916             :   }
     917             : 
     918             :   // check for valid element type
     919         390 :   checkElementType(mesh);
     920             : 
     921         384 :   initializeElemData(mesh);
     922             : 
     923             :   // store all information from the incoming mesh that is needed to rebuild it from scratch
     924             :   std::vector<dof_id_type> boundary_elem_ids;
     925             :   std::vector<unsigned int> boundary_face_ids;
     926             :   std::vector<std::vector<boundary_id_type>> boundary_ids;
     927             :   std::vector<std::vector<dof_id_type>> node_ids;
     928             :   std::vector<dof_id_type> elem_ids;
     929             :   std::vector<subdomain_id_type> elem_block_ids;
     930         384 :   node_ids.resize(mesh->n_elem());
     931             : 
     932             :   // store boundary ID <-> name mapping
     933        3357 :   for (const auto & b : original_boundaries)
     934        8919 :     _boundary_id_to_name[b] = boundary_info.get_sideset_name(b);
     935             : 
     936      169974 :   for (auto & elem : mesh->element_ptr_range())
     937             :   {
     938             :     // store information about the element faces
     939      571683 :     for (unsigned short int s = 0; s < _n_sides; ++s)
     940             :     {
     941             :       std::vector<boundary_id_type> b;
     942      487080 :       boundary_info.boundary_ids(elem, s, b);
     943             : 
     944      487080 :       boundary_elem_ids.push_back(elem->id());
     945      487080 :       boundary_face_ids.push_back(s);
     946      487080 :       boundary_ids.push_back(b);
     947             :     }
     948         384 :   }
     949             : 
     950             :   int i = 0;
     951       85371 :   for (auto & elem : mesh->element_ptr_range())
     952             :   {
     953             :     // store information about the elements
     954       84603 :     elem_ids.push_back(elem->id());
     955       84603 :     elem_block_ids.push_back(elem->subdomain_id());
     956             : 
     957     1653435 :     for (unsigned int j = 0; j < _n_end_nodes; ++j)
     958     1568832 :       node_ids[i].push_back(elem->node_ref(j).id());
     959             : 
     960       84603 :     i++;
     961         384 :   }
     962             : 
     963             :   // move the nodes on the surface of interest
     964         384 :   moveNodes(mesh, polygon_layer_smoothing);
     965             : 
     966             :   // save and rebuild the nodes
     967             :   std::vector<Node> original_nodes;
     968             :   std::vector<dof_id_type> original_node_ids;
     969             : 
     970             :   // store information about the nodes
     971      795630 :   for (const auto & node : mesh->node_ptr_range())
     972             :   {
     973      794904 :     original_nodes.push_back(*node);
     974      794904 :     original_node_ids.push_back(node->id());
     975         363 :   }
     976             : 
     977         363 :   mesh->clear();
     978             : 
     979             :   // create the nodes
     980      795267 :   for (unsigned int i = 0; i < original_nodes.size(); ++i)
     981             :   {
     982      794904 :     Point pt(original_nodes[i](0), original_nodes[i](1), original_nodes[i](2));
     983      794904 :     mesh->add_point(pt, original_node_ids[i]);
     984             :   }
     985             : 
     986             :   // create the elements
     987       83247 :   for (unsigned int i = 0; i < elem_ids.size(); ++i)
     988             :   {
     989             :     Elem * elem;
     990       82884 :     switch (_etype)
     991             :     {
     992        9324 :       case QUAD9:
     993             :       {
     994        9324 :         if (_retain_original_elem_type)
     995           0 :           elem = new Quad9;
     996             :         else
     997        9324 :           elem = new Quad8;
     998             :         break;
     999             :       }
    1000       73560 :       case HEX27:
    1001             :       {
    1002       73560 :         if (_retain_original_elem_type)
    1003           0 :           elem = new Hex27;
    1004             :         else
    1005       73560 :           elem = new Hex20;
    1006             :         break;
    1007             :       }
    1008           0 :       default:
    1009           0 :         mooseError("Unhandled element type in generate()!");
    1010             :     }
    1011             : 
    1012       82884 :     elem->set_id(elem_ids[i]);
    1013       82884 :     elem->subdomain_id() = elem_block_ids[i];
    1014       82884 :     mesh->add_elem(elem);
    1015             : 
    1016             :     const auto & ids = node_ids[i];
    1017     1628676 :     for (unsigned int n = 0; n < ids.size(); ++n)
    1018             :     {
    1019     1545792 :       auto node_ptr = mesh->node_ptr(ids[n]);
    1020     1545792 :       elem->set_node(n) = node_ptr;
    1021             :     }
    1022             :   }
    1023             : 
    1024             :   // create the sidesets
    1025      479019 :   for (unsigned int i = 0; i < boundary_elem_ids.size(); ++i)
    1026             :   {
    1027      478656 :     auto elem_id = boundary_elem_ids[i];
    1028      478656 :     auto boundary = boundary_ids[i];
    1029      478656 :     const auto & elem = mesh->elem_ptr(elem_id);
    1030             : 
    1031             :     // if boundary is not included in the list to rebuild, skip it. Otherwise,
    1032             :     // rebuild it
    1033      629376 :     for (const auto & b : boundary)
    1034             :     {
    1035      150720 :       if (_boundaries_to_rebuild.find(b) == _boundaries_to_rebuild.end())
    1036       59184 :         continue;
    1037             :       else
    1038       91536 :         boundary_info.add_side(elem, boundary_face_ids[i], b);
    1039             :     }
    1040             :   }
    1041             : 
    1042        3153 :   for (const auto & b : _boundary_id_to_name)
    1043             :   {
    1044             :     // if boundary is not included in the list to rebuild, skip it
    1045        2790 :     if (_boundaries_to_rebuild.find(b.first) == _boundaries_to_rebuild.end())
    1046         867 :       continue;
    1047             : 
    1048        1923 :     boundary_info.sideset_name(b.first) = b.second;
    1049             :   }
    1050             : 
    1051         363 :   mesh->prepare_for_use();
    1052             : 
    1053         726 :   return dynamic_pointer_cast<MeshBase>(mesh);
    1054        1089 : }

Generated by: LCOV version 1.14