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 "PinMeshGenerator.h"
11 :
12 : #include "ReactorGeometryMeshBuilderBase.h"
13 : #include <cmath>
14 : #include "MooseApp.h"
15 : #include "MooseMeshUtils.h"
16 : #include "Factory.h"
17 : #include "CSGZCylinder.h"
18 : #include "CSGPlane.h"
19 : #include "CSGRegion.h"
20 : #include "CSGUtils.h"
21 : #include "libmesh/elem.h"
22 :
23 : registerMooseObject("ReactorApp", PinMeshGenerator);
24 :
25 : InputParameters
26 2036 : PinMeshGenerator::validParams()
27 : {
28 2036 : auto params = ReactorGeometryMeshBuilderBase::validParams();
29 :
30 4072 : params.addRequiredParam<MeshGeneratorName>(
31 : "reactor_params",
32 : "The ReactorMeshParams MeshGenerator that is the basis for this component conformal mesh.");
33 :
34 4072 : params.addRequiredParam<subdomain_id_type>("pin_type",
35 : "The integer ID for this pin type definition");
36 :
37 4072 : params.addRequiredRangeCheckedParam<Real>(
38 : "pitch", "pitch>0.0", "The pitch for the outermost boundary polygon");
39 :
40 4072 : params.addRangeCheckedParam<unsigned int>(
41 : "num_sectors", "num_sectors>0", "Number of azimuthal sectors in each quadrant");
42 :
43 4072 : params.addRangeCheckedParam<std::vector<Real>>(
44 : "ring_radii",
45 : "ring_radii>0.0",
46 : "Radii of major concentric circles of the pin. If unspecified, no pin is present.");
47 :
48 4072 : params.addRangeCheckedParam<std::vector<Real>>(
49 : "duct_halfpitch",
50 : "duct_halfpitch>0.0",
51 : "Apothem of the ducts. If unspecified, no duct is present.");
52 :
53 6108 : params.addRangeCheckedParam<std::vector<unsigned int>>(
54 : "mesh_intervals",
55 4072 : std::vector<unsigned int>{1},
56 : "mesh_intervals>0",
57 : "The number of meshing intervals for each region starting at the center. Parameter should be "
58 : "size:"
59 : "((length(ring_radii) + length(duct_halfpitch) + 1");
60 :
61 4072 : params.addParam<std::vector<std::vector<std::string>>>(
62 : "block_names",
63 : "Block names for each radial and axial zone. "
64 : "Inner indexing is radial zones (pin/background/duct), outer indexing is axial");
65 :
66 4072 : params.addParam<std::vector<std::vector<subdomain_id_type>>>(
67 : "region_ids",
68 : "IDs for each radial and axial zone for assignment of region_id extra element "
69 : "id. "
70 : "Inner indexing is radial zones (pin/background/duct), outer indexing is axial");
71 :
72 4072 : params.addParam<bool>("extrude",
73 4072 : false,
74 : "Determines if this is the final step in the geometry construction"
75 : " and extrudes the 2D geometry to 3D. If this is true then this mesh "
76 : "cannot be used in further mesh building in the Reactor workflow");
77 4072 : params.addParam<bool>(
78 4072 : "homogenized", false, "Determines whether homogenized pin mesh should be generated");
79 4072 : params.addParam<bool>(
80 4072 : "use_as_assembly", false, "Determines whether pin mesh should be used as an assembly mesh");
81 :
82 4072 : params.addParam<bool>(
83 4072 : "quad_center_elements", true, "Whether the center elements are quad or triangular.");
84 4072 : params.addParamNamesToGroup("region_ids pin_type", "ID assigment");
85 4072 : params.addParamNamesToGroup(
86 : "mesh_intervals ring_radii num_sectors pin_type homogenized use_as_assembly",
87 : "Pin specifications");
88 4072 : params.addParamNamesToGroup("mesh_intervals duct_halfpitch num_sectors", "Duct specifications");
89 :
90 2036 : params.addClassDescription("This PinMeshGenerator object is designed to generate pin-like "
91 : "structures, with IDs, from a reactor geometry. "
92 : "Whether it be a square or hexagonal pin, they are divided into three "
93 : "substructures - the innermost "
94 : "radial pin regions, the single bridging background region, and the "
95 : "square or hexagonal ducts regions.");
96 :
97 : // Declare that this generator has a generateCSG method
98 2036 : MeshGenerator::setHasGenerateCSG(params);
99 :
100 2036 : return params;
101 0 : }
102 :
103 1015 : PinMeshGenerator::PinMeshGenerator(const InputParameters & parameters)
104 : : ReactorGeometryMeshBuilderBase(parameters),
105 1015 : _pin_type(getParam<subdomain_id_type>("pin_type")),
106 2030 : _pitch(getParam<Real>("pitch")),
107 3854 : _num_sectors(isParamValid("num_sectors") ? getParam<unsigned int>("num_sectors") : 0),
108 3687 : _ring_radii(isParamValid("ring_radii") ? getParam<std::vector<Real>>("ring_radii")
109 : : std::vector<Real>()),
110 3352 : _duct_halfpitch(isParamValid("duct_halfpitch") ? getParam<std::vector<Real>>("duct_halfpitch")
111 : : std::vector<Real>()),
112 2030 : _intervals(getParam<std::vector<unsigned int>>("mesh_intervals")),
113 4060 : _region_ids(isParamValid("region_ids")
114 1015 : ? getParam<std::vector<std::vector<subdomain_id_type>>>("region_ids")
115 : : std::vector<std::vector<subdomain_id_type>>()),
116 2030 : _extrude(getParam<bool>("extrude")),
117 2030 : _quad_center(getParam<bool>("quad_center_elements")),
118 2030 : _homogenized(getParam<bool>("homogenized")),
119 3045 : _is_assembly(getParam<bool>("use_as_assembly"))
120 : {
121 : // Initialize ReactorMeshParams object
122 3045 : initializeReactorMeshParams(getParam<MeshGeneratorName>("reactor_params"));
123 :
124 1015 : _mesh_dimensions = getReactorParam<unsigned int>(RGMB::mesh_dimensions);
125 1015 : _mesh_geometry = getReactorParam<std::string>(RGMB::mesh_geometry);
126 :
127 1015 : if (_is_assembly)
128 : {
129 160 : auto assembly_pitch = getReactorParam<Real>(RGMB::assembly_pitch);
130 160 : if (assembly_pitch != _pitch)
131 0 : mooseError("Pitch defined in PinMeshGenerator must match assembly_pitch defined in "
132 : "ReactorMeshParams if use_as_assembly is set to true");
133 : }
134 :
135 1015 : if (_extrude && _mesh_dimensions != 3)
136 0 : paramError("extrude",
137 : "In order to extrude this mesh, ReactorMeshParams/dim needs to be set to 3\n");
138 2106 : if (_extrude && (!hasReactorParam<boundary_id_type>(RGMB::top_boundary_id) ||
139 1091 : !hasReactorParam<boundary_id_type>(RGMB::bottom_boundary_id)))
140 0 : mooseError("Both top_boundary_id and bottom_boundary_id must be provided in ReactorMeshParams "
141 : "if using extruded geometry");
142 :
143 1015 : if (_homogenized)
144 : {
145 103 : if (_mesh_geometry == "Square")
146 0 : mooseError("Homogenization in PinMeshGenerator is only supported for hexagonal geometries");
147 : const std::vector<std::string> disallowed_parameters = {
148 103 : "num_sectors", "ring_radii", "duct_halfpitch", "mesh_intervals"};
149 515 : for (const auto & parameter : disallowed_parameters)
150 412 : if (parameters.isParamSetByUser(parameter))
151 0 : paramError(parameter,
152 0 : "Parameter " + parameter + " should not be defined for a homogenized pin mesh");
153 103 : }
154 : else
155 : {
156 912 : if (_num_sectors == 0)
157 0 : mooseError(
158 : "Number of sectors must be assigned with parameter num_sectors for non-homogenized pins");
159 912 : if (_intervals.size() != (_ring_radii.size() + _duct_halfpitch.size() + 1))
160 0 : mooseError(
161 : "The number of mesh intervals must be equal to the number of annular regions + the "
162 : "number of duct regions + 1"
163 : " for the region between the rings and ducts\n");
164 : }
165 :
166 2030 : if (isParamValid("region_ids"))
167 : {
168 : unsigned int n_axial_levels =
169 1015 : (_mesh_dimensions == 3)
170 : ? getReactorParam<std::vector<unsigned int>>(RGMB::axial_mesh_intervals).size()
171 1840 : : 1;
172 1015 : if (_region_ids.size() != n_axial_levels)
173 0 : mooseError("The size of region IDs must be equal to the number of axial levels as defined in "
174 : "the ReactorMeshParams object");
175 1015 : if (_region_ids[0].size() != (_ring_radii.size() + _duct_halfpitch.size() + 1))
176 0 : mooseError("The number of region IDs given needs to be one more than the number of "
177 : "ring_radii + the number of duct_radii\n");
178 : }
179 : else
180 : {
181 0 : mooseError("Region IDs must be assigned with parameter region_ids");
182 : }
183 2030 : if (isParamValid("block_names"))
184 : {
185 173 : if (getReactorParam<bool>(RGMB::region_id_as_block_name))
186 2 : paramError("block_names",
187 : "If ReactorMeshParams/region_id_as_block_name is set, block_names should not be "
188 : "specified in PinMeshGenerator");
189 171 : _has_block_names = true;
190 513 : _block_names = getParam<std::vector<std::vector<std::string>>>("block_names");
191 171 : if (_region_ids.size() != _block_names.size())
192 0 : mooseError("The size of block_names must match the size of region_ids");
193 513 : for (const auto i : index_range(_region_ids))
194 342 : if (_region_ids[i].size() != _block_names[i].size())
195 0 : mooseError("The size of block_names must match the size of region_ids");
196 : }
197 : else
198 842 : _has_block_names = false;
199 :
200 1013 : const auto use_flexible_stitching = getReactorParam<bool>(RGMB::flexible_assembly_stitching);
201 : std::string build_mesh_name;
202 :
203 : // No subgenerators will be called if option to bypass mesh generators is enabled
204 1013 : if (!getReactorParam<bool>(RGMB::bypass_meshgen))
205 : {
206 854 : if (_homogenized)
207 : {
208 : // If flexible assembly stitching is invoked and this is a homogeneous assembly mesh, do not
209 : // call mesh subgenerators here. The homogeneous assembly mesh should be created entirely in
210 : // generateFlexibleAssemblyBoundaries()
211 84 : bool skip_assembly_generation = _is_assembly && use_flexible_stitching;
212 :
213 84 : auto params = _app.getFactory().getValidParams("SimpleHexagonGenerator");
214 :
215 84 : params.set<Real>("hexagon_size") = _pitch / 2.0;
216 84 : params.set<boundary_id_type>("external_boundary_id") =
217 84 : RGMB::PIN_BOUNDARY_ID_START + _pin_type;
218 : const auto boundary_name =
219 84 : (_is_assembly ? RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX : RGMB::PIN_BOUNDARY_NAME_PREFIX) +
220 252 : std::to_string(_pin_type);
221 168 : params.set<BoundaryName>("external_boundary_name") = boundary_name;
222 84 : params.set<std::vector<subdomain_id_type>>("block_id") = {
223 216 : _quad_center ? RGMB::PIN_BLOCK_ID_START : RGMB::PIN_BLOCK_ID_TRI};
224 216 : params.set<MooseEnum>("element_type") = _quad_center ? "QUAD" : "TRI";
225 84 : auto block_name = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R0";
226 84 : if (!_quad_center)
227 : block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
228 252 : params.set<std::vector<SubdomainName>>("block_name") = {block_name};
229 :
230 84 : if (!skip_assembly_generation)
231 : {
232 54 : build_mesh_name = name() + "_2D";
233 108 : addMeshSubgenerator("SimpleHexagonGenerator", build_mesh_name, params);
234 : }
235 84 : }
236 : else
237 : {
238 : // Define all id variables used in the pin
239 : std::vector<unsigned int> ring_intervals;
240 : std::vector<subdomain_id_type> ring_blk_ids;
241 : std::vector<SubdomainName> ring_blk_names;
242 : unsigned int background_intervals = 1;
243 : std::vector<subdomain_id_type> background_blk_ids;
244 : std::vector<SubdomainName> background_blk_names;
245 : std::vector<unsigned int> duct_intervals;
246 : std::vector<subdomain_id_type> duct_blk_ids;
247 : std::vector<SubdomainName> duct_blk_names;
248 :
249 2363 : for (const auto i : index_range(_intervals))
250 : {
251 : const auto block_name =
252 4779 : RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R" + std::to_string(i);
253 1593 : const auto block_id = RGMB::PIN_BLOCK_ID_START + i;
254 :
255 1593 : if (i < _ring_radii.size())
256 : {
257 565 : ring_intervals.push_back(_intervals[i]);
258 565 : ring_blk_ids.push_back(block_id);
259 565 : ring_blk_names.push_back(block_name);
260 : }
261 1028 : else if (i > _ring_radii.size())
262 : {
263 258 : duct_intervals.push_back(_intervals[i]);
264 258 : duct_blk_ids.push_back(block_id);
265 258 : duct_blk_names.push_back(block_name);
266 : }
267 : else
268 : {
269 770 : background_intervals = _intervals[i];
270 770 : background_blk_ids.push_back(block_id);
271 770 : background_blk_names.push_back(block_name);
272 : }
273 : }
274 770 : if (ring_intervals.size() > 0)
275 : {
276 529 : if (ring_intervals.front() != 1)
277 : {
278 : // If quad center elements, copy element at beginning of block names and
279 : // block ids. Otherwise add RGMB::TRI_BLOCK_NAME_SUFFIX to block names and generate new
280 : // block id
281 18 : if (_quad_center)
282 : {
283 9 : ring_blk_ids.insert(ring_blk_ids.begin(), ring_blk_ids.front());
284 9 : ring_blk_names.insert(ring_blk_names.begin(), ring_blk_names.front());
285 : }
286 : else
287 : {
288 9 : const auto block_name = ring_blk_names.front() + RGMB::TRI_BLOCK_NAME_SUFFIX;
289 9 : const auto block_id = RGMB::PIN_BLOCK_ID_TRI;
290 9 : ring_blk_ids.insert(ring_blk_ids.begin(), block_id);
291 9 : ring_blk_names.insert(ring_blk_names.begin(), block_name);
292 : }
293 : }
294 : // Add RGMB::TRI_BLOCK_NAME_SUFFIX if only one radial region and tri center elements
295 511 : else if (!_quad_center)
296 : {
297 276 : ring_blk_ids[0] = RGMB::PIN_BLOCK_ID_TRI;
298 276 : ring_blk_names[0] += RGMB::TRI_BLOCK_NAME_SUFFIX;
299 : }
300 : }
301 : else
302 : {
303 241 : if (background_intervals > 1)
304 : {
305 : // If quad center elements, copy element at beginning of block names and
306 : // block ids. Otherwise add RGMB::TRI_BLOCK_NAME_SUFFIX to block names and generate new
307 : // block id
308 168 : if (_quad_center)
309 : {
310 159 : background_blk_ids.insert(background_blk_ids.begin(), background_blk_ids.front());
311 159 : background_blk_names.insert(background_blk_names.begin(), background_blk_names.front());
312 : }
313 : else
314 : {
315 9 : const auto block_name = background_blk_names.front() + RGMB::TRI_BLOCK_NAME_SUFFIX;
316 9 : const auto block_id = RGMB::PIN_BLOCK_ID_TRI;
317 9 : background_blk_ids.insert(background_blk_ids.begin(), block_id);
318 9 : background_blk_names.insert(background_blk_names.begin(), block_name);
319 : }
320 : }
321 : // Add RGMB::TRI_BLOCK_NAME_SUFFIX if only one background region and tri center elements
322 : // and no ring regions
323 73 : else if (!_quad_center)
324 : {
325 64 : background_blk_ids[0] = RGMB::PIN_BLOCK_ID_TRI;
326 64 : background_blk_names[0] += RGMB::TRI_BLOCK_NAME_SUFFIX;
327 : }
328 : }
329 :
330 : // If flexible assembly stitching is invoked and this is an assembly mesh with only a
331 : // background region, do not call mesh subgenerators here. This assembly mesh should be
332 : // created entirely in generateFlexibleAssemblyBoundaries()
333 : bool skip_assembly_generation =
334 770 : _is_assembly && use_flexible_stitching && _intervals.size() == 1;
335 :
336 : if (!skip_assembly_generation)
337 : {
338 : // Generate Cartesian/hex pin using PolygonConcentricCircleMeshGenerator
339 : {
340 : // Get and assign parameters for the main geometry feature of the Pin
341 : // which is created with a PolygonConcentricCircleMeshGenerator subgenerator
342 760 : auto params = _app.getFactory().getValidParams("PolygonConcentricCircleMeshGenerator");
343 760 : params.set<bool>("preserve_volumes") = true;
344 760 : params.set<bool>("quad_center_elements") = _quad_center;
345 1520 : params.set<MooseEnum>("polygon_size_style") = "apothem";
346 760 : params.set<Real>("polygon_size") = _pitch / 2.0;
347 760 : params.set<boundary_id_type>("external_boundary_id") =
348 760 : RGMB::PIN_BOUNDARY_ID_START + _pin_type;
349 760 : const auto boundary_name = (_is_assembly ? RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX
350 : : RGMB::PIN_BOUNDARY_NAME_PREFIX) +
351 2280 : std::to_string(_pin_type);
352 1520 : params.set<BoundaryName>("external_boundary_name") = boundary_name;
353 760 : bool flat_side_up = (_mesh_geometry == "Square");
354 760 : params.set<bool>("flat_side_up") = flat_side_up;
355 760 : params.set<bool>("create_outward_interface_boundaries") = false;
356 :
357 760 : const auto num_sides = (_mesh_geometry == "Square") ? 4 : 6;
358 760 : params.set<unsigned int>("num_sides") = num_sides;
359 760 : params.set<std::vector<unsigned int>>("num_sectors_per_side") =
360 1520 : std::vector<unsigned int>(num_sides, _num_sectors);
361 :
362 760 : if (ring_intervals.size() > 0)
363 : {
364 529 : params.set<std::vector<Real>>("ring_radii") = _ring_radii;
365 529 : params.set<std::vector<subdomain_id_type>>("ring_block_ids") = ring_blk_ids;
366 529 : params.set<std::vector<SubdomainName>>("ring_block_names") = ring_blk_names;
367 1058 : params.set<std::vector<unsigned int>>("ring_intervals") = ring_intervals;
368 : }
369 :
370 760 : params.set<std::vector<subdomain_id_type>>("background_block_ids") = background_blk_ids;
371 760 : params.set<std::vector<SubdomainName>>("background_block_names") = background_blk_names;
372 760 : params.set<unsigned int>("background_intervals") = background_intervals;
373 :
374 760 : if (duct_intervals.size() > 0)
375 : {
376 516 : params.set<MooseEnum>("duct_sizes_style") = "apothem";
377 258 : params.set<std::vector<Real>>("duct_sizes") = _duct_halfpitch;
378 258 : params.set<std::vector<subdomain_id_type>>("duct_block_ids") = duct_blk_ids;
379 258 : params.set<std::vector<SubdomainName>>("duct_block_names") = duct_blk_names;
380 516 : params.set<std::vector<unsigned int>>("duct_intervals") = duct_intervals;
381 : }
382 :
383 1520 : addMeshSubgenerator("PolygonConcentricCircleMeshGenerator", name() + "_2D", params);
384 760 : }
385 :
386 : // Remove extra sidesets created by PolygonConcentricCircleMeshGenerator
387 : {
388 1520 : auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
389 :
390 2280 : params.set<MeshGeneratorName>("input") = name() + "_2D";
391 :
392 760 : auto num_sides = (_mesh_geometry == "Square") ? 4 : 6;
393 : std::vector<BoundaryName> boundaries_to_delete = {};
394 4680 : for (const auto i : make_range(num_sides))
395 11760 : boundaries_to_delete.insert(boundaries_to_delete.end(),
396 11760 : {std::to_string(10001 + i), std::to_string(15001 + i)});
397 1520 : params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
398 :
399 760 : build_mesh_name = name() + "_delbds";
400 1520 : addMeshSubgenerator("BoundaryDeletionGenerator", build_mesh_name, params);
401 760 : }
402 : }
403 770 : }
404 :
405 : // For pin acting as assembly, modify outermost mesh interval to enable flexible assembly
406 : // stitching
407 854 : if (_is_assembly && use_flexible_stitching)
408 : {
409 55 : generateFlexibleAssemblyBoundaries();
410 110 : build_mesh_name = name() + "_fpg_delbds";
411 : }
412 :
413 : // Pass mesh meta-data defined in subgenerator constructor to this MeshGenerator
414 1708 : if (hasMeshProperty<Real>("pitch_meta", name() + "_2D"))
415 1628 : copyMeshProperty<Real>("pitch_meta", name() + "_2D");
416 1708 : if (hasMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta", name() + "_2D"))
417 1628 : copyMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta", name() + "_2D");
418 1708 : if (hasMeshProperty<Real>("max_radius_meta", name() + "_2D"))
419 1628 : copyMeshProperty<Real>("max_radius_meta", name() + "_2D");
420 1708 : if (hasMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D"))
421 1628 : copyMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D");
422 1708 : if (hasMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D"))
423 1628 : copyMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D");
424 :
425 854 : if (_is_assembly)
426 272 : declareMeshProperty("pattern_pitch_meta", getReactorParam<Real>(RGMB::assembly_pitch));
427 1436 : else if (hasMeshProperty<Real>("pattern_pitch_meta", name() + "_2D"))
428 1382 : copyMeshProperty<Real>("pattern_pitch_meta", name() + "_2D");
429 854 : declareMeshProperty("is_control_drum_meta", false);
430 :
431 854 : if (_extrude)
432 36 : build_mesh_name = callExtrusionMeshSubgenerators(build_mesh_name);
433 :
434 : // Store final mesh subgenerator
435 854 : _build_mesh = &getMeshByName(build_mesh_name);
436 : }
437 :
438 1013 : generateMetadata();
439 1013 : }
440 :
441 : void
442 55 : PinMeshGenerator::generateFlexibleAssemblyBoundaries()
443 : {
444 : SubdomainName outermost_block_name;
445 : bool has_single_mesh_interval;
446 :
447 : // Assemblies that invoke this method are either homogenized or have a single pin. First check if
448 : // the assembly only has a single region. Otherwise, determine the outermost region for deletion
449 55 : if (_homogenized || (_intervals.size() == 1))
450 : {
451 80 : outermost_block_name = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R0";
452 : has_single_mesh_interval = true;
453 : }
454 : else
455 : {
456 45 : outermost_block_name = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R" +
457 15 : std::to_string(_intervals.size() - 1);
458 : has_single_mesh_interval = false;
459 :
460 : // Invoke BlockDeletionGenerator to delete outermost mesh interval of assembly
461 30 : auto params = _app.getFactory().getValidParams("BlockDeletionGenerator");
462 :
463 45 : params.set<std::vector<SubdomainName>>("block") = {outermost_block_name};
464 45 : params.set<MeshGeneratorName>("input") = _homogenized ? name() + "_2D" : name() + "_delbds";
465 :
466 30 : addMeshSubgenerator("BlockDeletionGenerator", name() + "_del_outer", params);
467 15 : }
468 :
469 : {
470 : // Invoke FlexiblePatternGenerator to triangulate deleted mesh interval
471 55 : auto params = _app.getFactory().getValidParams("FlexiblePatternGenerator");
472 :
473 55 : if (has_single_mesh_interval)
474 80 : params.set<std::vector<MeshGeneratorName>>("inputs") = {};
475 : else
476 : {
477 75 : params.set<std::vector<MeshGeneratorName>>("inputs") = {name() + "_del_outer"};
478 15 : params.set<std::vector<libMesh::Point>>("extra_positions") = {libMesh::Point(0, 0, 0)};
479 30 : params.set<std::vector<unsigned int>>("extra_positions_mg_indices") = {0};
480 : }
481 55 : params.set<bool>("use_auto_area_func") = true;
482 55 : params.set<bool>("verify_holes") = false;
483 125 : params.set<MooseEnum>("boundary_type") = (_mesh_geometry == "Hex") ? "HEXAGON" : "CARTESIAN";
484 55 : params.set<unsigned int>("boundary_sectors") =
485 55 : getReactorParam<unsigned int>(RGMB::num_sectors_flexible_stitching);
486 55 : params.set<Real>("boundary_size") = getReactorParam<Real>(RGMB::assembly_pitch);
487 55 : params.set<boundary_id_type>("external_boundary_id") = RGMB::PIN_BOUNDARY_ID_START + _pin_type;
488 110 : params.set<BoundaryName>("external_boundary_name") =
489 55 : RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX + std::to_string(_pin_type);
490 110 : params.set<SubdomainName>("background_subdomain_name") =
491 55 : outermost_block_name + RGMB::TRI_BLOCK_NAME_SUFFIX;
492 55 : params.set<unsigned short>("background_subdomain_id") = RGMB::PIN_BLOCK_ID_TRI_FLEXIBLE;
493 :
494 110 : addMeshSubgenerator("FlexiblePatternGenerator", name() + "_fpg", params);
495 55 : }
496 : {
497 : // Delete extra boundary created by FlexiblePatternGenerator
498 110 : auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
499 :
500 220 : params.set<MeshGeneratorName>("input") = name() + "_fpg";
501 : std::vector<BoundaryName> boundaries_to_delete = {};
502 55 : if (!has_single_mesh_interval)
503 30 : boundaries_to_delete.push_back(std::to_string(1));
504 55 : params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
505 :
506 110 : addMeshSubgenerator("BoundaryDeletionGenerator", name() + "_fpg_delbds", params);
507 55 : }
508 55 : }
509 :
510 : void
511 1013 : PinMeshGenerator::generateMetadata()
512 : {
513 : // Store pin region ids and block names for id swap after extrusion if needed
514 : // by future mesh generators
515 : std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>> region_id_map{
516 2026 : {_pin_type, _region_ids}};
517 :
518 : // Declare mesh properties that need to be moved up to the assembly level
519 1013 : if (_is_assembly)
520 : {
521 160 : declareMeshProperty(RGMB::assembly_type, _pin_type);
522 160 : declareMeshProperty(RGMB::background_block_name, std::vector<std::string>());
523 160 : declareMeshProperty(RGMB::duct_block_names, std::vector<std::vector<std::string>>());
524 160 : declareMeshProperty(RGMB::is_single_pin, _is_assembly);
525 160 : declareMeshProperty(RGMB::is_control_drum, false);
526 : // Following metadata is only relevant if an output mesh is generated by RGMB
527 : // because it pertains to region & block ids of elements in the output mesh
528 160 : if (!getReactorParam<bool>(RGMB::bypass_meshgen))
529 : {
530 : std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>> pin_region_id_map;
531 136 : pin_region_id_map.insert(
532 136 : std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
533 136 : region_id_map.begin()->first, region_id_map.begin()->second));
534 : declareMeshProperty(RGMB::pin_region_id_map, pin_region_id_map);
535 : std::map<subdomain_id_type, std::vector<std::vector<std::string>>> pin_block_name_map;
536 272 : pin_block_name_map.insert(std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(
537 136 : _pin_type, _block_names));
538 : declareMeshProperty(RGMB::pin_block_name_map, pin_block_name_map);
539 : }
540 : }
541 : // Declare mesh properties that are only relevant to pin meshes
542 : else
543 : {
544 853 : declareMeshProperty(RGMB::pin_type, _pin_type);
545 : declareMeshProperty(RGMB::pin_region_ids, region_id_map);
546 853 : declareMeshProperty(RGMB::pin_block_names, _block_names);
547 : }
548 :
549 : // Set metadata to describe pin attributes
550 1013 : declareMeshProperty(RGMB::pitch, _pitch);
551 1013 : declareMeshProperty(RGMB::is_homogenized, _homogenized);
552 1013 : declareMeshProperty(RGMB::ring_radii, _ring_radii);
553 1013 : declareMeshProperty(RGMB::duct_halfpitches, _duct_halfpitch);
554 1013 : declareMeshProperty(RGMB::extruded, _extrude);
555 :
556 : unsigned int n_axial_levels =
557 1013 : (_mesh_dimensions == 3)
558 : ? getReactorParam<std::vector<unsigned int>>(RGMB::axial_mesh_intervals).size()
559 1838 : : 1;
560 : std::vector<std::vector<subdomain_id_type>> ring_region_ids(
561 1013 : n_axial_levels, std::vector<subdomain_id_type>(_ring_radii.size()));
562 : std::vector<std::vector<subdomain_id_type>> duct_region_ids(
563 1013 : n_axial_levels, std::vector<subdomain_id_type>(_duct_halfpitch.size()));
564 1013 : std::vector<subdomain_id_type> background_region_ids(n_axial_levels);
565 :
566 2341 : for (const auto axial_idx : make_range(n_axial_levels))
567 : {
568 2228 : for (const auto ring_idx : index_range(_ring_radii))
569 900 : ring_region_ids[axial_idx][ring_idx] = _region_ids[axial_idx][ring_idx];
570 :
571 1328 : background_region_ids[axial_idx] = _region_ids[axial_idx][_ring_radii.size()];
572 :
573 1328 : for (unsigned int duct_idx = _ring_radii.size() + 1;
574 1770 : duct_idx < _duct_halfpitch.size() + _ring_radii.size() + 1;
575 : ++duct_idx)
576 442 : duct_region_ids[axial_idx][duct_idx - _ring_radii.size() - 1] =
577 : _region_ids[axial_idx][duct_idx];
578 : }
579 :
580 : // Define mesh properties related to region ids
581 : declareMeshProperty(RGMB::ring_region_ids, ring_region_ids);
582 : declareMeshProperty(RGMB::background_region_id, background_region_ids);
583 : declareMeshProperty(RGMB::duct_region_ids, duct_region_ids);
584 2026 : }
585 :
586 : std::unique_ptr<MeshBase>
587 828 : PinMeshGenerator::generate()
588 : {
589 : // Must be called to free the ReactorMeshParams mesh
590 828 : freeReactorParamsMesh();
591 :
592 : // If bypass_mesh is true, return a null mesh. In this mode, an output mesh is not
593 : // generated and only metadata is defined on the generator, so logic related to
594 : // generation of output mesh will not be called
595 828 : if (getReactorParam<bool>(RGMB::bypass_meshgen))
596 : {
597 : auto null_mesh = nullptr;
598 : return null_mesh;
599 : }
600 :
601 : // Update metadata at this point since values for these metadata only get set by PCCMG
602 : // at generate() stage
603 1656 : if (hasMeshProperty<Real>("max_radius_meta", name() + "_2D"))
604 : {
605 788 : const auto max_radius_meta = getMeshProperty<Real>("max_radius_meta", name() + "_2D");
606 1576 : setMeshProperty("max_radius_meta", max_radius_meta);
607 : }
608 1656 : if (hasMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D"))
609 : {
610 : const auto background_intervals_meta =
611 788 : getMeshProperty<unsigned int>("background_intervals_meta", name() + "_2D");
612 1576 : setMeshProperty("background_intervals_meta", background_intervals_meta);
613 : }
614 1656 : if (hasMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D"))
615 : {
616 : const auto node_id_background_meta =
617 788 : getMeshProperty<dof_id_type>("node_id_background_meta", name() + "_2D");
618 1576 : setMeshProperty("node_id_background_meta", node_id_background_meta);
619 : }
620 :
621 : // This generate() method will be called once the subgenerators that we depend on
622 : // have been called. This is where we reassign subdomain ids/names according to what
623 : // the user has provided, and also where we set region_id, pin_type_id, and radial_id
624 : // extra element integers
625 :
626 : // Add region id, pin type id, and radial id to the mesh as element integers
627 828 : std::string region_id_name = "region_id";
628 828 : std::string pin_type_id_name = "pin_type_id";
629 828 : std::string assembly_type_id_name = "assembly_type_id";
630 828 : std::string plane_id_name = "plane_id";
631 828 : std::string radial_id_name = "radial_id";
632 : const std::string default_block_name =
633 828 : (_is_assembly ? RGMB::ASSEMBLY_BLOCK_NAME_PREFIX : RGMB::PIN_BLOCK_NAME_PREFIX) +
634 1656 : std::to_string(_pin_type);
635 :
636 828 : auto region_id_int = getElemIntegerFromMesh(*(*_build_mesh), region_id_name);
637 828 : auto radial_id_int = getElemIntegerFromMesh(*(*_build_mesh), radial_id_name);
638 828 : auto pin_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), pin_type_id_name);
639 : unsigned int plane_id_int = 0;
640 : unsigned int assembly_type_id_int = 0;
641 828 : if (_extrude)
642 36 : plane_id_int = getElemIntegerFromMesh(*(*_build_mesh), plane_id_name, true);
643 828 : if (_is_assembly)
644 272 : assembly_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), assembly_type_id_name);
645 :
646 : // Get next free block ID in mesh in case subdomain ids need to be remapped
647 828 : auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*(*(_build_mesh)));
648 : std::map<std::string, SubdomainID> rgmb_name_id_map;
649 :
650 : // Loop through all elements and set regions ids, pin type id, and radial idx.
651 : // These element integers are also used to infer the block name for the region,
652 : // and block IDs/names will be reassigned on the pin mesh if necessary
653 64208 : for (auto & elem : (*_build_mesh)->active_element_ptr_range())
654 : {
655 31276 : const auto base_block_id = elem->subdomain_id();
656 31276 : const auto base_block_name = (*_build_mesh)->subdomain_name(base_block_id);
657 :
658 : // Check if block name has correct prefix
659 62552 : std::string prefix = RGMB::PIN_BLOCK_NAME_PREFIX + std::to_string(_pin_type) + "_R";
660 31276 : if (!(base_block_name.find(prefix, 0) == 0))
661 : continue;
662 : // Radial index is integer value of substring after prefix
663 31276 : std::string radial_str = base_block_name.substr(prefix.length());
664 :
665 : // Filter out RGMB::TRI_BLOCK_NAME_SUFFIX if needed
666 31276 : const std::string suffix = RGMB::TRI_BLOCK_NAME_SUFFIX;
667 : const std::size_t found = radial_str.find(suffix);
668 31276 : if (found != std::string::npos)
669 14984 : radial_str.replace(found, suffix.length(), "");
670 31276 : const unsigned int radial_idx = std::stoi(radial_str);
671 :
672 : // Region id is inferred from z_id and radial_idx
673 31276 : dof_id_type z_id = _extrude ? elem->get_extra_integer(plane_id_int) : 0;
674 31276 : const subdomain_id_type elem_region_id = _region_ids[std::size_t(z_id)][radial_idx];
675 :
676 : // Set element integers
677 31276 : elem->set_extra_integer(region_id_int, elem_region_id);
678 31276 : elem->set_extra_integer(pin_type_id_int, _pin_type);
679 31276 : elem->set_extra_integer(radial_id_int, radial_idx);
680 31276 : if (_is_assembly)
681 12462 : elem->set_extra_integer(assembly_type_id_int, _pin_type);
682 :
683 : // Set element block name and block id
684 31276 : auto elem_block_name = default_block_name;
685 31276 : if (_has_block_names)
686 7020 : elem_block_name += "_" + _block_names[std::size_t(z_id)][radial_idx];
687 27766 : else if (getReactorParam<bool>(RGMB::region_id_as_block_name))
688 29720 : elem_block_name += "_REG" + std::to_string(elem_region_id);
689 31276 : if (elem->type() == TRI3 || elem->type() == PRISM6)
690 : elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
691 62552 : updateElementBlockNameId(
692 31276 : *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
693 828 : }
694 :
695 : // Mark mesh as not prepared, as block IDs were re-assigned in this method
696 828 : (*_build_mesh)->set_isnt_prepared();
697 :
698 : return std::move(*_build_mesh);
699 : }
700 :
701 : std::unique_ptr<CSG::CSGBase>
702 45 : PinMeshGenerator::generateCSG()
703 : {
704 : // Must be called to free the ReactorMeshParams mesh
705 45 : freeReactorParamsCSG();
706 :
707 45 : auto csg_obj = std::make_unique<CSG::CSGBase>();
708 :
709 : unsigned int radial_index = 0;
710 : std::vector<std::vector<std::reference_wrapper<const CSG::CSGSurface>>> surfaces_by_radial_region;
711 :
712 : // Add surfaces corresponding to pin rings
713 100 : for (const auto & radius : _ring_radii)
714 : {
715 110 : const auto surf_name = name() + "_radial_ring_" + std::to_string(radial_index);
716 : std::unique_ptr<CSG::CSGSurface> ring_surf_ptr =
717 55 : std::make_unique<CSG::CSGZCylinder>(surf_name, 0, 0, radius);
718 55 : const auto & ring_surf = csg_obj->addSurface(std::move(ring_surf_ptr));
719 110 : surfaces_by_radial_region.push_back({ring_surf});
720 55 : ++radial_index;
721 55 : }
722 :
723 : // Add surfaces corresponding to pin ducts
724 65 : for (const auto & duct_halfpitch : _duct_halfpitch)
725 : {
726 20 : const auto & duct_surfaces = getOuterRadialSurfaces(radial_index, duct_halfpitch, *csg_obj);
727 20 : surfaces_by_radial_region.push_back(duct_surfaces);
728 20 : ++radial_index;
729 20 : }
730 :
731 : // Add surfaces corresponding to outer pin boundary
732 45 : const auto & duct_surfaces = getOuterRadialSurfaces(radial_index, _pitch / 2., *csg_obj);
733 45 : surfaces_by_radial_region.push_back(duct_surfaces);
734 :
735 : // Define all radial regions
736 : std::vector<CSG::CSGRegion> radial_regions;
737 45 : CSG::CSGRegion inner_region, outer_region;
738 165 : for (const auto & radial_surfaces : surfaces_by_radial_region)
739 : {
740 120 : CSG::CSGRegion radial_region;
741 120 : if (inner_region.getRegionType() == CSG::CSGRegion::RegionType::EMPTY)
742 : {
743 : // We are in the innermost radial region, the radial region is inner_region
744 45 : inner_region = CSGUtils::getInnerRegion(radial_surfaces);
745 45 : radial_region = inner_region;
746 : }
747 : else
748 : {
749 : // For all other regions, the radial region is the intersection of inner_region and
750 : // outer_region
751 75 : outer_region = ~inner_region;
752 75 : inner_region = CSGUtils::getInnerRegion(radial_surfaces);
753 75 : radial_region = inner_region & outer_region;
754 : }
755 120 : radial_regions.push_back(radial_region);
756 120 : }
757 :
758 : // Define all axial surfaces and regions
759 : std::vector<CSG::CSGRegion> axial_regions;
760 : std::vector<std::reference_wrapper<const CSG::CSGSurface>> surfaces_by_axial_region;
761 45 : if (_extrude)
762 : {
763 20 : const auto axial_boundaries = getReactorParam<std::vector<Real>>(RGMB::axial_mesh_sizes);
764 20 : Real axial_level = 0.;
765 70 : for (const auto i : make_range(axial_boundaries.size() + 1))
766 : {
767 50 : axial_level += (i != 0) ? axial_boundaries[i - 1] : 0.;
768 100 : const auto surf_name = name() + "_axial_plane_" + std::to_string(i);
769 : std::unique_ptr<CSG::CSGSurface> plane_surf_ptr =
770 50 : std::make_unique<CSG::CSGPlane>(surf_name, 0, 0, 1, axial_level);
771 50 : const auto & plane_surf = csg_obj->addSurface(std::move(plane_surf_ptr));
772 50 : surfaces_by_axial_region.push_back(plane_surf);
773 50 : if (i != 0)
774 : {
775 30 : const auto & lower_surf = surfaces_by_axial_region[i - 1].get();
776 : const auto & upper_surf = surfaces_by_axial_region[i].get();
777 30 : axial_regions.push_back((+lower_surf & -upper_surf));
778 : }
779 50 : }
780 20 : }
781 :
782 : // Define all cells within pin domain
783 165 : for (const auto i : index_range(radial_regions))
784 : {
785 330 : for (const auto j : make_range(_extrude ? axial_regions.size() : 1))
786 : {
787 155 : auto cell_region = radial_regions[i];
788 310 : auto cell_name = name() + "_cell_radial_" + std::to_string(i);
789 155 : const auto region_id = _region_ids[j][i];
790 155 : const auto mat_name = "rgmb_region_" + std::to_string(region_id);
791 155 : if (_extrude)
792 : {
793 : // update name and region with axial info only if extruded
794 90 : const auto axial_region = axial_regions[j];
795 90 : cell_region &= axial_region;
796 90 : cell_name += "_axial_" + std::to_string(j);
797 90 : }
798 155 : csg_obj->createCell(cell_name, mat_name, cell_region);
799 155 : }
800 : }
801 :
802 : // Define void cell to cover region outside pin domain, where the complement of the last set
803 : // inner_region is the outermost defined zone of the pin
804 45 : const auto void_cell_name = name() + "_void_cell";
805 45 : auto void_region = ~inner_region;
806 45 : if (_extrude)
807 : {
808 : const auto & lowest_axial_surf = surfaces_by_axial_region.front().get();
809 : const auto & highest_axial_surf = surfaces_by_axial_region.back().get();
810 40 : void_region = (~inner_region & +lowest_axial_surf & -highest_axial_surf) | -lowest_axial_surf |
811 60 : +highest_axial_surf;
812 : }
813 45 : csg_obj->createCell(void_cell_name, void_region);
814 :
815 45 : return csg_obj;
816 90 : }
817 :
818 : std::vector<std::reference_wrapper<const CSG::CSGSurface>>
819 65 : PinMeshGenerator::getOuterRadialSurfaces(unsigned int radial_index,
820 : Real halfpitch,
821 : CSG::CSGBase & csg_obj)
822 : {
823 : std::vector<std::reference_wrapper<const CSG::CSGSurface>> duct_surfaces;
824 65 : auto n_surfaces = _mesh_geometry == "Square" ? 4 : 6;
825 :
826 : // Convert halfpitch to radius (distance from vertex to center)
827 65 : Real angle_offset_degrees = _mesh_geometry == "Square" ? 45 : 30;
828 65 : Real angle_offset_radians = angle_offset_degrees * (M_PI / 180.);
829 65 : const auto radius = halfpitch / std::cos(angle_offset_radians);
830 :
831 65 : Real angle_increment_radians = 360. / n_surfaces * (M_PI / 180.);
832 :
833 415 : for (const auto i : make_range(n_surfaces))
834 : {
835 : const auto surf_name =
836 1050 : name() + "_radial_duct_" + std::to_string(radial_index) + "_surf_" + std::to_string(i);
837 :
838 : // Define 3 points on the surface
839 350 : const auto current_angle = i * angle_increment_radians + angle_offset_radians;
840 350 : const auto next_angle = (i + 1) * angle_increment_radians + angle_offset_radians;
841 350 : libMesh::Point p0(radius * std::cos(current_angle), radius * std::sin(current_angle), 0.);
842 350 : libMesh::Point p1(radius * std::cos(next_angle), radius * std::sin(next_angle), 0.);
843 : libMesh::Point p2 = (p0 + p1) / 2.;
844 : // Place third point above the two others to form a vertical plane
845 350 : p2(2) = angle_offset_degrees;
846 :
847 : std::unique_ptr<CSG::CSGSurface> duct_surf_ptr =
848 350 : std::make_unique<CSG::CSGPlane>(surf_name, p0, p1, p2);
849 350 : const auto & duct_surf = csg_obj.addSurface(std::move(duct_surf_ptr));
850 350 : duct_surfaces.push_back(duct_surf);
851 350 : }
852 :
853 65 : return duct_surfaces;
854 0 : }
|