LCOV - code coverage report
Current view: top level - src/meshgenerators - AssemblyMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #32971 (54bef8) with base c6cf66 Lines: 388 416 93.3 %
Date: 2026-05-29 20:39:24 Functions: 9 9 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 "AssemblyMeshGenerator.h"
      11             : 
      12             : #include "ReactorGeometryMeshBuilderBase.h"
      13             : #include "MooseApp.h"
      14             : #include "Factory.h"
      15             : #include "libmesh/elem.h"
      16             : #include "MooseMeshUtils.h"
      17             : #include "CSGCartesianLattice.h"
      18             : #include "CSGHexagonalLattice.h"
      19             : #include "CSGUtils.h"
      20             : 
      21             : registerMooseObject("ReactorApp", AssemblyMeshGenerator);
      22             : 
      23             : InputParameters
      24         894 : AssemblyMeshGenerator::validParams()
      25             : {
      26         894 :   auto params = ReactorGeometryMeshBuilderBase::validParams();
      27             : 
      28        1788 :   params.addRequiredParam<std::vector<MeshGeneratorName>>(
      29             :       "inputs", "The PinMeshGenerators that form the components of the assembly.");
      30             : 
      31        1788 :   params.addRequiredParam<subdomain_id_type>("assembly_type",
      32             :                                              "The integer ID for this assembly type definition");
      33             : 
      34        1788 :   params.addRequiredParam<std::vector<std::vector<unsigned int>>>(
      35             :       "pattern",
      36             :       "A double-indexed array starting with the upper-left corner where the index"
      37             :       "represents the layout of input pins in the assembly lattice.");
      38             : 
      39        1788 :   params.addRangeCheckedParam<std::vector<Real>>(
      40             :       "duct_halfpitch",
      41             :       "duct_halfpitch>0.0",
      42             :       "Distance(s) from center to duct(s) inner boundaries.");
      43             : 
      44        1788 :   params.addRangeCheckedParam<unsigned int>("background_intervals",
      45             :                                             "background_intervals>0",
      46             :                                             "Radial intervals in the assembly peripheral region.");
      47             : 
      48        1788 :   params.addRangeCheckedParam<std::vector<unsigned int>>(
      49             :       "duct_intervals", "duct_intervals>0", "Number of meshing intervals in each enclosing duct.");
      50             : 
      51        1788 :   params.addParam<std::vector<subdomain_id_type>>(
      52             :       "background_region_id",
      53             :       "The region id for the background area between the pins and the ducts to set region_id "
      54             :       "extra-element integer");
      55             : 
      56        1788 :   params.addParam<std::vector<std::vector<subdomain_id_type>>>(
      57             :       "duct_region_ids",
      58             :       "The region id for the ducts from innermost to outermost, to set region_id "
      59             :       "extra-element integer.");
      60             : 
      61        1788 :   params.addParam<std::vector<std::string>>("background_block_name",
      62             :                                             "The block names for the assembly background regions");
      63             : 
      64        1788 :   params.addParam<std::vector<std::vector<std::string>>>(
      65             :       "duct_block_names",
      66             :       "The block names for the assembly duct regions from innermost to outermost");
      67             : 
      68        1788 :   params.addParam<bool>("extrude",
      69        1788 :                         false,
      70             :                         "Determines if this is the final step in the geometry construction"
      71             :                         " and extrudes the 2D geometry to 3D. If this is true then this mesh "
      72             :                         "cannot be used in further mesh building in the Reactor workflow");
      73        1788 :   params.addParamNamesToGroup("background_region_id duct_region_ids assembly_type", "ID assigment");
      74        1788 :   params.addParamNamesToGroup("background_intervals background_region_id",
      75             :                               "Background specifications");
      76        1788 :   params.addParamNamesToGroup("duct_intervals duct_region_ids duct_halfpitch",
      77             :                               "Duct specifications");
      78             : 
      79         894 :   params.addClassDescription("This AssemblyMeshGenerator object is designed to generate "
      80             :                              "assembly-like structures, with IDs, from a reactor geometry. "
      81             :                              "The assembly-like structures must consist of a full pattern of equal "
      82             :                              "sized pins from PinMeshGenerator. "
      83             :                              "A hexagonal assembly will be placed inside of a bounding hexagon "
      84             :                              "consisting of a background region and, optionally,"
      85             :                              " duct regions.");
      86             :   // depletion id generation params are added
      87         894 :   addDepletionIDParams(params);
      88             : 
      89             :   // Declare that this generator has a generateCSG method
      90         894 :   MeshGenerator::setHasGenerateCSG(params);
      91             : 
      92         894 :   return params;
      93           0 : }
      94             : 
      95         447 : AssemblyMeshGenerator::AssemblyMeshGenerator(const InputParameters & parameters)
      96             :   : ReactorGeometryMeshBuilderBase(parameters),
      97         447 :     _inputs(getParam<std::vector<MeshGeneratorName>>("inputs")),
      98         894 :     _assembly_type(getParam<subdomain_id_type>("assembly_type")),
      99         894 :     _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern")),
     100        1541 :     _duct_sizes(isParamValid("duct_halfpitch") ? getParam<std::vector<Real>>("duct_halfpitch")
     101             :                                                : std::vector<Real>()),
     102         447 :     _background_intervals(
     103        1516 :         isParamValid("background_intervals") ? getParam<unsigned int>("background_intervals") : 0),
     104        1294 :     _duct_intervals(isParamValid("duct_intervals")
     105         447 :                         ? getParam<std::vector<unsigned int>>("duct_intervals")
     106             :                         : std::vector<unsigned int>()),
     107        1516 :     _background_region_id(isParamValid("background_region_id")
     108         447 :                               ? getParam<std::vector<subdomain_id_type>>("background_region_id")
     109             :                               : std::vector<subdomain_id_type>()),
     110        1294 :     _duct_region_ids(isParamValid("duct_region_ids")
     111         447 :                          ? getParam<std::vector<std::vector<subdomain_id_type>>>("duct_region_ids")
     112             :                          : std::vector<std::vector<subdomain_id_type>>()),
     113        1341 :     _extrude(getParam<bool>("extrude"))
     114             : {
     115             :   MeshGeneratorName reactor_params =
     116         447 :       MeshGeneratorName(getMeshProperty<std::string>(RGMB::reactor_params_name, _inputs[0]));
     117             :   // Check that MG name for reactor params is consistent across all assemblies
     118         710 :   for (unsigned int i = 1; i < _inputs.size(); i++)
     119         263 :     if (getMeshProperty<std::string>(RGMB::reactor_params_name, _inputs[i]) != reactor_params)
     120           0 :       mooseError("The name of all reactor_params objects should be identical across all input pins "
     121             :                  "in the assembly.\n");
     122             : 
     123             :   // Initialize ReactorMeshParams object stored in pin input
     124         894 :   initializeReactorMeshParams(reactor_params);
     125             : 
     126         447 :   _geom_type = getReactorParam<std::string>(RGMB::mesh_geometry);
     127         447 :   _mesh_dimensions = getReactorParam<unsigned int>(RGMB::mesh_dimensions);
     128             : 
     129         447 :   if (_extrude && _mesh_dimensions != 3)
     130           0 :     paramError("extrude",
     131             :                "In order to extrude this mesh, ReactorMeshParams/dim needs to be set to 3\n");
     132        1030 :   if (_extrude && (!hasReactorParam<boundary_id_type>(RGMB::top_boundary_id) ||
     133         583 :                    !hasReactorParam<boundary_id_type>(RGMB::bottom_boundary_id)))
     134           0 :     mooseError("Both top_boundary_id and bottom_boundary_id must be provided in ReactorMeshParams "
     135             :                "if using extruded geometry");
     136             : 
     137             :   Real base_pitch = 0.0;
     138             : 
     139             :   // Check constitutent pins do not have shared pin_type ids
     140             :   std::map<subdomain_id_type, std::string> pin_map_type_to_name;
     141        1155 :   for (const auto i : index_range(_inputs))
     142             :   {
     143             :     auto pin = _inputs[i];
     144         710 :     if (i == 0)
     145         447 :       base_pitch = getMeshProperty<Real>(RGMB::pitch, pin);
     146             :     else
     147             :     {
     148         263 :       auto pitch = getMeshProperty<Real>(RGMB::pitch, pin);
     149         263 :       if (!MooseUtils::absoluteFuzzyEqual(pitch, base_pitch))
     150           0 :         mooseError("All pins within an assembly must have the same pitch");
     151             :     }
     152         710 :     if (getMeshProperty<bool>(RGMB::extruded, pin))
     153           0 :       mooseError("Pins that have already been extruded cannot be used in AssemblyMeshGenerator "
     154             :                  "definition.\n");
     155         710 :     const auto pin_type = getMeshProperty<subdomain_id_type>(RGMB::pin_type, pin);
     156         712 :     if (pin_map_type_to_name.find(pin_type) != pin_map_type_to_name.end() &&
     157           2 :         pin_map_type_to_name[pin_type] != pin)
     158           2 :       mooseError("Constituent pins have shared pin_type ids but different names. Each uniquely "
     159             :                  "defined pin in PinMeshGenerator must have its own pin_type id.");
     160         708 :     pin_map_type_to_name[pin_type] = pin;
     161             :   }
     162         445 :   auto assembly_pitch = getReactorParam<Real>(RGMB::assembly_pitch);
     163             : 
     164             :   unsigned int n_axial_levels =
     165         445 :       (_mesh_dimensions == 3)
     166             :           ? getReactorParam<std::vector<unsigned int>>(RGMB::axial_mesh_intervals).size()
     167         835 :           : 1;
     168         445 :   if (_geom_type == "Square")
     169             :   {
     170             :     const auto ny = _pattern.size();
     171             :     const auto nx = _pattern[0].size();
     172         178 :     if (_background_region_id.size() == 0)
     173             :     {
     174         134 :       if ((!MooseUtils::absoluteFuzzyEqual(base_pitch * ny, assembly_pitch)) ||
     175         134 :           (!MooseUtils::absoluteFuzzyEqual(base_pitch * nx, assembly_pitch)))
     176           0 :         mooseError(
     177             :             "Assembly pitch must be equal to lattice dimension times pin pitch for Cartesian "
     178             :             "assemblies with no background region");
     179         134 :       if (_background_intervals > 0)
     180           0 :         mooseError("\"background_region_id\" must be defined if \"background_intervals\" is "
     181             :                    "greater than 0");
     182             :     }
     183             :     else
     184             :     {
     185          44 :       if ((base_pitch * ny > assembly_pitch) || (base_pitch * nx > assembly_pitch))
     186           0 :         mooseError(
     187             :             "Assembly pitch must be larger than lattice dimension times pin pitch for Cartesian "
     188             :             "assemblies with background region");
     189          44 :       if (_background_intervals == 0)
     190           0 :         mooseError("\"background_intervals\" must be greater than 0 if \"background_region_id\" is "
     191             :                    "defined");
     192          44 :       if (_background_region_id.size() != n_axial_levels)
     193           0 :         mooseError(
     194             :             "The size of background_region_id must be equal to the number of axial levels as "
     195             :             "defined in the ReactorMeshParams object");
     196             :     }
     197             :   }
     198             :   else
     199             :   {
     200         267 :     if ((_background_region_id.size() == 0) || _background_intervals == 0)
     201           0 :       mooseError("Hexagonal assemblies must have a background region defined");
     202         267 :     if (assembly_pitch / std::sin(M_PI / 3.0) < _pattern.size() * base_pitch)
     203           0 :       mooseError("Hexagonal diameter of assembly must be larger than the number of assembly rows "
     204             :                  "times the pin pitch");
     205             :     // Check size of background region id matches number of axial levels
     206         267 :     if (_background_region_id.size() != n_axial_levels)
     207           0 :       mooseError("The size of background_region_id must be equal to the number of axial levels as "
     208             :                  "defined in the ReactorMeshParams object");
     209             :   }
     210             : 
     211         445 :   if (_duct_sizes.size() != _duct_intervals.size())
     212           0 :     mooseError("If ducts are defined then \"duct_intervals\" and \"duct_region_ids\" must also be "
     213             :                "defined and of equal size.");
     214             : 
     215         445 :   if (_duct_sizes.size() != 0)
     216             :   {
     217             :     // Check size of duct region id matches number of axial levels
     218         200 :     if (_duct_region_ids.size() != n_axial_levels)
     219           0 :       mooseError("The size of duct_region_id must be equal to the number of axial levels as "
     220             :                  "defined in the ReactorMeshParams object");
     221         200 :     if (_duct_region_ids[0].size() != _duct_sizes.size())
     222           0 :       paramError("duct_halfpitch",
     223             :                  "If ducts are defined, then \"duct_intervals\" and \"duct_region_ids\" "
     224             :                  "must also be defined and of equal size.");
     225             :   }
     226             : 
     227             :   // Check whether block names are defined properly
     228         890 :   if (isParamValid("background_block_name"))
     229             :   {
     230         100 :     if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     231           2 :       paramError("background_block_name",
     232             :                  "If ReactorMeshParams/region_id_as_block_name is set, background_block_name "
     233             :                  "should not be specified in AssemblyMeshGenerator");
     234          98 :     _has_background_block_name = true;
     235         294 :     _background_block_name = getParam<std::vector<std::string>>("background_block_name");
     236          98 :     if (_background_region_id.size() != _background_block_name.size())
     237           0 :       mooseError("The size of background_block_name must match the size of background_region_id");
     238             :   }
     239             :   else
     240         345 :     _has_background_block_name = false;
     241             : 
     242         886 :   if (isParamValid("duct_block_names"))
     243             :   {
     244          49 :     if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     245           0 :       paramError("duct_block_names",
     246             :                  "If ReactorMeshParams/region_id_as_block_name is set, duct_block_names should not "
     247             :                  "be specified in AssemblyMeshGenerator");
     248          49 :     _has_duct_block_names = true;
     249         147 :     _duct_block_names = getParam<std::vector<std::vector<std::string>>>("duct_block_names");
     250          49 :     if (_duct_region_ids.size() != _duct_block_names.size())
     251           0 :       mooseError("The size of duct_block_names must match the size of duct_region_ids");
     252         147 :     for (const auto i : index_range(_duct_region_ids))
     253          98 :       if (_duct_region_ids[i].size() != _duct_block_names[i].size())
     254           0 :         mooseError("The size of duct_block_names must match the size of duct_region_ids");
     255             :   }
     256             :   else
     257         394 :     _has_duct_block_names = false;
     258             : 
     259             :   // No subgenerators will be called if option to bypass mesh generators is enabled
     260         443 :   if (!getReactorParam<bool>(RGMB::bypass_meshgen))
     261             :   {
     262             :     // Declare dependency of inputs to sub generator calls. If mesh generation
     263         353 :     declareMeshesForSub("inputs");
     264             : 
     265         353 :     _assembly_boundary_id = RGMB::ASSEMBLY_BOUNDARY_ID_START + _assembly_type;
     266         353 :     _assembly_boundary_name = RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX + std::to_string(_assembly_type);
     267             : 
     268             :     // Call PatternedHexMeshGenerator or PatternedCartesianMeshGenerator to stitch assembly
     269             :     {
     270             :       const auto patterned_mg_name =
     271         353 :           _geom_type == "Hex" ? "PatternedHexMeshGenerator" : "PatternedCartesianMeshGenerator";
     272         353 :       auto params = _app.getFactory().getValidParams(patterned_mg_name);
     273             : 
     274         353 :       if (_geom_type == "Hex")
     275             :       {
     276         227 :         params.set<Real>("hexagon_size") = getReactorParam<Real>(RGMB::assembly_pitch) / 2.0;
     277         454 :         params.set<MooseEnum>("hexagon_size_style") = "apothem";
     278             :       }
     279             :       else
     280             :       {
     281         126 :         if (_background_region_id.size() == 0)
     282         184 :           params.set<MooseEnum>("pattern_boundary") = "none";
     283             :         else
     284             :         {
     285          68 :           params.set<MooseEnum>("pattern_boundary") = "expanded";
     286          34 :           params.set<Real>("square_size") = getReactorParam<Real>(RGMB::assembly_pitch);
     287          34 :           params.set<bool>("uniform_mesh_on_sides") = true;
     288             :         }
     289             :       }
     290             : 
     291        1059 :       params.set<std::vector<std::string>>("id_name") = {"pin_id"};
     292         706 :       params.set<std::vector<MooseEnum>>("assign_type") = {
     293        1412 :           MooseEnum("cell", "cell")}; // give elems IDs relative to position in assembly
     294         353 :       params.set<std::vector<MeshGeneratorName>>("inputs") = _inputs;
     295         353 :       params.set<std::vector<std::vector<unsigned int>>>("pattern") = _pattern;
     296         353 :       params.set<bool>("create_outward_interface_boundaries") = false;
     297             : 
     298         353 :       if (_background_intervals > 0)
     299             :       {
     300         261 :         params.set<unsigned int>("background_intervals") = _background_intervals;
     301             :         // Initial block id used to define peripheral regions of assembly
     302             : 
     303             :         const auto background_block_name =
     304         261 :             RGMB::ASSEMBLY_BLOCK_NAME_PREFIX + std::to_string(_assembly_type) + "_R0";
     305             :         const auto background_block_id = RGMB::ASSEMBLY_BLOCK_ID_START;
     306         261 :         params.set<subdomain_id_type>("background_block_id") = background_block_id;
     307         522 :         params.set<SubdomainName>("background_block_name") = background_block_name;
     308             :       }
     309             : 
     310         353 :       if (_duct_sizes.size() > 0)
     311             :       {
     312             :         std::vector<subdomain_id_type> duct_block_ids;
     313             :         std::vector<SubdomainName> duct_block_names;
     314         334 :         for (const auto duct_it : index_range(_duct_region_ids[0]))
     315             :         {
     316         167 :           const auto duct_block_name = RGMB::ASSEMBLY_BLOCK_NAME_PREFIX +
     317         334 :                                        std::to_string(_assembly_type) + "_R" +
     318         334 :                                        std::to_string(duct_it + 1);
     319         167 :           const auto duct_block_id = RGMB::ASSEMBLY_BLOCK_ID_START + duct_it + 1;
     320         167 :           duct_block_ids.push_back(duct_block_id);
     321         167 :           duct_block_names.push_back(duct_block_name);
     322             :         }
     323             : 
     324         167 :         params.set<std::vector<Real>>("duct_sizes") = _duct_sizes;
     325         167 :         params.set<std::vector<subdomain_id_type>>("duct_block_ids") = duct_block_ids;
     326         167 :         params.set<std::vector<SubdomainName>>("duct_block_names") = duct_block_names;
     327         167 :         params.set<std::vector<unsigned int>>("duct_intervals") = _duct_intervals;
     328         167 :       }
     329             : 
     330         353 :       params.set<boundary_id_type>("external_boundary_id") = _assembly_boundary_id;
     331         353 :       params.set<BoundaryName>("external_boundary_name") = _assembly_boundary_name;
     332             : 
     333         706 :       addMeshSubgenerator(patterned_mg_name, name() + "_pattern", params);
     334             : 
     335             :       // Pass mesh meta-data defined in subgenerator constructor to this MeshGenerator
     336         706 :       copyMeshProperty<bool>("is_control_drum_meta", name() + "_pattern");
     337         706 :       copyMeshProperty<std::vector<Point>>("control_drum_positions", name() + "_pattern");
     338         706 :       copyMeshProperty<std::vector<Real>>("control_drum_angles", name() + "_pattern");
     339         706 :       copyMeshProperty<std::vector<std::vector<Real>>>("control_drums_azimuthal_meta",
     340         706 :                                                        name() + "_pattern");
     341         706 :       copyMeshProperty<std::string>("position_file_name", name() + "_pattern");
     342         706 :       copyMeshProperty<Real>("pattern_pitch_meta", name() + "_pattern");
     343         353 :     }
     344             : 
     345         353 :     std::string build_mesh_name = name() + "_delbds";
     346             : 
     347             :     // Remove outer pin sidesets created by PolygonConcentricCircleMeshGenerator
     348             :     {
     349             :       // Get outer boundaries of all constituent pins based on pin_type
     350             :       std::vector<BoundaryName> boundaries_to_delete = {};
     351        1311 :       for (const auto & pattern_x : _pattern)
     352             :       {
     353        3196 :         for (const auto & pattern_idx : pattern_x)
     354             :         {
     355        2238 :           const auto pin_name = _inputs[pattern_idx];
     356        2238 :           const auto pin_id = getMeshProperty<subdomain_id_type>(RGMB::pin_type, pin_name);
     357             :           const BoundaryName boundary_name =
     358        4476 :               RGMB::PIN_BOUNDARY_NAME_PREFIX + std::to_string(pin_id);
     359        2238 :           if (!std::count(boundaries_to_delete.begin(), boundaries_to_delete.end(), boundary_name))
     360         548 :             boundaries_to_delete.push_back(boundary_name);
     361             :         }
     362             :       }
     363         706 :       auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
     364             : 
     365        1059 :       params.set<MeshGeneratorName>("input") = name() + "_pattern";
     366         353 :       params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
     367             : 
     368         706 :       addMeshSubgenerator("BoundaryDeletionGenerator", build_mesh_name, params);
     369         353 :     }
     370             : 
     371             :     // Modify outermost mesh interval to enable flexible assembly stitching
     372         353 :     const auto use_flexible_stitching = getReactorParam<bool>(RGMB::flexible_assembly_stitching);
     373         353 :     if (use_flexible_stitching)
     374             :     {
     375          75 :       generateFlexibleAssemblyBoundaries();
     376         150 :       build_mesh_name = name() + "_fpg_delbds";
     377             :     }
     378             : 
     379         901 :     for (auto pinMG : _inputs)
     380             :     {
     381             :       std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>> region_id_map =
     382             :           getMeshProperty<std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>>(
     383         548 :               RGMB::pin_region_ids, pinMG);
     384        1096 :       _pin_region_id_map.insert(
     385           0 :           std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
     386         548 :               region_id_map.begin()->first, region_id_map.begin()->second));
     387         548 :       subdomain_id_type pin_type_id = getMeshProperty<subdomain_id_type>(RGMB::pin_type, pinMG);
     388             :       std::vector<std::vector<std::string>> pin_block_names =
     389         548 :           getMeshProperty<std::vector<std::vector<std::string>>>(RGMB::pin_block_names, pinMG);
     390        1096 :       _pin_block_name_map.insert(
     391         548 :           std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(pin_type_id,
     392             :                                                                               pin_block_names));
     393         548 :     }
     394             : 
     395         353 :     if (_extrude && _mesh_dimensions == 3)
     396          98 :       build_mesh_name = callExtrusionMeshSubgenerators(build_mesh_name);
     397             : 
     398             :     // Store final mesh subgenerator
     399         353 :     _build_mesh = &getMeshByName(build_mesh_name);
     400             :   }
     401             :   // If mesh generation should be bypassed, call getMeshes to resolve MeshGeneratorSystem
     402             :   // dependencies
     403             :   else
     404         180 :     auto input_meshes = getMeshes("inputs");
     405             : 
     406             :   // If we are in CSG only mode, store the CSGBase objects associated with input MG's
     407         443 :   if (_app.getMeshGeneratorSystem().getCSGOnly())
     408          60 :     _input_csg_bases = getCSGBases("inputs");
     409             : 
     410         443 :   generateMetadata();
     411        1502 : }
     412             : 
     413             : void
     414         443 : AssemblyMeshGenerator::generateMetadata()
     415             : {
     416             :   // Declare metadata for use in downstream mesh generators
     417         443 :   declareMeshProperty(RGMB::assembly_type, _assembly_type);
     418             :   declareMeshProperty(RGMB::pitch, getReactorParam<Real>(RGMB::assembly_pitch));
     419         443 :   declareMeshProperty(RGMB::background_region_id, _background_region_id);
     420         443 :   declareMeshProperty(RGMB::background_block_name, _background_block_name);
     421         443 :   declareMeshProperty(RGMB::duct_halfpitches, _duct_sizes);
     422         443 :   declareMeshProperty(RGMB::duct_region_ids, _duct_region_ids);
     423         443 :   declareMeshProperty(RGMB::duct_block_names, _duct_block_names);
     424         443 :   declareMeshProperty(RGMB::is_homogenized, false);
     425         443 :   declareMeshProperty(RGMB::is_single_pin, false);
     426         822 :   declareMeshProperty(RGMB::extruded, _extrude && _mesh_dimensions == 3);
     427         443 :   declareMeshProperty(RGMB::is_control_drum, false);
     428             :   // Following metadata is only relevant if an output mesh is generated by RGMB
     429         443 :   if (!getReactorParam<bool>(RGMB::bypass_meshgen))
     430             :   {
     431         353 :     declareMeshProperty(RGMB::pin_region_id_map, _pin_region_id_map);
     432         353 :     declareMeshProperty(RGMB::pin_block_name_map, _pin_block_name_map);
     433             :   }
     434             : 
     435             :   // Determine constituent pin names and define lattice as metadata
     436             :   std::vector<std::vector<int>> pin_name_lattice;
     437             :   std::vector<std::string> input_pin_names;
     438        1619 :   for (const auto i : index_range(_pattern))
     439             :   {
     440        1176 :     std::vector<int> pin_name_idx(_pattern[i].size());
     441        3888 :     for (const auto j : index_range(_pattern[i]))
     442             :     {
     443        2712 :       const auto input_pin_name = _inputs[_pattern[i][j]];
     444        2712 :       const auto it = std::find(input_pin_names.begin(), input_pin_names.end(), input_pin_name);
     445        2712 :       if (it == input_pin_names.end())
     446             :       {
     447         702 :         pin_name_idx[j] = input_pin_names.size();
     448         702 :         input_pin_names.push_back(input_pin_name);
     449             :       }
     450             :       else
     451        2010 :         pin_name_idx[j] = it - input_pin_names.begin();
     452             :     }
     453        1176 :     pin_name_lattice.push_back(pin_name_idx);
     454        1176 :   }
     455             :   declareMeshProperty(RGMB::pin_names, input_pin_names);
     456             :   declareMeshProperty(RGMB::pin_lattice, pin_name_lattice);
     457         443 : }
     458             : 
     459             : void
     460          75 : AssemblyMeshGenerator::generateFlexibleAssemblyBoundaries()
     461             : {
     462             :   // Assemblies that invoke this method have constituent pin lattice, delete outermost background or
     463             :   // duct region (if present)
     464             :   SubdomainName block_to_delete = "";
     465          75 :   if (_background_region_id.size() == 0)
     466           0 :     mooseError("Attempting to use flexible stitching on assembly " + name() +
     467             :                " that does not have a background region. This is not yet supported.");
     468          75 :   const auto radial_index = _duct_region_ids.size() == 0 ? 0 : _duct_region_ids[0].size();
     469         225 :   block_to_delete = RGMB::ASSEMBLY_BLOCK_NAME_PREFIX + std::to_string(_assembly_type) + "_R" +
     470          75 :                     std::to_string(radial_index);
     471             : 
     472             :   {
     473             :     // Invoke BlockDeletionGenerator to delete outermost mesh interval of assembly
     474         150 :     auto params = _app.getFactory().getValidParams("BlockDeletionGenerator");
     475             : 
     476         225 :     params.set<std::vector<SubdomainName>>("block") = {block_to_delete};
     477         225 :     params.set<MeshGeneratorName>("input") = name() + "_delbds";
     478             : 
     479         150 :     addMeshSubgenerator("BlockDeletionGenerator", name() + "_del_outer", params);
     480          75 :   }
     481             :   {
     482             :     // Invoke FlexiblePatternGenerator to triangulate deleted mesh region
     483         150 :     auto params = _app.getFactory().getValidParams("FlexiblePatternGenerator");
     484             : 
     485         375 :     params.set<std::vector<MeshGeneratorName>>("inputs") = {name() + "_del_outer"};
     486          75 :     params.set<std::vector<libMesh::Point>>("extra_positions") = {libMesh::Point(0, 0, 0)};
     487          75 :     params.set<std::vector<unsigned int>>("extra_positions_mg_indices") = {0};
     488          75 :     params.set<bool>("use_auto_area_func") = true;
     489         170 :     params.set<MooseEnum>("boundary_type") = (_geom_type == "Hex") ? "HEXAGON" : "CARTESIAN";
     490          75 :     params.set<unsigned int>("boundary_sectors") =
     491          75 :         getReactorParam<unsigned int>(RGMB::num_sectors_flexible_stitching);
     492          75 :     params.set<Real>("boundary_size") = getReactorParam<Real>(RGMB::assembly_pitch);
     493          75 :     params.set<boundary_id_type>("external_boundary_id") = _assembly_boundary_id;
     494          75 :     params.set<BoundaryName>("external_boundary_name") = _assembly_boundary_name;
     495         150 :     params.set<SubdomainName>("background_subdomain_name") =
     496          75 :         block_to_delete + RGMB::TRI_BLOCK_NAME_SUFFIX;
     497          75 :     params.set<bool>("verify_holes") = false;
     498          75 :     params.set<unsigned short>("background_subdomain_id") = RGMB::ASSEMBLY_BLOCK_ID_TRI_FLEXIBLE;
     499             : 
     500         150 :     addMeshSubgenerator("FlexiblePatternGenerator", name() + "_fpg", params);
     501          75 :   }
     502             :   {
     503             :     // Delete extra boundary created by FlexiblePatternGenerator
     504         150 :     auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
     505             : 
     506         225 :     params.set<MeshGeneratorName>("input") = name() + "_fpg";
     507         300 :     params.set<std::vector<BoundaryName>>("boundary_names") = {std::to_string(1)};
     508             : 
     509         150 :     addMeshSubgenerator("BoundaryDeletionGenerator", name() + "_fpg_delbds", params);
     510          75 :   }
     511          75 : }
     512             : 
     513             : std::unique_ptr<MeshBase>
     514         341 : AssemblyMeshGenerator::generate()
     515             : {
     516             :   // Must be called to free the ReactorMeshParams mesh
     517         341 :   freeReactorParamsMesh();
     518             : 
     519             :   // If bypass_mesh is true, return a null mesh. In this mode, an output mesh is not
     520             :   // generated and only metadata is defined on the generator, so logic related to
     521             :   // generation of output mesh will not be called
     522         341 :   if (getReactorParam<bool>(RGMB::bypass_meshgen))
     523             :   {
     524             :     auto null_mesh = nullptr;
     525             :     return null_mesh;
     526             :   }
     527             : 
     528             :   // Update metadata at this point since values for these metadata only get set by PCCMG
     529             :   // at generate() stage
     530         682 :   if (hasMeshProperty<Real>("pattern_pitch_meta", name() + "_pattern"))
     531             :   {
     532             :     const auto pattern_pitch_meta =
     533         341 :         getMeshProperty<Real>("pattern_pitch_meta", name() + "_pattern");
     534         682 :     setMeshProperty("pattern_pitch_meta", pattern_pitch_meta);
     535             :   }
     536             : 
     537             :   // This generate() method will be called once the subgenerators that we depend on are
     538             :   // called. This is where we reassign subdomain ids/name in case they were merged when
     539             :   // stitching pins into an assembly. This is also where we set region_id and
     540             :   // assembly_type_id element integers.
     541             : 
     542             :   // Define all extra element names and integers
     543         341 :   std::string plane_id_name = "plane_id";
     544         341 :   std::string region_id_name = "region_id";
     545         341 :   std::string pin_type_id_name = "pin_type_id";
     546         341 :   std::string assembly_type_id_name = "assembly_type_id";
     547         341 :   std::string radial_id_name = "radial_id";
     548             :   const std::string default_block_name =
     549         341 :       RGMB::ASSEMBLY_BLOCK_NAME_PREFIX + std::to_string(_assembly_type);
     550             : 
     551         341 :   auto pin_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), pin_type_id_name, true);
     552         341 :   auto region_id_int = getElemIntegerFromMesh(*(*_build_mesh), region_id_name, true);
     553         341 :   auto radial_id_int = getElemIntegerFromMesh(*(*_build_mesh), radial_id_name, true);
     554             : 
     555         341 :   auto assembly_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), assembly_type_id_name);
     556             : 
     557             :   unsigned int plane_id_int = 0;
     558         341 :   if (_extrude)
     559          98 :     plane_id_int = getElemIntegerFromMesh(*(*_build_mesh), plane_id_name, true);
     560             : 
     561             :   // Get next free block ID in mesh in case subdomain ids need to be remapped
     562         341 :   auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*(*(_build_mesh)));
     563             :   std::map<std::string, SubdomainID> rgmb_name_id_map;
     564             : 
     565             :   // Loop through all mesh elements and set region ids and reassign block IDs/names
     566             :   // if they were merged during pin stitching
     567      275054 :   for (auto & elem : (*_build_mesh)->active_element_ptr_range())
     568             :   {
     569      137186 :     elem->set_extra_integer(assembly_type_id_int, _assembly_type);
     570      137186 :     const dof_id_type pin_type_id = elem->get_extra_integer(pin_type_id_int);
     571      137186 :     const dof_id_type z_id = _extrude ? elem->get_extra_integer(plane_id_int) : 0;
     572             : 
     573             :     // Element is part of a pin mesh
     574      137186 :     if (_pin_region_id_map.find(pin_type_id) != _pin_region_id_map.end())
     575             :     {
     576             :       // Get region ID from pin_type, z_id, and radial_idx
     577       66610 :       const dof_id_type radial_idx = elem->get_extra_integer(radial_id_int);
     578       66610 :       const auto elem_rid = _pin_region_id_map[pin_type_id][z_id][radial_idx];
     579       66610 :       elem->set_extra_integer(region_id_int, elem_rid);
     580             : 
     581             :       // Set element block name and block id
     582       66610 :       bool has_block_names = !_pin_block_name_map[pin_type_id].empty();
     583       66610 :       auto elem_block_name = default_block_name;
     584       66610 :       if (has_block_names)
     585       31080 :         elem_block_name += "_" + _pin_block_name_map[pin_type_id][z_id][radial_idx];
     586       51070 :       else if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     587       40820 :         elem_block_name += "_REG" + std::to_string(elem_rid);
     588       66610 :       if (elem->type() == TRI3 || elem->type() == PRISM6)
     589             :         elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     590      133220 :       updateElementBlockNameId(
     591       66610 :           *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     592             :     }
     593             :     else
     594             :     {
     595             :       // Assembly peripheral element (background / duct), set subdomains according
     596             :       // to user preferences and set pin type id to RGMB::MAX_PIN_TYPE_ID - peripheral index
     597             :       // Region id is inferred from z_id and peripheral_idx
     598       70576 :       const auto base_block_id = elem->subdomain_id();
     599       70576 :       const auto base_block_name = (*_build_mesh)->subdomain_name(base_block_id);
     600             : 
     601             :       // Check if block name has correct prefix
     602      141152 :       std::string prefix = RGMB::ASSEMBLY_BLOCK_NAME_PREFIX + std::to_string(_assembly_type) + "_R";
     603       70576 :       if (!(base_block_name.find(prefix, 0) == 0))
     604             :         continue;
     605             :       // Peripheral index is integer value of substring after prefix
     606      141152 :       const unsigned int peripheral_idx = std::stoi(base_block_name.substr(prefix.length()));
     607             : 
     608             :       bool is_background_region = peripheral_idx == 0;
     609             : 
     610       70576 :       subdomain_id_type pin_type = RGMB::MAX_PIN_TYPE_ID - peripheral_idx;
     611       70576 :       elem->set_extra_integer(pin_type_id_int, pin_type);
     612             : 
     613       70576 :       const auto elem_rid = (is_background_region ? _background_region_id[z_id]
     614       21038 :                                                   : _duct_region_ids[z_id][peripheral_idx - 1]);
     615       70576 :       elem->set_extra_integer(region_id_int, elem_rid);
     616             : 
     617             :       // Set element block name and block id
     618       70576 :       auto elem_block_name = default_block_name;
     619       70576 :       if (getReactorParam<bool>(RGMB::region_id_as_block_name))
     620      117576 :         elem_block_name += "_REG" + std::to_string(elem_rid);
     621       11788 :       else if (is_background_region && _has_background_block_name)
     622        6552 :         elem_block_name += "_" + _background_block_name[z_id];
     623        4648 :       else if (!is_background_region && _has_duct_block_names)
     624        3024 :         elem_block_name += "_" + _duct_block_names[z_id][peripheral_idx - 1];
     625       70576 :       if (elem->type() == TRI3 || elem->type() == PRISM6)
     626             :         elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
     627      141152 :       updateElementBlockNameId(
     628       70576 :           *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
     629             :     }
     630         341 :   }
     631             : 
     632         682 :   if (getParam<bool>("generate_depletion_id"))
     633             :   {
     634          28 :     const MooseEnum option = getParam<MooseEnum>("depletion_id_type");
     635          14 :     addDepletionId(*(*_build_mesh), option, DepletionIDGenerationLevel::Assembly, _extrude);
     636          14 :   }
     637             : 
     638             :   // Mark mesh as not prepared, as block IDs were re-assigned in this method
     639         341 :   (*_build_mesh)->unset_is_prepared();
     640             : 
     641         341 :   return std::move(*_build_mesh);
     642             : }
     643             : 
     644             : std::unique_ptr<CSG::CSGBase>
     645          30 : AssemblyMeshGenerator::generateCSG()
     646             : {
     647             :   // Must be called to free the ReactorMeshParams CSGBase object
     648          30 :   freeReactorParamsCSG();
     649             : 
     650          30 :   auto csg_obj = std::make_unique<CSG::CSGBase>();
     651             : 
     652             :   // Combine all bases from PinMG inputs into this base. Root universes from
     653             :   // inputs are renamed to a new universe name. These universes and their
     654             :   // cells will be discarded, so that only the infinite pin universe is retained
     655             :   std::unordered_map<unsigned int, std::string> univ_id_names;
     656             :   std::vector<std::string> univs_to_discard;
     657          85 :   for (const auto i : index_range(_inputs))
     658             :   {
     659          55 :     const auto input_univ_name_discard = _inputs[i] + "_root_univ";
     660          55 :     const auto input_univ_name = _inputs[i] + "_univ";
     661          55 :     csg_obj->joinOtherBase(std::move(*_input_csg_bases[i]), true, input_univ_name_discard);
     662          55 :     univs_to_discard.push_back(input_univ_name_discard);
     663         110 :     univ_id_names[i] = input_univ_name;
     664             :   }
     665             : 
     666             :   // Discard root universes of the input pins and their cells
     667          85 :   for (const auto & univ_name : univs_to_discard)
     668             :   {
     669             :     const auto & universe_to_delete = csg_obj->getUniverseByName(univ_name);
     670          55 :     const auto cells_to_delete = universe_to_delete.getAllCells();
     671          55 :     csg_obj->deleteUniverse(universe_to_delete);
     672         110 :     for (const auto & cell : cells_to_delete)
     673          55 :       csg_obj->deleteCell(cell.get());
     674          55 :   }
     675             : 
     676             :   // Build the universe pattern for the assembly lattice from the input pattern
     677             :   std::vector<std::vector<std::reference_wrapper<const CSG::CSGUniverse>>> universe_pattern;
     678         100 :   for (const auto & row : _pattern)
     679             :   {
     680             :     std::vector<std::reference_wrapper<const CSG::CSGUniverse>> universe_row;
     681         220 :     for (const auto & univ_id : row)
     682             :     {
     683             :       const auto & lattice_univ = csg_obj->getUniverseByName(univ_id_names[univ_id]);
     684         150 :       universe_row.push_back(lattice_univ);
     685             :     }
     686          70 :     universe_pattern.push_back(universe_row);
     687          70 :   }
     688             : 
     689             :   // Define axial boundaries for problem
     690             :   std::vector<std::reference_wrapper<const CSG::CSGSurface>> surfaces_by_axial_region;
     691          30 :   CSG::CSGRegion axial_extent;
     692          30 :   const auto extruded_assembly = _mesh_dimensions == 3;
     693          30 :   if (extruded_assembly)
     694             :   {
     695          30 :     surfaces_by_axial_region = getAxialPlaneSurfaces(*csg_obj);
     696             :     const auto & lowest_axial_surf = surfaces_by_axial_region.front().get();
     697             :     const auto & highest_axial_surf = surfaces_by_axial_region.back().get();
     698          15 :     axial_extent = +lowest_axial_surf & -highest_axial_surf;
     699             :   }
     700             : 
     701             :   // Define all duct boundaries and create the appropriate cell to fill each duct region.
     702             :   // Add these cells to a separate universe
     703          30 :   std::vector<Real> duct_boundaries = _duct_sizes;
     704          30 :   duct_boundaries.push_back(getReactorParam<Real>(RGMB::assembly_pitch) / 2.);
     705          30 :   CSG::CSGRegion inner_region;
     706          60 :   const auto & assembly_univ = csg_obj->createUniverse(name() + "_univ");
     707          70 :   for (const auto i : index_range(duct_boundaries))
     708             :   {
     709          40 :     bool is_last_radial_region = i == duct_boundaries.size() - 1;
     710          40 :     if (i == 0)
     711             :     {
     712             :       // For innermost duct region, we create a lattice cell as the fill
     713          30 :       const auto pin_pitch = getMeshProperty<Real>(RGMB::pitch, _inputs[0]);
     714          30 :       auto & assembly_lattice = createAssemblyLattice(pin_pitch, universe_pattern, *csg_obj);
     715          30 :       setAssemblyLatticeOuter(assembly_lattice, surfaces_by_axial_region, *csg_obj);
     716             : 
     717             :       // Define lattice cell
     718          30 :       std::string lat_cell_name = name() + "_lattice_cell";
     719          30 :       if (!is_last_radial_region)
     720             :       {
     721             :         const auto & duct_surfaces =
     722          10 :             getOuterRadialSurfacesForUnitCell(i, duct_boundaries[i], *csg_obj);
     723          10 :         inner_region = CSGUtils::getInnerRegion(duct_surfaces, Point(0, 0, 0));
     724          10 :       }
     725          30 :       if (_geom_type == "Hex")
     726             :       {
     727             :         // For hex lattices, apply a 90 degree rotation to the lattice to match orientation
     728             :         // of FEM mesh
     729          10 :         csg_obj->applyAxisRotation(assembly_lattice, CSG::RotationAxisType::Z, 90.);
     730             :       }
     731          30 :       csg_obj->createCell(lat_cell_name, assembly_lattice, inner_region, &assembly_univ);
     732             :     }
     733             :     else
     734             :     {
     735             :       // Update ducted region
     736          10 :       CSG::CSGRegion radial_region = ~inner_region;
     737          10 :       if (!is_last_radial_region)
     738             :       {
     739             :         const auto & duct_surfaces =
     740           0 :             getOuterRadialSurfacesForUnitCell(i, duct_boundaries[i], *csg_obj);
     741           0 :         inner_region = CSGUtils::getInnerRegion(duct_surfaces, Point(0, 0, 0));
     742           0 :         radial_region &= inner_region;
     743           0 :       }
     744             : 
     745             :       // Define cell fill of lattice
     746          20 :       std::string duct_cell_name = name() + "_duct_cell_radial_" + std::to_string(i - 1);
     747          10 :       if (!extruded_assembly)
     748             :       {
     749             :         // In 2D, the cell fill will be a material
     750          10 :         std::string region_name = "rgmb_region_" + std::to_string(_duct_region_ids[0][i - 1]);
     751           5 :         csg_obj->createCell(duct_cell_name, region_name, radial_region, &assembly_univ);
     752             :       }
     753             :       else
     754             :       {
     755             :         // In 3D, the cell fill will be a universe
     756          10 :         const auto & name_prefix = name() + "_duct_radial_" + std::to_string(i - 1);
     757             :         std::vector<subdomain_id_type> duct_region_ids;
     758          15 :         for (const auto j : make_range(_duct_region_ids.size()))
     759          10 :           duct_region_ids.push_back(_duct_region_ids[j][i - 1]);
     760           5 :         auto & fill_univ = createDuctFillUniverse(
     761             :             name_prefix, surfaces_by_axial_region, duct_region_ids, *csg_obj);
     762           5 :         csg_obj->createCell(duct_cell_name, fill_univ, radial_region, &assembly_univ);
     763           5 :       }
     764          10 :     }
     765             :   }
     766             : 
     767             :   // Create new cell to bound universe based on assembly outer boundaries, and add this cell
     768             :   // to the root universe
     769             :   const auto & duct_surfaces = getOuterRadialSurfacesForUnitCell(
     770          30 :       duct_boundaries.size() - 1, duct_boundaries.back(), *csg_obj);
     771          30 :   auto assembly_region = CSGUtils::getInnerRegion(duct_surfaces, Point(0, 0, 0));
     772          30 :   if (extruded_assembly)
     773          15 :     assembly_region &= axial_extent;
     774          30 :   csg_obj->createCell(name() + "_root_cell", assembly_univ, assembly_region);
     775             : 
     776          30 :   return csg_obj;
     777          30 : }
     778             : 
     779             : const CSG::CSGLattice &
     780          30 : AssemblyMeshGenerator::createAssemblyLattice(
     781             :     const Real pitch,
     782             :     const std::vector<std::vector<std::reference_wrapper<const CSG::CSGUniverse>>> pattern,
     783             :     CSG::CSGBase & csg_obj)
     784             : {
     785             :   // Create lattice based on whether it is hexagonal or Cartesian
     786          30 :   std::string lat_name = name() + "_lattice";
     787          30 :   std::unique_ptr<CSG::CSGLattice> lat_ptr;
     788          30 :   if (_geom_type == "Square")
     789          20 :     lat_ptr = std::make_unique<CSG::CSGCartesianLattice>(lat_name, pitch, pattern);
     790             :   else // _geom_type == "Hex"
     791          10 :     lat_ptr = std::make_unique<CSG::CSGHexagonalLattice>(lat_name, pitch, pattern);
     792             : 
     793          30 :   auto & assembly_lattice = csg_obj.addLattice(std::move(lat_ptr));
     794          30 :   return assembly_lattice;
     795          30 : }
     796             : 
     797             : void
     798          30 : AssemblyMeshGenerator::setAssemblyLatticeOuter(
     799             :     const CSG::CSGLattice & assembly_lattice,
     800             :     const std::vector<std::reference_wrapper<const CSG::CSGSurface>> & surfaces_by_axial_region,
     801             :     CSG::CSGBase & csg_obj)
     802             : {
     803          30 :   if (_background_region_id.size() == 0)
     804             :     // Cartesian lattices may not have a background region. In this case, the outer is left as void
     805             :     return;
     806             : 
     807             :   // Define outer fill of lattice
     808          20 :   if (_mesh_dimensions == 2)
     809             :   {
     810             :     // In 2D, the outer fill will be a material fill
     811          10 :     std::string region_name = "rgmb_region_" + std::to_string(_background_region_id[0]);
     812          10 :     csg_obj.setLatticeOuter(assembly_lattice, region_name);
     813             :   }
     814             :   else // _mesh_dimension == 3
     815             :   {
     816             :     // In 3D, we define the outer fill as a universe
     817          10 :     auto & outer_univ = createDuctFillUniverse(
     818          10 :         name() + "_lattice_outer", surfaces_by_axial_region, _background_region_id, csg_obj);
     819          10 :     csg_obj.setLatticeOuter(assembly_lattice, outer_univ);
     820             :   }
     821             : }
     822             : 
     823             : const CSG::CSGUniverse &
     824          15 : AssemblyMeshGenerator::createDuctFillUniverse(
     825             :     const std::string & name_prefix,
     826             :     const std::vector<std::reference_wrapper<const CSG::CSGSurface>> & surfaces_by_axial_region,
     827             :     const std::vector<subdomain_id_type> & region_ids,
     828             :     CSG::CSGBase & csg_obj)
     829             : {
     830             :   mooseAssert(surfaces_by_axial_region.size() - region_ids.size() == 1,
     831             :               "Incorrect length of axial data vectors");
     832          30 :   auto & fill_univ = csg_obj.createUniverse(name_prefix + "_univ");
     833          45 :   for (const auto i : make_range(surfaces_by_axial_region.size() - 1))
     834             :   {
     835          30 :     CSG::CSGRegion axial_region;
     836             :     const auto & lower_surf = surfaces_by_axial_region[i].get();
     837          30 :     if (lower_surf != surfaces_by_axial_region.front())
     838          15 :       axial_region = +lower_surf;
     839          30 :     const auto & upper_surf = surfaces_by_axial_region[i + 1].get();
     840          30 :     if (upper_surf != surfaces_by_axial_region.back())
     841             :     {
     842          15 :       if (axial_region.getRegionType() == CSG::CSGRegion::RegionType::EMPTY)
     843          15 :         axial_region = -upper_surf;
     844             :       else
     845           0 :         axial_region &= -upper_surf;
     846             :     }
     847          60 :     auto cell_name = name_prefix + "_axial_" + std::to_string(i);
     848          30 :     const auto mat_name = "rgmb_region_" + std::to_string(region_ids[i]);
     849          30 :     csg_obj.createCell(cell_name, mat_name, axial_region, &fill_univ);
     850          30 :   }
     851             : 
     852          15 :   return fill_univ;
     853             : }

Generated by: LCOV version 1.14