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 : }
|