https://mooseframework.inl.gov
CoreMeshGenerator.C
Go to the documentation of this file.
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 
18 
21 {
23 
24  params.addRequiredParam<std::vector<MeshGeneratorName>>(
25  "inputs",
26  "The AssemblyMeshGenerator and ControlDrumMeshGenerator objects that form the components of "
27  "the assembly.");
28 
29  params.addParam<std::string>(
30  "dummy_assembly_name",
31  "dummy",
32  "The place holder name in \"inputs\" that indicates an empty position.");
33 
34  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 assemblies in the core lattice.");
38  params.addParam<bool>(
39  "mesh_periphery", false, "Determines if the core periphery should be meshed.");
40  MooseEnum periphery_mesher("triangle quad_ring", "triangle");
41  params.addParam<MooseEnum>("periphery_generator",
42  periphery_mesher,
43  "The meshgenerator to use when meshing the core boundary.");
44 
45  // Periphery meshing interface
46  params.addRangeCheckedParam<Real>(
47  "outer_circle_radius", 0, "outer_circle_radius>=0", "Radius of outer circle boundary.");
48  params.addRangeCheckedParam<unsigned int>(
49  "outer_circle_num_segments",
50  0,
51  "outer_circle_num_segments>=0",
52  "Number of radial segments to subdivide outer circle boundary.");
53  params.addRangeCheckedParam<unsigned int>(
54  "periphery_num_layers",
55  1,
56  "periphery_num_layers>0",
57  "Number of layers to subdivide the periphery boundary.");
58  params.addParam<std::string>(
59  "periphery_block_name", RGMB::CORE_BLOCK_NAME_PREFIX, "Block name for periphery zone.");
60  params.addParam<subdomain_id_type>(
61  "periphery_region_id",
62  -1,
63  "ID for periphery zone for assignment of region_id extra element id.");
64  params.addRangeCheckedParam<Real>(
65  "desired_area",
66  0,
67  "desired_area>=0",
68  "Desired (maximum) triangle area, or 0 to skip uniform refinement");
69  params.addParam<std::string>(
70  "desired_area_func",
71  std::string(),
72  "Desired (local) triangle area as a function of x,y; omit to skip non-uniform refinement");
73  params.addParam<bool>("assign_control_drum_id",
74  true,
75  "Whether control drum id is assigned to the mesh as an extra integer.");
76  params.addParamNamesToGroup("periphery_block_name periphery_region_id outer_circle_radius "
77  "mesh_periphery periphery_generator",
78  "Periphery Meshing");
79  params.addParamNamesToGroup("outer_circle_num_segments desired_area desired_area_func",
80  "Periphery Meshing: PTMG specific");
81  params.addParamNamesToGroup("periphery_num_layers", "Periphery Meshing: PRMG specific");
82  // end meshing interface
83 
84  params.addParam<bool>("extrude",
85  false,
86  "Determines if this is the final step in the geometry construction"
87  " and extrudes the 2D geometry to 3D. If this is true then this mesh "
88  "cannot be used in further mesh building in the Reactor workflow");
89 
90  params.addClassDescription(
91  "This CoreMeshGenerator object is designed to generate a core-like "
92  "structure, with IDs, from a reactor geometry. "
93  "The core-like structure consists of a pattern of assembly-like "
94  "structures generated with AssemblyMeshGenerator and/or ControlDrumMeshGenerator "
95  "and is permitted to have \"empty\" locations. The size and spacing "
96  "of the assembly-like structures is defined, and "
97  "enforced by declaration in the ReactorMeshParams.");
98  // depletion id generation params are added
99  addDepletionIDParams(params);
100 
101  return params;
102 }
103 
105  : ReactorGeometryMeshBuilderBase(parameters),
106  _inputs(getParam<std::vector<MeshGeneratorName>>("inputs")),
107  _empty_key(getParam<std::string>("dummy_assembly_name")),
108  _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern")),
109  _extrude(getParam<bool>("extrude")),
110  _mesh_periphery(getParam<bool>("mesh_periphery")),
111  _periphery_meshgenerator(getParam<MooseEnum>("periphery_generator")),
112  _periphery_region_id(getParam<subdomain_id_type>("periphery_region_id")),
113  _outer_circle_radius(getParam<Real>("outer_circle_radius")),
114  _outer_circle_num_segments(getParam<unsigned int>("outer_circle_num_segments")),
115  _periphery_block_name(getParam<std::string>("periphery_block_name")),
116  _periphery_num_layers(getParam<unsigned int>("periphery_num_layers")),
117  _desired_area(getParam<Real>("desired_area")),
118  _desired_area_func(getParam<std::string>("desired_area_func"))
119 {
120  // This sets it so that any mesh that is input with the name _empty_key is considered a "null"
121  // mesh, that is, whenever we try to get it with the standard getMesh() API we get a nullptr
122  // mesh instead. In the specific case of the CoreMeshGenerator, we use said "null" mesh to
123  // represent an empty position
125 
126  // periphery meshing input checking
127  if (_mesh_periphery)
128  {
129  // missing required input
130  if (!parameters.isParamSetByUser("outer_circle_radius"))
131  {
132  paramError("outer_circle_radius",
133  "Outer circle radius must be specified when using periphery meshing.");
134  }
135  if (!parameters.isParamSetByUser("periphery_region_id"))
136  {
137  paramError("periphery_region_id",
138  "Periphery region id must be specified when using periphery meshing.");
139  }
140  // using PTMG-specific options with PRMG
141  if (_periphery_meshgenerator == "quad_ring")
142  {
143  if (parameters.isParamSetByUser("outer_circle_num_segments"))
144  {
145  paramError("outer_circle_num_segments",
146  "outer_circle_num_segments cannot be used with PRMG periphery mesher.");
147  }
148  if (parameters.isParamSetByUser("extra_circle_radii"))
149  {
150  paramError("extra_circle_radii",
151  "extra_circle_radii cannot be used with PRMG periphery mesher.");
152  }
153  if (parameters.isParamSetByUser("extra_circle_num_segments"))
154  {
155  paramError("extra_circle_num_segments",
156  "extra_circle_num_segments cannot be used with PRMG periphery mesher.");
157  }
158  }
159  // using PRMG-specific options with PTMG
160  else if (_periphery_meshgenerator == "triangle")
161  {
162  if (parameters.isParamSetByUser("periphery_num_layers"))
163  {
164  paramError("periphery_num_layers",
165  "periphery_num_layers cannot be used with PTMG periphery mesher.");
166  }
167  }
168  else
169  paramError("periphery_generator",
170  "Provided periphery meshgenerator has not been implemented.");
171  }
172 
173  MeshGeneratorName first_nondummy_assembly = "";
174  MeshGeneratorName reactor_params = "";
175  bool assembly_homogenization = false;
176  bool pin_as_assembly = false;
177  std::map<subdomain_id_type, std::string> global_pin_map_type_to_name;
178  std::map<subdomain_id_type, std::string> assembly_map_type_to_name;
179  // Check that MG name for reactor params and assembly homogenization schemes are
180  // consistent across all assemblies, and there is no overlap in pin_type / assembly_type ids
181  for (const auto i : index_range(_inputs))
182  {
183  // Skip if assembly name is equal to dummy assembly name
184  if (_inputs[i] == _empty_key)
185  continue;
186 
187  // Save properties of first non-dummy assembly to compare to other assemblies
188  if (first_nondummy_assembly == "")
189  {
190  first_nondummy_assembly = MeshGeneratorName(_inputs[i]);
191  reactor_params =
192  MeshGeneratorName(getMeshProperty<std::string>(RGMB::reactor_params_name, _inputs[i]));
193  assembly_homogenization = getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]);
194  pin_as_assembly = getMeshProperty<bool>(RGMB::is_single_pin, _inputs[i]);
195  }
196  if (getMeshProperty<std::string>(RGMB::reactor_params_name, _inputs[i]) != reactor_params)
197  mooseError("The name of all reactor_params objects should be identical across all pins in "
198  "the input assemblies.\n");
199  if ((getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]) != assembly_homogenization) &&
200  !getMeshProperty<bool>(RGMB::flexible_assembly_stitching, reactor_params))
201  mooseError("In order to stitch heterogeneous assemblies with homogeneous assemblies in "
202  "CoreMeshGenerator, ReactorMeshParams/flexible_assembly_stitching should be set "
203  "to true\n");
204 
205  // Check assembly_types across constituent assemblies are uniquely defined
206  const auto assembly_type = getMeshProperty<subdomain_id_type>(RGMB::assembly_type, _inputs[i]);
207  if (assembly_map_type_to_name.find(assembly_type) != assembly_map_type_to_name.end() &&
208  assembly_map_type_to_name[assembly_type] != _inputs[i])
209  mooseError(
210  "Constituent assemblies have shared assembly_type ids but different names. Each uniquely "
211  "defined assembly in AssemblyMeshGenerator must have its own assembly_type id.");
212  assembly_map_type_to_name[assembly_type] = _inputs[i];
213 
214  // If assembly is composed of pins, check pin_types across all constituent assemblies are
215  // uniquely defined
216  if (hasMeshProperty<std::vector<std::string>>(RGMB::pin_names, _inputs[i]))
217  {
218  const auto pin_names = getMeshProperty<std::vector<std::string>>(RGMB::pin_names, _inputs[i]);
219  for (const auto & input_pin_name : pin_names)
220  {
221  const auto pin_type = getMeshProperty<subdomain_id_type>(RGMB::pin_type, input_pin_name);
222  if (global_pin_map_type_to_name.find(pin_type) != global_pin_map_type_to_name.end() &&
223  global_pin_map_type_to_name[pin_type] != input_pin_name)
224  mooseError(
225  "Constituent pins within assemblies have shared pin_type ids but different names. "
226  "Each uniquely defined pin in AssemblyMeshGenerator must have its own pin_type id.");
227  global_pin_map_type_to_name[pin_type] = input_pin_name;
228  }
229  }
230  }
231 
232  // Check that there is at least one non-dummy assemby defined in lattice
233  if (first_nondummy_assembly == "")
234  paramError("inputs", "At least one non-dummy assembly must be defined in input assembly names");
235 
236  // Initialize ReactorMeshParams object stored in pin input
237  initializeReactorMeshParams(reactor_params);
238 
239  _geom_type = getReactorParam<std::string>(RGMB::mesh_geometry);
240  _mesh_dimensions = getReactorParam<unsigned int>(RGMB::mesh_dimensions);
241 
242  if (_extrude && _mesh_dimensions != 3)
243  paramError("extrude",
244  "In order to extrude this mesh, ReactorMeshParams/dim needs to be set to 3\n");
245  if (_extrude && (!hasReactorParam<boundary_id_type>(RGMB::top_boundary_id) ||
246  !hasReactorParam<boundary_id_type>(RGMB::bottom_boundary_id)))
247  mooseError("Both top_boundary_id and bottom_boundary_id must be provided in ReactorMeshParams "
248  "if using extruded geometry");
249  if (!hasReactorParam<boundary_id_type>(RGMB::radial_boundary_id))
250  mooseError("radial_boundary_id must be provided in ReactorMeshParams for CoreMeshGenerators");
251 
252  if (parameters.isParamSetByUser("periphery_block_name") &&
253  getReactorParam<bool>(RGMB::region_id_as_block_name))
254  paramError("periphery_block_name",
255  "If ReactorMeshParams/region_id_as_block_name is set, periphery_block_name should "
256  "not be specified in CoreMeshGenerator");
257 
258  std::size_t empty_pattern_loc = 0;
259  bool make_empty = false;
260  for (auto assembly : _inputs)
261  {
262  if (assembly != _empty_key)
263  {
264  ++empty_pattern_loc;
265  if (getMeshProperty<bool>(RGMB::extruded, assembly))
266  mooseError("Assemblies that have already been extruded cannot be used in CoreMeshGenerator "
267  "definition.\n");
268  }
269  else
270  {
271  // Found dummy assembly in input assembly names
272  make_empty = true;
273  for (const auto i : index_range(_pattern))
274  {
275  for (const auto j : index_range(_pattern[i]))
276  {
277  // Found dummy assembly in input lattice definition
278  if (_pattern[i][j] == empty_pattern_loc)
279  _empty_pos = true;
280  }
281  }
282  }
283  }
284 
285  // No subgenerators will be called if option to bypass mesh generators is enabled
286  if (!getReactorParam<bool>(RGMB::bypass_meshgen))
287  {
288  // Check whether flexible stitching should be used for constituent assemblies and throw a
289  // warning if flexible stitching option is not enabled
290  if (!getReactorParam<bool>(RGMB::flexible_assembly_stitching) &&
292  mooseWarning("Constituent assemblies do not share the same number of nodes at the outer "
293  "boundary. In order to ensure that output mesh does not having hanging nodes, a "
294  "flexible stitching approach should be used by setting "
295  "ReactorMeshParams/flexible_assembly_stitching = true.");
296 
297  // Declare that all of the meshes in the "inputs" parameter are to be used by
298  // a sub mesh generator.
299  declareMeshesForSub("inputs");
300 
301  // Stitch assemblies into a hexagonal / Cartesian core lattice
302  {
303  // create a dummy assembly that is a renamed version of one of the inputs
304  if (make_empty)
305  {
306  {
307  if (assembly_homogenization)
308  {
309  auto params = _app.getFactory().getValidParams("SimpleHexagonGenerator");
310 
311  params.set<Real>("hexagon_size") = getReactorParam<Real>(RGMB::assembly_pitch) / 2.0;
312  params.set<std::vector<subdomain_id_type>>("block_id") = {
314 
315  addMeshSubgenerator("SimpleHexagonGenerator", std::string(_empty_key), params);
316  }
317  else
318  {
319  const auto adaptive_mg_name =
320  _geom_type == "Hex" ? "HexagonConcentricCircleAdaptiveBoundaryMeshGenerator"
321  : "CartesianConcentricCircleAdaptiveBoundaryMeshGenerator";
322  auto params = _app.getFactory().getValidParams(adaptive_mg_name);
323 
324  const auto assembly_pitch = getReactorParam<Real>(RGMB::assembly_pitch);
325  if (_geom_type == "Hex")
326  {
327  params.set<Real>("hexagon_size") = assembly_pitch / 2.0;
328  params.set<std::vector<unsigned int>>("num_sectors_per_side") =
329  std::vector<unsigned int>(6, 2);
330  }
331  else
332  {
333  params.set<Real>("square_size") = assembly_pitch;
334  params.set<std::vector<unsigned int>>("num_sectors_per_side") =
335  std::vector<unsigned int>(4, 2);
336  }
337  params.set<std::vector<unsigned int>>("sides_to_adapt") = std::vector<unsigned int>{0};
338  params.set<std::vector<MeshGeneratorName>>("meshes_to_adapt_to") =
339  std::vector<MeshGeneratorName>{first_nondummy_assembly};
340  params.set<std::vector<subdomain_id_type>>("background_block_ids") =
341  std::vector<subdomain_id_type>{RGMB::DUMMY_ASSEMBLY_BLOCK_ID};
342 
343  addMeshSubgenerator(adaptive_mg_name, std::string(_empty_key), params);
344  }
345  }
346  }
347  {
348  const auto patterned_mg_name =
349  _geom_type == "Hex" ? "PatternedHexMeshGenerator" : "PatternedCartesianMeshGenerator";
350  auto params = _app.getFactory().getValidParams(patterned_mg_name);
351 
352  params.set<std::vector<std::string>>("id_name") = {"assembly_id"};
353  params.set<std::vector<MooseEnum>>("assign_type") = {
354  MooseEnum("cell", "cell")}; // give elems IDs relative to position in assembly
355  params.set<std::vector<MeshGeneratorName>>("inputs") = _inputs;
356  params.set<std::vector<std::vector<unsigned int>>>("pattern") = _pattern;
357  params.set<MooseEnum>("pattern_boundary") = "none";
358  params.set<bool>("generate_core_metadata") = !pin_as_assembly;
359  params.set<bool>("create_outward_interface_boundaries") = false;
360  params.set<bool>("assign_control_drum_id") = getParam<bool>("assign_control_drum_id");
361  if (make_empty)
362  {
363  params.set<std::vector<MeshGeneratorName>>("exclude_id") =
364  std::vector<MeshGeneratorName>{_empty_key};
365  }
366 
367  const auto radial_boundary = getReactorParam<boundary_id_type>(RGMB::radial_boundary_id);
368  params.set<boundary_id_type>("external_boundary_id") = radial_boundary;
369  params.set<BoundaryName>("external_boundary_name") = RGMB::CORE_BOUNDARY_NAME;
370  params.set<double>("rotate_angle") = 0.0;
371  params.set<bool>("allow_unused_inputs") = true;
372 
373  addMeshSubgenerator(patterned_mg_name, name() + "_pattern", params);
374  }
375  }
376  if (_empty_pos)
377  {
378  auto params = _app.getFactory().getValidParams("BlockDeletionGenerator");
379 
380  params.set<std::vector<SubdomainName>>("block") = {
381  std::to_string(RGMB::DUMMY_ASSEMBLY_BLOCK_ID)};
382  params.set<MeshGeneratorName>("input") = name() + "_pattern";
383  params.set<BoundaryName>("new_boundary") = RGMB::CORE_BOUNDARY_NAME;
384 
385  addMeshSubgenerator("BlockDeletionGenerator", name() + "_deleted", params);
386  }
387 
388  std::string build_mesh_name;
389 
390  // Remove outer assembly sidesets created during assembly generation
391  {
392  // Get outer boundaries of all constituent assemblies based on assembly_type,
393  // skipping all dummy assemblies
394  std::vector<BoundaryName> boundaries_to_delete = {};
395  for (const auto & pattern_x : _pattern)
396  {
397  for (const auto & pattern_idx : pattern_x)
398  {
399  const auto assembly_name = _inputs[pattern_idx];
400  if (assembly_name == _empty_key)
401  continue;
402  const auto assembly_id =
403  getMeshProperty<subdomain_id_type>(RGMB::assembly_type, assembly_name);
404  const BoundaryName boundary_name =
405  RGMB::ASSEMBLY_BOUNDARY_NAME_PREFIX + std::to_string(assembly_id);
406  if (!std::count(boundaries_to_delete.begin(), boundaries_to_delete.end(), boundary_name))
407  boundaries_to_delete.push_back(boundary_name);
408  }
409  }
410  auto params = _app.getFactory().getValidParams("BoundaryDeletionGenerator");
411 
412  params.set<MeshGeneratorName>("input") =
413  _empty_pos ? name() + "_deleted" : name() + "_pattern";
414  params.set<std::vector<BoundaryName>>("boundary_names") = boundaries_to_delete;
415 
416  build_mesh_name = name() + "_delbds";
417  addMeshSubgenerator("BoundaryDeletionGenerator", build_mesh_name, params);
418  }
419 
420  for (auto assembly : _inputs)
421  {
422  if (assembly != _empty_key)
423  {
425  getMeshProperty<subdomain_id_type>(RGMB::assembly_type, assembly);
426  if (!getMeshProperty<bool>(RGMB::is_control_drum, assembly))
427  {
428  // For assembly structures, store region ID and block names of assembly regions and
429  // constituent pins
430  const auto & pin_region_id_map = getMeshProperty<
431  std::map<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>>(
432  RGMB::pin_region_id_map, assembly);
433  for (auto pin = pin_region_id_map.begin(); pin != pin_region_id_map.end(); ++pin)
434  _pin_region_id_map.insert(
435  std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
436  pin->first, pin->second));
437 
438  const auto & pin_block_name_map =
439  getMeshProperty<std::map<subdomain_id_type, std::vector<std::vector<std::string>>>>(
440  RGMB::pin_block_name_map, assembly);
441  for (auto pin = pin_block_name_map.begin(); pin != pin_block_name_map.end(); ++pin)
442  _pin_block_name_map.insert(
443  std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(pin->first,
444  pin->second));
445 
446  // Define background and duct region ID map from constituent assemblies
448  {
449  // Store region ids and block names associated with duct and background regions for each
450  // assembly, in case block names need to be recovered from region ids after
451  // multiple assemblies have been stitched together into a core
452  std::vector<subdomain_id_type> background_region_ids =
453  getMeshProperty<std::vector<subdomain_id_type>>(RGMB::background_region_id,
454  assembly);
455  std::vector<std::vector<subdomain_id_type>> duct_region_ids =
456  getMeshProperty<std::vector<std::vector<subdomain_id_type>>>(RGMB::duct_region_ids,
457  assembly);
459  std::pair<subdomain_id_type, std::vector<subdomain_id_type>>(
460  assembly_type, background_region_ids));
461  _duct_region_id_map.insert(
462  std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
464 
465  std::vector<std::string> background_block_names =
466  getMeshProperty<std::vector<std::string>>(RGMB::background_block_name, assembly);
467  std::vector<std::vector<std::string>> duct_block_names =
468  getMeshProperty<std::vector<std::vector<std::string>>>(RGMB::duct_block_names,
469  assembly);
471  std::pair<subdomain_id_type, std::vector<std::string>>(assembly_type,
472  background_block_names));
473  _duct_block_name_map.insert(
474  std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(
476  }
477  }
478  else
479  {
480  // For control drum structures, store region ID and block name information of drum regions
481  const auto & drum_region_ids =
482  getMeshProperty<std::vector<std::vector<subdomain_id_type>>>(RGMB::drum_region_ids,
483  assembly);
484  _drum_region_id_map.insert(
485  std::pair<subdomain_id_type, std::vector<std::vector<subdomain_id_type>>>(
487  std::vector<std::vector<std::string>> drum_block_names =
488  getMeshProperty<std::vector<std::vector<std::string>>>(RGMB::drum_block_names,
489  assembly);
490  _drum_block_name_map.insert(
491  std::pair<subdomain_id_type, std::vector<std::vector<std::string>>>(
493  }
494  }
495  }
496 
497  // periphery meshing
498  if (_mesh_periphery)
499  {
500  std::string periphery_mg_name = (_periphery_meshgenerator == "triangle")
501  ? "PeripheralTriangleMeshGenerator"
502  : "PeripheralRingMeshGenerator";
503 
504  // set up common options
505  auto params = _app.getFactory().getValidParams(periphery_mg_name);
506  params.set<MeshGeneratorName>("input") = name() + "_delbds";
507  params.set<Real>("peripheral_ring_radius") = _outer_circle_radius;
508  params.set<BoundaryName>("external_boundary_name") = "outside_periphery";
509  params.set<SubdomainName>("peripheral_ring_block_name") = RGMB::PERIPHERAL_RING_BLOCK_NAME;
510 
511  // unique MG options
512  if (_periphery_meshgenerator == "triangle")
513  {
514  params.set<unsigned int>("peripheral_ring_num_segments") = _outer_circle_num_segments;
515  params.set<Real>("desired_area") = _desired_area;
516  params.set<std::string>("desired_area_func") = _desired_area_func;
517  }
518  else if (_periphery_meshgenerator == "quad_ring")
519  {
520  params.set<subdomain_id_type>("peripheral_ring_block_id") = RGMB::PERIPHERAL_RING_BLOCK_ID;
521  params.set<BoundaryName>("input_mesh_external_boundary") = RGMB::CORE_BOUNDARY_NAME;
522  params.set<unsigned int>("peripheral_layer_num") = _periphery_num_layers;
523  }
524 
525  // finish periphery input
526  build_mesh_name = name() + "_periphery";
527  addMeshSubgenerator(periphery_mg_name, build_mesh_name, params);
528  }
529 
530  if (_extrude && _mesh_dimensions == 3)
531  build_mesh_name = callExtrusionMeshSubgenerators(build_mesh_name);
532 
533  // Store final mesh subgenerator
534  _build_mesh = &getMeshByName(build_mesh_name);
535  }
536  // If mesh generation should be bypassed, call getMeshes to resolve MeshGeneratorSystem
537  // dependencies
538  else
539  auto input_meshes = getMeshes("inputs");
540 
542 }
543 
544 void
546 {
547  // Define metadata related to downstream function calls
548  if (_mesh_periphery)
549  {
552  }
553 
554  // Determine constituent pin type ids and define lattice
555  std::vector<std::vector<int>> assembly_name_lattice;
556  std::vector<std::string> input_assembly_names;
557  std::vector<std::string> input_pin_names;
558 
559  // Iterate through input assembly names and define constituent assemblies and pins
560  for (const auto i : index_range(_inputs))
561  {
562  const auto input_assembly_name = _inputs[i];
563  if (input_assembly_name != _empty_key)
564  {
565  input_assembly_names.push_back(input_assembly_name);
566  if (!getMeshProperty<bool>(RGMB::is_control_drum, input_assembly_name) &&
567  !getMeshProperty<bool>(RGMB::is_single_pin, input_assembly_name))
568  {
569  const auto pin_names =
570  getMeshProperty<std::vector<std::string>>(RGMB::pin_names, input_assembly_name);
571  for (const auto & pin_name : pin_names)
572  if (std::find(input_pin_names.begin(), input_pin_names.end(), pin_name) ==
573  input_pin_names.end())
574  input_pin_names.push_back(pin_name);
575  }
576  }
577  }
578 
579  // Iterate through pattern and remap dummy assemblies with index -1
580  for (const auto i : index_range(_pattern))
581  {
582  std::vector<int> assembly_name_idx(_pattern[i].size());
583  for (const auto j : index_range(_pattern[i]))
584  {
585  const auto input_assembly_name = _inputs[_pattern[i][j]];
586  // Use an assembly type of -1 to represent a dummy assembly
587  if (input_assembly_name == _empty_key)
588  assembly_name_idx[j] = -1;
589  // Set index of assembly name based on `input_assembly_names` variable
590  else
591  {
592  const auto it = std::find(
593  input_assembly_names.begin(), input_assembly_names.end(), input_assembly_name);
594  assembly_name_idx[j] = it - input_assembly_names.begin();
595  }
596  }
597  assembly_name_lattice.push_back(assembly_name_idx);
598  }
599 
600  declareMeshProperty(RGMB::pin_names, input_pin_names);
601  declareMeshProperty(RGMB::assembly_names, input_assembly_names);
602  declareMeshProperty(RGMB::assembly_lattice, assembly_name_lattice);
604 }
605 
606 bool
608 {
609  MeshGeneratorName first_nondummy_assembly = "";
610  bool assembly_homogenization = false;
611  unsigned int n_constituent_pins = 0;
612  unsigned int n_pin_sectors = 0;
613 
614  // Loop through all non-dummy input assemblies. Flexible assembly stitching is needed if one of
615  // the following criteria are met:
616  // 1. The number of constituent pins within the assembly does not match with another assembly
617  // 2. The value of is_single_pin and is_homogenized metadata do not agree with another assembly
618  // 3. The number of sectors of the constituent pins of an assembly do not match with the
619  // constituent pins of another assembly
620  for (const auto i : index_range(_inputs))
621  {
622  // Skip if assembly name is equal to dummy assembly name
623  if (_inputs[i] == _empty_key)
624  continue;
625 
626  // Compute total number of constituent pins in assembly, as well as the number of sectors per
627  // side for each pin Note: number of sectors per side is defined uniformly across constituent
628  // pins of an assembly, so only first one needs to be checked
629  unsigned int total_pins = 0;
630  unsigned int pin_sectors_per_side = 0;
631  if (!getMeshProperty<bool>(RGMB::is_single_pin, _inputs[i]))
632  {
633  const auto first_pin_name =
634  getMeshProperty<std::vector<std::string>>(RGMB::pin_names, _inputs[i])[0];
635  pin_sectors_per_side = getMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta",
636  first_pin_name + "_2D")[0];
637  const auto pin_lattice =
638  getMeshProperty<std::vector<std::vector<int>>>(RGMB::pin_lattice, _inputs[i]);
639  for (const auto i : index_range(pin_lattice))
640  total_pins += pin_lattice[i].size();
641  }
642  else
643  {
644  if (getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]))
645  {
646  // Homogenized assembly
647  total_pins = 0;
648  pin_sectors_per_side = 0;
649  }
650  else
651  {
652  // Assembly with single constituent pin
653  total_pins = 1;
654  pin_sectors_per_side = getMeshProperty<std::vector<unsigned int>>(
655  "num_sectors_per_side_meta", _inputs[i] + "_2D")[0];
656  }
657  }
658 
659  if (first_nondummy_assembly == "")
660  {
661  first_nondummy_assembly = MeshGeneratorName(_inputs[i]);
662  assembly_homogenization = getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]);
663  n_constituent_pins = total_pins;
664  n_pin_sectors = pin_sectors_per_side;
665  }
666  else
667  {
668  if (getMeshProperty<bool>(RGMB::is_homogenized, _inputs[i]) != assembly_homogenization)
669  {
670  mooseWarning("Detected mix of homogenized and heterogeneous assemblies between " +
671  first_nondummy_assembly + " and " + _inputs[i]);
672  return true;
673  }
674  if (total_pins != n_constituent_pins)
675  {
676  mooseWarning(
677  "Detected assemblies with different number of total constituent pins between " +
678  first_nondummy_assembly + " and " + _inputs[i]);
679  return true;
680  }
681  if (pin_sectors_per_side != n_pin_sectors)
682  {
683  mooseWarning("Constituent pins in " + first_nondummy_assembly + " and " + _inputs[i] +
684  " differ in terms of number of sectors per side");
685  return true;
686  }
687  }
688  }
689  return false;
690 }
691 
692 std::unique_ptr<MeshBase>
694 {
695  // Must be called to free the ReactorMeshParams mesh
697 
698  // If bypass_mesh is true, return a null mesh. In this mode, an output mesh is not
699  // generated and only metadata is defined on the generator, so logic related to
700  // generation of output mesh will not be called
701  if (getReactorParam<bool>(RGMB::bypass_meshgen))
702  {
703  auto null_mesh = nullptr;
704  return null_mesh;
705  }
706  // This generate() method will be called once the subgenerators that we depend on are
707  // called. This is where we reassign subdomain ids/names in case they were merged
708  // when stitching assemblies into the core. This is also where we set region_id extra
709  // element integers, which has not been set yet for extruded geometries
710 
711  // Define all extra element names and integers
712  std::string pin_type_id_name = "pin_type_id";
713  std::string assembly_type_id_name = "assembly_type_id";
714  std::string plane_id_name = "plane_id";
715  std::string region_id_name = "region_id";
716  std::string radial_id_name = "radial_id";
717  const std::string default_block_name = RGMB::CORE_BLOCK_NAME_PREFIX;
718 
719  auto pin_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), pin_type_id_name, true);
720  auto assembly_type_id_int = getElemIntegerFromMesh(*(*_build_mesh), assembly_type_id_name, true);
721  auto radial_id_int = getElemIntegerFromMesh(*(*_build_mesh), radial_id_name, true);
722  auto region_id_int = getElemIntegerFromMesh(*(*_build_mesh), region_id_name, true);
723  unsigned int plane_id_int = 0;
724  if (_extrude)
725  plane_id_int = getElemIntegerFromMesh(*(*_build_mesh), plane_id_name, true);
726 
727  // Get next free block ID in mesh in case subdomain ids need to be remapped
728  auto next_block_id = MooseMeshUtils::getNextFreeSubdomainID(*(*(_build_mesh)));
729  std::map<std::string, SubdomainID> rgmb_name_id_map;
730 
731  // Loop through all mesh elements and set region ids and reassign block IDs/names
732  // if they were merged during assembly stitching
733  for (auto & elem : (*_build_mesh)->active_element_ptr_range())
734  {
735  dof_id_type z_id = _extrude ? elem->get_extra_integer(plane_id_int) : 0;
736  dof_id_type pin_type_id = elem->get_extra_integer(pin_type_id_int);
737 
738  if (_pin_region_id_map.find(pin_type_id) != _pin_region_id_map.end())
739  {
740  // Pin type element, get region ID from pin_type, z_id, and radial_idx
741  const dof_id_type radial_idx = elem->get_extra_integer(radial_id_int);
742  const auto elem_rid = _pin_region_id_map[pin_type_id][z_id][radial_idx];
743  elem->set_extra_integer(region_id_int, elem_rid);
744 
745  // Set element block name and block id
746  bool has_block_names = !_pin_block_name_map[pin_type_id].empty();
747  auto elem_block_name = default_block_name;
748  if (has_block_names)
749  elem_block_name += "_" + _pin_block_name_map[pin_type_id][z_id][radial_idx];
750  else if (getReactorParam<bool>(RGMB::region_id_as_block_name))
751  elem_block_name += "_REG" + std::to_string(elem_rid);
752  if (elem->type() == TRI3 || elem->type() == PRISM6)
753  elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
755  *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
756  }
757  else if ((*_build_mesh)->subdomain_name(elem->subdomain_id()) ==
759  // periphery type element
760  {
761  // set region ID of core periphery element
762  elem->set_extra_integer(region_id_int, _periphery_region_id);
763  // set block name and block name of core periphery element
764  auto elem_block_name = _periphery_block_name;
765  if (getReactorParam<bool>(RGMB::region_id_as_block_name))
766  elem_block_name += "_REG" + std::to_string(_periphery_region_id);
767  if (elem->type() == TRI3 || elem->type() == PRISM6)
768  elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
770  *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
771  }
772  else
773  {
774  dof_id_type assembly_type_id = elem->get_extra_integer(assembly_type_id_int);
775  // Infer peripheral index of assembly background, assembly duct, or control drum regions from
776  // pin_type_id
777  unsigned int peripheral_idx = RGMB::MAX_PIN_TYPE_ID - pin_type_id;
778 
779  // check if element is part of drum region
780  if (_drum_region_id_map.find(assembly_type_id) != _drum_region_id_map.end())
781  {
782  // Element is in a control drum region. Infer region id from assembly_type_id, z_id, and
783  // peripheral_index
784  const auto elem_rid = _drum_region_id_map[assembly_type_id][z_id][peripheral_idx];
785  elem->set_extra_integer(region_id_int, elem_rid);
786 
787  // Set element block name and block id
788  auto elem_block_name = default_block_name;
789  if (getReactorParam<bool>(RGMB::region_id_as_block_name))
790  elem_block_name += "_REG" + std::to_string(elem_rid);
791  else
792  {
793  bool has_drum_block_name = !_drum_block_name_map[assembly_type_id].empty();
794  if (has_drum_block_name)
795  elem_block_name += "_" + _drum_block_name_map[assembly_type_id][z_id][peripheral_idx];
796  }
797  if (elem->type() == TRI3 || elem->type() == PRISM6)
798  elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
800  *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
801  }
802  else
803  {
804  // Element is in an assembly duct or background region since it doesn't
805  // have an assembly type id in the drum region map. Infer region id from
806  // assembly_type_id, z_id, and peripheral_index
807  bool is_background_region = peripheral_idx == 0;
808  const auto elem_rid =
809  (is_background_region
810  ? _background_region_id_map[assembly_type_id][z_id]
811  : _duct_region_id_map[assembly_type_id][z_id][peripheral_idx - 1]);
812  elem->set_extra_integer(region_id_int, elem_rid);
813 
814  // Set element block name and block id
815  auto elem_block_name = default_block_name;
816  if (getReactorParam<bool>(RGMB::region_id_as_block_name))
817  elem_block_name += "_REG" + std::to_string(elem_rid);
818  else
819  {
820  if (is_background_region)
821  {
822  bool has_background_block_name = !_background_block_name_map[assembly_type_id].empty();
823  if (has_background_block_name)
824  elem_block_name += "_" + _background_block_name_map[assembly_type_id][z_id];
825  }
826  else
827  {
828  bool has_duct_block_names = !_duct_block_name_map[assembly_type_id].empty();
829  if (has_duct_block_names)
830  elem_block_name +=
831  "_" + _duct_block_name_map[assembly_type_id][z_id][peripheral_idx - 1];
832  }
833  }
834  if (elem->type() == TRI3 || elem->type() == PRISM6)
835  elem_block_name += RGMB::TRI_BLOCK_NAME_SUFFIX;
837  *(*_build_mesh), elem, rgmb_name_id_map, elem_block_name, next_block_id);
838  }
839  }
840  }
841 
842  // Sideset 10000 does not get stitched properly when BlockDeletionGenerator
843  // is used for deleting dummy assemblies. This block copies missing sides
844  // into sideset 10000 from sideset RGMB::CORE_BOUNDARY_NAME
845  BoundaryInfo & boundary_info = (*_build_mesh)->get_boundary_info();
846  boundary_id_type source_id =
848  boundary_id_type target_id = 10000;
849  const auto sideset_map = boundary_info.get_sideset_map();
850 
851  for (const auto & [elem, id_pair] : sideset_map)
852  {
853  const auto side_id = id_pair.first;
854  const auto sideset_id = id_pair.second;
855 
856  // Filter all sides that belong to RGMB::CORE_BOUNDARY_NAME sideset
857  if (sideset_id == source_id)
858  {
859  auto mm_it = sideset_map.equal_range(elem);
860  bool found = false;
861  // Check if side is defined in sideset 10000
862  for (auto it = mm_it.first; it != mm_it.second; it++)
863  {
864  if (it->second.first == side_id && it->second.second == target_id)
865  found = true;
866  }
867  // Add side if not found in sideset 10000
868  if (!found)
869  boundary_info.add_side(elem, side_id, target_id);
870  }
871  }
872 
873  if (getParam<bool>("generate_depletion_id"))
874  {
875  const MooseEnum option = getParam<MooseEnum>("depletion_id_type");
877  }
878 
879  // Mark mesh as not prepared, as block ID's were re-assigned in this method
880  (*_build_mesh)->set_isnt_prepared();
881 
882  return std::move(*_build_mesh);
883 }
const T & getMeshProperty(const std::string &data_name, const std::string &prefix)
std::unique_ptr< MeshBase > & getMeshByName(const MeshGeneratorName &mesh_generator_name)
static void addDepletionIDParams(InputParameters &parameters)
static InputParameters validParams()
const bool _mesh_periphery
Whether the core periphery should be meshed.
void updateElementBlockNameId(MeshBase &input_mesh, Elem *elem, std::map< std::string, SubdomainID > &name_id_map, std::string elem_block_name, SubdomainID &next_free_id)
Updates the block names and ids of the element in an input mesh according to a map of block name to b...
static const std::string duct_block_names
static const std::string background_region_id
static const std::string assembly_type
static const std::string region_id_as_block_name
std::map< subdomain_id_type, std::vector< std::vector< subdomain_id_type > > > _duct_region_id_map
A mapping from assembly-type IDs to region IDs in the assembly duct regions used when assigning regio...
static const std::string assembly_lattice
const SubdomainName PERIPHERAL_RING_BLOCK_NAME
static const std::string is_single_pin
Mesh generator for defining a reactor core using a Cartesian or hexagonal lattice with the option to ...
static const std::string peripheral_ring_radius
const subdomain_id_type DUMMY_ASSEMBLY_BLOCK_ID
unsigned int getElemIntegerFromMesh(MeshBase &input_mesh, std::string extra_int_name, bool should_exist=false)
Initializes extra element integer from id name for a given mesh and throws an error if it should exis...
const boundary_id_type side_id
T & set(const std::string &name, bool quiet_mode=false)
static const std::string reactor_params_name
static const std::string mesh_geometry
void initializeReactorMeshParams(const std::string reactor_param_name)
Initializes and checks validity of ReactorMeshParams mesh generator object.
InputParameters getValidParams(const std::string &name) const
static const std::string assembly_names
static const std::string background_block_name
void addDepletionId(MeshBase &input_mesh, const MooseEnum &option, const DepletionIDGenerationLevel generation_level, const bool extrude)
add depletion IDs
static const std::string peripheral_ring_region_id
static const std::string assembly_pitch
static const std::string radial_boundary_id
static const std::string pin_type
const Real _outer_circle_radius
outer circle boundary radius
const SubdomainName TRI_BLOCK_NAME_SUFFIX
virtual const std::string & name() const
void mooseWarning(Args &&... args) const
bool _empty_pos
Whether empty positions are to be used in the pattern.
Factory & getFactory()
std::string _desired_area_func
Desired (local) triangle area as a function of (x,y)
TRI3
static const std::string drum_block_names
const std::vector< MeshGeneratorName > _inputs
The names of the assemblies that compose the core.
static const std::string pin_region_id_map
std::map< subdomain_id_type, std::vector< subdomain_id_type > > _background_region_id_map
A mapping from assembly-type IDs to region IDs in the assembly background regions used when assigning...
static const std::string extruded
const subdomain_id_type PERIPHERAL_RING_BLOCK_ID
const bool _extrude
Whether this mesh should be extruded to 3-D, the core is always assumed to be the last...
const MooseEnum _periphery_meshgenerator
Which periphery meshgenerator to use.
int8_t boundary_id_type
std::map< subdomain_id_type, std::vector< std::vector< std::string > > > _pin_block_name_map
A mapping from pin-type IDs to block names used when assigning block names during the assembly stitch...
static const std::string mesh_dimensions
const unsigned int _outer_circle_num_segments
Number of segments in the outer circle boundary.
void addMeshSubgenerator(const std::string &type, const std::string &name, Ts... extra_input_parameters)
MeshGeneratorName callExtrusionMeshSubgenerators(const MeshGeneratorName input_mesh_name)
Calls mesh subgenerators related to extrusion, renaming of top / bottom boundaries, and defining plane IDs.
registerMooseObject("ReactorApp", CoreMeshGenerator)
const unsigned int _periphery_num_layers
Number of periphery layers.
std::string _geom_type
The geometry type for the reactor that is stored on the ReactorMeshParams object. ...
const SubdomainName CORE_BLOCK_NAME_PREFIX
bool constituentAssembliesNeedFlexibleStiching()
static const std::string top_boundary_id
std::vector< BoundaryID > getBoundaryIDs(const libMesh::MeshBase &mesh, const std::vector< BoundaryName > &boundary_name, bool generate_unknown, const std::set< BoundaryID > &mesh_boundary_ids)
std::vector< std::unique_ptr< MeshBase > *> getMeshes(const std::string &param_name)
void paramError(const std::string &param, Args... args) const
std::map< subdomain_id_type, std::vector< std::vector< subdomain_id_type > > > _pin_region_id_map
A mapping from pin-type IDs to region IDs used when assigning region IDs during the assembly stitchin...
std::map< subdomain_id_type, std::vector< std::vector< std::string > > > _duct_block_name_map
A mapping from assembly-type IDs to block names in the assembly duct regions used when assigning bloc...
static const std::string is_homogenized
static const std::string is_control_drum
bool isParamSetByUser(const std::string &name) const
std::map< subdomain_id_type, std::vector< std::string > > _background_block_name_map
A mapping from assembly-type IDs to block names in the assembly background regions used when assignin...
static const std::string flexible_assembly_stitching
static const std::string pin_lattice
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const BoundaryName CORE_BOUNDARY_NAME
MooseApp & _app
std::unique_ptr< MeshBase > generate() override
const std::string _periphery_block_name
The subdomain name for the generated mesh outer boundary.
const MeshGeneratorName _empty_key
The name of "filler" assembly given in the input to represent an empty space in the core pattern...
A base class that contains common members for Reactor Geometry Mesh Builder mesh generators.
const BoundaryName ASSEMBLY_BOUNDARY_NAME_PREFIX
void mooseError(Args &&... args) const
void freeReactorMeshParams()
Releases the mesh obtained in _reactor_params_mesh.
const Real _desired_area
Desired (maximum) triangle area.
const InputParameters & parameters() const
T & declareMeshProperty(const std::string &data_name, Args &&... args)
static const std::string drum_region_ids
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
const subdomain_id_type _periphery_region_id
"region_id" extra-element integer of the periphery mesh elements
static const std::string bypass_meshgen
static const std::string pin_names
std::map< subdomain_id_type, std::vector< std::vector< subdomain_id_type > > > _drum_region_id_map
A mapping from assembly-type IDs to region IDs in the drum regions used when assigning region IDs dur...
PRISM6
void declareMeshesForSub(const std::string &param_name)
void declareNullMeshName(const MeshGeneratorName &name)
SubdomainID getNextFreeSubdomainID(MeshBase &input_mesh)
std::unique_ptr< MeshBase > * _build_mesh
The final mesh that is generated by the subgenerators; This mesh is generated by the subgenerators wi...
const std::vector< std::vector< unsigned int > > _pattern
The 2D assembly layout of the core.
bool hasMeshProperty(const std::string &data_name, const std::string &prefix) const
CoreMeshGenerator(const InputParameters &parameters)
static const std::string duct_region_ids
void ErrorVector unsigned int
const subdomain_id_type MAX_PIN_TYPE_ID
auto index_range(const T &sizable)
std::map< subdomain_id_type, std::vector< std::vector< std::string > > > _drum_block_name_map
A mapping from assembly-type IDs to block names in the drum regions used when assigning block names d...
static const std::string bottom_boundary_id
static const std::string pin_block_name_map
unsigned int _mesh_dimensions
The number of dimensions the mesh is ultimately going to have (2 or 3, declared in the ReactorMeshPar...
uint8_t dof_id_type