LCOV - code coverage report
Current view: top level - src/meshgenerators - XYTriangleBoundaryLayerGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 9a5f1f Lines: 100 104 96.2 %
Date: 2026-06-21 21:23:42 Functions: 3 3 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 "XYTriangleBoundaryLayerGenerator.h"
      11             : 
      12             : #include "CastUniquePointer.h"
      13             : #include "MooseMeshUtils.h"
      14             : #include "BoundaryLayerUtils.h"
      15             : 
      16             : #include "libmesh/mesh_modification.h"
      17             : #include "libmesh/mesh_serializer.h"
      18             : #include "libmesh/unstructured_mesh.h"
      19             : 
      20             : registerMooseObject("MooseApp", XYTriangleBoundaryLayerGenerator);
      21             : 
      22             : InputParameters
      23        3261 : XYTriangleBoundaryLayerGenerator::validParams()
      24             : {
      25        3261 :   InputParameters params = MeshGenerator::validParams();
      26             : 
      27       13044 :   params.addRequiredParam<MeshGeneratorName>(
      28             :       "input", "The input mesh based on which to create the conformal boundary layer.");
      29       13044 :   params.addRequiredParam<Real>("thickness",
      30             :                                 "The total thickness of the boundary layer to be created.");
      31       16305 :   params.addRangeCheckedParam<unsigned int>(
      32        6522 :       "num_layers", 1, "num_layers>0", "The total number of boundary layers to be created.");
      33        9783 :   params.addParam<Real>(
      34             :       "layer_bias",
      35        6522 :       1.0,
      36             :       "The bias factor for the thickness of each layer of elements. A value > 1 leads to thicker "
      37             :       "layers away from the input mesh, while a value < 1 leads to thicker layers close to the "
      38             :       "input mesh. The default value of 1 leads to uniform layer thickness.");
      39             : 
      40       13044 :   MooseEnum boundary_layer_direction("OUTWARD INWARD", "OUTWARD");
      41       13044 :   params.addParam<MooseEnum>(
      42             :       "boundary_layer_direction",
      43             :       boundary_layer_direction,
      44             :       "In which direction the boundary layer is created with respect to the side normal of the "
      45             :       "elements along the boundary of the input mesh.");
      46             : 
      47        9783 :   params.addParam<std::vector<BoundaryName>>(
      48             :       "boundary_names",
      49        6522 :       std::vector<BoundaryName>(),
      50             :       "The names of the boundaries around which the boundary layer will be created.");
      51             : 
      52       13044 :   params.addParam<BoundaryName>("interface_name",
      53             :                                 "The optional boundary name to be assigned to the interface "
      54             :                                 "between the generated boundary layer and the input mesh.");
      55       13044 :   params.addParam<BoundaryName>(
      56             :       "surface_name",
      57             :       "The optional boundary name to be assigned to the surface of the generated boundary layer.");
      58             : 
      59       13044 :   params.addParam<SubdomainName>("subdomain_name",
      60             :                                  "Subdomain name to set for the boundary layer mesh.");
      61       13044 :   params.addParam<SubdomainID>("subdomain_id", "Subdomain id to set for the boundary layer mesh.");
      62             : 
      63       13044 :   MooseEnum tri_elem_type("TRI3 TRI6 TRI7", "TRI3");
      64       13044 :   params.addParam<MooseEnum>("tri_elem_type",
      65             :                              tri_elem_type,
      66             :                              "The type of triangular elements to use for the boundary layer.");
      67             : 
      68        9783 :   params.addParam<bool>("keep_input",
      69        6522 :                         false,
      70             :                         "Whether to keep the input mesh in the final output. If false, only the "
      71             :                         "boundary layers will be included in the output mesh.");
      72             : 
      73        3261 :   params.addClassDescription(
      74             :       "Generate a 2D layered mesh that represents a conformal boundary layer along "
      75             :       "the boundary of an input 2D mesh or a 1D loop mesh.");
      76             : 
      77        6522 :   return params;
      78        3261 : }
      79             : 
      80         100 : XYTriangleBoundaryLayerGenerator::XYTriangleBoundaryLayerGenerator(
      81         100 :     const InputParameters & parameters)
      82             :   : MeshGenerator(parameters),
      83         100 :     _input(getMesh("input")),
      84         200 :     _thickness(getParam<Real>("thickness")),
      85         200 :     _num_layers(getParam<unsigned int>("num_layers")),
      86         200 :     _layer_bias(getParam<Real>("layer_bias")),
      87         100 :     _boundary_layer_direction(
      88         300 :         getParam<MooseEnum>("boundary_layer_direction").template getEnum<BoundaryLayerDirection>()),
      89         200 :     _boundary_names(getParam<std::vector<BoundaryName>>("boundary_names")),
      90         220 :     _interface_name(isParamValid("interface_name") ? getParam<BoundaryName>("interface_name")
      91             :                                                    : BoundaryName()),
      92         220 :     _surface_name(isParamValid("surface_name") ? getParam<BoundaryName>("surface_name")
      93             :                                                : BoundaryName()),
      94         360 :     _subdomain_name(isParamValid("subdomain_name") ? getParam<SubdomainName>("subdomain_name")
      95             :                                                    : SubdomainName()),
      96         360 :     _output_subdomain_id(isParamValid("subdomain_id") ? getParam<SubdomainID>("subdomain_id") : 0),
      97         100 :     _tri_elem_type(parameters.get<MooseEnum>("tri_elem_type")),
      98         300 :     _keep_input(getParam<bool>("keep_input"))
      99             : {
     100         100 : }
     101             : 
     102             : std::unique_ptr<MeshBase>
     103         100 : XYTriangleBoundaryLayerGenerator::generate()
     104             : {
     105         100 :   const bool outward = (_boundary_layer_direction == BoundaryLayerDirection::OUTWARD);
     106             : 
     107             :   // The MeshGenerator system requires that the input mesh be moved out of its unique_ptr during
     108             :   // generate(). Move it now; when keep_input is false the local unique_ptr is simply dropped at
     109             :   // the end, when keep_input is true we stitch from this local pointer.
     110         100 :   std::unique_ptr<MeshBase> input_mesh = std::move(_input);
     111             : 
     112             :   auto ring = BoundaryLayerUtils::buildBoundaryLayerRing(*this,
     113         100 :                                                          *input_mesh,
     114         100 :                                                          _boundary_names,
     115         100 :                                                          _num_layers,
     116         100 :                                                          _thickness,
     117         100 :                                                          _layer_bias,
     118             :                                                          outward,
     119         100 :                                                          _tri_elem_type,
     120         100 :                                                          _output_subdomain_id,
     121         100 :                                                          _subdomain_name);
     122             : 
     123             :   // The ring carries boundary ids 0 ... 2*num_layers - 1; the innermost is bcid 1 and the
     124             :   // outermost is bcid (num_layers - 1) * 2. The "interface" side (touching the input mesh) is the
     125             :   // innermost for outward layers and the outermost for inward layers; the "surface" side (away
     126             :   // from the input mesh) is the opposite.
     127         100 :   const boundary_id_type ring_innermost = 1;
     128         100 :   const boundary_id_type ring_outermost = boundary_id_type((_num_layers - 1) * 2);
     129         100 :   const boundary_id_type interface_bid = outward ? ring_innermost : ring_outermost;
     130         100 :   const boundary_id_type surface_bid = outward ? ring_outermost : ring_innermost;
     131             : 
     132         100 :   if (_keep_input)
     133             :   {
     134          20 :     auto & ring_u = dynamic_cast<libMesh::UnstructuredMesh &>(*ring);
     135          20 :     auto & inp_u = dynamic_cast<libMesh::UnstructuredMesh &>(*input_mesh);
     136          20 :     if (!ring_u.is_prepared())
     137          20 :       ring_u.prepare_for_use();
     138          20 :     if (!inp_u.is_prepared())
     139           0 :       inp_u.prepare_for_use();
     140             :     // Avoid bcid overlap between ring and input; renumber input's bcids out of the ring's range.
     141          20 :     const auto ring_bids = ring_u.get_boundary_info().get_global_boundary_ids();
     142          20 :     const auto inp_bids = inp_u.get_boundary_info().get_global_boundary_ids();
     143          20 :     const auto max_bid = std::max(ring_bids.empty() ? boundary_id_type(0) : *ring_bids.rbegin(),
     144          20 :                                   inp_bids.empty() ? boundary_id_type(0) : *inp_bids.rbegin());
     145          20 :     BoundaryID ext_id = 1;
     146          20 :     bool overlap = false;
     147          60 :     for (auto b : inp_bids)
     148          40 :       if (ring_bids.count(b))
     149          20 :         overlap = true;
     150          20 :     if (overlap)
     151             :     {
     152          20 :       BoundaryID idx = 1;
     153          60 :       for (auto b : inp_bids)
     154          40 :         inp_u.get_boundary_info().renumber_id(b, max_bid + (idx++));
     155          20 :       ext_id = max_bid + idx;
     156             :     }
     157             :     else
     158           0 :       ext_id = max_bid + 1;
     159          20 :     inp_u.comm().max(ext_id);
     160          20 :     bool has_ext = false;
     161          20 :     MooseMeshUtils::addExternalBoundary(inp_u, ext_id, has_ext);
     162             :     mooseAssert(has_ext, "A 2D-XY mesh should have an external boundary.");
     163          20 :     libMesh::MeshSerializer s1(ring_u), s2(inp_u);
     164             :     // Stitch at the side of the ring that physically touches the input boundary. Keep the
     165             :     // interface_bid tag so the interface boundary survives for downstream naming.
     166          20 :     ring_u.stitch_meshes(inp_u,
     167             :                          interface_bid,
     168             :                          ext_id,
     169             :                          TOLERANCE,
     170             :                          /*clear_stitched_bcids=*/false,
     171             :                          /*verbose=*/false,
     172             :                          /*use_binary_search=*/true,
     173             :                          /*enforce_all_nodes_match_on_boundaries=*/false,
     174             :                          /*merge_boundary_nodes_all_or_nothing=*/false,
     175             :                          /*remap_subdomain_ids=*/false);
     176          20 :   }
     177             : 
     178         100 :   auto & bi = ring->get_boundary_info();
     179             :   // Delete the interface/surface if not requested to retain
     180         100 :   if (_interface_name.empty())
     181          90 :     bi.remove_id(interface_bid);
     182         100 :   if (_surface_name.empty())
     183          90 :     bi.remove_id(surface_bid);
     184             : 
     185             :   // Apply interface_name / surface_name. The interface and surface bcids may collide with the
     186             :   // user-requested ids; do a two-stage shift to avoid clobbering.
     187         100 :   bool has_conflict = false;
     188         100 :   if (!_interface_name.empty())
     189             :   {
     190             :     const auto interface_user_bcid =
     191          30 :         MooseMeshUtils::getBoundaryIDs(*ring, {_interface_name}, true).front();
     192          10 :     if (interface_user_bcid == surface_bid && !_surface_name.empty())
     193             :     {
     194           0 :       libMesh::MeshTools::Modification::change_boundary_id(*ring, surface_bid, 10001);
     195           0 :       has_conflict = true;
     196             :     }
     197          10 :     libMesh::MeshTools::Modification::change_boundary_id(*ring, interface_bid, interface_user_bcid);
     198          10 :     bi.sideset_name(interface_user_bcid) = _interface_name;
     199             :   }
     200         100 :   if (!_surface_name.empty())
     201             :   {
     202             :     const auto surface_user_bcid =
     203          30 :         MooseMeshUtils::getBoundaryIDs(*ring, {_surface_name}, true).front();
     204          20 :     libMesh::MeshTools::Modification::change_boundary_id(
     205          10 :         *ring, has_conflict ? boundary_id_type(10001) : surface_bid, surface_user_bcid);
     206          10 :     bi.sideset_name(surface_user_bcid) = _surface_name;
     207             :   }
     208             : 
     209             :   // Match the parent MooseMesh's remote-element-removal setting so SetupMeshAction's sync check
     210             :   // passes when the user uses the mesh in contexts (e.g. boundary postprocessors) that flip this
     211             :   // flag on MooseMesh before the mesh is set.
     212         100 :   ring->allow_remote_element_removal(_mesh->allowRemoteElementRemoval());
     213         100 :   ring->unset_is_prepared();
     214         200 :   return ring;
     215         120 : }

Generated by: LCOV version 1.14