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