https://mooseframework.inl.gov
PatternedHexMeshGenerator.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 
12 #include "MooseUtils.h"
13 #include "MooseMeshUtils.h"
14 
15 // C++ includes
16 #include <cmath> // provides round, not std::round (see http://www.cplusplus.com/reference/cmath/round/)
17 #include <fstream> // used to generate the optional control drum position file
18 
20 
23 {
25  params.addRequiredParam<std::vector<MeshGeneratorName>>("inputs", "The input MeshGenerators.");
26  params.addRequiredRangeCheckedParam<std::vector<std::vector<unsigned int>>>(
27  "pattern",
28  "pattern>=0",
29  "A double-indexed hexagonal-shaped array starting with the upper-left corner.");
30  MooseEnum pattern_boundary("none hexagon", "hexagon");
31  params.addParam<MooseEnum>(
32  "pattern_boundary", pattern_boundary, "The boundary shape of the patterned mesh.");
33  params.addParam<bool>(
34  "generate_core_metadata",
35  false,
36  "A Boolean parameter that controls whether the core related metadata "
37  "is generated for other MOOSE objects such as 'MultiControlDrumFunction' or not.");
38  params.addRangeCheckedParam<unsigned int>("background_intervals",
39  3,
40  "background_intervals>0",
41  "Radial intervals in the assembly peripheral region.");
42  params.addRangeCheckedParam<Real>("hexagon_size",
43  "hexagon_size>0.0",
44  "Size of the outmost hexagon boundary to be generated; this is "
45  "required only when pattern type is 'hexagon'.");
46  MooseEnum hexagon_size_style("apothem radius", "apothem");
47  params.addParam<MooseEnum>(
48  "hexagon_size_style",
49  hexagon_size_style,
50  "Style in which the hexagon size is given (default: apothem i.e. half-pitch).");
51  params.addRangeCheckedParam<std::vector<Real>>(
52  "duct_sizes", "duct_sizes>0.0", "Distance(s) from center to duct(s) inner boundaries.");
53  MooseEnum duct_sizes_style("apothem radius", "apothem");
54  params.addParam<MooseEnum>("duct_sizes_style",
55  duct_sizes_style,
56  "Style in which hexagon center to duct distance(s) is given (apothem "
57  "= center-to-face, radius = center-to-vertex).");
58  params.addRangeCheckedParam<std::vector<unsigned int>>(
59  "duct_intervals", "duct_intervals>0", "Number of meshing intervals in each enclosing duct.");
60  params.addParam<bool>("uniform_mesh_on_sides",
61  false,
62  "Whether the side elements are reorganized to have a uniform size.");
63  params.addParam<bool>("generate_control_drum_positions_file",
64  false,
65  "Whether a positions file is generated in the core mesh mode.");
66  params.addParam<bool>("assign_control_drum_id",
67  false,
68  "Whether control drum id is assigned to the mesh as an extra integer.");
69  std::string position_file_default = "positions_meta.data";
70  params.addParam<std::string>(
71  "position_file", position_file_default, "Data file name to store control drum positions.");
72  // A hexagon pattern_boundary mesh can be used in "inputs" of `PatternedHexMeshGenerator` after a
73  // 90-degree rotation.
74  params.addParam<Real>(
75  "rotate_angle",
76  90.0,
77  "Rotate the entire patterned mesh by a certain degrees that is defined here.");
79  "background_block_id",
80  "Optional customized block id for the background block in 'assembly' mode; must be provided "
81  "along with 'duct_block_ids' if 'duct_sizes' is provided.");
82  params.addParam<SubdomainName>(
83  "background_block_name",
84  "Optional customized block name for the background block in 'assembly' mode; must be "
85  "provided along with 'duct_block_names' if 'duct_sizes' is provided.");
86  params.addParam<std::vector<subdomain_id_type>>(
87  "duct_block_ids",
88  "Optional customized block ids for each duct geometry block in 'assembly' mode; must be "
89  "provided along with 'background_block_id'.");
90  params.addParam<std::vector<SubdomainName>>(
91  "duct_block_names",
92  std::vector<SubdomainName>(),
93  "Optional customized block names for each duct geometry block in 'assembly' mode; must be "
94  "provided along with 'background_block_name'.");
95  params.addRangeCheckedParam<boundary_id_type>("external_boundary_id",
96  "external_boundary_id>0",
97  "Optional customized external boundary id.");
98  params.addParam<bool>("create_inward_interface_boundaries",
99  false,
100  "Whether the inward interface boundary sidesets are created.");
101  params.addParam<bool>("create_outward_interface_boundaries",
102  true,
103  "Whether the outward interface boundary sidesets are created.");
104  params.addParam<BoundaryName>(
105  "external_boundary_name", BoundaryName(), "Optional customized external boundary name.");
106  params.addParam<bool>("deform_non_circular_region",
107  true,
108  "Whether the non-circular region (outside the rings) can be deformed.");
109  params.addParam<std::vector<std::string>>("id_name", "List of extra integer ID set names");
110  params.addParam<std::vector<MeshGeneratorName>>(
111  "exclude_id", "Name of input meshes to be excluded in ID generation");
112  std::vector<MooseEnum> option = {MooseEnum("cell pattern manual", "cell")};
113  params.addParam<std::vector<MooseEnum>>(
114  "assign_type", option, "List of integer ID assignment types");
115  params.addParam<std::vector<std::vector<std::vector<dof_id_type>>>>(
116  "id_pattern",
117  "User-defined element IDs. A double-indexed array starting with the upper-left corner. When "
118  "providing multiple patterns, each pattern should be separated using '|'");
119  params.addParam<std::vector<std::vector<boundary_id_type>>>(
120  "interface_boundary_id_shift_pattern",
121  "User-defined shift values for each pattern cell. A double-indexed array starting with the "
122  "upper-left corner.");
123  MooseEnum quad_elem_type("QUAD4 QUAD8 QUAD9", "QUAD4");
124  params.addParam<MooseEnum>(
125  "boundary_region_element_type",
126  quad_elem_type,
127  "Type of the quadrilateral elements to be generated in the boundary region.");
128  params.addParam<bool>(
129  "allow_unused_inputs",
130  false,
131  "Whether additional input assemblies can be part of inputs without being used in lattice");
132  params.addParamNamesToGroup(
133  "background_block_id background_block_name duct_block_ids boundary_region_element_type "
134  "duct_block_names external_boundary_id external_boundary_name "
135  "create_inward_interface_boundaries create_outward_interface_boundaries",
136  "Customized Subdomain/Boundary");
137  params.addParamNamesToGroup(
138  "generate_control_drum_positions_file assign_control_drum_id position_file", "Control Drum");
139  params.addParamNamesToGroup(
140  "background_intervals duct_intervals uniform_mesh_on_sides deform_non_circular_region",
141  "Mesh Density");
142  params.addParamNamesToGroup("id_name exclude_id assign_type id_pattern", "Reporting ID");
143  params.addClassDescription(
144  "This PatternedHexMeshGenerator source code assembles hexagonal meshes into a hexagonal grid "
145  "and optionally forces the outer boundary to be hexagonal and/or adds a duct.");
146 
147  return params;
148 }
149 
151  : PolygonMeshGeneratorBase(parameters),
152  _mesh_ptrs(getMeshes("inputs")),
153  _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
154  _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern")),
155  _pattern_boundary(getParam<MooseEnum>("pattern_boundary")),
156  _generate_core_metadata(getParam<bool>("generate_core_metadata")),
157  _background_intervals(getParam<unsigned int>("background_intervals")),
158  _has_assembly_duct(isParamValid("duct_sizes")),
159  _duct_sizes(isParamValid("duct_sizes") ? getParam<std::vector<Real>>("duct_sizes")
160  : std::vector<Real>()),
161  _duct_sizes_style(getParam<MooseEnum>("duct_sizes_style").template getEnum<PolygonSizeStyle>()),
162  _duct_intervals(isParamValid("duct_intervals")
163  ? getParam<std::vector<unsigned int>>("duct_intervals")
164  : std::vector<unsigned int>()),
165  _uniform_mesh_on_sides(getParam<bool>("uniform_mesh_on_sides")),
166  _generate_control_drum_positions_file(getParam<bool>("generate_control_drum_positions_file")),
167  _assign_control_drum_id(getParam<bool>("assign_control_drum_id")),
168  _rotate_angle(getParam<Real>("rotate_angle")),
169  _duct_block_ids(isParamValid("duct_block_ids")
170  ? getParam<std::vector<subdomain_id_type>>("duct_block_ids")
171  : std::vector<subdomain_id_type>()),
172  _duct_block_names(getParam<std::vector<SubdomainName>>("duct_block_names")),
173  _external_boundary_id(isParamValid("external_boundary_id")
174  ? getParam<boundary_id_type>("external_boundary_id")
175  : 0),
176  _external_boundary_name(getParam<BoundaryName>("external_boundary_name")),
177  _create_inward_interface_boundaries(getParam<bool>("create_inward_interface_boundaries")),
178  _create_outward_interface_boundaries(getParam<bool>("create_outward_interface_boundaries")),
179  _hexagon_size_style(
180  getParam<MooseEnum>("hexagon_size_style").template getEnum<PolygonSizeStyle>()),
181  _deform_non_circular_region(getParam<bool>("deform_non_circular_region")),
182  _use_reporting_id(isParamValid("id_name")),
183  _use_exclude_id(isParamValid("exclude_id")),
184  _use_interface_boundary_id_shift(isParamValid("interface_boundary_id_shift_pattern")),
185  _boundary_quad_elem_type(
186  getParam<MooseEnum>("boundary_region_element_type").template getEnum<QUAD_ELEM_TYPE>()),
187  _allow_unused_inputs(getParam<bool>("allow_unused_inputs"))
188 {
189  declareMeshProperty("pattern_pitch_meta", 0.0);
190  declareMeshProperty("input_pitch_meta", 0.0);
191  declareMeshProperty<bool>("is_control_drum_meta", false);
192  declareMeshProperty<std::vector<Point>>("control_drum_positions", std::vector<Point>());
193  declareMeshProperty<std::vector<Real>>("control_drum_angles", std::vector<Real>());
194  declareMeshProperty<std::vector<std::vector<Real>>>("control_drums_azimuthal_meta",
195  std::vector<std::vector<Real>>());
196  declareMeshProperty<std::string>("position_file_name", getParam<std::string>("position_file"));
197  declareMeshProperty<bool>("hexagon_peripheral_trimmability", !_generate_core_metadata);
198  declareMeshProperty<bool>("hexagon_center_trimmability", true);
199  declareMeshProperty<bool>("peripheral_modifier_compatible", _pattern_boundary == "hexagon");
200 
201  const unsigned int n_pattern_layers = _pattern.size();
202  declareMeshProperty("pattern_size", n_pattern_layers);
203  if (n_pattern_layers % 2 == 0)
204  paramError(
205  "pattern",
206  "The length (layer number) of this parameter must be odd to ensure a hexagonal shape.");
207  if (n_pattern_layers == 1)
208  paramError("pattern", "The length (layer number) of this parameter must be larger than unity.");
209  std::vector<unsigned int> pattern_max_array;
210  std::vector<unsigned int> pattern_1d;
211  for (unsigned int i = 0; i <= n_pattern_layers / 2; i++)
212  {
213  pattern_max_array.push_back(*std::max_element(_pattern[i].begin(), _pattern[i].end()));
214  pattern_max_array.push_back(*std::max_element(_pattern[n_pattern_layers - 1 - i].begin(),
215  _pattern[n_pattern_layers - 1 - i].end()));
216  pattern_1d.insert(pattern_1d.end(), _pattern[i].begin(), _pattern[i].end());
217  pattern_1d.insert(pattern_1d.end(),
218  _pattern[n_pattern_layers - 1 - i].begin(),
219  _pattern[n_pattern_layers - 1 - i].end());
220  if (_pattern[i].size() != n_pattern_layers / 2 + i + 1 ||
221  _pattern[n_pattern_layers - 1 - i].size() != n_pattern_layers / 2 + i + 1)
222  paramError(
223  "pattern",
224  "The two-dimentional array parameter pattern must have a correct hexagonal shape.");
225  }
226  if (*std::max_element(pattern_max_array.begin(), pattern_max_array.end()) >= _input_names.size())
227  paramError("pattern",
228  "Elements of this parameter must be smaller than the length of input_name.");
229  if (std::set<unsigned int>(pattern_1d.begin(), pattern_1d.end()).size() < _input_names.size() &&
231  paramError("pattern",
232  "All the meshes provided in inputs must be used in the lattice pattern. To bypass "
233  "this requirement, set 'allow_unused_inputs = true'");
234 
235  if (isParamValid("background_block_id"))
236  {
237  _peripheral_block_ids.push_back(getParam<subdomain_id_type>("background_block_id"));
238  _peripheral_block_ids.insert(
240  }
241  else if (!_duct_block_ids.empty())
242  paramError("background_block_id",
243  "This parameter and duct_block_ids must be "
244  "provided simultaneously.");
245  if (isParamValid("background_block_name"))
246  {
247  _peripheral_block_names.push_back(getParam<SubdomainName>("background_block_name"));
250  }
251  else if (!_duct_block_names.empty())
252  paramError("background_block_name",
253  "This parameter and duct_block_names must be provided simultaneously.");
254 
255  if (_pattern_boundary == "hexagon")
256  {
257  for (unsigned int i = 1; i < _duct_sizes.size(); i++)
258  if (_duct_sizes[i] <= _duct_sizes[i - 1])
259  paramError("duct_sizes", "This parameter must be strictly ascending.");
260  if (!_peripheral_block_ids.empty() && _peripheral_block_ids.size() != _duct_sizes.size() + 1)
261  paramError("duct_block_ids",
262  "This parameter, if provided, must have a length equal to length of duct_sizes.");
263  if (!_peripheral_block_names.empty() &&
264  _peripheral_block_names.size() != _duct_sizes.size() + 1)
265  paramError("duct_block_names",
266  "This parameter, if provided, must have a length equal to length of duct_sizes.");
267  }
268  else
269  {
270  if (!_peripheral_block_ids.empty() || !_peripheral_block_names.empty())
271  paramError("background_block_id",
272  "This parameter and background_block_name must not be set when the "
273  "pattern_boundary is none.");
274  }
275 
277  {
278  // check "interface_boundary_id_shift_pattern" parameter
280  getParam<std::vector<std::vector<boundary_id_type>>>("interface_boundary_id_shift_pattern");
281  if (_interface_boundary_id_shift_pattern.size() != _pattern.size())
282  paramError("interface_boundary_id_shift_pattern",
283  "This parameter, if provided, should have the same two-dimensional array shape as "
284  "the 'pattern' parameter. First dimension does not match");
285  for (const auto i : make_range(_pattern.size()))
286  if (_interface_boundary_id_shift_pattern[i].size() != _pattern[i].size())
287  paramError("interface_boundary_id_shift_pattern",
288  "This parameter, if provided, should have the same two-dimensional array shape "
289  "as the 'pattern' parameter.");
290  }
291  // declare metadata for internal interface boundaries
292  declareMeshProperty<bool>("interface_boundaries", false);
293  declareMeshProperty<std::set<boundary_id_type>>("interface_boundary_ids", {});
294 
295  if (_use_reporting_id)
296  {
297  // get reporting id name input
298  _reporting_id_names = getParam<std::vector<std::string>>("id_name");
299  const unsigned int num_reporting_ids = _reporting_id_names.size();
300  // get reporting id assign type input
301  const auto input_assign_types = getParam<std::vector<MooseEnum>>("assign_type");
302  if (input_assign_types.size() != num_reporting_ids)
303  paramError("assign_type", "This parameter must have a length equal to length of id_name.");
304  // list of reporting id names using manual id patterns;
305  std::vector<std::string> manual_ids;
306  for (const auto i : make_range(num_reporting_ids))
307  {
308  _assign_types.push_back(
309  input_assign_types[i].getEnum<ReportingIDGeneratorUtils::AssignType>());
311  manual_ids.push_back(_reporting_id_names[i]);
312  }
313  // processing "id_pattern" input parameter
314  if (manual_ids.size() > 0 && !isParamValid("id_pattern"))
315  paramError("id_pattern", "required when 'manual' is defined in \"assign_type\"");
316  if (isParamValid("id_pattern"))
317  {
318  const auto input_id_patterns =
319  getParam<std::vector<std::vector<std::vector<dof_id_type>>>>("id_pattern");
320  if (input_id_patterns.size() != manual_ids.size())
321  paramError("id_pattern",
322  "The number of patterns must be equal to the number of 'manual' types defined "
323  "in \"assign_type\".");
324  for (unsigned int i = 0; i < manual_ids.size(); ++i)
325  _id_patterns[manual_ids[i]] = input_id_patterns[i];
326  }
327  // processing exlude id
328  _exclude_ids.resize(_input_names.size());
329  if (_use_exclude_id)
330  {
331  std::vector<MeshGeneratorName> exclude_id_name =
332  getParam<std::vector<MeshGeneratorName>>("exclude_id");
333  for (unsigned int i = 0; i < _input_names.size(); ++i)
334  {
335  _exclude_ids[i] = false;
336  for (auto input_name : exclude_id_name)
337  if (_input_names[i] == input_name)
338  {
339  _exclude_ids[i] = true;
340  break;
341  }
342  }
343  }
344  else
345  for (unsigned int i = 0; i < _input_names.size(); ++i)
346  _exclude_ids[i] = false;
347  }
348 }
349 
350 std::unique_ptr<MeshBase>
352 {
353  std::vector<std::unique_ptr<ReplicatedMesh>> meshes(_input_names.size());
354  for (const auto i : index_range(_input_names))
355  {
356  meshes[i] = dynamic_pointer_cast<ReplicatedMesh>(std::move(*_mesh_ptrs[i]));
357  if (!meshes[i])
358  paramError("inputs",
359  "Mesh '",
360  _input_names[i],
361  "' is not a replicated mesh. Only replicated meshes are supported");
362 
363  // throw an error message if the input mesh has a flat side up
364  if (hasMeshProperty<bool>("flat_side_up", _input_names[i]))
365  if (getMeshProperty<bool>("flat_side_up", _input_names[i]))
366  paramError("inputs",
367  "Mesh '",
368  _input_names[i],
369  "' has a flat side facing up, which is not supported.");
370  }
371 
372  std::vector<Real> pitch_array;
373  std::vector<unsigned int> num_sectors_per_side_array;
374  std::vector<unsigned int> num_sectors_per_side_array_tmp;
375  std::vector<std::vector<Real>> control_drum_azimuthal_array;
376  std::vector<unsigned int> background_intervals_array;
377  std::vector<dof_id_type> node_id_background_array;
378  std::vector<Real> max_radius_array;
379  std::vector<bool> is_control_drum_array;
380  Real max_radius_global(0.0);
381  std::vector<Real> pattern_pitch_array;
382 
384  {
385  for (MooseIndex(_input_names) i = 0; i < _input_names.size(); ++i)
386  {
387  // throw an error message if the input mesh does not contain the required meta data
388  if (!hasMeshProperty<Real>("pattern_pitch_meta", _input_names[i]))
389  mooseError(
390  "In PatternedHexMeshGenerator ",
391  _name,
392  ": the unit hexagonal input mesh does not contain appropriate meta data "
393  "required for generating a core mesh. Involved input mesh: ",
394  _input_names[i],
395  "; metadata issue: 'pattern_pitch_meta' is missing. Note that "
396  "'generate_core_metadata' is set to true, which"
397  "means that the mesh generator is producing a core mesh by stitching the input "
398  "assembly meshes together. Therefore,"
399  "the input meshes must contain the metadata of assembly meshes, which can "
400  "usually be either automatically assigned "
401  "by using another PatternedHexMeshGenerator with 'generate_core_metadata' set as "
402  "false and 'pattern_boundary' set as hexagon, or manually assigned by "
403  "AddMetaDataGenerator.");
404  pattern_pitch_array.push_back(getMeshProperty<Real>("pattern_pitch_meta", _input_names[i]));
405  // throw an error message if the input mesh contains non-sense meta data
406  if (pattern_pitch_array.back() == 0.0)
407  mooseError(
408  "In PatternedHexMeshGenerator ",
409  _name,
410  ": the unit hexagonal input mesh does not contain appropriate meta data "
411  "required for generating a core mesh. Involved input mesh: ",
412  _input_names[i],
413  "; metadata issue: 'pattern_pitch_meta' is zero. Note that "
414  "'generate_core_metadata' is set to true, which"
415  "means that the mesh generator is producing a core mesh by stitching the input "
416  "assembly meshes together. Therefore,"
417  "the input meshes must contain the metadata of assembly meshes, which can "
418  "usually be either automatically assigned "
419  "by using another PatternedHexMeshGenerator with 'generate_core_metadata' set as "
420  "false and 'pattern_boundary' set as hexagon, or manually assigned by "
421  "AddMetaDataGenerator.");
422  is_control_drum_array.push_back(
423  getMeshProperty<bool>("is_control_drum_meta", _input_names[i]));
424  control_drum_azimuthal_array.push_back(
425  getMeshProperty<bool>("is_control_drum_meta", _input_names[i])
426  ? getMeshProperty<std::vector<Real>>("azimuthal_angle_meta", _input_names[i])
427  : std::vector<Real>());
428  }
430  *std::max_element(pattern_pitch_array.begin(), pattern_pitch_array.end()),
431  *std::min_element(pattern_pitch_array.begin(), pattern_pitch_array.end())))
432  mooseError(
433  "In PatternedHexMeshGenerator ",
434  _name,
435  ": pattern_pitch metadata values of all input mesh generators must be identical "
436  "when pattern_boundary is 'none' and generate_core_metadata is true. Please check the "
437  "parameters of the mesh generators that produce the input meshes."
438  "Note that some of these mesh generators, such as "
439  "HexagonConcentricCircleAdaptiveBoundaryMeshGenerator and FlexiblePatternGenerator,"
440  "may have different definitions of hexagon size in their input parameters. Please refer "
441  "to the documentation of these mesh generators.",
442  pitchMetaDataErrorGenerator(_input_names, pattern_pitch_array, "pattern_pitch_meta"));
443  else
444  {
445  _pattern_pitch = pattern_pitch_array.front();
446  setMeshProperty("input_pitch_meta", _pattern_pitch);
447  }
448  }
449  else
450  {
451  if (_pattern_boundary == "hexagon")
452  {
453  if (!isParamValid("hexagon_size"))
454  paramError("hexagon_size",
455  "This parameter must be provided when pattern_boundary is hexagon.");
456  else
458  ? getParam<Real>("hexagon_size")
459  : getParam<Real>("hexagon_size") * std::cos(M_PI / 6.0));
460  }
461  for (MooseIndex(_input_names) i = 0; i < _input_names.size(); ++i)
462  {
463  // throw an error message if the input mesh does not contain the required meta data
464  if (!hasMeshProperty<Real>("pitch_meta", _input_names[i]))
465  mooseError("In PatternedHexMeshGenerator ",
466  _name,
467  ": the unit hexagonal input mesh does not contain appropriate meta data "
468  "required for generating an assembly. Involved input mesh: ",
469  _input_names[i],
470  "; metadata issue: 'pitch_meta' is missing");
471  pitch_array.push_back(getMeshProperty<Real>("pitch_meta", _input_names[i]));
472 
473  num_sectors_per_side_array_tmp =
474  getMeshProperty<std::vector<unsigned int>>("num_sectors_per_side_meta", _input_names[i]);
475  if (*std::max_element(num_sectors_per_side_array_tmp.begin(),
476  num_sectors_per_side_array_tmp.end()) !=
477  *std::min_element(num_sectors_per_side_array_tmp.begin(),
478  num_sectors_per_side_array_tmp.end()))
479  mooseError("In PatternedHexMeshGenerator ",
480  _name,
481  ": num_sectors_per_side metadata values of all six sides of each input mesh "
482  "generator must be identical.");
483  if (num_sectors_per_side_array_tmp.front() == 1 && _pattern_boundary == "hexagon")
484  paramError(
485  "inputs",
486  "for each input mesh, the number of sectors on each side must be greater than unity.");
487  num_sectors_per_side_array.push_back(*num_sectors_per_side_array_tmp.begin());
488  background_intervals_array.push_back(
489  getMeshProperty<unsigned int>("background_intervals_meta", _input_names[i]));
490  node_id_background_array.push_back(
491  getMeshProperty<dof_id_type>("node_id_background_meta", _input_names[i]));
492  max_radius_array.push_back(getMeshProperty<Real>("max_radius_meta", _input_names[i]));
493  }
494  max_radius_global = *max_element(max_radius_array.begin(), max_radius_array.end());
495  if (!MooseUtils::absoluteFuzzyEqual(*std::max_element(pitch_array.begin(), pitch_array.end()),
496  *std::min_element(pitch_array.begin(), pitch_array.end())))
497  mooseError("In PatternedHexMeshGenerator ",
498  _name,
499  ": pitch metadata values of all input mesh generators must be identical. Please "
500  "check the "
501  "parameters of the mesh generators that produce the input meshes.",
502  pitchMetaDataErrorGenerator(_input_names, pitch_array, "pitch_meta"));
503  setMeshProperty("input_pitch_meta", pitch_array.front());
504  if (*std::max_element(num_sectors_per_side_array.begin(), num_sectors_per_side_array.end()) !=
505  *std::min_element(num_sectors_per_side_array.begin(), num_sectors_per_side_array.end()))
506  mooseError(
507  "In PatternedHexMeshGenerator ",
508  _name,
509  ": num_sectors_per_side metadata values of all input mesh generators must be identical.");
510  }
511 
512  std::vector<Real> extra_dist;
513  Real extra_dist_shift(0.0);
514  Real y_min(0.0);
515  Real y_max_0(0.0);
516  Real y_max_n(0.0);
517  const Real extra_dist_tol = _pattern_boundary == "hexagon" ? pitch_array.front() / 10.0 : 0.0;
518  const Real extra_dist_shift_0 = _pattern_boundary == "hexagon" ? pitch_array.front() / 5.0 : 0.0;
519  std::vector<unsigned int> peripheral_duct_intervals;
520  if (_pattern_boundary == "hexagon")
521  {
522  if (_has_assembly_duct)
523  {
524  for (unsigned int i = 0; i < _duct_sizes.size(); i++)
525  {
527  _duct_sizes[i] /= std::cos(M_PI / 6.0);
528  extra_dist.push_back(0.5 *
529  (_duct_sizes[i] * 2.0 - pitch_array.front() / std::sqrt(3.0) *
530  (Real)((_pattern.size() / 2) * 3 + 2)));
531  peripheral_duct_intervals.push_back(_duct_intervals[i]);
532  }
533  if (_duct_sizes.back() >= _pattern_pitch / 2.0)
534  paramError("duct_sizes",
535  "The duct sizes should not exceed the size of the hexagonal boundary.");
536  }
537  // calculate the distance between the larger hexagon boundary and the boundary of stitched unit
538  // hexagons this is used to decide whether deformation is needed when cut-off happens or when
539  // the distance is small.
540  extra_dist.push_back(0.5 * (_pattern_pitch - pitch_array.front() / std::sqrt(3.0) *
541  (Real)((_pattern.size() / 2) * 3 + 2)));
542  peripheral_duct_intervals.insert(peripheral_duct_intervals.begin(), _background_intervals);
543 
544  // In same cases, when the external hexagon size is small enough, the external hexagon boundary
545  // may either be very close to the input hexagon meshes that are near the boundary or even cut
546  // off the these hexagons. As long as the ring regions are not cut off, the input hexagons can
547  // be deformed to accommodate the external hexagon shape. This block sets up the range of mesh
548  // region that needs to be deformed.
549  if (extra_dist.front() <= extra_dist_tol)
550  {
551  extra_dist_shift = extra_dist_shift_0 - extra_dist.front();
552  for (Real & d : extra_dist)
553  d += extra_dist_shift;
555  ? max_radius_global // Currently use this, ideally this should be the max of the
556  // outer layer radii
557  : (pitch_array.front() / std::sqrt(3.0));
558  y_max_0 = pitch_array.front() / std::sqrt(3.0) + extra_dist.front();
559  y_max_n = y_max_0 - extra_dist_shift;
560  if (y_max_n <= y_min)
561  mooseError("In PatternedHexMeshGenerator ",
562  _name,
563  ": the assembly is cut off so much that the internal structure that should not "
564  "be altered is compromised.");
565  }
566  }
567 
568  setMeshProperty("pattern_pitch_meta", _pattern_pitch);
569 
570  // create a list of interface boundary ids for each input mesh
571  // NOTE: list of interface boundary ids is stored in mesh metadata
572  std::vector<std::set<boundary_id_type>> input_interface_boundary_ids;
573  input_interface_boundary_ids.resize(_input_names.size());
575  {
576  for (const auto i : make_range(_input_names.size()))
577  {
578  if (!hasMeshProperty<bool>("interface_boundaries", _input_names[i]))
579  mooseError("Metadata 'interface_boundaries' could not be found on the input mesh: ",
580  _input_names[i]);
581  if (!getMeshProperty<bool>("interface_boundaries", _input_names[i]))
582  mooseError("Interface boundary ids were not constructed in the input mesh",
583  _input_names[i]);
584  if (!hasMeshProperty<std::set<boundary_id_type>>("interface_boundary_ids", _input_names[i]))
585  mooseError("Metadata 'interface_boundary_ids' could not be found on the input mesh: ",
586  _input_names[i]);
587  }
588  }
589  for (const auto i : make_range(_input_names.size()))
591  hasMeshProperty<std::set<boundary_id_type>>("interface_boundary_ids", _input_names[i]))
592  input_interface_boundary_ids[i] =
593  getMeshProperty<std::set<boundary_id_type>>("interface_boundary_ids", _input_names[i]);
594 
595  int x_mov = 0;
596 
597  const Real input_pitch((_pattern_boundary == "hexagon" || !_generate_core_metadata)
598  ? pitch_array.front()
599  : _pattern_pitch);
600  std::vector<Real> control_drum_positions_x;
601  std::vector<Real> control_drum_positions_y;
602  std::vector<std::vector<Real>> control_drum_azimuthals;
603 
604  std::unique_ptr<ReplicatedMesh> out_mesh;
605 
606  for (unsigned i = 0; i < _pattern.size(); i++)
607  {
608  Real deltax = -x_mov * input_pitch / 2;
609  if (i == _pattern.size() - 1)
610  x_mov--;
611  else if (_pattern[i].size() < _pattern[i + 1].size())
612  x_mov++;
613  else
614  x_mov--;
615  Real deltay = -(Real)(i)*input_pitch * std::sin(M_PI / 3);
616 
617  for (unsigned int j = 0; j < _pattern[i].size(); j++)
618  {
619  const auto pattern = _pattern[i][j];
620  ReplicatedMesh & pattern_mesh = *meshes[pattern];
621 
622  // No pattern boundary, so no need to wrap with a peripheral mesh
623  // Use the inputs as they stand and translate accordingly later
624  if (_pattern_boundary == "none")
625  {
626  if (_generate_core_metadata && is_control_drum_array[pattern] == 1)
627  {
628  control_drum_positions_x.push_back(deltax + j * input_pitch);
629  control_drum_positions_y.push_back(deltay);
630  control_drum_azimuthals.push_back(control_drum_azimuthal_array[pattern]);
631  }
632 
633  if (j == 0 && i == 0)
634  {
635  out_mesh = dynamic_pointer_cast<ReplicatedMesh>(pattern_mesh.clone());
637  out_mesh->add_elem_integer(
638  "control_drum_id",
639  true,
640  is_control_drum_array[pattern] ? control_drum_azimuthals.size() : 0);
641  // Reassign interface boundary ids
643  reassignBoundaryIDs(*out_mesh,
645  input_interface_boundary_ids[pattern]);
646  continue;
647  }
648  }
649  // Has a pattern boundary so we need to wrap with a peripheral mesh
650  else // has a pattern boundary
651  {
652  Real rotation_angle = std::numeric_limits<Real>::max();
653  Real orientation = std::numeric_limits<Real>::max();
654  unsigned int mesh_type = std::numeric_limits<unsigned int>::max();
655  bool on_periphery = true;
656 
657  if (j == 0 && i == 0)
658  {
659  rotation_angle = 60.;
660  mesh_type = CORNER_MESH;
661  orientation = 0.;
662  }
663  else if (j == 0 && i == _pattern.size() - 1)
664  {
665  rotation_angle = 180.;
666  mesh_type = CORNER_MESH;
667  orientation = -120.;
668  }
669  else if (j == 0 && i == (_pattern.size() - 1) / 2)
670  {
671  rotation_angle = 120.;
672  mesh_type = CORNER_MESH;
673  orientation = -60.;
674  }
675  else if (j == _pattern[i].size() - 1 && i == 0)
676  {
677  rotation_angle = 0.;
678  mesh_type = CORNER_MESH;
679  orientation = 60.;
680  }
681  else if (j == _pattern[i].size() - 1 && i == _pattern.size() - 1)
682  {
683  rotation_angle = -120.;
684  mesh_type = CORNER_MESH;
685  orientation = 180.;
686  }
687  else if (j == _pattern[i].size() - 1 && i == (_pattern.size() - 1) / 2)
688  {
689  rotation_angle = -60.;
690  mesh_type = CORNER_MESH;
691  orientation = 120.;
692  }
693  else if (i == 0)
694  {
695  rotation_angle = 0.;
696  mesh_type = BOUNDARY_MESH;
697  orientation = 0.;
698  }
699  else if (i == _pattern.size() - 1)
700  {
701  rotation_angle = 180.;
702  mesh_type = BOUNDARY_MESH;
703  orientation = -180.;
704  }
705  else if (j == 0 && i < (_pattern.size() - 1) / 2)
706  {
707  rotation_angle = 60.;
708  mesh_type = BOUNDARY_MESH;
709  orientation = -60.;
710  }
711  else if (j == 0 && i > (_pattern.size() - 1) / 2)
712  {
713  rotation_angle = 120.;
714  mesh_type = BOUNDARY_MESH;
715  orientation = -120.;
716  }
717  else if (j == _pattern[i].size() - 1 && i < (_pattern.size() - 1) / 2)
718  {
719  rotation_angle = -60.;
720  mesh_type = BOUNDARY_MESH;
721  orientation = 60.;
722  }
723  else if (j == _pattern[i].size() - 1 && i > (_pattern.size() - 1) / 2)
724  {
725  rotation_angle = -120.;
726  mesh_type = BOUNDARY_MESH;
727  orientation = 120;
728  }
729  else
730  on_periphery = false;
731 
732  if (on_periphery)
733  {
734  auto tmp_peripheral_mesh = dynamic_pointer_cast<ReplicatedMesh>(pattern_mesh.clone());
735  addPeripheralMesh(*tmp_peripheral_mesh,
736  pattern,
737  pitch_array.front(),
738  extra_dist,
739  num_sectors_per_side_array,
740  peripheral_duct_intervals,
741  rotation_angle,
742  mesh_type);
743 
744  if (extra_dist_shift != 0)
745  cutOffPolyDeform(*tmp_peripheral_mesh, orientation, y_max_0, y_max_n, y_min, mesh_type);
746 
747  // Reassign interface boundary ids
749  reassignBoundaryIDs(*tmp_peripheral_mesh,
751  input_interface_boundary_ids[pattern]);
752 
753  if (i == 0 && j == 0)
754  out_mesh = std::move(tmp_peripheral_mesh);
755  else
756  {
757  // Retrieve subdomain name map from the mesh to be stitched and insert it to the main
758  // subdomain map
759  const auto & increment_subdomain_map = tmp_peripheral_mesh->get_subdomain_name_map();
760  out_mesh->set_subdomain_name_map().insert(increment_subdomain_map.begin(),
761  increment_subdomain_map.end());
762 
763  MeshTools::Modification::translate(
764  *tmp_peripheral_mesh, deltax + j * input_pitch, deltay, 0);
765  out_mesh->stitch_meshes(*tmp_peripheral_mesh,
768  TOLERANCE,
769  /*clear_stitched_boundary_ids=*/true,
770  /*verbose=*/false);
771  }
772 
773  continue;
774  }
775  }
776 
778  pattern_mesh.add_elem_integer(
779  "control_drum_id",
780  true,
781  is_control_drum_array[pattern] ? control_drum_azimuthals.size() : 0);
782 
783  // Translate to correct position
784  MeshTools::Modification::translate(pattern_mesh, deltax + j * input_pitch, deltay, 0);
785 
786  // Define a reference map variable for subdomain map
787  auto & main_subdomain_map = out_mesh->set_subdomain_name_map();
788  // Retrieve subdomain name map from the mesh to be stitched and insert it to the main
789  // subdomain map
790  const auto & increment_subdomain_map = pattern_mesh.get_subdomain_name_map();
791  main_subdomain_map.insert(increment_subdomain_map.begin(), increment_subdomain_map.end());
792  // Check if one SubdomainName is shared by more than one subdomain ids
793  std::set<SubdomainName> main_subdomain_map_name_list;
794  for (auto const & id_name_pair : main_subdomain_map)
795  main_subdomain_map_name_list.emplace(id_name_pair.second);
796  if (main_subdomain_map.size() != main_subdomain_map_name_list.size())
797  paramError("inputs", "The input meshes contain subdomain name maps with conflicts.");
798  // Shift interface boundary ids
800  reassignBoundaryIDs(pattern_mesh,
802  input_interface_boundary_ids[pattern]);
803 
804  out_mesh->stitch_meshes(pattern_mesh,
807  TOLERANCE,
808  /*clear_stitched_boundary_ids=*/false,
809  /*verbose=*/false);
810 
811  // Translate back now that we've stitched so that anyone else that uses this mesh has it at
812  // the origin
813  MeshTools::Modification::translate(pattern_mesh, -(deltax + j * input_pitch), -deltay, 0);
814  // Roll back the changes in interface boundary ids for the same reason
816  reassignBoundaryIDs(pattern_mesh,
818  input_interface_boundary_ids[pattern],
819  true);
820  }
821  }
822 
823  // Check if OUTER_SIDESET_ID is really the external boundary. Correct if needed.
824  auto side_list = out_mesh->get_boundary_info().build_side_list();
825  for (auto & sl : side_list)
826  {
827  if (std::get<2>(sl) == OUTER_SIDESET_ID)
828  if (out_mesh->elem_ptr(std::get<0>(sl))->neighbor_ptr(std::get<1>(sl)) != nullptr)
829  out_mesh->get_boundary_info().remove_side(
830  out_mesh->elem_ptr(std::get<0>(sl)), std::get<1>(sl), std::get<2>(sl));
831  }
832  out_mesh->get_boundary_info().clear_boundary_node_ids();
833 
834  out_mesh->get_boundary_info().build_node_list_from_side_list();
835  const auto node_list = out_mesh->get_boundary_info().build_node_list();
836 
837  std::vector<Real> bd_x_list;
838  std::vector<Real> bd_y_list;
839  std::vector<std::pair<Real, dof_id_type>> node_azi_list;
840  const Point origin_pt = MooseMeshUtils::meshCentroidCalculator(*out_mesh);
841  const Real origin_x = origin_pt(0);
842  const Real origin_y = origin_pt(1);
843 
844  MeshTools::Modification::translate(*out_mesh, -origin_x, -origin_y, 0);
845 
847  {
848  const Real azi_tol = 1E-8;
849  for (unsigned int i = 0; i < node_list.size(); ++i)
850  {
851  if (std::get<1>(node_list[i]) == OUTER_SIDESET_ID)
852  {
853  node_azi_list.push_back(
854  std::make_pair(atan2(out_mesh->node_ref(std::get<0>(node_list[i]))(1),
855  out_mesh->node_ref(std::get<0>(node_list[i]))(0)) *
856  180.0 / M_PI,
857  std::get<0>(node_list[i])));
858  // correct the last node's angle value (180.0) if it becomes a negative value.
859  if (node_azi_list.back().first + 180.0 <= azi_tol)
860  node_azi_list.back().first = 180;
861  }
862  }
863  std::sort(node_azi_list.begin(), node_azi_list.end());
864  const unsigned int side_intervals = node_azi_list.size() / HEXAGON_NUM_SIDES;
865  for (unsigned int i = 0; i < HEXAGON_NUM_SIDES; i++)
866  {
867  for (unsigned int j = 1; j <= side_intervals; j++)
868  {
869  Real azi_corr_tmp = atan2((Real)j * 2.0 / (Real)side_intervals - 1.0, std::sqrt(3.0));
870  Real x_tmp = _pattern_pitch / 2.0;
871  Real y_tmp = x_tmp * std::tan(azi_corr_tmp);
872  nodeCoordRotate(x_tmp, y_tmp, (Real)i * 60.0 - 150.0);
873  Point p_tmp = Point(x_tmp, y_tmp, 0.0);
874  out_mesh->add_point(p_tmp, node_azi_list[i * side_intervals + j - 1].second);
875  }
876  }
877 
878  // if quadratic elements are used, additional nodes need to be adjusted based on the new
879  // boundary node locations. adjust side mid-edge nodes to the midpoints of the corner
880  // points, and if QUAD9, adjust center point to new centroid.
883  }
884 
885  MeshTools::Modification::rotate(*out_mesh, _rotate_angle, 0.0, 0.0);
886 
887  // This combination of input parameters is usually used for core mesh generation by stitching
888  // assembly meshes together.
890  {
891  const Real azi_tol = 1E-8;
892  std::vector<std::tuple<Real, Point, std::vector<Real>, dof_id_type>> control_drum_tmp;
893  std::vector<dof_id_type> control_drum_id_sorted;
894  for (unsigned int i = 0; i < control_drum_positions_x.size(); ++i)
895  {
896  control_drum_positions_x[i] -= origin_x;
897  control_drum_positions_y[i] -= origin_y;
898  nodeCoordRotate(control_drum_positions_x[i], control_drum_positions_y[i], _rotate_angle);
899  Real cd_angle = atan2(control_drum_positions_y[i], control_drum_positions_x[i]);
900 
901  for (unsigned int j = 0; j < control_drum_azimuthals[i].size(); j++)
902  {
903  control_drum_azimuthals[i][j] += _rotate_angle;
904  control_drum_azimuthals[i][j] =
905  atan2(std::sin(control_drum_azimuthals[i][j] / 180.0 * M_PI),
906  std::cos(control_drum_azimuthals[i][j] / 180.0 * M_PI)) /
907  M_PI * 180.0; // quick way to move to -M_PI to M_PI
908  }
909  std::sort(control_drum_azimuthals[i].begin(), control_drum_azimuthals[i].end());
910 
911  if (std::abs(cd_angle) < azi_tol)
912  cd_angle = 0;
913  else if (cd_angle < 0.0)
914  cd_angle += 2 * M_PI;
915  control_drum_tmp.push_back(
916  std::make_tuple(cd_angle,
917  Point(control_drum_positions_x[i], control_drum_positions_y[i], 0.0),
918  control_drum_azimuthals[i],
919  i + 1)); // control drum index to help sort control_drum_id
920  }
921  std::sort(control_drum_tmp.begin(), control_drum_tmp.end());
922  std::vector<Point> control_drum_positions;
923  std::vector<Real> control_drum_angles;
924  std::vector<std::vector<Real>> control_drums_azimuthal_meta;
925  for (unsigned int i = 0; i < control_drum_tmp.size(); ++i)
926  {
927  control_drum_positions.push_back(std::get<1>(control_drum_tmp[i]));
928  control_drum_angles.push_back(std::get<0>(control_drum_tmp[i]));
929  control_drums_azimuthal_meta.push_back(std::get<2>(control_drum_tmp[i]));
930  control_drum_id_sorted.push_back(std::get<3>(control_drum_tmp[i]));
931  }
932  setMeshProperty("control_drum_positions", control_drum_positions);
933  setMeshProperty("control_drum_angles", control_drum_angles);
934  setMeshProperty("control_drums_azimuthal_meta", control_drums_azimuthal_meta);
935 
937  {
938  unsigned int id = out_mesh->get_elem_integer_index("control_drum_id");
939  for (const auto & elem : out_mesh->element_ptr_range())
940  {
941  dof_id_type unsorted_control_drum_id = elem->get_extra_integer(id);
942  if (unsorted_control_drum_id != 0)
943  {
944  auto sorted_iter = std::find(control_drum_id_sorted.begin(),
945  control_drum_id_sorted.end(),
946  unsorted_control_drum_id);
947  elem->set_extra_integer(id,
948  std::distance(control_drum_id_sorted.begin(), sorted_iter) + 1);
949  }
950  }
951  }
952 
954  {
955  std::string position_file_name = getMeshProperty<std::string>("position_file_name", name());
956  std::ofstream pos_file(position_file_name);
957  for (unsigned int i = 0; i < control_drum_positions.size(); ++i)
958  pos_file << control_drum_positions[i](0) << " " << control_drum_positions[i](1) << " 0.0\n";
959  pos_file.close();
960  }
961  }
962 
963  // before return, add reporting IDs if _use_reporting_id is set true
964  if (_use_reporting_id)
965  addReportingIDs(*out_mesh, meshes);
966 
967  // Assign customized peripheral block ids and names
968  if (!_peripheral_block_ids.empty())
969  for (const auto & elem : out_mesh->active_element_ptr_range())
971  i++)
972  if (elem->subdomain_id() == i)
973  {
974  elem->subdomain_id() = _peripheral_block_ids[i - PERIPHERAL_ID_SHIFT];
975  break;
976  }
977  if (!_peripheral_block_names.empty())
978  {
979  for (unsigned i = 0; i < _peripheral_block_names.size(); i++)
980  out_mesh->subdomain_name(_peripheral_block_ids.empty() ? (PERIPHERAL_ID_SHIFT + i)
981  : _peripheral_block_ids[i]) =
983  }
984  // Assign customized outer surface boundary id and name
985  if (_external_boundary_id > 0)
987  if (!_external_boundary_name.empty())
988  {
989  out_mesh->get_boundary_info().sideset_name(
992  out_mesh->get_boundary_info().nodeset_name(
995  }
996  // Merge the boundary name maps of all the input meshed to generate the output mesh's boundary
997  // name maps
998  auto & new_sideset_map = out_mesh->get_boundary_info().set_sideset_name_map();
999  auto & new_nodeset_map = out_mesh->get_boundary_info().set_nodeset_name_map();
1000  for (unsigned int i = 0; i < meshes.size(); i++)
1001  {
1002  const auto input_sideset_map = meshes[i]->get_boundary_info().get_sideset_name_map();
1003  new_sideset_map.insert(input_sideset_map.begin(), input_sideset_map.end());
1004  const auto input_nodeset_map = meshes[i]->get_boundary_info().get_nodeset_name_map();
1005  new_nodeset_map.insert(input_nodeset_map.begin(), input_nodeset_map.end());
1006  }
1007 
1008  // set mesh metadata related with interface boundary ids
1009  const std::set<boundary_id_type> boundary_ids = out_mesh->get_boundary_info().get_boundary_ids();
1010  const std::set<boundary_id_type> interface_boundary_ids = getInterfaceBoundaryIDs(
1011  _pattern,
1013  boundary_ids,
1014  input_interface_boundary_ids,
1017  extra_dist.size());
1018  if (interface_boundary_ids.size() > 0)
1019  {
1020  setMeshProperty("interface_boundaries", true);
1021  setMeshProperty("interface_boundary_ids", interface_boundary_ids);
1022  }
1023 
1024  out_mesh->set_isnt_prepared();
1025  auto mesh = dynamic_pointer_cast<MeshBase>(out_mesh);
1026  return mesh;
1027 }
1028 
1029 void
1031  ReplicatedMesh & mesh,
1032  const unsigned int pattern,
1033  const Real pitch,
1034  const std::vector<Real> & extra_dist,
1035  const std::vector<unsigned int> & num_sectors_per_side_array,
1036  const std::vector<unsigned int> & peripheral_duct_intervals,
1037  const Real rotation_angle,
1038  const unsigned int mesh_type)
1039 {
1040  std::vector<std::pair<Real, Real>> positions_inner;
1041  std::vector<std::pair<Real, Real>> d_positions_outer;
1042 
1043  std::vector<std::vector<unsigned int>> peripheral_point_index;
1044  std::vector<std::pair<Real, Real>> sub_positions_inner;
1045  std::vector<std::pair<Real, Real>> sub_d_positions_outer;
1046 
1047  if (mesh_type == CORNER_MESH)
1048  // corner mesh has three sides that need peripheral meshes.
1049  // each element has three sub-elements, representing beginning, middle, and ending azimuthal
1050  // points
1051  peripheral_point_index = {{0, 1, 2}, {2, 3, 4}, {4, 5, 6}};
1052  else
1053  // side mesh has two sides that need peripheral meshes.
1054  peripheral_point_index = {{0, 1, 2}, {2, 7, 8}};
1055 
1056  // extra_dist includes background and ducts.
1057  // Loop to calculate the positions of the boundaries.
1058  for (unsigned int i = 0; i < extra_dist.size(); i++)
1059  {
1060  positionSetup(positions_inner,
1061  d_positions_outer,
1062  i == 0 ? 0.0 : extra_dist[i - 1],
1063  extra_dist[i],
1064  pitch,
1065  i);
1066 
1067  // Loop for all applicable sides that need peripheral mesh (3 for corner and 2 for edge)
1068  for (unsigned int peripheral_index = 0; peripheral_index < peripheral_point_index.size();
1069  peripheral_index++)
1070  {
1071  // Loop for beginning, middle and ending positions of a side
1072  for (unsigned int vector_index = 0; vector_index < 3; vector_index++)
1073  {
1074  sub_positions_inner.push_back(
1075  positions_inner[peripheral_point_index[peripheral_index][vector_index]]);
1076  sub_d_positions_outer.push_back(
1077  d_positions_outer[peripheral_point_index[peripheral_index][vector_index]]);
1078  }
1079  auto meshp0 = buildSimplePeripheral(num_sectors_per_side_array[pattern],
1080  peripheral_duct_intervals[i],
1081  sub_positions_inner,
1082  sub_d_positions_outer,
1083  i,
1086  (i != extra_dist.size() - 1) &&
1088 
1089  // The other_mesh must be prepared before stitching
1090  meshp0->prepare_for_use();
1091 
1092  // rotate the peripheral mesh to the desired side of the hexagon.
1093  MeshTools::Modification::rotate(*meshp0, rotation_angle, 0, 0);
1094  mesh.stitch_meshes(*meshp0, OUTER_SIDESET_ID, OUTER_SIDESET_ID, TOLERANCE, true, false);
1095  sub_positions_inner.resize(0);
1096  sub_d_positions_outer.resize(0);
1097  }
1098  }
1099 }
1100 
1101 void
1102 PatternedHexMeshGenerator::positionSetup(std::vector<std::pair<Real, Real>> & positions_inner,
1103  std::vector<std::pair<Real, Real>> & d_positions_outer,
1104  const Real extra_dist_in,
1105  const Real extra_dist_out,
1106  const Real pitch,
1107  const unsigned int radial_index) const
1108 {
1109  positions_inner.resize(0);
1110  d_positions_outer.resize(0);
1111 
1112  // Nine sets of positions are generated here, as shown below.
1113  // CORNER MESH Peripheral {0 1 2}, {2 3 4} and {4 5 6}
1114  // 3 2 1 0
1115  // \ : : :
1116  // \ : : :
1117  // 4. . : :
1118  // ` . :
1119  // 5. | |
1120  // ` | |
1121  // 6. | |
1122  // ` | |
1123  // . .
1124  // .
1125  //
1126  // EDGE MESH Peripheral {0 1 2} and {2 7 8}
1127  // 8 7 2 1 0
1128  // : : : : :
1129  // : : : : :
1130  // : : : :
1131  // : :
1132  // | |
1133  // | |
1134  // | |
1135  // | |
1136  // . .
1137  // .
1138 
1139  positions_inner.push_back(
1140  std::make_pair(-pitch / 2.0,
1141  std::sqrt(3.0) * pitch / 6.0 +
1142  (radial_index != 0 ? std::sqrt(3.0) * pitch / 6.0 + extra_dist_in : 0.0)));
1143  positions_inner.push_back(std::make_pair(
1144  -pitch / 4.0,
1145  std::sqrt(3.0) * pitch / 4.0 +
1146  (radial_index != 0 ? std::sqrt(3.0) * pitch / 12.0 + extra_dist_in : 0.0)));
1147  positions_inner.push_back(std::make_pair(
1148  0.0, std::sqrt(3.0) * pitch / 3.0 + (radial_index != 0 ? extra_dist_in : 0.0)));
1149  positions_inner.push_back(std::make_pair(
1150  pitch / 4.0 + (radial_index != 0 ? pitch / 12.0 + std::sqrt(3.0) * extra_dist_in / 3.0 : 0.0),
1151  std::sqrt(3.0) * pitch / 4.0 +
1152  (radial_index != 0 ? pitch * std::sqrt(3.0) / 12.0 + extra_dist_in : 0.0)));
1153  positions_inner.push_back(std::make_pair(
1154  pitch / 2.0 + (radial_index != 0 ? std::sqrt(3.0) * extra_dist_in / 2.0 : 0.0),
1155  std::sqrt(3.0) * pitch / 6.0 + (radial_index != 0 ? extra_dist_in / 2.0 : 0.0)));
1156  positions_inner.push_back(std::make_pair(
1157  pitch / 2.0 + (radial_index != 0 ? pitch / 8.0 + std::sqrt(3.0) * extra_dist_in / 2.0 : 0.0),
1158  0.0 + (radial_index != 0 ? std::sqrt(3.0) * pitch / 24.0 + extra_dist_in / 2.0 : 0.0)));
1159  positions_inner.push_back(std::make_pair(
1160  pitch / 2.0 + (radial_index != 0 ? pitch / 4.0 + std::sqrt(3.0) * extra_dist_in / 2.0 : 0.0),
1161  -std::sqrt(3.0) * pitch / 6.0 +
1162  (radial_index != 0 ? std::sqrt(3.0) * pitch / 12.0 + extra_dist_in / 2.0 : 0.0)));
1163  positions_inner.push_back(std::make_pair(
1164  pitch / 4.0,
1165  std::sqrt(3.0) * pitch / 4.0 +
1166  (radial_index != 0 ? std::sqrt(3.0) * pitch / 12.0 + extra_dist_in : 0.0)));
1167  positions_inner.push_back(
1168  std::make_pair(pitch / 2.0,
1169  std::sqrt(3.0) * pitch / 6.0 +
1170  (radial_index != 0 ? std::sqrt(3.0) * pitch / 6.0 + extra_dist_in : 0.0)));
1171 
1172  d_positions_outer.push_back(
1173  std::make_pair(0.0,
1174  std::sqrt(3.0) * pitch / 6.0 + extra_dist_out -
1175  (radial_index != 0 ? std::sqrt(3.0) * pitch / 6.0 + extra_dist_in : 0.0)));
1176  d_positions_outer.push_back(std::make_pair(
1177  0.0,
1178  std::sqrt(3.0) * pitch / 12.0 + extra_dist_out -
1179  (radial_index != 0 ? std::sqrt(3.0) * pitch / 12.0 + extra_dist_in : 0.0)));
1180  d_positions_outer.push_back(
1181  std::make_pair(0.0, extra_dist_out - (radial_index != 0 ? extra_dist_in : 0.0)));
1182  d_positions_outer.push_back(std::make_pair(
1183  pitch / 12.0 + std::sqrt(3.0) * extra_dist_out / 3.0 -
1184  (radial_index != 0 ? pitch / 12.0 + std::sqrt(3.0) * extra_dist_in / 3.0 : 0.0),
1185  pitch * std::sqrt(3.0) / 12.0 + extra_dist_out -
1186  (radial_index != 0 ? pitch * std::sqrt(3.0) / 12.0 + extra_dist_in : 0.0)));
1187  d_positions_outer.push_back(
1188  std::make_pair(std::sqrt(3.0) * extra_dist_out / 2.0 -
1189  (radial_index != 0 ? std::sqrt(3.0) * extra_dist_in / 2.0 : 0.0),
1190  extra_dist_out / 2.0 - (radial_index != 0 ? extra_dist_in / 2.0 : 0.0)));
1191  d_positions_outer.push_back(std::make_pair(
1192  pitch / 8.0 + std::sqrt(3.0) * extra_dist_out / 2.0 -
1193  (radial_index != 0 ? pitch / 8.0 + std::sqrt(3.0) * extra_dist_in / 2.0 : 0.0),
1194  std::sqrt(3.0) * pitch / 24.0 + extra_dist_out / 2.0 -
1195  (radial_index != 0 ? std::sqrt(3.0) * pitch / 24.0 + extra_dist_in / 2.0 : 0.0)));
1196  d_positions_outer.push_back(std::make_pair(
1197  pitch / 4.0 + std::sqrt(3.0) * extra_dist_out / 2.0 -
1198  (radial_index != 0 ? pitch / 4.0 + std::sqrt(3.0) * extra_dist_in / 2.0 : 0.0),
1199  std::sqrt(3.0) * pitch / 12.0 + extra_dist_out / 2.0 -
1200  (radial_index != 0 ? std::sqrt(3.0) * pitch / 12.0 + extra_dist_in / 2.0 : 0.0)));
1201  d_positions_outer.push_back(std::make_pair(
1202  0.0,
1203  std::sqrt(3.0) * pitch / 12.0 + extra_dist_out -
1204  (radial_index != 0 ? std::sqrt(3.0) * pitch / 12.0 + extra_dist_in : 0.0)));
1205  d_positions_outer.push_back(
1206  std::make_pair(0.0,
1207  std::sqrt(3.0) * pitch / 6.0 + extra_dist_out -
1208  (radial_index != 0 ? std::sqrt(3.0) * pitch / 6.0 + extra_dist_in : 0.0)));
1209 }
1210 
1211 void
1213  MeshBase & mesh, const std::vector<std::unique_ptr<ReplicatedMesh>> & from_meshes) const
1214 {
1215  const unsigned int num_reporting_ids = _reporting_id_names.size();
1216  for (unsigned int i = 0; i < num_reporting_ids; ++i)
1217  {
1218  const std::string element_id_name = _reporting_id_names[i];
1219  unsigned int extra_id_index;
1220  if (!mesh.has_elem_integer(element_id_name))
1221  extra_id_index = mesh.add_elem_integer(element_id_name);
1222  else
1223  {
1224  extra_id_index = mesh.get_elem_integer_index(element_id_name);
1225  paramWarning(
1226  "id_name", "An element integer with the name '", element_id_name, "' already exists");
1227  }
1228 
1229  // assign reporting IDs to individual elements
1230  // NOTE: background block id should be set "PERIPHERAL_ID_SHIFT" because this function is called
1231  // before assigning the user-defined background block id
1232  std::set<subdomain_id_type> background_block_ids =
1233  (isParamValid("background_block_id")) ? std::set<subdomain_id_type>({PERIPHERAL_ID_SHIFT})
1234  : std::set<subdomain_id_type>();
1235 
1236  const bool using_manual_id =
1239  extra_id_index,
1240  _assign_types[i],
1242  _exclude_ids,
1243  _pattern_boundary == "hexagon",
1244  background_block_ids,
1245  from_meshes,
1246  _pattern,
1247  (using_manual_id)
1248  ? _id_patterns.at(element_id_name)
1249  : std::vector<std::vector<dof_id_type>>());
1250  }
1251 }
const T & getMeshProperty(const std::string &data_name, const std::string &prefix)
const bool _use_reporting_id
Whether reporting ID is added to mesh.
const PolygonSizeStyle _duct_sizes_style
Style of the duct size parameter(s)
void positionSetup(std::vector< std::pair< Real, Real >> &positions_inner, std::vector< std::pair< Real, Real >> &d_positions_outer, const Real extra_dist_in, const Real extra_dist_out, const Real pitch, const unsigned int radial_index) const
Computes the inner and outer node positions of the peripheral region for a single layer...
const std::vector< unsigned int > _duct_intervals
Number(s) of radial intervals of duct layer(s)
std::vector< std::string > _reporting_id_names
names of reporting ID
void addRequiredRangeCheckedParam(const std::string &name, const std::string &parsed_function, const std::string &doc_string)
const bool _allow_unused_inputs
Whether to allow additional assembly types to be passed to "inputs" parameter without being used in l...
T & setMeshProperty(const std::string &data_name, Args &&... args)
bool absoluteFuzzyEqual(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
const MooseEnum _pattern_boundary
Type of the external boundary shape.
const bool _has_assembly_duct
Whether the hexagonal pattern has external duct(s)
const unsigned int _background_intervals
Number of radial intervals in the background region.
void assignReportingIDs(MeshBase &mesh, const unsigned int extra_id_index, const ReportingIDGeneratorUtils::AssignType assign_type, const bool use_exclude_id, const std::vector< bool > &exclude_ids, const bool has_assembly_boundary, const std::set< subdomain_id_type > background_block_ids, const std::vector< std::unique_ptr< libMesh::ReplicatedMesh >> &input_meshes, const std::vector< std::vector< unsigned int >> &pattern, const std::vector< std::vector< dof_id_type >> &id_pattern)
assign the reporting IDs to the output mesh from the cartesian or hexagonal patterned mesh generator ...
assign IDs based on user-defined mapping
std::vector< SubdomainName > _peripheral_block_names
Subdomain Names of the peripheral regions.
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
const bool _assign_control_drum_id
Wheter control drum IDs are assigned as an extra element integer.
const std::vector< std::vector< unsigned int > > & _pattern
2D vector of the hexagonal pattern
MeshBase & mesh
const PolygonSizeStyle _hexagon_size_style
Style of the polygon size parameter.
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
const bool _create_inward_interface_boundaries
Whether inward interface boundaries are created.
const bool _create_outward_interface_boundaries
Whether outward interface boundaries are created.
void changeBoundaryId(const boundary_id_type old_id, const boundary_id_type new_id, bool delete_prev)
virtual const std::string & name() const
void addRequiredParam(const std::string &name, const std::string &doc_string)
void nodeCoordRotate(Real &x, Real &y, const Real theta) const
Calculates x and y coordinates after rotating by theta angle.
std::vector< std::vector< boundary_id_type > > _interface_boundary_id_shift_pattern
hold user-defined shift values for each pattern cell
const std::vector< MeshGeneratorName > & _input_names
Names of input meshes.
bool isParamValid(const std::string &name) const
const Real _rotate_angle
The mesh rotation angle after mesh generation.
void adjustPeripheralQuadraticElements(MeshBase &out_mesh, const QUAD_ELEM_TYPE boundary_quad_elem_type) const
Adjusts the mid-edge node locations in boundary regions when using quadratic elements with uniform bo...
static InputParameters validParams()
registerMooseObject("ReactorApp", PatternedHexMeshGenerator)
int8_t boundary_id_type
const std::vector< SubdomainName > _duct_block_names
Subdomain Names of the duct layers.
std::string pitchMetaDataErrorGenerator(const std::vector< MeshGeneratorName > &input_names, const std::vector< Real > &metadata_vals, const std::string &metadata_name) const
Generate a string that contains the detailed metadata information for inconsistent input mesh metadat...
std::vector< ReportingIDGeneratorUtils::AssignType > _assign_types
reporting ID assignment type
const bool _uniform_mesh_on_sides
Whether the nodes on the external boundary are uniformly distributed.
static const std::string pitch
QUAD_ELEM_TYPE _boundary_quad_elem_type
Type of quadrilateral elements to be generated in the periphery region.
std::vector< Real > _duct_sizes
Size parameter(s) of duct(s)
const std::vector< std::unique_ptr< MeshBase > * > _mesh_ptrs
The input meshes.
const bool _use_exclude_id
flag to indicate if exclude_id is defined
const std::vector< subdomain_id_type > _duct_block_ids
Subdomain IDs of the duct layers.
const boundary_id_type _external_boundary_id
Boundary ID of mesh&#39;s external boundary.
const bool _deform_non_circular_region
Whether the non-circular region (outside the rings) can be deformed.
const bool _generate_control_drum_positions_file
Whether a text file containing control drum positions is generated.
void paramError(const std::string &param, Args... args) const
std::vector< subdomain_id_type > _peripheral_block_ids
Subdomain IDs of the peripheral regions.
const std::string _name
std::unique_ptr< MeshBase > generate() override
const bool _use_interface_boundary_id_shift
whether the interface boundary ids from input meshes are shifted, using a user-defined pattern of val...
PatternedHexMeshGenerator(const InputParameters &parameters)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void addPeripheralMesh(ReplicatedMesh &mesh, const unsigned int pattern, const Real pitch, const std::vector< Real > &extra_dist, const std::vector< unsigned int > &num_sectors_per_side_array, const std::vector< unsigned int > &peripheral_duct_intervals, const Real rotation_angle, const unsigned int mesh_type)
Adds background and duct region mesh to stitched hexagon meshes.
This PatternedHexMeshGenerator source code assembles hexagonal meshes into a hexagonal grid and optio...
void addReportingIDs(MeshBase &mesh, const std::vector< std::unique_ptr< ReplicatedMesh >> &from_meshes) const
Adds the reporting IDs onto the input mesh.
A base class that contains common members for Reactor module mesh generators.
Real _pattern_pitch
Pitch size of the input assembly mesh.
static InputParameters validParams()
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
void addClassDescription(const std::string &doc_string)
T & declareMeshProperty(const std::string &data_name, Args &&... args)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const BoundaryName _external_boundary_name
Boundary name of mesh&#39;s external boundary.
void paramWarning(const std::string &param, Args... args) const
Point meshCentroidCalculator(const MeshBase &mesh)
std::vector< bool > _exclude_ids
vector indicating which ids in the pattern to exclude (true at pattern positions to exclude) ...
void reassignBoundaryIDs(MeshBase &mesh, const boundary_id_type id_shift, const std::set< boundary_id_type > &boundary_ids, const bool reverse=false)
reassign interface boundary IDs on the input mesh by applying the boundary ID shift ...
bool hasMeshProperty(const std::string &data_name, const std::string &prefix) const
void ErrorVector unsigned int
auto index_range(const T &sizable)
std::map< std::string, std::vector< std::vector< dof_id_type > > > _id_patterns
hold ID patterns for each manual reporting ID. Individual ID pattern contains ID values for each patt...
std::unique_ptr< ReplicatedMesh > buildSimplePeripheral(const unsigned int num_sectors_per_side, const unsigned int peripheral_invervals, const std::vector< std::pair< Real, Real >> &position_inner, const std::vector< std::pair< Real, Real >> &d_position_outer, const subdomain_id_type id_shift, const QUAD_ELEM_TYPE quad_elem_type, const bool create_inward_interface_boundaries=false, const bool create_outward_interface_boundaries=true)
Creates peripheral area mesh for the patterned hexagon mesh.
uint8_t dof_id_type
const bool _generate_core_metadata
Whether a reactor core mesh with core metadata is generated.
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
PolygonSizeStyle
An enum class for style of input polygon size.
std::set< boundary_id_type > getInterfaceBoundaryIDs(const std::vector< std::vector< unsigned int >> &pattern, const std::vector< std::vector< boundary_id_type >> &interface_boundary_id_shift_pattern, const std::set< boundary_id_type > &boundary_ids, const std::vector< std::set< boundary_id_type >> &input_interface_boundary_ids, const bool use_interface_boundary_id_shift, const bool create_interface_boundary_id, const unsigned int num_extra_layers) const
returns a list of interface boundary IDs on the mesh generated by this mesh generator ...
void cutOffPolyDeform(MeshBase &mesh, const Real orientation, const Real y_max_0, const Real y_max_n, const Real y_min, const unsigned int mesh_type, const Real unit_angle=60.0, const Real tols=1E-5) const
Deforms peripheral region when the external side of a polygon assembly of stitched meshes cuts off th...