LCOV - code coverage report
Current view: top level - src/meshgenerators - PatternedPolygonPeripheralModifierBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #31405 (292dce) with base fef103 Lines: 153 157 97.5 %
Date: 2025-09-04 07:56:24 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "PatternedPolygonPeripheralModifierBase.h"
      11             : 
      12             : #include "MooseMeshUtils.h"
      13             : #include "FillBetweenPointVectorsTools.h"
      14             : #include "KDTree.h"
      15             : 
      16             : InputParameters
      17         360 : PatternedPolygonPeripheralModifierBase::validParams()
      18             : {
      19         360 :   InputParameters params = PolygonMeshGeneratorBase::validParams();
      20         720 :   params.addRequiredParam<MeshGeneratorName>(
      21             :       "input",
      22             :       "The input mesh to be modified. Note that this generator only works with "
      23             :       "PatternedHex/CartesianMeshGenerator and its derived classes such as "
      24             :       "HexIDPatternedMeshGenerator.");
      25         720 :   params.addRequiredParam<BoundaryName>("input_mesh_external_boundary",
      26             :                                         "The external boundary of the input mesh.");
      27         720 :   params.addRequiredParam<unsigned int>("new_num_sector",
      28             :                                         "Number of sectors of each side for the new mesh.");
      29         720 :   params.addParam<subdomain_id_type>(
      30             :       "transition_layer_id",
      31         720 :       TRANSITION_LAYER_DEFAULT,
      32             :       "Optional customized block id for the transition layer block.");
      33         720 :   params.addParam<SubdomainName>("transition_layer_name",
      34             :                                  "transition_layer",
      35             :                                  "Optional customized block name for the transition layer block.");
      36        1080 :   params.addRangeCheckedParam<unsigned int>(
      37         720 :       "num_layers", 1, "num_layers>0", "Layers of elements for transition.");
      38         720 :   params.addParam<std::vector<std::string>>(
      39             :       "extra_id_names_to_modify",
      40             :       "Names of the element extra ids in the peripheral region that should be modified");
      41         360 :   params.addParam<std::vector<dof_id_type>>(
      42             :       "new_extra_id_values_to_assign",
      43         360 :       std::vector<dof_id_type>(),
      44             :       "Values of the modified extra ids in the peripheral region.");
      45         360 :   params.addClassDescription("PatternedPolygonPeripheralModifierBase is the base class for "
      46             :                              "PatternedCartPeripheralModifier and PatternedHexPeripheralModifier.");
      47             : 
      48         360 :   return params;
      49           0 : }
      50             : 
      51         176 : PatternedPolygonPeripheralModifierBase::PatternedPolygonPeripheralModifierBase(
      52         176 :     const InputParameters & parameters)
      53             :   : PolygonMeshGeneratorBase(parameters),
      54           0 :     _input_name(getParam<MeshGeneratorName>("input")),
      55         176 :     _input_mesh_external_boundary(getParam<BoundaryName>("input_mesh_external_boundary")),
      56         352 :     _new_num_sector(getParam<unsigned int>("new_num_sector")),
      57         352 :     _transition_layer_id(getParam<subdomain_id_type>("transition_layer_id")),
      58         176 :     _transition_layer_name(getParam<SubdomainName>("transition_layer_name")),
      59         352 :     _num_layers(getParam<unsigned int>("num_layers")),
      60         374 :     _extra_id_names_to_modify(isParamValid("extra_id_names_to_modify")
      61         176 :                                   ? getParam<std::vector<std::string>>("extra_id_names_to_modify")
      62             :                                   : std::vector<std::string>()),
      63         352 :     _new_extra_id_values_to_assign(
      64             :         getParam<std::vector<dof_id_type>>("new_extra_id_values_to_assign")),
      65             :     // Use CartesianConcentricCircleAdaptiveBoundaryMeshGenerator for cartesian control drum meshing
      66             :     // Use HexagonConcentricCircleAdaptiveBoundaryMeshGenerator for hexagonal control drum meshing
      67         352 :     _mesh(getMeshByName(_input_name))
      68             : {
      69         176 :   declareMeshProperty<Real>("pattern_pitch_meta", 0.0);
      70         352 :   declareMeshProperty<bool>("is_control_drum_meta", false);
      71         176 :   if (_extra_id_names_to_modify.size() != _new_extra_id_values_to_assign.size())
      72           2 :     paramError("new_extra_id_values_to_assign",
      73             :                "This parameter must have the same size as extra_id_names_to_modify.");
      74         174 :   declareMeshProperty<bool>("peripheral_modifier_compatible", false);
      75         174 : }
      76             : 
      77             : std::unique_ptr<MeshBase>
      78         166 : PatternedPolygonPeripheralModifierBase::generate()
      79             : {
      80             :   // Transmit the Mesh Metadata
      81         332 :   auto pattern_pitch_meta = setMeshProperty(
      82         166 :       "pattern_pitch_meta", getMeshProperty<Real>("pattern_pitch_meta", _input_name));
      83             :   // Load the input mesh as ReplicatedMesh
      84         166 :   auto input_mesh = dynamic_pointer_cast<ReplicatedMesh>(_mesh);
      85             :   // Load boundary information
      86         166 :   _input_mesh_external_bid =
      87         166 :       MooseMeshUtils::getBoundaryID(_input_mesh_external_boundary, *input_mesh);
      88         166 :   if (_input_mesh_external_bid == Moose::INVALID_BOUNDARY_ID)
      89           0 :     paramError("input_mesh_external_boundary",
      90             :                "External boundary does not exist in the input mesh");
      91             :   std::vector<std::tuple<dof_id_type, unsigned short int, boundary_id_type>> side_list =
      92         166 :       input_mesh->get_boundary_info().build_side_list();
      93         166 :   input_mesh->get_boundary_info().build_node_list_from_side_list();
      94             :   std::vector<std::tuple<dof_id_type, boundary_id_type>> node_list =
      95         166 :       input_mesh->get_boundary_info().build_node_list();
      96             :   // Find neighbors
      97         166 :   if (!input_mesh->is_prepared())
      98         166 :     input_mesh->find_neighbors();
      99             :   // Load all the extra integer indexing information from the input mesh
     100             :   const unsigned int n_extra_integer_input = input_mesh->n_elem_integers();
     101             :   std::vector<std::string> extra_integer_names;
     102         186 :   for (unsigned int i = 0; i < n_extra_integer_input; i++)
     103          20 :     extra_integer_names.push_back(input_mesh->get_elem_integer_name(i));
     104             : 
     105             :   // Check elem extra integers names and convert them into indices
     106         166 :   std::vector<bool> extra_id_modify_flags = std::vector<bool>(n_extra_integer_input, false);
     107         166 :   std::vector<dof_id_type> extra_modify_values = std::vector<dof_id_type>(n_extra_integer_input, 0);
     108         175 :   for (unsigned int i = 0; i < _extra_id_names_to_modify.size(); i++)
     109             :   {
     110          11 :     if (!input_mesh->has_elem_integer(_extra_id_names_to_modify[i]))
     111           2 :       paramError(
     112             :           "extra_id_names_to_modify",
     113             :           "The parameter contains an extra element integer that does not exist in the input mesh.");
     114             :     else
     115             :     {
     116           9 :       extra_id_modify_flags[input_mesh->get_elem_integer_index(_extra_id_names_to_modify[i])] =
     117             :           true;
     118           9 :       extra_modify_values[input_mesh->get_elem_integer_index(_extra_id_names_to_modify[i])] =
     119             :           _new_extra_id_values_to_assign[i];
     120             :     }
     121             :   }
     122             : 
     123             :   std::vector<dof_id_type> bid_elem_list;
     124             :   std::vector<dof_id_type> new_bid_elem_list;
     125             :   std::vector<dof_id_type> ext_node_list;
     126             :   // We check the type of the QUAD elements on the boundary
     127             :   unsigned short quad_type = 0;
     128       39956 :   for (unsigned int i = 0; i < side_list.size(); i++)
     129             :   {
     130       39792 :     if (std::get<2>(side_list[i]) == _input_mesh_external_bid)
     131             :     {
     132             :       // List of elements on the boundary
     133        6744 :       bid_elem_list.push_back(std::get<0>(side_list[i]));
     134             :       // Check if the element is QUAD4 or QUAD8/9
     135        6744 :       const auto elem_type = input_mesh->elem_ptr(std::get<0>(side_list[i]))->type();
     136        6744 :       if (elem_type != QUAD4 && elem_type != QUAD8 && elem_type != QUAD9)
     137           0 :         paramError("input",
     138             :                    "The input mesh has non-QUAD4/QUAD8/QUAD9 elements in its peripheral area, "
     139             :                    "which is not supported.");
     140             :       // Generally, we need to check if the sideset involves mixed order elements
     141             :       // But this is not possible for a mesh generated by PatternedHex/CartesianMeshGenerator
     142             :       // So we only need to check the first element
     143        6744 :       else if (quad_type == 0)
     144         164 :         quad_type = elem_type == QUAD4 ? 1 : (elem_type == QUAD8 ? 8 : 9);
     145             : 
     146             :       // Note this object only works with quad elements
     147             :       // (side_id + 2)%4 gives the opposite side's neighboring element
     148             :       // Thus, list of elements on the new boundary is made
     149             :       new_bid_elem_list.push_back(input_mesh->elem_ptr(std::get<0>(side_list[i]))
     150        6744 :                                       ->neighbor_ptr((std::get<1>(side_list[i]) + 2) % 4)
     151        6744 :                                       ->id());
     152             :     }
     153             :   }
     154             :   // This eliminates the duplicate elements, which happen at the vertices.
     155         164 :   auto bid_elem_list_it = std::unique(bid_elem_list.begin(), bid_elem_list.end());
     156         164 :   auto new_bid_elem_list_it = std::unique(new_bid_elem_list.begin(), new_bid_elem_list.end());
     157         164 :   bid_elem_list.resize(std::distance(bid_elem_list.begin(), bid_elem_list_it));
     158         164 :   new_bid_elem_list.resize(std::distance(new_bid_elem_list.begin(), new_bid_elem_list_it));
     159             :   // Create a data structure to record centroid positions and extra element IDS of the
     160             :   // elements to be deleted
     161             :   std::vector<std::pair<Point, std::vector<dof_id_type>>> del_elem_extra_ids;
     162             :   // Delete the outer layer QUAD elements so that they can be replaced by TRI elements later.
     163        6908 :   for (unsigned int i = 0; i < bid_elem_list.size(); i++)
     164             :   {
     165        6744 :     const auto & tmp_elem_ptr = input_mesh->elem_ptr(bid_elem_list[i]);
     166             :     // Retain all the extra integer information first if any of them need to be retained
     167        6744 :     if (n_extra_integer_input > _extra_id_names_to_modify.size())
     168             :     {
     169             :       del_elem_extra_ids.push_back(
     170         648 :           std::make_pair(tmp_elem_ptr->true_centroid(), std::vector<dof_id_type>()));
     171         648 :       for (unsigned int j = 0; j < n_extra_integer_input; j++)
     172         324 :         del_elem_extra_ids[i].second.push_back(
     173         324 :             extra_id_modify_flags[j] ? extra_modify_values[j] : tmp_elem_ptr->get_extra_integer(j));
     174             :     }
     175        6744 :     input_mesh->delete_elem(tmp_elem_ptr);
     176             :   }
     177             :   // if all the extra id will need to be re-assigned, just a dummy reference vector is needed.
     178         164 :   if (n_extra_integer_input == _extra_id_names_to_modify.size())
     179             :     del_elem_extra_ids.push_back(
     180         310 :         std::make_pair(Point(0.0, 0.0, 0.0), _new_extra_id_values_to_assign));
     181             :   // As some elements are deleted, update the neighbor list
     182         164 :   input_mesh->find_neighbors();
     183             :   // Identify the new external boundary
     184             :   BoundaryInfo & boundary_info = input_mesh->get_boundary_info();
     185        6908 :   for (unsigned int i = 0; i < new_bid_elem_list.size(); i++)
     186             :   {
     187             :     // Assign default external Sideset ID to the new boundary
     188       33720 :     for (unsigned int j = 0; j < input_mesh->elem_ptr(new_bid_elem_list[i])->n_sides(); j++)
     189             :     {
     190       26976 :       if (input_mesh->elem_ptr(new_bid_elem_list[i])->neighbor_ptr(j) == nullptr)
     191        6744 :         boundary_info.add_side(new_bid_elem_list[i], j, OUTER_SIDESET_ID);
     192             :     }
     193             :   }
     194             : 
     195             :   // Refresh the mesh as it lost some elements
     196         164 :   input_mesh->contract();
     197         164 :   input_mesh->prepare_for_use();
     198             : 
     199             :   // Clone the original mesh without outer layer for information extraction
     200         164 :   auto input_mesh_origin = dynamic_pointer_cast<ReplicatedMesh>(input_mesh->clone());
     201             : 
     202             :   // Make four (cartesian) or six (hexagonal) meshes of new outer layer for four or six sides.
     203        1000 :   for (unsigned i_side = 0; i_side < _num_sides; i_side++)
     204             :   {
     205             :     // Use azimuthalAnglesCollector to assign boundary Points to a reference vector
     206             :     std::vector<Point> inner_pts;
     207             :     std::vector<Real> new_bdry_azi = azimuthalAnglesCollector(*input_mesh_origin,
     208             :                                                               inner_pts,
     209             :                                                               -180.0 / (Real)_num_sides,
     210         836 :                                                               180.0 / (Real)_num_sides,
     211         836 :                                                               ANGLE_DEGREE);
     212         836 :     MeshTools::Modification::rotate(*input_mesh_origin, -360.0 / (Real)_num_sides, 0, 0);
     213             : 
     214             :     // Setting up of the outer boundary is done by defining the two ends
     215             :     // And then use libMesh interpolation tool to make the straight line
     216             :     std::vector<Point> outer_pts;
     217             :     const Point outer_end_1 = Point(pattern_pitch_meta / 2.0,
     218         836 :                                     -pattern_pitch_meta / 2.0 * std::tan(M_PI / (Real)_num_sides),
     219         836 :                                     0.0);
     220             :     const Point outer_end_2 = Point(pattern_pitch_meta / 2.0,
     221             :                                     pattern_pitch_meta / 2.0 * std::tan(M_PI / (Real)_num_sides),
     222         836 :                                     0.0);
     223             : 
     224         836 :     const std::vector<Real> input_arg{0.0, 1.0};
     225         836 :     const std::vector<Real> input_x{outer_end_1(0), outer_end_2(0)};
     226         836 :     const std::vector<Real> input_y{outer_end_1(1), outer_end_2(1)};
     227             :     std::unique_ptr<LinearInterpolation> linear_outer_x =
     228         836 :         std::make_unique<LinearInterpolation>(input_arg, input_x);
     229             :     std::unique_ptr<LinearInterpolation> linear_outer_y =
     230         836 :         std::make_unique<LinearInterpolation>(input_arg, input_y);
     231             : 
     232             :     // Uniformly spaced nodes on the outer boundary to facilitate stitching
     233        9492 :     for (unsigned int i = 0; i < _new_num_sector + 1; i++)
     234             :     {
     235        8656 :       outer_pts.push_back(Point(linear_outer_x->sample((Real)i / (Real)_new_num_sector),
     236       17312 :                                 linear_outer_y->sample((Real)i / (Real)_new_num_sector),
     237             :                                 0.0));
     238             :     }
     239             :     // Now we have the inner and outer boundary points as input for the transition layer
     240             :     // For linear elements, that's all we need;
     241             :     // For quadratic elements, the inner point vector contains both vertices and midpoints.
     242             :     // fillBetweenPointVectorsGenerator only supports linear elements
     243             :     // So we need to
     244             :     // (1) remove the midpoints from the inner point vector
     245             :     // (2) generate linear mesh using fillBetweenPointVectorsGenerator
     246             :     // (3) convert the linear mesh to quadratic mesh
     247             :     // Generally, in the input mesh, the midpoint might not be located in the middle of the two
     248             :     // vertices. (4) In that case, the midpoint needs to be moved to match the input mesh In the
     249             :     // case of PatternedHex/CartesianMeshGenerator, the midpoints of the inner boundary shoule be
     250             :     // the average of the vertices unless the non-circular regions are deformed. So this step still
     251             :     // needs to be done.
     252             : 
     253             :     // Use fillBetweenPointVectorsGenerator to generate the transition layer
     254             : 
     255             :     // Remove the midpoints from the inner point vector
     256             :     // But record the midpoints for later use
     257             :     // In the meantime, calculate the average of the two vertices
     258             :     std::vector<std::pair<Point, Point>> inner_midpts_pairs;
     259         836 :     if (quad_type > 4)
     260             :     {
     261          90 :       auto inner_pts_tmp(inner_pts);
     262          90 :       inner_pts.clear();
     263        1260 :       for (const auto i : index_range(inner_pts_tmp))
     264        1170 :         if (i % 2 == 0)
     265         630 :           inner_pts.push_back(inner_pts_tmp[i]);
     266             :         else
     267             :         {
     268             :           // We check if the midpoint is located in the middle of the two vertices
     269             :           // If not, we record the them for later use
     270         540 :           const Point avgpt = 0.5 * (inner_pts_tmp[i - 1] + inner_pts_tmp[i + 1]);
     271         540 :           if (!MooseUtils::absoluteFuzzyEqual((avgpt - inner_pts_tmp[i]).norm(), 0.0))
     272         108 :             inner_midpts_pairs.push_back(std::make_pair(avgpt, inner_pts_tmp[i]));
     273             :         }
     274          90 :       inner_pts_tmp.clear();
     275          90 :     }
     276             : 
     277         836 :     auto mesh = buildReplicatedMesh(2);
     278         836 :     FillBetweenPointVectorsTools::fillBetweenPointVectorsGenerator(*mesh,
     279             :                                                                    inner_pts,
     280             :                                                                    outer_pts,
     281         836 :                                                                    _num_layers,
     282         836 :                                                                    _transition_layer_id,
     283             :                                                                    OUTER_SIDESET_ID,
     284         836 :                                                                    _type,
     285         836 :                                                                    _name);
     286             : 
     287             :     // Convert the linear mesh to quadratic mesh
     288         836 :     if (quad_type == 8)
     289          54 :       mesh->all_second_order();
     290         782 :     else if (quad_type == 9)
     291          36 :       mesh->all_complete_order();
     292             : 
     293             :     // Move the midpoints to match the input mesh if applicable
     294         836 :     if (inner_midpts_pairs.size())
     295             :     {
     296          54 :       mesh->get_boundary_info().build_node_list_from_side_list();
     297          54 :       auto tl_node_list = mesh->get_boundary_info().build_node_list();
     298             :       std::vector<dof_id_type> bdry_node_ids;
     299             :       std::vector<Point> bdry_node_pts;
     300        2214 :       for (const auto & tl_node_tuple : tl_node_list)
     301             :       {
     302        2160 :         if (std::get<1>(tl_node_tuple) == OUTER_SIDESET_ID)
     303             :         {
     304        2160 :           bdry_node_ids.push_back(std::get<0>(tl_node_tuple));
     305        2160 :           bdry_node_pts.push_back(*(mesh->node_ptr(std::get<0>(tl_node_tuple))));
     306             :         }
     307             :       }
     308          54 :       KDTree ref_kd_tree(bdry_node_pts, 4);
     309         162 :       for (const auto & midpt_pair : inner_midpts_pairs)
     310             :       {
     311             :         std::vector<std::size_t> nn_id;
     312         108 :         ref_kd_tree.neighborSearch(midpt_pair.first, 1, nn_id);
     313         108 :         *(mesh->node_ptr(bdry_node_ids[nn_id.front()])) = midpt_pair.second;
     314         108 :       }
     315          54 :     }
     316             : 
     317         836 :     MeshTools::Modification::rotate(*mesh, (Real)i_side * 360.0 / (Real)_num_sides, 0, 0);
     318             :     // Assign extra element id based on the nearest deleted element
     319         836 :     mesh->add_elem_integers(extra_integer_names, true);
     320         836 :     transferExtraElemIntegers(*mesh, del_elem_extra_ids);
     321         836 :     mesh->prepare_for_use();
     322         836 :     input_mesh->stitch_meshes(*mesh, OUTER_SIDESET_ID, OUTER_SIDESET_ID, TOLERANCE, true);
     323         836 :     mesh->clear();
     324         836 :   }
     325         164 :   input_mesh->subdomain_name(_transition_layer_id) = _transition_layer_name;
     326         164 :   return input_mesh;
     327         328 : }
     328             : 
     329             : void
     330         836 : PatternedPolygonPeripheralModifierBase::transferExtraElemIntegers(
     331             :     ReplicatedMesh & mesh,
     332             :     const std::vector<std::pair<Point, std::vector<dof_id_type>>> ref_extra_ids)
     333             : {
     334             :   // Build points vector for k-d tree constructor
     335             :   std::vector<Point> ref_pts;
     336        3562 :   for (auto & pt_extra_id : ref_extra_ids)
     337        2726 :     ref_pts.push_back(pt_extra_id.first);
     338             :   // K-d tree construction
     339         836 :   KDTree ref_kd_tree(ref_pts, 4);
     340             :   // Use the k-d tree for nearest neighbor searching
     341       58604 :   for (const auto & elem : as_range(mesh.active_elements_begin(), mesh.active_elements_end()))
     342             :   {
     343             :     std::vector<std::size_t> nn_id;
     344       28048 :     ref_kd_tree.neighborSearch(elem->true_centroid(), 1, nn_id);
     345       31504 :     for (unsigned int i = 0; i < ref_extra_ids[nn_id.front()].second.size(); i++)
     346        3456 :       elem->set_extra_integer(i, ref_extra_ids[nn_id.front()].second[i]);
     347       28884 :   }
     348         836 : }

Generated by: LCOV version 1.14