LCOV - code coverage report
Current view: top level - src/meshgenerators - CoreMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #33187 (5aa0b2) with base d7c4bd Lines: 416 444 93.7 %
Date: 2026-06-30 12:23:22 Functions: 6 6 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 "CoreMeshGenerator.h"
      11             : 
      12             : #include "MooseApp.h"
      13             : #include "MooseMeshUtils.h"
      14             : #include "Factory.h"
      15             : #include "libmesh/elem.h"
      16             : #include "CSGZCylinder.h"
      17             : 
      18             : registerMooseObject("ReactorApp", CoreMeshGenerator);
      19             : 
      20             : InputParameters
      21         508 : CoreMeshGenerator::validParams()
      22             : {
      23         508 :   auto params = ReactorGeometryMeshBuilderBase::validParams();
      24             : 
      25        1016 :   params.addRequiredParam<std::vector<MeshGeneratorName>>(
      26             :       "inputs",
      27             :       "The AssemblyMeshGenerator and ControlDrumMeshGenerator objects that form the components of "
      28             :       "the assembly.");
      29             : 
      30        1016 :   params.addParam<std::string>(
      31             :       "dummy_assembly_name",
      32             :       "dummy",
      33             :       "The place holder name in \"inputs\" that indicates an empty position.");
      34             : 
      35        1016 :   params.addRequiredParam<std::vector<std::vector<unsigned int>>>(
      36             :       "pattern",
      37             :       "A double-indexed array starting with the upper-left corner where the index"
      38             :       "represents the layout of input assemblies in the core lattice.");
      39        1016 :   params.addParam<bool>(
      40        1016 :       "mesh_periphery", false, "Determines if the core periphery should be meshed.");
      41        1016 :   MooseEnum periphery_mesher("triangle quad_ring", "triangle");
      42        1016 :   params.addParam<MooseEnum>("periphery_generator",
      43             :                              periphery_mesher,
      44             :                              "The meshgenerator to use when meshing the core boundary.");
      45             : 
      46             :   // Periphery meshing interface
      47        1016 :   params.addRangeCheckedParam<Real>(
      48             :       "outer_circle_radius", 0, "outer_circle_radius>=0", "Radius of outer circle boundary.");
      49        1524 :   params.addRangeCheckedParam<unsigned int>(
      50             :       "outer_circle_num_segments",
      51        1016 :       0,
      52             :       "outer_circle_num_segments>=0",
      53             :       "Number of radial segments to subdivide outer circle boundary.");
      54        1524 :   params.addRangeCheckedParam<unsigned int>(
      55             :       "periphery_num_layers",
      56        1016 :       1,
      57             :       "periphery_num_layers>0",
      58             :       "Number of layers to subdivide the periphery boundary.");
      59        1016 :   params.addParam<std::string>(
      60             :       "periphery_block_name", RGMB::CORE_BLOCK_NAME_PREFIX, "Block name for periphery zone.");
      61        1016 :   params.addParam<subdomain_id_type>(
      62             :       "periphery_region_id",
      63        1016 :       -1,
      64             :       "ID for periphery zone for assignment of region_id extra element id.");
      65        1016 :   params.addRangeCheckedParam<Real>(
      66             :       "desired_area",
      67             :       0,
      68             :       "desired_area>=0",
      69             :       "Desired (maximum) triangle area, or 0 to skip uniform refinement");
      70        1016 :   params.addParam<std::string>(
      71             :       "desired_area_func",
      72         508 :       std::string(),
      73             :       "Desired (local) triangle area as a function of x,y; omit to skip non-uniform refinement");
      74        1016 :   params.addParam<bool>("assign_control_drum_id",
      75        1016 :                         true,
      76             :                         "Whether control drum id is assigned to the mesh as an extra integer.");
      77        1016 :   params.addParamNamesToGroup("periphery_block_name periphery_region_id outer_circle_radius "
      78             :                               "mesh_periphery periphery_generator",
      79             :                               "Periphery Meshing");
      80        1016 :   params.addParamNamesToGroup("outer_circle_num_segments desired_area desired_area_func",
      81             :                               "Periphery Meshing: PTMG specific");
      82        1016 :   params.addParamNamesToGroup("periphery_num_layers", "Periphery Meshing: PRMG specific");
      83             :   // end meshing interface
      84             : 
      85        1016 :   params.addParam<bool>("extrude",
      86        1016 :                         false,
      87             :                         "Determines if this is the final step in the geometry construction"
      88             :                         " and extrudes the 2D geometry to 3D. If this is true then this mesh "
      89             :                         "cannot be used in further mesh building in the Reactor workflow");
      90             : 
      91         508 :   params.addClassDescription(
      92             :       "This CoreMeshGenerator object is designed to generate a core-like "
      93             :       "structure, with IDs, from a reactor geometry. "
      94             :       "The core-like structure consists of a pattern of assembly-like "
      95             :       "structures generated with AssemblyMeshGenerator and/or ControlDrumMeshGenerator "
      96             :       "and is permitted to have \"empty\" locations. The size and spacing "
      97             :       "of the assembly-like structures is defined, and "
      98             :       "enforced by declaration in the ReactorMeshParams.");
      99             :   // depletion id generation params are added
     100         508 :   addDepletionIDParams(params);
     101             : 
     102             :   // Declare that this generator has a generateCSG method
     103         508 :   MeshGenerator::setHasGenerateCSG(params);
     104             : 
     105         508 :   return params;
     106         508 : }
     107             : 
     108         258 : CoreMeshGenerator::CoreMeshGenerator(const InputParameters & parameters)
     109             :   : ReactorGeometryMeshBuilderBase(parameters),
     110         258 :     _inputs(getParam<std::vector<MeshGeneratorName>>("inputs")),
     111         258 :     _empty_key(getParam<std::string>("dummy_assembly_name")),
     112         258 :     _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern")),
     113         516 :     _extrude(getParam<bool>("extrude")),
     114         516 :     _mesh_periphery(getParam<bool>("mesh_periphery")),
     115         516 :     _periphery_meshgenerator(getParam<MooseEnum>("periphery_generator")),
     116         516 :     _periphery_region_id(getParam<subdomain_id_type>("periphery_region_id")),
     117         516 :     _outer_circle_radius(getParam<Real>("outer_circle_radius")),
     118         516 :     _outer_circle_num_segments(getParam<unsigned int>("outer_circle_num_segments")),
     119         516 :     _periphery_block_name(getParam<std::string>("periphery_block_name")),
     120         516 :     _periphery_num_layers(getParam<unsigned int>("periphery_num_layers")),
     121         516 :     _desired_area(getParam<Real>("desired_area")),
     122        1290 :     _desired_area_func(getParam<std::string>("desired_area_func"))
     123             : {
     124             :   // This sets it so that any mesh that is input with the name _empty_key is considered a "null"
     125             :   // mesh, that is, whenever we try to get it with the standard getMesh() API we get a nullptr
     126             :   // mesh instead. In the specific case of the CoreMeshGenerator, we use said "null" mesh to
     127             :   // represent an empty position
     128         258 :   declareNullMeshName(_empty_key);
     129             : 
     130             :   // periphery meshing input checking
     131         258 :   if (_mesh_periphery)
     132             :   {
     133             :     // missing required input
     134          98 :     if (!parameters.isParamSetByUser("outer_circle_radius"))
     135             :     {
     136           0 :       paramError("outer_circle_radius",
     137             :                  "Outer circle radius must be specified when using periphery meshing.");
     138             :     }
     139          98 :     if (!parameters.isParamSetByUser("periphery_region_id"))
     140             :     {
     141           0 :       paramError("periphery_region_id",
     142             :                  "Periphery region id must be specified when using periphery meshing.");
     143             :     }
     144             :     // using PTMG-specific options with PRMG
     145          49 :     if (_periphery_meshgenerator == "quad_ring")
     146             :     {
     147          70 :       if (parameters.isParamSetByUser("outer_circle_num_segments"))
     148             :       {
     149           0 :         paramError("outer_circle_num_segments",
     150             :                    "outer_circle_num_segments cannot be used with PRMG periphery mesher.");
     151             :       }
     152          70 :       if (parameters.isParamSetByUser("extra_circle_radii"))
     153             :       {
     154           0 :         paramError("extra_circle_radii",
     155             :                    "extra_circle_radii cannot be used with PRMG periphery mesher.");
     156             :       }
     157          70 :       if (parameters.isParamSetByUser("extra_circle_num_segments"))
     158             :       {
     159           0 :         paramError("extra_circle_num_segments",
     160             :                    "extra_circle_num_segments cannot be used with PRMG periphery mesher.");
     161             :       }
     162             :     }
     163             :     // using PRMG-specific options with PTMG
     164          14 :     else if (_periphery_meshgenerator == "triangle")
     165             :     {
     166          28 :       if (parameters.isParamSetByUser("periphery_num_layers"))
     167             :       {
     168           0 :         paramError("periphery_num_layers",
     169             :                    "periphery_num_layers cannot be used with PTMG periphery mesher.");
     170             :       }
     171             :     }
     172             :     else
     173           0 :       paramError("periphery_generator",
     174             :                  "Provided periphery meshgenerator has not been implemented.");
     175             :   }
     176             : 
     177             :   MeshGeneratorName first_nondummy_assembly = "";
     178             :   MeshGeneratorName reactor_params = "";
     179             :   bool assembly_homogenization = false;
     180             :   bool pin_as_assembly = false;
     181             :   std::map<subdomain_id_type, std::string> global_pin_map_type_to_name;
     182             :   std::map<subdomain_id_type, std::string> assembly_map_type_to_name;
     183             :   // Check that MG name for reactor params and assembly homogenization schemes are
     184             :   // consistent across all assemblies, and there is no overlap in pin_type / assembly_type ids
     185        1048 :   for (const auto i : index_range(_inputs))
     186             :   {
     187             :     // Skip if assembly name is equal to dummy assembly name
     188         794 :     if (_inputs[i] == _empty_key)
     189         226 :       continue;
     190             : 
     191             :     // Save properties of first non-dummy assembly to compare to other assemblies
     192         568 :     if (first_nondummy_assembly == "")
     193             :     {
     194         256 :       first_nondummy_assembly = MeshGeneratorName(_inputs[i]);
     195             :       reactor_params =
     196         512 :           MeshGeneratorName(getMeshProperty<std::string>(RGMB::reactor_params_name, _inputs[i]));
     197         256 :       assembly_homogenization = getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]);
     198         256 :       pin_as_assembly = getMeshProperty<bool>(RGMB::is_single_pin, _inputs[i]);
     199             :     }
     200         568 :     if (getMeshProperty<std::string>(RGMB::reactor_params_name, _inputs[i]) != reactor_params)
     201           0 :       mooseError("The name of all reactor_params objects should be identical across all pins in "
     202             :                  "the input assemblies.\n");
     203         568 :     if ((getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]) != assembly_homogenization) &&
     204          37 :         !getMeshProperty<bool>(RGMB::flexible_assembly_stitching, reactor_params))
     205           0 :       mooseError("In order to stitch heterogeneous assemblies with homogeneous assemblies in "
     206             :                  "CoreMeshGenerator, ReactorMeshParams/flexible_assembly_stitching should be set "
     207             :                  "to true\n");
     208             : 
     209             :     // Check assembly_types across constituent assemblies are uniquely defined
     210         568 :     const auto assembly_type = getMeshProperty<subdomain_id_type>(RGMB::assembly_type, _inputs[i]);
     211         570 :     if (assembly_map_type_to_name.find(assembly_type) != assembly_map_type_to_name.end() &&
     212           2 :         assembly_map_type_to_name[assembly_type] != _inputs[i])
     213           2 :       mooseError(
     214             :           "Constituent assemblies have shared assembly_type ids but different names. Each uniquely "
     215             :           "defined assembly in AssemblyMeshGenerator must have its own assembly_type id.");
     216         566 :     assembly_map_type_to_name[assembly_type] = _inputs[i];
     217             : 
     218             :     // If assembly is composed of pins, check pin_types across all constituent assemblies are
     219             :     // uniquely defined
     220         566 :     if (hasMeshProperty<std::vector<std::string>>(RGMB::pin_names, _inputs[i]))
     221             :     {
     222         400 :       const auto pin_names = getMeshProperty<std::vector<std::string>>(RGMB::pin_names, _inputs[i]);
     223        1013 :       for (const auto & input_pin_name : pin_names)
     224             :       {
     225         615 :         const auto pin_type = getMeshProperty<subdomain_id_type>(RGMB::pin_type, input_pin_name);
     226         692 :         if (global_pin_map_type_to_name.find(pin_type) != global_pin_map_type_to_name.end() &&
     227          77 :             global_pin_map_type_to_name[pin_type] != input_pin_name)
     228           2 :           mooseError(
     229             :               "Constituent pins within assemblies have shared pin_type ids but different names. "
     230             :               "Each uniquely defined pin in AssemblyMeshGenerator must have its own pin_type id.");
     231         613 :         global_pin_map_type_to_name[pin_type] = input_pin_name;
     232             :       }
     233         398 :     }
     234             :   }
     235             : 
     236             :   // Check that there is at least one non-dummy assemby defined in lattice
     237         254 :   if (first_nondummy_assembly == "")
     238           2 :     paramError("inputs", "At least one non-dummy assembly must be defined in input assembly names");
     239             : 
     240             :   // Initialize ReactorMeshParams object stored in pin input
     241         504 :   initializeReactorMeshParams(reactor_params);
     242             : 
     243         252 :   _geom_type = getReactorParam<std::string>(RGMB::mesh_geometry);
     244         252 :   _mesh_dimensions = getReactorParam<unsigned int>(RGMB::mesh_dimensions);
     245             : 
     246         252 :   if (_extrude && _mesh_dimensions != 3)
     247           0 :     paramError("extrude",
     248             :                "In order to extrude this mesh, ReactorMeshParams/dim needs to be set to 3\n");
     249         882 :   if (_extrude && (!hasReactorParam<boundary_id_type>(RGMB::top_boundary_id) ||
     250         630 :                    !hasReactorParam<boundary_id_type>(RGMB::bottom_boundary_id)))
     251           0 :     mooseError("Both top_boundary_id and bottom_boundary_id must be provided in ReactorMeshParams "
     252             :                "if using extruded geometry");
     253         504 :   if (!hasReactorParam<boundary_id_type>(RGMB::radial_boundary_id))
     254           0 :     mooseError("radial_boundary_id must be provided in ReactorMeshParams for CoreMeshGenerators");
     255             : 
     256         504 :   if (parameters.isParamSetByUser("periphery_block_name") &&
     257          14 :       getReactorParam<bool>(RGMB::region_id_as_block_name))
     258           2 :     paramError("periphery_block_name",
     259             :                "If ReactorMeshParams/region_id_as_block_name is set, periphery_block_name should "
     260             :                "not be specified in CoreMeshGenerator");
     261             : 
     262             :   std::size_t empty_pattern_loc = 0;
     263             :   bool make_empty = false;
     264        1028 :   for (auto assembly : _inputs)
     265             :   {
     266         778 :     if (assembly != _empty_key)
     267             :     {
     268         556 :       ++empty_pattern_loc;
     269         556 :       if (getMeshProperty<bool>(RGMB::extruded, assembly))
     270           0 :         mooseError("Assemblies that have already been extruded cannot be used in CoreMeshGenerator "
     271             :                    "definition.\n");
     272             :     }
     273             :     else
     274             :     {
     275             :       // Found dummy assembly in input assembly names
     276             :       make_empty = true;
     277         803 :       for (const auto i : index_range(_pattern))
     278             :       {
     279        1900 :         for (const auto j : index_range(_pattern[i]))
     280             :         {
     281             :           // Found dummy assembly in input lattice definition
     282        1319 :           if (_pattern[i][j] == empty_pattern_loc)
     283         346 :             _empty_pos = true;
     284             :         }
     285             :       }
     286             :     }
     287             :   }
     288             : 
     289             :   // No subgenerators will be called if option to bypass mesh generators is enabled
     290         250 :   if (!getReactorParam<bool>(RGMB::bypass_meshgen))
     291             :   {
     292             :     // Check whether flexible stitching should be used for constituent assemblies and throw a
     293             :     // warning if flexible stitching option is not enabled
     294         306 :     if (!getReactorParam<bool>(RGMB::flexible_assembly_stitching) &&
     295         133 :         constituentAssembliesNeedFlexibleStiching())
     296           0 :       mooseWarning("Constituent assemblies do not share the same number of nodes at the outer "
     297             :                    "boundary. In order to ensure that output mesh does not having hanging nodes, a "
     298             :                    "flexible stitching approach should be used by setting "
     299             :                    "ReactorMeshParams/flexible_assembly_stitching = true.");
     300             : 
     301             :     // Declare that all of the meshes in the "inputs" parameter are to be used by
     302             :     // a sub mesh generator.
     303         173 :     declareMeshesForSub("inputs");
     304             : 
     305             :     // Stitch assemblies into a hexagonal / Cartesian core lattice
     306             :     {
     307             :       // create a dummy assembly that is a renamed version of one of the inputs
     308         173 :       if (make_empty)
     309             :       {
     310             :         {
     311         152 :           if (assembly_homogenization)
     312             :           {
     313          14 :             auto params = _app.getFactory().getValidParams("SimpleHexagonGenerator");
     314             : 
     315           7 :             params.set<Real>("hexagon_size") = getReactorParam<Real>(RGMB::assembly_pitch) / 2.0;
     316           7 :             params.set<std::vector<subdomain_id_type>>("block_id") = {
     317          14 :                 RGMB::DUMMY_ASSEMBLY_BLOCK_ID};
     318             : 
     319          14 :             addMeshSubgenerator("SimpleHexagonGenerator", std::string(_empty_key), params);
     320           7 :           }
     321             :           else
     322             :           {
     323             :             const auto adaptive_mg_name =
     324         145 :                 _geom_type == "Hex" ? "HexagonConcentricCircleAdaptiveBoundaryMeshGenerator"
     325             :                                     : "CartesianConcentricCircleAdaptiveBoundaryMeshGenerator";
     326         290 :             auto params = _app.getFactory().getValidParams(adaptive_mg_name);
     327             : 
     328         145 :             const auto assembly_pitch = getReactorParam<Real>(RGMB::assembly_pitch);
     329         145 :             if (_geom_type == "Hex")
     330             :             {
     331          86 :               params.set<Real>("hexagon_size") = assembly_pitch / 2.0;
     332          86 :               params.set<std::vector<unsigned int>>("num_sectors_per_side") =
     333         172 :                   std::vector<unsigned int>(6, 2);
     334             :             }
     335             :             else
     336             :             {
     337          59 :               params.set<Real>("square_size") = assembly_pitch;
     338          59 :               params.set<std::vector<unsigned int>>("num_sectors_per_side") =
     339         118 :                   std::vector<unsigned int>(4, 2);
     340             :             }
     341         290 :             params.set<std::vector<unsigned int>>("sides_to_adapt") = std::vector<unsigned int>{0};
     342         145 :             params.set<std::vector<MeshGeneratorName>>("meshes_to_adapt_to") =
     343         435 :                 std::vector<MeshGeneratorName>{first_nondummy_assembly};
     344         145 :             params.set<std::vector<subdomain_id_type>>("background_block_ids") =
     345         290 :                 std::vector<subdomain_id_type>{RGMB::DUMMY_ASSEMBLY_BLOCK_ID};
     346             : 
     347         290 :             addMeshSubgenerator(adaptive_mg_name, std::string(_empty_key), params);
     348         145 :           }
     349             :         }
     350             :       }
     351             :       {
     352             :         const auto patterned_mg_name =
     353         173 :             _geom_type == "Hex" ? "PatternedHexMeshGenerator" : "PatternedCartesianMeshGenerator";
     354         173 :         auto params = _app.getFactory().getValidParams(patterned_mg_name);
     355             : 
     356         519 :         params.set<std::vector<std::string>>("id_name") = {"assembly_id"};
     357         346 :         params.set<std::vector<MooseEnum>>("assign_type") = {
     358         692 :             MooseEnum("cell", "cell")}; // give elems IDs relative to position in assembly
     359         173 :         params.set<std::vector<MeshGeneratorName>>("inputs") = _inputs;
     360         173 :         params.set<std::vector<std::vector<unsigned int>>>("pattern") = _pattern;
     361         346 :         params.set<MooseEnum>("pattern_boundary") = "none";
     362         173 :         params.set<bool>("generate_core_metadata") = !pin_as_assembly;
     363         173 :         params.set<bool>("create_outward_interface_boundaries") = false;
     364         346 :         params.set<bool>("assign_control_drum_id") = getParam<bool>("assign_control_drum_id");
     365         173 :         if (make_empty)
     366             :         {
     367         152 :           params.set<std::vector<MeshGeneratorName>>("exclude_id") =
     368         456 :               std::vector<MeshGeneratorName>{_empty_key};
     369             :         }
     370             : 
     371         173 :         const auto radial_boundary = getReactorParam<boundary_id_type>(RGMB::radial_boundary_id);
     372         173 :         params.set<boundary_id_type>("external_boundary_id") = radial_boundary;
     373         173 :         params.set<BoundaryName>("external_boundary_name") = RGMB::CORE_BOUNDARY_NAME;
     374         173 :         params.set<double>("rotate_angle") = 0.0;
     375         173 :         params.set<bool>("allow_unused_inputs") = true;
     376             : 
     377         346 :         addMeshSubgenerator(patterned_mg_name, name() + "_pattern", params);
     378         173 :       }
     379             :     }
     380         173 :     if (_empty_pos)
     381             :     {
     382         103 :       auto params = _app.getFactory().getValidParams("BlockDeletionGenerator");
     383             : 
     384         206 :       params.set<std::vector<SubdomainName>>("block") = {
     385         618 :           std::to_string(RGMB::DUMMY_ASSEMBLY_BLOCK_ID)};
     386         309 :       params.set<MeshGeneratorName>("input") = name() + "_pattern";
     387         103 :       params.set<BoundaryName>("new_boundary") = RGMB::CORE_BOUNDARY_NAME;
     388             : 
     389         206 :       addMeshSubgenerator("BlockDeletionGenerator", name() + "_deleted", params);
     390         103 :     }
     391             : 
     392             :     std::string build_mesh_name;
     393             : 
     394             :     // Remove outer assembly sidesets created during assembly generation
     395             :     {
     396             :       // Get outer boundaries of all constituent assemblies based on assembly_type,
     397             :       // skipping all dummy assemblies
     398             :       std::vector<BoundaryName> boundaries_to_delete = {};
     399         643 :       for (const auto & pattern_x : _pattern)
     400             :       {
     401        1554 :         for (const auto & pattern_idx : pattern_x)
     402             :         {
     403        1084 :           const auto assembly_name = _inputs[pattern_idx];
     404        1084 :           if (assembly_name == _empty_key)
     405             :             continue;
     406             :           const auto assembly_id =
     407         823 :               getMeshProperty<subdomain_id_type>(RGMB::assembly_type, assembly_name);
     408             :           const BoundaryName boundary_name =
     409        1646 :               RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX + std::to_string(assembly_id);
     410         823 :           if (!std::count(boundaries_to_delete.begin(), boundaries_to_delete.end(), boundary_name))
     411         395 :             boundaries_to_delete.push_back(boundary_name);
     412             :         }
     413             :       }
     414         173 :       auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
     415             : 
     416         346 :       params.set<MeshGeneratorName>("input") =
     417         173 :           _empty_pos ? name() + "_deleted" : name() + "_pattern";
     418         346 :       params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
     419             : 
     420         173 :       build_mesh_name = name() + "_delbds";
     421         346 :       addMeshSubgenerator("BoundaryDeletionGenerator", build_mesh_name, params);
     422         173 :     }
     423             : 
     424         727 :     for (auto assembly : _inputs)
     425             :     {
     426         554 :       if (assembly != _empty_key)
     427             :       {
     428             :         subdomain_id_type assembly_type =
     429         402 :             getMeshProperty<subdomain_id_type>(RGMB::assembly_type, assembly);
     430         402 :         if (!getMeshProperty<bool>(RGMB::is_control_drum, assembly))
     431             :         {
     432             :           // For assembly structures, store region ID and block names of assembly regions and
     433             :           // constituent pins
     434             :           const auto & pin_region_id_map = getMeshProperty<
     435         377 :               std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>>(
     436             :               RGMB::pin_region_id_map, assembly);
     437         887 :           for (auto pin = pin_region_id_map.begin(); pin != pin_region_id_map.end(); ++pin)
     438         510 :             _pin_region_id_map.insert(
     439         510 :                 std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
     440         510 :                     pin->first, pin->second));
     441             : 
     442             :           const auto & pin_block_name_map =
     443         377 :               getMeshProperty<std::map<subdomain_id_type, std::vector<std::vector<std::string>>>>(
     444             :                   RGMB::pin_block_name_map, assembly);
     445         887 :           for (auto pin = pin_block_name_map.begin(); pin != pin_block_name_map.end(); ++pin)
     446         510 :             _pin_block_name_map.insert(
     447         510 :                 std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(pin->first,
     448         510 :                                                                                     pin->second));
     449             : 
     450             :           // Define background and duct region ID map from constituent assemblies
     451         377 :           if (_background_region_id_map.find(assembly_type) == _background_region_id_map.end())
     452             :           {
     453             :             // Store region ids and block names associated with duct and background regions for each
     454             :             // assembly, in case block names need to be recovered from region ids after
     455             :             // multiple assemblies have been stitched together into a core
     456             :             std::vector<subdomain_id_type> background_region_ids =
     457             :                 getMeshProperty<std::vector<subdomain_id_type>>(RGMB::background_region_id,
     458         377 :                                                                 assembly);
     459             :             std::vector<std::vector<subdomain_id_type>> duct_region_ids =
     460             :                 getMeshProperty<std::vector<std::vector<subdomain_id_type>>>(RGMB::duct_region_ids,
     461         377 :                                                                              assembly);
     462         754 :             _background_region_id_map.insert(
     463         377 :                 std::pair<subdomain_id_type, std::vector<subdomain_id_type>>(
     464             :                     assembly_type, background_region_ids));
     465         754 :             _duct_region_id_map.insert(
     466         377 :                 std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
     467             :                     assembly_type, duct_region_ids));
     468             : 
     469             :             std::vector<std::string> background_block_names =
     470         377 :                 getMeshProperty<std::vector<std::string>>(RGMB::background_block_name, assembly);
     471             :             std::vector<std::vector<std::string>> duct_block_names =
     472             :                 getMeshProperty<std::vector<std::vector<std::string>>>(RGMB::duct_block_names,
     473         377 :                                                                        assembly);
     474         754 :             _background_block_name_map.insert(
     475         377 :                 std::pair<subdomain_id_type, std::vector<std::string>>(assembly_type,
     476             :                                                                        background_block_names));
     477         754 :             _duct_block_name_map.insert(
     478         377 :                 std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(
     479             :                     assembly_type, duct_block_names));
     480         377 :           }
     481             :         }
     482             :         else
     483             :         {
     484             :           // For control drum structures, store region ID and block name information of drum regions
     485             :           const auto & drum_region_ids =
     486          25 :               getMeshProperty<std::vector<std::vector<subdomain_id_type>>>(RGMB::drum_region_ids,
     487             :                                                                            assembly);
     488          50 :           _drum_region_id_map.insert(
     489           0 :               std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
     490             :                   assembly_type, drum_region_ids));
     491             :           std::vector<std::vector<std::string>> drum_block_names =
     492             :               getMeshProperty<std::vector<std::vector<std::string>>>(RGMB::drum_block_names,
     493          25 :                                                                      assembly);
     494          50 :           _drum_block_name_map.insert(
     495          25 :               std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(
     496             :                   assembly_type, drum_block_names));
     497          25 :         }
     498             :       }
     499             :     }
     500             : 
     501             :     // periphery meshing
     502         173 :     if (_mesh_periphery)
     503             :     {
     504          35 :       std::string periphery_mg_name = (_periphery_meshgenerator == "triangle")
     505             :                                           ? "PeripheralTriangleMeshGenerator"
     506          63 :                                           : "PeripheralRingMeshGenerator";
     507             : 
     508             :       // set up common options
     509          35 :       auto params = _app.getFactory().getValidParams(periphery_mg_name);
     510         105 :       params.set<MeshGeneratorName>("input") = name() + "_delbds";
     511          35 :       params.set<Real>("peripheral_ring_radius") = _outer_circle_radius;
     512          70 :       params.set<BoundaryName>("external_boundary_name") = "outside_periphery";
     513          35 :       params.set<SubdomainName>("peripheral_ring_block_name") = RGMB::PERIPHERAL_RING_BLOCK_NAME;
     514             : 
     515             :       // unique MG options
     516          35 :       if (_periphery_meshgenerator == "triangle")
     517             :       {
     518           7 :         params.set<unsigned int>("peripheral_ring_num_segments") = _outer_circle_num_segments;
     519           7 :         params.set<Real>("desired_area") = _desired_area;
     520          14 :         params.set<std::string>("desired_area_func") = _desired_area_func;
     521             :       }
     522          28 :       else if (_periphery_meshgenerator == "quad_ring")
     523             :       {
     524          28 :         params.set<subdomain_id_type>("peripheral_ring_block_id") = RGMB::PERIPHERAL_RING_BLOCK_ID;
     525          28 :         params.set<BoundaryName>("input_mesh_external_boundary") = RGMB::CORE_BOUNDARY_NAME;
     526          28 :         params.set<unsigned int>("peripheral_layer_num") = _periphery_num_layers;
     527             :       }
     528             : 
     529             :       // finish periphery input
     530          35 :       build_mesh_name = name() + "_periphery";
     531          35 :       addMeshSubgenerator(periphery_mg_name, build_mesh_name, params);
     532          35 :     }
     533             : 
     534         173 :     if (_extrude && _mesh_dimensions == 3)
     535         248 :       build_mesh_name = callExtrusionMeshSubgenerators(build_mesh_name);
     536             : 
     537             :     // Store final mesh subgenerator
     538         173 :     _build_mesh = &getMeshByName(build_mesh_name);
     539             :   }
     540             :   // If mesh generation should be bypassed, call getMeshes to resolve MeshGeneratorSystem
     541             :   // dependencies
     542             :   else
     543         154 :     auto input_meshes = getMeshes("inputs");
     544             : 
     545             :   // If we are in CSG only mode, store the CSGBase objects associated with input MG's
     546         250 :   if (_app.getMeshGeneratorSystem().getCSGOnly())
     547          80 :     _input_csg_bases = getCSGBases("inputs");
     548             : 
     549         250 :   generateMetadata();
     550         769 : }
     551             : 
     552             : void
     553         250 : CoreMeshGenerator::generateMetadata()
     554             : {
     555             :   // Define metadata related to downstream function calls
     556         250 :   if (_mesh_periphery)
     557             :   {
     558          47 :     declareMeshProperty(RGMB::peripheral_ring_radius, _outer_circle_radius);
     559          47 :     declareMeshProperty(RGMB::peripheral_ring_region_id, _periphery_region_id);
     560             :   }
     561             : 
     562             :   // Determine constituent pin type ids and define lattice
     563             :   std::vector<std::vector<int>> assembly_name_lattice;
     564             :   std::vector<std::string> input_assembly_names;
     565             :   std::vector<std::string> input_pin_names;
     566             : 
     567             :   // Iterate through input assembly names and define constituent assemblies and pins
     568        1028 :   for (const auto i : index_range(_inputs))
     569             :   {
     570             :     const auto input_assembly_name = _inputs[i];
     571         778 :     if (input_assembly_name != _empty_key)
     572             :     {
     573         556 :       input_assembly_names.push_back(input_assembly_name);
     574         556 :       if (!getMeshProperty<bool>(RGMB::is_control_drum, input_assembly_name) &&
     575         524 :           !getMeshProperty<bool>(RGMB::is_single_pin, input_assembly_name))
     576             :       {
     577             :         const auto pin_names =
     578         390 :             getMeshProperty<std::vector<std::string>>(RGMB::pin_names, input_assembly_name);
     579         987 :         for (const auto & pin_name : pin_names)
     580         597 :           if (std::find(input_pin_names.begin(), input_pin_names.end(), pin_name) ==
     581             :               input_pin_names.end())
     582         522 :             input_pin_names.push_back(pin_name);
     583         390 :       }
     584             :     }
     585             :   }
     586             : 
     587             :   // Iterate through pattern and remap dummy assemblies with index -1
     588         915 :   for (const auto i : index_range(_pattern))
     589             :   {
     590         665 :     std::vector<int> assembly_name_idx(_pattern[i].size());
     591        2180 :     for (const auto j : index_range(_pattern[i]))
     592             :     {
     593        1515 :       const auto input_assembly_name = _inputs[_pattern[i][j]];
     594             :       // Use an assembly type of -1 to represent a dummy assembly
     595        1515 :       if (input_assembly_name == _empty_key)
     596         346 :         assembly_name_idx[j] = -1;
     597             :       // Set index of assembly name based on `input_assembly_names` variable
     598             :       else
     599             :       {
     600        1169 :         const auto it = std::find(
     601             :             input_assembly_names.begin(), input_assembly_names.end(), input_assembly_name);
     602        1169 :         assembly_name_idx[j] = it - input_assembly_names.begin();
     603             :       }
     604             :     }
     605         665 :     assembly_name_lattice.push_back(assembly_name_idx);
     606         665 :   }
     607             : 
     608         250 :   declareMeshProperty(RGMB::pin_names, input_pin_names);
     609             :   declareMeshProperty(RGMB::assembly_names, input_assembly_names);
     610             :   declareMeshProperty(RGMB::assembly_lattice, assembly_name_lattice);
     611         311 :   declareMeshProperty(RGMB::extruded, _extrude && _mesh_dimensions == 3);
     612         250 : }
     613             : 
     614             : bool
     615         133 : CoreMeshGenerator::constituentAssembliesNeedFlexibleStiching()
     616             : {
     617             :   MeshGeneratorName first_nondummy_assembly = "";
     618             :   bool assembly_homogenization = false;
     619             :   unsigned int n_constituent_pins = 0;
     620             :   unsigned int n_pin_sectors = 0;
     621             : 
     622             :   // Loop through all non-dummy input assemblies. Flexible assembly stitching is needed if one of
     623             :   // the following criteria are met:
     624             :   // 1. The number of constituent pins within the assembly does not match with another assembly
     625             :   // 2. The value of is_single_pin and is_homogenized metadata do not agree with another assembly
     626             :   // 3. The number of sectors of the constituent pins of an assembly do not match with the
     627             :   // constituent pins of another assembly
     628         497 :   for (const auto i : index_range(_inputs))
     629             :   {
     630             :     // Skip if assembly name is equal to dummy assembly name
     631         364 :     if (_inputs[i] == _empty_key)
     632         112 :       continue;
     633             : 
     634             :     // Compute total number of constituent pins in assembly, as well as the number of sectors per
     635             :     // side for each pin Note: number of sectors per side is defined uniformly across constituent
     636             :     // pins of an assembly, so only first one needs to be checked
     637             :     unsigned int total_pins = 0;
     638             :     unsigned int pin_sectors_per_side = 0;
     639         252 :     if (!getMeshProperty<bool>(RGMB::is_single_pin, _inputs[i]))
     640             :     {
     641             :       const auto first_pin_name =
     642         210 :           getMeshProperty<std::vector<std::string>>(RGMB::pin_names, _inputs[i])[0];
     643         210 :       pin_sectors_per_side = getMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta",
     644         420 :                                                                         first_pin_name + "_2D")[0];
     645             :       const auto pin_lattice =
     646         210 :           getMeshProperty<std::vector<std::vector<int>>>(RGMB::pin_lattice, _inputs[i]);
     647         756 :       for (const auto i : index_range(pin_lattice))
     648         546 :         total_pins += pin_lattice[i].size();
     649         210 :     }
     650             :     else
     651             :     {
     652          42 :       if (getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]))
     653             :       {
     654             :         // Homogenized assembly
     655             :         total_pins = 0;
     656             :         pin_sectors_per_side = 0;
     657             :       }
     658             :       else
     659             :       {
     660             :         // Assembly with single constituent pin
     661             :         total_pins = 1;
     662          28 :         pin_sectors_per_side = getMeshProperty<std::vector<unsigned int>>(
     663          56 :             "num_sectors_per_side_meta", _inputs[i] + "_2D")[0];
     664             :       }
     665             :     }
     666             : 
     667         252 :     if (first_nondummy_assembly == "")
     668             :     {
     669         133 :       first_nondummy_assembly = MeshGeneratorName(_inputs[i]);
     670         133 :       assembly_homogenization = getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]);
     671             :       n_constituent_pins = total_pins;
     672             :       n_pin_sectors = pin_sectors_per_side;
     673             :     }
     674             :     else
     675             :     {
     676         119 :       if (getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]) != assembly_homogenization)
     677             :       {
     678           0 :         mooseWarning("Detected mix of homogenized and heterogeneous assemblies between " +
     679           0 :                      first_nondummy_assembly + " and " + _inputs[i]);
     680           0 :         return true;
     681             :       }
     682         119 :       if (total_pins != n_constituent_pins)
     683             :       {
     684           0 :         mooseWarning(
     685           0 :             "Detected assemblies with different number of total constituent pins between " +
     686           0 :             first_nondummy_assembly + " and " + _inputs[i]);
     687           0 :         return true;
     688             :       }
     689         119 :       if (pin_sectors_per_side != n_pin_sectors)
     690             :       {
     691           0 :         mooseWarning("Constituent pins in " + first_nondummy_assembly + " and " + _inputs[i] +
     692             :                      " differ in terms of number of sectors per side");
     693           0 :         return true;
     694             :       }
     695             :     }
     696             :   }
     697             :   return false;
     698             : }
     699             : 
     700             : std::unique_ptr<MeshBase>
     701         175 : CoreMeshGenerator::generate()
     702             : {
     703             :   // Must be called to free the ReactorMeshParams mesh
     704         175 :   freeReactorParamsMesh();
     705             : 
     706             :   // If bypass_mesh is true, return a null mesh. In this mode, an output mesh is not
     707             :   // generated and only metadata is defined on the generator, so logic related to
     708             :   // generation of output mesh will not be called
     709         175 :   if (getReactorParam<bool>(RGMB::bypass_meshgen))
     710             :   {
     711             :     auto null_mesh = nullptr;
     712             :     return null_mesh;
     713             :   }
     714             :   // This generate() method will be called once the subgenerators that we depend on are
     715             :   // called. This is where we reassign subdomain ids/names in case they were merged
     716             :   // when stitching assemblies into the core. This is also where we set region_id extra
     717             :   // element integers, which has not been set yet for extruded geometries
     718             : 
     719             :   // Define all extra element names and integers
     720         173 :   std::string pin_type_id_name = "pin_type_id";
     721         173 :   std::string assembly_type_id_name = "assembly_type_id";
     722         173 :   std::string plane_id_name = "plane_id";
     723         173 :   std::string region_id_name = "region_id";
     724         173 :   std::string radial_id_name = "radial_id";
     725         173 :   const std::string default_block_name = RGMB::CORE_BLOCK_NAME_PREFIX;
     726             : 
     727         173 :   auto pin_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), pin_type_id_name, true);
     728         173 :   auto assembly_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), assembly_type_id_name, true);
     729         173 :   auto radial_id_int = getElemIntegerFromMesh(*(*_build_mesh), radial_id_name, true);
     730         173 :   auto region_id_int = getElemIntegerFromMesh(*(*_build_mesh), region_id_name, true);
     731             :   unsigned int plane_id_int = 0;
     732         173 :   if (_extrude)
     733         248 :     plane_id_int = getElemIntegerFromMesh(*(*_build_mesh), plane_id_name, true);
     734             : 
     735             :   // Get next free block ID in mesh in case subdomain ids need to be remapped
     736         173 :   auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*(*(_build_mesh)));
     737             :   std::map<std::string, SubdomainID> rgmb_name_id_map;
     738             : 
     739             :   // Loop through all mesh elements and set region ids and reassign block IDs/names
     740             :   // if they were merged during assembly stitching
     741      529226 :   for (auto & elem : (*_build_mesh)->active_element_ptr_range())
     742             :   {
     743      264440 :     dof_id_type z_id = _extrude ? elem->get_extra_integer(plane_id_int) : 0;
     744      264440 :     dof_id_type pin_type_id = elem->get_extra_integer(pin_type_id_int);
     745             : 
     746      264440 :     if (_pin_region_id_map.find(pin_type_id) != _pin_region_id_map.end())
     747             :     {
     748             :       // Pin type element, get region ID from pin_type, z_id, and radial_idx
     749      140830 :       const dof_id_type radial_idx = elem->get_extra_integer(radial_id_int);
     750      140830 :       const auto elem_rid = _pin_region_id_map[pin_type_id][z_id][radial_idx];
     751      140830 :       elem->set_extra_integer(region_id_int, elem_rid);
     752             : 
     753             :       // Set element block name and block id
     754      140830 :       bool has_block_names = !_pin_block_name_map[pin_type_id].empty();
     755      140830 :       auto elem_block_name = default_block_name;
     756      140830 :       if (has_block_names)
     757       90048 :         elem_block_name += "_" + _pin_block_name_map[pin_type_id][z_id][radial_idx];
     758       95806 :       else if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     759       65976 :         elem_block_name += "_REG" + std::to_string(elem_rid);
     760      140830 :       if (elem->type() == TRI3 || elem->type() == PRISM6)
     761             :         elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     762      281660 :       updateElementBlockNameId(
     763      140830 :           *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     764             :     }
     765      123610 :     else if ((*_build_mesh)->subdomain_name(elem->subdomain_id()) ==
     766             :              RGMB::PERIPHERAL_RING_BLOCK_NAME)
     767             :     // periphery type element
     768             :     {
     769             :       // set region ID of core periphery element
     770       13570 :       elem->set_extra_integer(region_id_int, _periphery_region_id);
     771             :       // set block name and block name of core periphery element
     772       13570 :       auto elem_block_name = _periphery_block_name;
     773       13570 :       if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     774           0 :         elem_block_name += "_REG" + std::to_string(_periphery_region_id);
     775       13570 :       if (elem->type() == TRI3 || elem->type() == PRISM6)
     776             :         elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     777       27140 :       updateElementBlockNameId(
     778       13570 :           *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     779             :     }
     780             :     else
     781             :     {
     782      110040 :       dof_id_type assembly_type_id = elem->get_extra_integer(assembly_type_id_int);
     783             :       // Infer peripheral index of assembly background, assembly duct, or control drum regions from
     784             :       // pin_type_id
     785      110040 :       unsigned int peripheral_idx = RGMB::MAX_PIN_TYPE_ID - pin_type_id;
     786             : 
     787             :       // check if element is part of drum region
     788      110040 :       if (_drum_region_id_map.find(assembly_type_id) != _drum_region_id_map.end())
     789             :       {
     790             :         // Element is in a control drum region. Infer region id from assembly_type_id, z_id, and
     791             :         // peripheral_index
     792       24128 :         const auto elem_rid = _drum_region_id_map[assembly_type_id][z_id][peripheral_idx];
     793       24128 :         elem->set_extra_integer(region_id_int, elem_rid);
     794             : 
     795             :         // Set element block name and block id
     796       24128 :         auto elem_block_name = default_block_name;
     797       24128 :         if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     798       48256 :           elem_block_name += "_REG" + std::to_string(elem_rid);
     799             :         else
     800             :         {
     801           0 :           bool has_drum_block_name = !_drum_block_name_map[assembly_type_id].empty();
     802           0 :           if (has_drum_block_name)
     803           0 :             elem_block_name += "_" + _drum_block_name_map[assembly_type_id][z_id][peripheral_idx];
     804             :         }
     805       24128 :         if (elem->type() == TRI3 || elem->type() == PRISM6)
     806             :           elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     807       48256 :         updateElementBlockNameId(
     808       24128 :             *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     809             :       }
     810             :       else
     811             :       {
     812             :         // Element is in an assembly duct or background region since it doesn't
     813             :         // have an assembly type id in the drum region map. Infer region id from
     814             :         // assembly_type_id, z_id, and peripheral_index
     815             :         bool is_background_region = peripheral_idx == 0;
     816             :         const auto elem_rid =
     817             :             (is_background_region
     818       85912 :                  ? _background_region_id_map[assembly_type_id][z_id]
     819       27090 :                  : _duct_region_id_map[assembly_type_id][z_id][peripheral_idx - 1]);
     820       85912 :         elem->set_extra_integer(region_id_int, elem_rid);
     821             : 
     822             :         // Set element block name and block id
     823       85912 :         auto elem_block_name = default_block_name;
     824       85912 :         if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     825      114480 :           elem_block_name += "_REG" + std::to_string(elem_rid);
     826             :         else
     827             :         {
     828       28672 :           if (is_background_region)
     829             :           {
     830       17108 :             bool has_background_block_name = !_background_block_name_map[assembly_type_id].empty();
     831       17108 :             if (has_background_block_name)
     832       16128 :               elem_block_name += "_" + _background_block_name_map[assembly_type_id][z_id];
     833             :           }
     834             :           else
     835             :           {
     836       11564 :             bool has_duct_block_names = !_duct_block_name_map[assembly_type_id].empty();
     837       11564 :             if (has_duct_block_names)
     838             :               elem_block_name +=
     839       12096 :                   "_" + _duct_block_name_map[assembly_type_id][z_id][peripheral_idx - 1];
     840             :           }
     841             :         }
     842       85912 :         if (elem->type() == TRI3 || elem->type() == PRISM6)
     843             :           elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     844      171824 :         updateElementBlockNameId(
     845       85912 :             *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     846             :       }
     847             :     }
     848         173 :   }
     849             : 
     850             :   // Sideset 10000 does not get stitched properly when BlockDeletionGenerator
     851             :   // is used for deleting dummy assemblies. This block copies missing sides
     852             :   // into sideset 10000 from sideset RGMB::CORE_BOUNDARY_NAME
     853         173 :   BoundaryInfo & boundary_info = (*_build_mesh)->get_boundary_info();
     854             :   boundary_id_type source_id =
     855         346 :       MooseMeshUtils::getBoundaryIDs(**_build_mesh, {RGMB::CORE_BOUNDARY_NAME}, true)[0];
     856             :   boundary_id_type target_id = 10000;
     857             :   const auto sideset_map = boundary_info.get_sideset_map();
     858             : 
     859      330262 :   for (const auto & [elem, id_pair] : sideset_map)
     860             :   {
     861      330089 :     const auto side_id = id_pair.first;
     862      330089 :     const auto sideset_id = id_pair.second;
     863             : 
     864             :     // Filter all sides that belong to RGMB::CORE_BOUNDARY_NAME sideset
     865      330089 :     if (sideset_id == source_id)
     866             :     {
     867             :       auto mm_it = sideset_map.equal_range(elem);
     868             :       bool found = false;
     869             :       // Check if side is defined in sideset 10000
     870       57424 :       for (auto it = mm_it.first; it != mm_it.second; it++)
     871             :       {
     872       42162 :         if (it->second.first == side_id && it->second.second == target_id)
     873             :           found = true;
     874             :       }
     875             :       // Add side if not found in sideset 10000
     876       15262 :       if (!found)
     877        3879 :         boundary_info.add_side(elem, side_id, target_id);
     878             :     }
     879             :   }
     880             : 
     881         346 :   if (getParam<bool>("generate_depletion_id"))
     882             :   {
     883          48 :     const MooseEnum option = getParam<MooseEnum>("depletion_id_type");
     884          24 :     addDepletionId(*(*_build_mesh), option, DepletionIDGenerationLevel::Core, _extrude);
     885          24 :   }
     886             : 
     887             :   // Mark mesh as not prepared, as block ID's were re-assigned in this method
     888         173 :   (*_build_mesh)->unset_is_prepared();
     889             : 
     890         173 :   return std::move(*_build_mesh);
     891             : }
     892             : 
     893             : std::unique_ptr<CSG::CSGBase>
     894          40 : CoreMeshGenerator::generateCSG()
     895             : {
     896             :   // Must be called to free the ReactorMeshParams CSGBase object
     897          40 :   freeReactorParamsCSG();
     898             : 
     899          40 :   auto csg_obj = std::make_unique<CSG::CSGBase>();
     900             : 
     901          40 :   const auto dummy_univ_name = _empty_key + "_univ";
     902          40 :   if (_empty_pos || !_mesh_periphery)
     903             :   {
     904             :     // Create universe with a single void cell with an empty region. This universe is used for
     905             :     // defining dummy assemblies in the core lattice and the lattce outer universe for lattices
     906             :     // that do not have a mesh periphery
     907          35 :     const auto dummy_cell_name = _empty_key + "_cell";
     908             :     const auto & dummy_univ = csg_obj->createUniverse(dummy_univ_name);
     909          35 :     CSG::CSGRegion empty_region;
     910          35 :     csg_obj->createCell(dummy_cell_name, empty_region, &dummy_univ);
     911          35 :   }
     912             : 
     913             :   // Combine all bases from AssemblyMG inputs into this base. We expect each AssemblyMG
     914             :   // input to contain a root universe with a single cell that constrains the assembly based
     915             :   // on the FEM boundary. Root universes from inputs are renamed to a new universe name.
     916             :   // These universes and their cells will be discarded, so that only the infinite assembly
     917             :   // universes are retained.
     918             :   std::unordered_map<unsigned int, std::string> univ_id_names;
     919             :   std::vector<std::string> univs_to_discard;
     920         160 :   for (const auto i : index_range(_inputs))
     921             :   {
     922         120 :     if (_inputs[i] == _empty_key)
     923          80 :       univ_id_names[i] = dummy_univ_name;
     924             :     else
     925             :     {
     926          80 :       const auto input_univ_name_discard = _inputs[i] + "_root_univ";
     927          80 :       const auto input_univ_name = _inputs[i] + "_univ";
     928          80 :       csg_obj->joinOtherBase(std::move(*_input_csg_bases[i]), true, input_univ_name_discard);
     929          80 :       univs_to_discard.push_back(input_univ_name_discard);
     930         160 :       univ_id_names[i] = input_univ_name;
     931             :     }
     932             :   }
     933             : 
     934             :   // Discard root universes of the input assemblies and their cells
     935         120 :   for (const auto & univ_name : univs_to_discard)
     936             :   {
     937             :     const auto & universe_to_delete = csg_obj->getUniverseByName(univ_name);
     938          80 :     const auto cells_to_delete = universe_to_delete.getAllCells();
     939          80 :     csg_obj->deleteUniverse(universe_to_delete);
     940         160 :     for (const auto & cell : cells_to_delete)
     941          80 :       csg_obj->deleteCell(cell.get());
     942          80 :   }
     943             : 
     944             :   // Build the universe pattern for the assembly lattice from the input pattern
     945             :   std::vector<std::vector<std::reference_wrapper<const CSG::CSGUniverse>>> universe_pattern;
     946         140 :   for (const auto & row : _pattern)
     947             :   {
     948             :     std::vector<std::reference_wrapper<const CSG::CSGUniverse>> universe_row;
     949         320 :     for (const auto & univ_id : row)
     950             :     {
     951             :       const auto & lattice_univ = csg_obj->getUniverseByName(univ_id_names[univ_id]);
     952         220 :       universe_row.push_back(lattice_univ);
     953             :     }
     954         100 :     universe_pattern.push_back(universe_row);
     955         100 :   }
     956             : 
     957          40 :   const auto assembly_pitch = getReactorParam<Real>(RGMB::assembly_pitch);
     958          40 :   auto & core_lattice = createRGMBLattice(assembly_pitch, universe_pattern, *csg_obj);
     959             : 
     960             :   // Define universe that fills region outside of lattice. For an explicity
     961             :   // defined outer ring, this is a material outer corresponding to the region ID
     962             :   // of the ring region. Otherwise, the outer is defined as a universe containing a void cell
     963          40 :   if (_mesh_periphery)
     964             :   {
     965          10 :     std::string region_name = "rgmb_region_" + std::to_string(_periphery_region_id);
     966           5 :     csg_obj->setLatticeOuter(core_lattice, region_name);
     967             :   }
     968             :   else
     969             :   {
     970             :     const auto & outer_univ = csg_obj->getUniverseByName(dummy_univ_name);
     971          35 :     csg_obj->setLatticeOuter(core_lattice, outer_univ);
     972             :   }
     973             : 
     974             :   // Define lattice cell, with the lattice surrounded by a bounding circle whose radius is
     975             :   // determined by the mesh periphery radius. If no mesh periphery is defined, the radius will be (N
     976             :   // + 1) times the assembly pitch of the lattice for hex lattices, where N is the number of rings
     977             :   // for a hexagonal lattice. For Cartesian lattices, the radius will be (N / 2 * sqrt(2)) times the
     978             :   // assembly pitch, where N is the number of assembly widths that span a square lattice. This
     979             :   // ensures that the ring radius completely surrounds the underlying lattice.
     980          40 :   std::string lat_cell_name = name() + "_lattice_cell";
     981          40 :   const auto ring_radius = _mesh_periphery ? _outer_circle_radius
     982          35 :                            : (_geom_type == "Hex")
     983          35 :                                ? (universe_pattern.size() + 2) / 2. * assembly_pitch
     984          40 :                                : universe_pattern.size() / 2. * sqrt(2.) * assembly_pitch;
     985          40 :   const auto ring_surf_name = name() + "_radial_ring";
     986             :   std::unique_ptr<CSG::CSGSurface> ring_surf_ptr =
     987          40 :       std::make_unique<CSG::CSGZCylinder>(ring_surf_name, 0, 0, ring_radius);
     988          40 :   const auto & ring_surf = csg_obj->addSurface(std::move(ring_surf_ptr));
     989          40 :   auto lat_cell_region = -ring_surf;
     990             : 
     991          40 :   if (_mesh_dimensions == 3)
     992             :   {
     993          40 :     const auto surfaces_by_axial_region = getAxialPlaneSurfaces(*csg_obj);
     994             :     const auto & lowest_axial_surf = surfaces_by_axial_region.front().get();
     995             :     const auto & highest_axial_surf = surfaces_by_axial_region.back().get();
     996          40 :     lat_cell_region = lat_cell_region & +lowest_axial_surf & -highest_axial_surf;
     997          40 :   }
     998          40 :   csg_obj->createCell(lat_cell_name, core_lattice, lat_cell_region);
     999             : 
    1000          40 :   return csg_obj;
    1001          80 : }

Generated by: LCOV version 1.14