https://mooseframework.inl.gov
SCMDetailedTriAssemblyMeshGenerator.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 
11 #include "TriSubChannelMesh.h"
12 #include <array>
13 #include <cmath>
14 #include <memory>
15 #include "libmesh/cell_prism6.h"
16 #include "libmesh/unstructured_mesh.h"
17 
19 registerMooseObjectRenamed("SubChannelApp",
20  SCMDetailedTriSubChannelMeshGenerator,
21  "06/30/2027 24:00",
23 registerMooseObjectRenamed("SubChannelApp",
24  DetailedTriSubChannelMeshGenerator,
25  "06/30/2027 24:00",
27 registerMooseObjectRenamed("SubChannelApp",
28  SCMDetailedTriPinMeshGenerator,
29  "06/30/2027 24:00",
31 registerMooseObjectRenamed("SubChannelApp",
32  DetailedTriPinMeshGenerator,
33  "06/30/2027 24:00",
35 
38 {
40  params.addClassDescription(
41  "Creates a detailed mesh of subchannels and pins in a triangular lattice arrangement");
42  params.addRequiredParam<Real>("pitch", "Pitch [m]");
43  params.addRequiredParam<Real>("pin_diameter", "Rod diameter [m]");
44  params.addParam<Real>("unheated_length_entry", 0.0, "Unheated length at entry [m]");
45  params.addRequiredParam<Real>("heated_length", "Heated length [m]");
46  params.addParam<Real>("unheated_length_exit", 0.0, "Unheated length at exit [m]");
47  params.addRequiredParam<unsigned int>(
48  "nrings",
49  "Number of fuel-pin rings per assembly, counting the center pin as the first ring [-]");
50  params.addRequiredParam<Real>("flat_to_flat",
51  "Flat to flat distance for the hexagonal assembly [m]");
52  params.addRequiredParam<unsigned int>("n_cells", "The number of cells in the axial direction");
53  params.addRangeCheckedParam<unsigned int>("num_sectors",
54  16,
55  "num_sectors>=4",
56  "Number of azimuthal sectors used to discretize each "
57  "circular pin cross section.");
58  params.addParam<unsigned int>("block_id", 0, "Subchannel block id.");
59  params.deprecateParam("block_id", "subchannel_block_id", "07/01/2027");
60  params.addParam<unsigned int>("pin_block_id", 1, "Fuel pin block id.");
61  params.addParam<bool>("verbose_flag", false, "Flag to print out the mesh coordinates.");
62  return params;
63 }
64 
66  const InputParameters & parameters)
67  : MeshGenerator(parameters),
68  _unheated_length_entry(getParam<Real>("unheated_length_entry")),
69  _heated_length(getParam<Real>("heated_length")),
70  _unheated_length_exit(getParam<Real>("unheated_length_exit")),
71  _pitch(getParam<Real>("pitch")),
72  _pin_diameter(getParam<Real>("pin_diameter")),
73  _n_rings(getParam<unsigned int>("nrings")),
74  _flat_to_flat(getParam<Real>("flat_to_flat")),
75  _num_sectors(getParam<unsigned int>("num_sectors")),
76  _subchannel_block_id(getParam<unsigned int>("subchannel_block_id")),
77  _pin_block_id(getParam<unsigned int>("pin_block_id")),
78  _n_cells(getParam<unsigned int>("n_cells")),
79  _nrods(0),
80  _n_channels(0),
81  _verbose(getParam<bool>("verbose_flag")),
82  _elem_id(0)
83 {
85 
86  if (_n_rings < 2)
87  paramError("nrings",
88  "'nrings' must be at least 2. In this mesh generator, the center pin counts as "
89  "the first ring, so a 7-pin bundle uses nrings = 2.");
90 
91  if (_n_cells == 0)
92  paramError("n_cells", "The number of axial cells must be greater than zero");
93 
94  if (L <= 0.0)
95  mooseError("Total bundle length must be greater than zero");
96 
97  Real dz = L / _n_cells;
98  for (unsigned int i = 0; i < _n_cells + 1; i++)
99  _z_grid.push_back(dz * i);
100 
101  // x coordinate for the first position
102  Real x0 = 0.0;
103  // y coordinate for the first position
104  Real y0 = 0.0;
105  // x coordinate for the second position
106  Real x1 = 0.0;
107  // y coordinate for the second position dummy variable
108  Real y1 = 0.0;
109  // dummy variable
110  Real a1 = 0.0;
111  // dummy variable
112  Real a2 = 0.0;
113  // average x coordinate
114  Real avg_coor_x = 0.0;
115  // average y coordinate
116  Real avg_coor_y = 0.0;
117  // distance between two points
118  Real dist = 0.0;
119  // distance between two points
120  Real dist0 = 0.0;
121  // the indicator used while setting _gap_to_chan_map array
122  std::vector<std::pair<unsigned int, unsigned int>> gap_fill;
124  _nrods = _pin_position.size();
125  // assign the pins to the corresponding rings
126  unsigned int k = 0; // initialize the fuel Pin counter index
127  _pins_in_rings.resize(_n_rings);
128  _pins_in_rings[0].push_back(k++);
129  for (unsigned int i = 1; i < _n_rings; i++)
130  for (unsigned int j = 0; j < i * 6; j++)
131  _pins_in_rings[i].push_back(k++);
132  // Given the number of pins and number of fuel Pin rings, the number of subchannels can be
133  // computed as follows:
134  unsigned int chancount = 0;
135  // Summing internal channels
136  for (unsigned int j = 0; j < _n_rings - 1; j++)
137  chancount += j * 6;
138  // Adding external channels to the total count
139  _n_channels = chancount + _nrods - 1 + (_n_rings - 1) * 6 + 6;
140 
141  // Utils for building the mesh
143  _subch_type.resize(_n_channels);
145 
146  for (unsigned int i = 0; i < _n_channels; i++)
147  {
148  _subchannel_position[i].reserve(3);
149  for (unsigned int j = 0; j < 3; j++)
150  {
151  _subchannel_position.at(i).push_back(0.0);
152  }
153  }
154 
155  // create the subchannels
156  k = 0; // initialize the subchannel counter index
157  for (unsigned int i = 1; i < _n_rings; i++)
158  {
159  // find the closest Pin at back ring
160  for (unsigned int j = 0; j < _pins_in_rings[i].size(); j++)
161  {
162  if (j == _pins_in_rings[i].size() - 1)
163  {
164  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j]);
165  _chan_to_pin_map[k].push_back(_pins_in_rings[i][0]);
166  avg_coor_x =
167  0.5 * (_pin_position[_pins_in_rings[i][j]](0) + _pin_position[_pins_in_rings[i][0]](0));
168  avg_coor_y =
169  0.5 * (_pin_position[_pins_in_rings[i][j]](1) + _pin_position[_pins_in_rings[i][0]](1));
170  }
171  else
172  {
173  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j]);
174  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j + 1]);
175  avg_coor_x = 0.5 * (_pin_position[_pins_in_rings[i][j]](0) +
176  _pin_position[_pins_in_rings[i][j + 1]](0));
177  avg_coor_y = 0.5 * (_pin_position[_pins_in_rings[i][j]](1) +
178  _pin_position[_pins_in_rings[i][j + 1]](1));
179  }
180  dist0 = 1.0e+5;
181  _chan_to_pin_map[k].push_back(_pins_in_rings[i - 1][0]);
182  for (unsigned int l = 0; l < _pins_in_rings[i - 1].size(); l++)
183  {
184  dist = std::sqrt(pow(_pin_position[_pins_in_rings[i - 1][l]](0) - avg_coor_x, 2) +
185  pow(_pin_position[_pins_in_rings[i - 1][l]](1) - avg_coor_y, 2));
186 
187  if (dist < dist0)
188  {
189  _chan_to_pin_map[k][2] = _pins_in_rings[i - 1][l];
190  dist0 = dist;
191  }
192  }
194  _orientation_map.insert(std::make_pair(k, 0.0));
195  k = k + 1;
196  }
197 
198  // find the closest Pin at front ring
199  for (unsigned int j = 0; j < _pins_in_rings[i].size(); j++)
200  {
201  if (j == _pins_in_rings[i].size() - 1)
202  {
203  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j]);
204  _chan_to_pin_map[k].push_back(_pins_in_rings[i][0]);
205  avg_coor_x =
206  0.5 * (_pin_position[_pins_in_rings[i][j]](0) + _pin_position[_pins_in_rings[i][0]](0));
207  avg_coor_y =
208  0.5 * (_pin_position[_pins_in_rings[i][j]](1) + _pin_position[_pins_in_rings[i][0]](1));
209  }
210  else
211  {
212  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j]);
213  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j + 1]);
214  avg_coor_x = 0.5 * (_pin_position[_pins_in_rings[i][j]](0) +
215  _pin_position[_pins_in_rings[i][j + 1]](0));
216  avg_coor_y = 0.5 * (_pin_position[_pins_in_rings[i][j]](1) +
217  _pin_position[_pins_in_rings[i][j + 1]](1));
218  }
219  // if the outermost ring, set the edge subchannels first... then the corner subchannels
220  if (i == _n_rings - 1)
221  {
222  // add edges
223  _subch_type[k] = EChannelType::EDGE; // an edge subchannel is created
224  k = k + 1;
225  if (j % i == 0)
226  {
227  // corner subchannel
228  _chan_to_pin_map[k].push_back(_pins_in_rings[i][j]);
230  k = k + 1;
231  }
232  // if not the outer most ring
233  }
234  else
235  {
236  dist0 = 1.0e+5;
237  _chan_to_pin_map[k].push_back(_pins_in_rings[i + 1][0]);
238  for (unsigned int l = 0; l < _pins_in_rings[i + 1].size(); l++)
239  {
240  dist = std::sqrt(pow(_pin_position[_pins_in_rings[i + 1][l]](0) - avg_coor_x, 2) +
241  pow(_pin_position[_pins_in_rings[i + 1][l]](1) - avg_coor_y, 2));
242  if (dist < dist0)
243  {
244  _chan_to_pin_map[k][2] = _pins_in_rings[i + 1][l];
245  dist0 = dist;
246  }
247  }
249  _orientation_map.insert(std::make_pair(k, libMesh::pi));
250  k = k + 1;
251  }
252  }
253  }
254 
255  for (auto & pin : _chan_to_pin_map)
256  pin.shrink_to_fit();
257 
258  // set the subchannel positions
259  Real _duct_to_pin_gap =
260  0.5 * (_flat_to_flat - (_n_rings - 1) * _pitch * std::sqrt(3.0) - _pin_diameter);
261  for (unsigned int i = 0; i < _n_channels; i++)
262  {
264  {
265  _subchannel_position[i][0] =
267  _pin_position[_chan_to_pin_map[i][2]](0)) /
268  3.0;
269  _subchannel_position[i][1] =
271  _pin_position[_chan_to_pin_map[i][2]](1)) /
272  3.0;
273  }
274  else if (_subch_type[i] == EChannelType::EDGE)
275  {
276  for (unsigned int j = 0; j < _n_channels; j++)
277  {
279  ((_chan_to_pin_map[i][0] == _chan_to_pin_map[j][0] &&
280  _chan_to_pin_map[i][1] == _chan_to_pin_map[j][1]) ||
281  (_chan_to_pin_map[i][0] == _chan_to_pin_map[j][1] &&
282  _chan_to_pin_map[i][1] == _chan_to_pin_map[j][0])))
283  {
284  x0 = _pin_position[_chan_to_pin_map[j][2]](0);
285  y0 = _pin_position[_chan_to_pin_map[j][2]](1);
286  }
287  else if (_subch_type[j] == EChannelType::CENTER &&
288  ((_chan_to_pin_map[i][0] == _chan_to_pin_map[j][0] &&
289  _chan_to_pin_map[i][1] == _chan_to_pin_map[j][2]) ||
290  (_chan_to_pin_map[i][0] == _chan_to_pin_map[j][2] &&
291  _chan_to_pin_map[i][1] == _chan_to_pin_map[j][0])))
292  {
293  x0 = _pin_position[_chan_to_pin_map[j][1]](0);
294  y0 = _pin_position[_chan_to_pin_map[j][1]](1);
295  }
296  else if (_subch_type[j] == EChannelType::CENTER &&
297  ((_chan_to_pin_map[i][0] == _chan_to_pin_map[j][1] &&
298  _chan_to_pin_map[i][1] == _chan_to_pin_map[j][2]) ||
299  (_chan_to_pin_map[i][0] == _chan_to_pin_map[j][2] &&
300  _chan_to_pin_map[i][1] == _chan_to_pin_map[j][1])))
301  {
302  x0 = _pin_position[_chan_to_pin_map[j][0]](0);
303  y0 = _pin_position[_chan_to_pin_map[j][0]](1);
304  }
305  x1 = 0.5 *
307  y1 = 0.5 *
309  a1 = _pin_diameter / 2.0 + _duct_to_pin_gap / 2.0;
310  a2 = std::sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) + a1;
311  _subchannel_position[i][0] = (a2 * x1 - a1 * x0) / (a2 - a1);
312  _subchannel_position[i][1] = (a2 * y1 - a1 * y0) / (a2 - a1);
313  } // j
314  }
315  else if (_subch_type[i] == EChannelType::CORNER)
316  {
317  x0 = _pin_position[0](0);
318  y0 = _pin_position[0](1);
319  x1 = _pin_position[_chan_to_pin_map[i][0]](0);
320  y1 = _pin_position[_chan_to_pin_map[i][0]](1);
321  a1 = _pin_diameter / 2.0 + _duct_to_pin_gap / 2.0;
322  a2 = std::sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) + a1;
323  _subchannel_position[i][0] = (a2 * x1 - a1 * x0) / (a2 - a1);
324  _subchannel_position[i][1] = (a2 * y1 - a1 * y0) / (a2 - a1);
325  }
326  }
327 }
328 
329 void
330 SCMDetailedTriAssemblyMeshGenerator::generatePin(std::unique_ptr<MeshBase> & mesh_base,
331  const Point & center)
332 {
333  const Real dalpha = 360. / _num_sectors;
334  const Real radius = _pin_diameter / 2.;
335 
336  // Add a center node and radial boundary nodes on each axial level so each pin is discretized into
337  // triangular prism sectors.
338  std::vector<std::vector<Node *>> nodes;
339  nodes.resize(_n_cells + 1);
340  for (unsigned int k = 0; k < _n_cells + 1; k++)
341  {
342  const Real elev = _z_grid[k];
343  nodes[k].push_back(mesh_base->add_point(Point(center(0), center(1), elev)));
344  Real alpha = 0.;
345  for (unsigned int i = 0; i < _num_sectors; i++, alpha += dalpha)
346  {
347  const Real dx = radius * std::cos(alpha * M_PI / 180.);
348  const Real dy = radius * std::sin(alpha * M_PI / 180.);
349  nodes[k].push_back(mesh_base->add_point(Point(center(0) + dx, center(1) + dy, elev)));
350  }
351  }
352 
353  // Add the pin volume elements, linking matching radial sectors between adjacent axial levels.
354  for (unsigned int k = 0; k < _n_cells; k++)
355  for (unsigned int i = 0; i < _num_sectors; i++)
356  {
357  Elem * elem = mesh_base->add_elem(std::make_unique<Prism6>());
358  elem->subdomain_id() = _pin_block_id;
359  elem->set_id(_elem_id++);
360  const unsigned int ctr_idx = 0;
361  const unsigned int idx1 = (i % _num_sectors) + 1;
362  const unsigned int idx2 = ((i + 1) % _num_sectors) + 1;
363  elem->set_node(0, nodes[k][ctr_idx]);
364  elem->set_node(1, nodes[k][idx1]);
365  elem->set_node(2, nodes[k][idx2]);
366  elem->set_node(3, nodes[k + 1][ctr_idx]);
367  elem->set_node(4, nodes[k + 1][idx1]);
368  elem->set_node(5, nodes[k + 1][idx2]);
369  }
370 }
371 
372 std::unique_ptr<MeshBase>
374 {
375  auto mesh_base = buildMeshBaseObject();
376  BoundaryInfo & boundary_info = mesh_base->get_boundary_info();
377  mesh_base->set_spatial_dimension(3);
378  // Define the resolution (the number of points used to represent a circle).
379  // This must be divisible by 4.
380  const unsigned int theta_res_triangle = 24; // TODO: parameterize
381  const unsigned int theta_res_square = 24; // TODO: parameterize
382  // Compute the number of points needed to represent one sixth and quadrant of a circle.
383  const unsigned int points_per_sixth = theta_res_triangle / 6 + 1;
384  const unsigned int points_per_quadrant = theta_res_square / 4 + 1;
385 
386  // Compute the points needed to represent one axial cross-flow of a subchannel.
387  // For the center subchannel (sc) there is one center point plus the points from 3 side
388  // circles.
389  const unsigned int points_per_center = points_per_sixth * 3 + 1;
390  // For the corner sc there is one center point plus the points from 1 side circle plus 3
391  // corners
392  const unsigned int points_per_corner = points_per_sixth * 1 + 1 + 3;
393  // For the side sc there is one center point plus the points from 2 intersecting circles plus 2
394  // corners
395  const unsigned int points_per_side = points_per_quadrant * 2 + 1 + 2;
396 
397  if (_verbose)
398  {
399  _console << "Points per center: " << points_per_center << std::endl;
400  _console << "Points per side: " << points_per_side << std::endl;
401  _console << "Points per corner: " << points_per_corner << std::endl;
402  }
403 
404  // Compute the number of elements (Prism6) which combined base creates the sub-channel
405  // cross-section
406  const unsigned int elems_per_center = theta_res_triangle * 3 / 6 + 3; // TODO: check
407  const unsigned int elems_per_corner = theta_res_triangle / 6 + 4;
408  const unsigned int elems_per_side = 2 * theta_res_square / 4 + 4;
409  if (_verbose)
410  {
411  _console << "Elems per center: " << elems_per_center << std::endl;
412  _console << "Elems per side: " << elems_per_side << std::endl;
413  _console << "Elems per corner: " << elems_per_corner << std::endl;
414  }
415 
416  // specify number and type of sub-channel
417  unsigned int n_corner = 6;
418  unsigned int n_side = (_n_rings - 1) * 6;
419  unsigned int n_center = _n_channels - n_side - n_corner;
420  if (_verbose)
421  {
422  _console << "Centers: " << n_center << std::endl;
423  _console << "Sides: " << n_side << std::endl;
424  _console << "Corners: " << n_corner << std::endl;
425  }
426 
427  // Compute the total number of points and elements.
428  const unsigned int points_per_level =
429  n_corner * points_per_corner + n_side * points_per_side + n_center * points_per_center;
430  const unsigned int elems_per_level =
431  n_corner * elems_per_corner + n_side * elems_per_side + n_center * elems_per_center;
432  if (_verbose)
433  {
434  _console << "Points per level: " << points_per_level << std::endl;
435  _console << "Elements per level: " << elems_per_level << std::endl;
436  }
437  const unsigned int n_points = points_per_level * (_n_cells + 1);
438  const unsigned int n_elems = elems_per_level * _n_cells;
439  const unsigned int n_pins = _pin_position.size();
440  const unsigned int pin_points = n_pins > 0 ? (_n_cells + 1) * (_num_sectors + 1) * n_pins : 0;
441  const unsigned int pin_elems = n_pins > 0 ? _n_cells * _num_sectors * n_pins : 0;
442  if (_verbose)
443  {
444  _console << "Number of points: " << n_points << std::endl;
445  _console << "Number of elements: " << n_elems << std::endl;
446  }
447  mesh_base->reserve_nodes(n_points + pin_points);
448  mesh_base->reserve_elem(n_elems + pin_elems);
449  // Build an array of points arranged in a circle on the xy-plane. (last and first node overlap)
450  // We build for both the square discretization in the edges and the triangular discretization
451  // within the mesh
452  const Real radius = _pin_diameter / 2.0;
453  std::array<Point, theta_res_square + 1> circle_points_square;
454  {
455  Real theta = 0;
456  for (unsigned int i = 0; i < theta_res_square + 1; i++)
457  {
458  circle_points_square[i](0) = radius * std::cos(theta);
459  circle_points_square[i](1) = radius * std::sin(theta);
460  theta += 2.0 * libMesh::pi / theta_res_square;
461  }
462  }
463  std::array<Point, theta_res_triangle + 1> circle_points_triangle;
464  {
465  Real theta = 0;
466  for (unsigned int i = 0; i < theta_res_triangle + 1; i++)
467  {
468  circle_points_triangle[i](0) = radius * std::cos(theta);
469  circle_points_triangle[i](1) = radius * std::sin(theta);
470  theta += 2.0 * libMesh::pi / theta_res_triangle;
471  }
472  }
473  // Define "quadrant center" reference points. These will be the centers of
474  // the 3 circles that represent the fuel pins. These centers are
475  // offset a little bit so that in the final mesh, there is a tiny gap between
476  // neighboring subchannel cells. That allows us to easily map a solution to
477  // this detailed mesh with a nearest-neighbor search.
478  const Real shrink_factor = 0.99999;
479  // Quadrants are used only for the side and corner subchannels
480  Real _duct_to_pin_gap =
481  0.5 * (_flat_to_flat - (_n_rings - 1) * _pitch * std::sqrt(3.0) - _pin_diameter);
482  std::array<Point, 2> quadrant_centers_sides;
483  quadrant_centers_sides[0] = Point(
484  -_pitch * 0.5 * shrink_factor, -(_duct_to_pin_gap + _pin_diameter) * 0.5 * shrink_factor, 0);
485  quadrant_centers_sides[1] = Point(
486  _pitch * 0.5 * shrink_factor, -(_duct_to_pin_gap + _pin_diameter) * 0.5 * shrink_factor, 0);
487  std::array<Point, 1> quadrant_centers_corner;
488  quadrant_centers_corner[0] =
489  Point(-(_duct_to_pin_gap + _pin_diameter) * 0.5 * std::sin(libMesh::pi / 6) * shrink_factor,
490  -(_duct_to_pin_gap + _pin_diameter) * 0.5 * std::cos(libMesh::pi / 6) * shrink_factor,
491  0);
492  // Triangles are used for all center subchannels
493  std::array<Point, 3> triangle_centers;
494  triangle_centers[0] = Point(0, _pitch * std::cos(libMesh::pi / 6) * 2 / 3 * shrink_factor, 0);
495  triangle_centers[1] = Point(-_pitch * 0.5 * shrink_factor,
496  -_pitch * std::cos(libMesh::pi / 6) * 1 / 3 * shrink_factor,
497  0);
498  triangle_centers[2] = Point(
499  _pitch * 0.5 * shrink_factor, -_pitch * std::cos(libMesh::pi / 6) * 1 / 3 * shrink_factor, 0);
500 
501  const unsigned int m_sixth = theta_res_triangle / 6;
502  const unsigned int m_quarter = theta_res_square / 4;
503  // Build an array of points that represent a cross section of a center subchannel
504  // cell. The points are ordered in this fashion:
505  // 3 1
506  // 2
507  // 0
508  // 4 5 8 9
509  // 6 7
510  std::array<Point, points_per_center> center_points{};
511  center_points[0] = Point(0, 0, 0);
512  {
513  unsigned int start;
514  for (unsigned int i = 0; i < 3; i++)
515  {
516  if (i == 0)
517  start = 5 * (m_sixth);
518  if (i == 1)
519  start = 1 * (m_sixth);
520  if (i == 2)
521  start = 3 * (m_sixth);
522  for (unsigned int ii = 0; ii < points_per_sixth; ii++)
523  {
524  auto c_pt = circle_points_triangle[start - ii];
525  center_points[i * points_per_sixth + ii + 1] = triangle_centers[i] + c_pt;
526  }
527  }
528  }
529 
530  // Build an array of points that represent a cross section of a top left corner subchannel
531  // cell. The points are ordered in this fashion (x direction towards point 4, y direction towards
532  // point 6):
533  // 5 6
534  //
535  // 0
536  // 4 2 3
537  // 1
538  std::array<Point, points_per_corner> corner_points{};
539  corner_points[0] = Point(0, 0, 0);
540  {
541  for (unsigned int ii = 0; ii < points_per_sixth; ii++)
542  {
543  auto c_pt = circle_points_triangle[1 * m_quarter - ii];
544  corner_points[ii + 1] = quadrant_centers_corner[0] + c_pt;
545  }
546  Real side_short = (_duct_to_pin_gap + _pin_diameter) * 0.5;
547  Real side_long = (2.0 * _duct_to_pin_gap + _pin_diameter) * 0.5;
548  Real side_length = std::sqrt(std::pow(side_short, 2) + std::pow(side_long, 2) -
549  2 * side_short * side_long * std::cos(libMesh::pi / 6));
550  Real angle =
551  libMesh::pi - libMesh::pi / 3 -
552  std::acos((-std::pow(side_long, 2) + std::pow(side_short, 2) + std::pow(side_length, 2)) /
553  (2 * side_short * side_length));
554  corner_points[points_per_sixth + 1] = Point(side_length * std::cos(angle) * shrink_factor,
555  -side_length * std::sin(angle) * shrink_factor,
556  0);
557  corner_points[points_per_sixth + 2] =
558  Point(side_length * std::sin(libMesh::pi / 2 - angle - libMesh::pi / 6) * shrink_factor *
559  std::tan(libMesh::pi / 6),
560  side_length * std::sin(libMesh::pi / 2 - angle - libMesh::pi / 6) * shrink_factor,
561  0);
562  corner_points[points_per_sixth + 3] =
563  Point(-side_length * std::cos(libMesh::pi / 2 - angle - libMesh::pi / 6) * shrink_factor,
564  side_length * std::sin(libMesh::pi / 2 - angle - libMesh::pi / 6) * shrink_factor,
565  0);
566  }
567 
568  // Build an array of points that represent a cross-section of a top side subchannel
569  // cell. The points are ordered in this fashion:
570  // 8 7
571  //
572  // 0
573  // 1 2 5 6
574  // 3 4
575  std::array<Point, points_per_side> side_points{};
576  side_points[0] = Point(0, 0, 0);
577  {
578  for (unsigned int ii = 0; ii < points_per_quadrant; ii++)
579  {
580  auto c_pt = circle_points_square[m_quarter - ii];
581  side_points[ii + 1] = quadrant_centers_sides[0] + c_pt;
582  }
583  for (unsigned int ii = 0; ii < points_per_quadrant; ii++)
584  {
585  auto c_pt = circle_points_square[2 * m_quarter - ii];
586  side_points[points_per_quadrant + ii + 1] = quadrant_centers_sides[1] + c_pt;
587  }
588  side_points[2 * points_per_quadrant + 1] =
589  Point(_pitch * 0.5 * shrink_factor, 0.5 * _duct_to_pin_gap * shrink_factor, 0);
590  side_points[2 * points_per_quadrant + 2] =
591  Point(-_pitch * 0.5 * shrink_factor, 0.5 * _duct_to_pin_gap * shrink_factor, 0);
592  }
593 
594  int point_counter = 0;
595  unsigned int node_id = 0;
596  for (unsigned int i = 0; i < _n_channels; i++)
597  {
598  // Real offset_x = _flat_to_flat / 2.0;
599  // Real offset_y = _flat_to_flat / 2.0;
600 
602  {
603  for (auto z : _z_grid)
604  {
605  // Get height
606  Point z0{0, 0, z};
607 
608  // Get suchannel position and assign to point
609  auto loc_position = getSubchannelPosition(i);
610  Point p0{loc_position[0], loc_position[1], 0};
611 
612  // Determine orientation of current subchannel
613  auto subchannel_pins = getSubChannelPins(i);
614  Point subchannel_side =
615  getPinPosition(subchannel_pins[0]) + getPinPosition(subchannel_pins[1]);
616  Point base_center_orientation = {0, -1};
617 
618  // Get rotation angle for current subchannel
619  Real dot_prod = 0;
620  for (unsigned int lp = 0; lp < 2; lp++)
621  dot_prod += base_center_orientation(lp) * subchannel_side(lp);
622  auto theta =
623  std::acos(dot_prod / (base_center_orientation.norm() * subchannel_side.norm()));
624  if (subchannel_side(0) < 0)
625  theta = 2.0 * libMesh::pi - theta;
626 
627  // Real distance_side = subchannel_side.norm();
628  // Real distance_top = getPinPosition(subchannel_pins[2]).norm();
629  // if (distance_top > distance_side)
630  // theta += libMesh::pi * 0.0;
631 
632  theta += _orientation_map[i];
633 
634  theta = trunc((theta + (libMesh::pi / 6.0)) / (libMesh::pi / 3.0)) * libMesh::pi / 3.0;
635 
636  if (_verbose)
637  {
638  if (z == 0)
639  {
640  _console << "Subchannel Position: " << p0 << std::endl;
641  auto pins = getSubChannelPins(i);
642  for (auto r : pins)
643  _console << r << " ";
644  _console << std::endl;
645  _console << "Theta: " << theta / libMesh::pi * 180. << std::endl;
646  }
647  }
648 
649  // Assigning points for center channels
650  for (unsigned int i = 0; i < points_per_center; i++)
651  {
652  auto new_point = rotatePoint(center_points[i], theta) + p0 + z0;
653  if (_verbose)
654  {
655  if (z == 0)
656  _console << i << " - " << new_point << std::endl;
657  }
658  mesh_base->add_point(new_point, node_id++);
659  point_counter += 1;
660  }
661  }
662  }
663  else if (getSubchannelType(i) == EChannelType::EDGE)
664  {
665  for (auto z : _z_grid)
666  {
667  // Get height
668  Point z0{0, 0, z};
669 
670  // Get suchannel position and assign to point
671  auto loc_position = getSubchannelPosition(i);
672  Point p0{loc_position[0], loc_position[1], 0};
673 
674  // Determine orientation of current subchannel
675  auto subchannel_pins = getSubChannelPins(i);
676  Point subchannel_side =
677  getPinPosition(subchannel_pins[0]) + getPinPosition(subchannel_pins[1]);
678  Point base_center_orientation = {0, 1};
679 
680  // Get rotation angle for current subchannel
681  Real dot_prod = 0;
682  for (unsigned int lp = 0; lp < 2; lp++)
683  dot_prod += base_center_orientation(lp) * subchannel_side(lp);
684  auto theta =
685  std::acos(dot_prod / (base_center_orientation.norm() * subchannel_side.norm()));
686  if (subchannel_side(0) > 0)
687  theta = 2. * libMesh::pi - theta;
688  theta = trunc((theta + (libMesh::pi / 6.0)) / (libMesh::pi / 3.0)) * libMesh::pi / 3.0;
689 
690  if (_verbose)
691  {
692  if (z == 0)
693  {
694  _console << "Subchannel Position: " << p0 << std::endl;
695  auto pins = getSubChannelPins(i);
696  for (auto r : pins)
697  _console << r << " ";
698  _console << std::endl;
699  _console << "Theta: " << theta * 180 / libMesh::pi << std::endl;
700  }
701  }
702 
703  // Assigning points for center channels
704  for (unsigned int i = 0; i < points_per_side; i++)
705  {
706  auto new_point = rotatePoint(side_points[i], theta) + p0 + z0;
707  if (_verbose)
708  {
709  if (z == 0)
710  _console << i << " - " << new_point << std::endl;
711  }
712  mesh_base->add_point(new_point, node_id++);
713  point_counter += 1;
714  }
715  }
716  }
717  else // getSubchannelType(i) == EChannelType::CORNER
718  {
719  for (auto z : _z_grid)
720  {
721  // Get height
722  Point z0{0, 0, z};
723 
724  // Get suchannel position and assign to point
725  auto loc_position = getSubchannelPosition(i);
726  Point p0{loc_position[0], loc_position[1], 0};
727 
728  // Determine orientation of current subchannel
729  auto subchannel_pins = getSubChannelPins(i);
730  Point subchannel_side = getPinPosition(subchannel_pins[0]);
731  Point base_center_orientation = {1, 1};
732 
733  // Get rotation angle for current subchannel
734  Real dot_prod = 0;
735  for (unsigned int lp = 0; lp < 2; lp++)
736  dot_prod += base_center_orientation(lp) * subchannel_side(lp);
737  auto theta =
738  std::acos(dot_prod / (base_center_orientation.norm() * subchannel_side.norm()));
739  if (subchannel_side(0) > 0)
740  theta = 2. * libMesh::pi - theta;
741  theta = trunc((theta + (libMesh::pi / 6.0)) / (libMesh::pi / 3.0)) * libMesh::pi / 3.0;
742 
743  if (_verbose)
744  {
745  if (z == 0)
746  {
747  _console << "Subchannel Position: " << p0 << std::endl;
748  auto pins = getSubChannelPins(i);
749  for (auto r : pins)
750  _console << r << " ";
751  _console << std::endl;
752  _console << "Theta: " << theta * 180 / libMesh::pi << std::endl;
753  }
754  }
755 
756  // Assigning points for center channels
757  for (unsigned int i = 0; i < points_per_corner; i++)
758  {
759  auto new_point = rotatePoint(corner_points[i], theta) + p0 + z0;
760  if (_verbose)
761  {
762  if (z == 0)
763  _console << i << " - " << new_point << std::endl;
764  }
765  mesh_base->add_point(new_point, node_id++);
766  point_counter += 1;
767  }
768  }
769  }
770  } // i
771  if (_verbose)
772  _console << "Point counter: " << point_counter << std::endl;
773 
774  int element_counter = 0;
775  unsigned int elem_id = 0;
776  unsigned int number_of_corner = 0;
777  unsigned int number_of_side = 0;
778  unsigned int number_of_center = 0;
779  unsigned int elems_per_channel = 0;
780  unsigned int points_per_channel = 0;
781  for (unsigned int i = 0; i < _n_channels; i++)
782  {
783  auto subch_type = getSubchannelType(i);
784  if (subch_type == EChannelType::CORNER)
785  {
786  number_of_corner++;
787  elems_per_channel = elems_per_corner;
788  points_per_channel = points_per_corner;
789  if (_verbose)
790  _console << "Corner" << std::endl;
791  }
792  else if (subch_type == EChannelType::EDGE)
793  {
794  number_of_side++;
795  elems_per_channel = elems_per_side;
796  points_per_channel = points_per_side;
797  if (_verbose)
798  _console << "Edge" << std::endl;
799  }
800  else if (subch_type == EChannelType::CENTER)
801  {
802  number_of_center++;
803  elems_per_channel = elems_per_center;
804  points_per_channel = points_per_center;
805  if (_verbose)
806  _console << "Center" << std::endl;
807  }
808  for (unsigned int iz = 0; iz < _n_cells; iz++)
809  {
810  unsigned int elapsed_points = number_of_corner * points_per_corner * (_n_cells + 1) +
811  number_of_side * points_per_side * (_n_cells + 1) +
812  number_of_center * points_per_center * (_n_cells + 1) -
813  points_per_channel * (_n_cells + 1);
814  // index of the central node at base of cell
815  unsigned int indx1 = iz * points_per_channel + elapsed_points;
816  // index of the central node at top of cell
817  unsigned int indx2 = (iz + 1) * points_per_channel + elapsed_points;
818 
819  for (unsigned int i = 0; i < elems_per_channel; i++)
820  {
821  Elem * elem = mesh_base->add_elem(std::make_unique<Prism6>());
822  elem->subdomain_id() = _subchannel_block_id;
823  elem->set_id(elem_id++);
824 
825  if (_verbose)
826  _console << "Node 0: " << *mesh_base->node_ptr(indx1) << std::endl;
827 
828  elem->set_node(0, mesh_base->node_ptr(indx1));
829  elem->set_node(1, mesh_base->node_ptr(indx1 + i + 1));
830  if (i != elems_per_channel - 1)
831  elem->set_node(2, mesh_base->node_ptr(indx1 + i + 2));
832  else
833  elem->set_node(2, mesh_base->node_ptr(indx1 + 1));
834 
835  elem->set_node(3, mesh_base->node_ptr(indx2));
836  elem->set_node(4, mesh_base->node_ptr(indx2 + i + 1));
837  if (i != elems_per_channel - 1)
838  elem->set_node(5, mesh_base->node_ptr(indx2 + i + 2));
839  else
840  elem->set_node(5, mesh_base->node_ptr(indx2 + 1));
841 
842  if (iz == 0)
843  boundary_info.add_side(elem, 0, 0);
844  if (iz == _n_cells - 1)
845  boundary_info.add_side(elem, 4, 1);
846 
847  element_counter += 1;
848  }
849  }
850  }
851  if (_verbose)
852  _console << "Element counter: " << element_counter << std::endl;
853  boundary_info.sideset_name(0) = "inlet";
854  boundary_info.sideset_name(1) = "outlet";
855  mesh_base->subdomain_name(_subchannel_block_id) = "subchannel";
856  if (n_pins > 0)
857  {
858  _elem_id = mesh_base->n_elem();
859  for (auto & ctr : _pin_position)
860  generatePin(mesh_base, ctr);
861  }
862  if (n_pins > 0)
863  mesh_base->subdomain_name(_pin_block_id) = "fuel_pins";
864  if (_verbose)
865  _console << "Mesh assembly done" << std::endl;
866  mesh_base->prepare_for_use();
867 
868  return mesh_base;
869 }
870 
871 Point
873 {
874  std::vector<std::vector<Real>> A(3, std::vector<Real>(3));
875 
876  A[0] = {std::cos(theta), -std::sin(theta), 0.0};
877  A[1] = {std::sin(theta), std::cos(theta), 0.0};
878  A[2] = {0.0, 0.0, 1.0};
879 
880  Point rotated_vector = Point(0.0, 0.0, 0.0);
881  for (unsigned int i = 0; i < 3; i++)
882  for (unsigned int j = 0; j < 3; j++)
883  rotated_vector(i) += A[i][j] * b(j);
884 
885  return rotated_vector;
886 }
const unsigned int _pin_block_id
Pin subdomain ID.
dof_id_type _elem_id
counter for element numbering
std::vector< Point > _pin_position
x,y coordinates of the fuel pins
void paramError(const std::string &param, Args... args) const
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
const unsigned int _num_sectors
Number of azimuthal sectors used to discretize each circular pin cross section.
const unsigned int _n_cells
Number of cells in the axial direction.
const Real _pitch
Distance between the neighbor fuel pins, pitch.
virtual std::unique_ptr< MeshBase > generate() override
static void pinPositions(std::vector< Point > &positions, unsigned int nrings, Real pitch, Point center)
Calculates and stores the pin positions/centers for a hexagonal assembly containing the given number ...
EChannelType getSubchannelType(unsigned int index) const
returns the type of the subchannel given the index
std::vector< std::vector< unsigned int > > _pins_in_rings
fuel pins that are belonging to each ring
const Real _flat_to_flat
Half of gap between adjacent assemblies.
void addRequiredParam(const std::string &name, const std::string &doc_string)
const Real _unheated_length_entry
unheated length of the fuel Pin at the entry of the assembly
std::vector< EChannelType > _subch_type
Subchannel type.
void deprecateParam(const std::string &old_name, const std::string &new_name, const std::string &removal_date)
const Real _heated_length
heated length of the fuel Pin
std::vector< Real > getSubchannelPosition(unsigned int i)
returns the position of subchannel given pin index
static InputParameters validParams()
const unsigned int _subchannel_block_id
Subchannel subdomain ID.
void generatePin(std::unique_ptr< MeshBase > &mesh_base, const Point &center)
Generate one detailed fuel pin volume centered at the supplied point.
ExpressionBuilder::EBTerm pow(const ExpressionBuilder::EBTerm &left, T exponent)
const bool _verbose
Flag to print out the detailed mesh assembly and coordinates.
Point rotatePoint(Point b, Real theta)
rotate a point by theta radians about the origin
const double radius
std::vector< std::vector< Real > > _subchannel_position
x,y coordinates of the subchannels
SCMDetailedTriAssemblyMeshGenerator(const InputParameters &parameters)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
static const std::string alpha
Definition: NS.h:138
void mooseError(Args &&... args) const
Point getPinPosition(unsigned int i)
returns the position of pin given pin index
void addClassDescription(const std::string &doc_string)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
registerMooseObject("SubChannelApp", SCMDetailedTriAssemblyMeshGenerator)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
const ConsoleStream _console
Mesh generator that builds a 3D mesh representing triangular subchannels and pins.
std::unique_ptr< MeshBase > buildMeshBaseObject(unsigned int dim=libMesh::invalid_uint)
registerMooseObjectRenamed("SubChannelApp", SCMDetailedTriSubChannelMeshGenerator, "06/30/2027 24:00", SCMDetailedTriAssemblyMeshGenerator)
std::vector< std::vector< unsigned int > > _chan_to_pin_map
stores the fuel pins belonging to each subchannel
std::vector< unsigned int > getSubChannelPins(unsigned int i)
returns the index of neighboring pins given subchannel index
MooseUnits pow(const MooseUnits &, int)
static const std::string k
Definition: NS.h:134
void ErrorVector unsigned int
const Real _unheated_length_exit
unheated length of the fuel Pin at the exit of the assembly
const unsigned int _n_rings
Number of rings in the geometry.
static const std::string center
Definition: NS.h:29
std::vector< Real > _z_grid
axial location of nodes
std::map< unsigned int, Real > _orientation_map
map inner and outer rings
const Real pi