https://mooseframework.inl.gov
Loading...
Searching...
No Matches
SCMDetailedQuadAssemblyMeshGenerator.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 "QuadSubChannelMesh.h"
12
13#include "libmesh/cell_prism6.h"
14#include "libmesh/unstructured_mesh.h"
15
16#include <array>
17#include <cmath>
18#include <memory>
19
22 SCMDetailedQuadSubChannelMeshGenerator,
23 "06/30/2027 24:00",
26 DetailedQuadSubChannelMeshGenerator,
27 "06/30/2027 24:00",
30 SCMDetailedQuadPinMeshGenerator,
31 "06/30/2027 24:00",
34 DetailedQuadPinMeshGenerator,
35 "06/30/2027 24:00",
37
40{
43 "Creates a detailed mesh of subchannels and fuel pins in a square lattice arrangement");
44 params.addRequiredParam<Real>("pitch", "Pitch [m]");
45 params.addRequiredParam<Real>("pin_diameter", "Rod diameter [m]");
46 params.addParam<Real>("unheated_length_entry", 0.0, "Unheated length at entry [m]");
47 params.addRequiredParam<Real>("heated_length", "Heated length [m]");
48 params.addParam<Real>("unheated_length_exit", 0.0, "Unheated length at exit [m]");
49 params.addRequiredParam<unsigned int>("n_cells", "The number of cells in the axial direction");
50 params.addRequiredParam<unsigned int>("nx", "Number of channels in the x direction [-]");
51 params.addRequiredParam<unsigned int>("ny", "Number of channels in the y direction [-]");
52 params.addRequiredParam<Real>(
53 "side_gap",
54 "The side gap, not to be confused with the gap between pins, this refers to the gap "
55 "next to the duct or else the distance between the subchannel centroid to the duct wall."
56 "distance(edge pin center, duct wall) = pitch / 2 + side_gap [m]");
57 params.addRangeCheckedParam<unsigned int>("num_sectors",
58 16,
59 "num_sectors>=4",
60 "Number of azimuthal sectors used to discretize each "
61 "circular pin cross section.");
62 params.addParam<unsigned int>("subchannel_block_id", 0, "Subchannel block id.");
63 params.addParam<unsigned int>("pin_block_id", 1, "Fuel pin block id.");
64 return params;
65}
66
68 const InputParameters & parameters)
69 : MeshGenerator(parameters),
70 _unheated_length_entry(getParam<Real>("unheated_length_entry")),
71 _heated_length(getParam<Real>("heated_length")),
72 _unheated_length_exit(getParam<Real>("unheated_length_exit")),
73 _pitch(getParam<Real>("pitch")),
74 _pin_diameter(getParam<Real>("pin_diameter")),
75 _n_cells(getParam<unsigned int>("n_cells")),
76 _nx(getParam<unsigned int>("nx")),
77 _ny(getParam<unsigned int>("ny")),
78 _n_channels(0),
79 _side_gap(getParam<Real>("side_gap")),
80 _num_sectors(getParam<unsigned int>("num_sectors")),
81 _subchannel_block_id(getParam<unsigned int>("subchannel_block_id")),
82 _pin_block_id(getParam<unsigned int>("pin_block_id")),
83 _elem_id(0)
84{
86
87 if (_n_cells == 0)
88 paramError("n_cells", "The number of axial cells must be greater than zero");
89
90 if (L <= 0.0)
91 mooseError("Total bundle length must be greater than zero");
92
93 if (_nx == 0 || _ny == 0)
94 mooseError("The number of subchannels must be greater than zero in each direction");
95
96 if (_nx < 2 && _ny < 2)
97 mooseError("The number of subchannels cannot be less than 2 in both directions. "
98 "Smallest assembly allowed is either 2X1 or 1X2.");
99
100 _n_channels = _nx * _ny;
101
102 const Real dz = L / _n_cells;
103 for (unsigned int i = 0; i < _n_cells + 1; i++)
104 _z_grid.push_back(dz * i);
105
107 for (unsigned int i = 0; i < _n_channels; i++)
108 {
109 _subchannel_position[i].reserve(3);
110 for (unsigned int j = 0; j < 3; j++)
111 _subchannel_position.at(i).push_back(0.0);
112 }
113
114 _subch_type.resize(_n_channels);
115 for (unsigned int iy = 0; iy < _ny; iy++)
116 for (unsigned int ix = 0; ix < _nx; ix++)
117 {
118 const unsigned int i_ch = _nx * iy + ix;
119 const bool is_corner = (ix == 0 && iy == 0) || (ix == _nx - 1 && iy == 0) ||
120 (ix == 0 && iy == _ny - 1) || (ix == _nx - 1 && iy == _ny - 1);
121 const bool is_edge = (ix == 0 || iy == 0 || ix == _nx - 1 || iy == _ny - 1);
122
123 if (is_corner)
125 else if (is_edge)
127 else
129
130 // Set the subchannel positions so that the center of the assembly is the zero point.
131 const Real offset_x = (_nx - 1) * _pitch / 2.0;
132 const Real offset_y = (_ny - 1) * _pitch / 2.0;
133 _subchannel_position[i_ch][0] = _pitch * ix - offset_x;
134 _subchannel_position[i_ch][1] = _pitch * iy - offset_y;
135 }
136}
137
138void
139SCMDetailedQuadAssemblyMeshGenerator::generatePin(std::unique_ptr<MeshBase> & mesh_base,
140 const Point & center)
141{
142 const Real dalpha = 360. / _num_sectors;
143 const Real radius = _pin_diameter / 2.;
144
145 // Add a center node and radial boundary nodes on each axial level so each pin is discretized into
146 // triangular prism sectors.
147 std::vector<std::vector<Node *>> nodes;
148 nodes.resize(_n_cells + 1);
149 for (unsigned int k = 0; k < _n_cells + 1; k++)
150 {
151 const Real elev = _z_grid[k];
152 nodes[k].push_back(mesh_base->add_point(Point(center(0), center(1), elev)));
153 Real alpha = 0.;
154 for (unsigned int i = 0; i < _num_sectors; i++, alpha += dalpha)
155 {
156 const Real dx = radius * std::cos(alpha * M_PI / 180.);
157 const Real dy = radius * std::sin(alpha * M_PI / 180.);
158 nodes[k].push_back(mesh_base->add_point(Point(center(0) + dx, center(1) + dy, elev)));
159 }
160 }
161
162 // Add the pin volume elements, linking matching radial sectors between adjacent axial levels.
163 for (unsigned int k = 0; k < _n_cells; k++)
164 for (unsigned int i = 0; i < _num_sectors; i++)
165 {
166 Elem * elem = mesh_base->add_elem(std::make_unique<Prism6>());
167 elem->subdomain_id() = _pin_block_id;
168 elem->set_id(_elem_id++);
169 const unsigned int ctr_idx = 0;
170 const unsigned int idx1 = (i % _num_sectors) + 1;
171 const unsigned int idx2 = ((i + 1) % _num_sectors) + 1;
172 elem->set_node(0, nodes[k][ctr_idx]);
173 elem->set_node(1, nodes[k][idx1]);
174 elem->set_node(2, nodes[k][idx2]);
175 elem->set_node(3, nodes[k + 1][ctr_idx]);
176 elem->set_node(4, nodes[k + 1][idx1]);
177 elem->set_node(5, nodes[k + 1][idx2]);
178 }
179}
180
181std::unique_ptr<MeshBase>
183{
184 auto mesh_base = buildMeshBaseObject();
185 BoundaryInfo & boundary_info = mesh_base->get_boundary_info();
186 mesh_base->set_spatial_dimension(3);
187
188 // Define the resolution (the number of points used to represent a circle). This must be
189 // divisible by 4.
190 const unsigned int n_pins = (_nx - 1) * (_ny - 1);
191
192 const unsigned int theta_res = 16;
193 // Compute the number of points needed to represent one quarter of a circle.
194 const unsigned int points_per_quad = theta_res / 4 + 1;
195
196 // Compute the points needed to represent one axial cross-flow of a subchannel. For the center
197 // subchannel there is one center point plus the points from 4 intersecting circles. For the
198 // corner subchannel there is one center point plus the points from 1 intersecting circle plus 3
199 // corners. For the side subchannel there is one center point plus the points from 2 intersecting
200 // circles plus 2 corners.
201 const unsigned int points_per_center = points_per_quad * 4 + 1;
202 const unsigned int points_per_corner = points_per_quad * 1 + 1 + 3;
203 const unsigned int points_per_side = points_per_quad * 2 + 1 + 2;
204
205 // Compute the number of Prism6 elements which combine to create each subchannel cross-section.
206 const unsigned int elems_per_center = theta_res + 4;
207 const unsigned int elems_per_corner = theta_res / 4 + 4;
208 const unsigned int elems_per_side = theta_res / 2 + 4;
209
210 // Specify the number and type of subchannels.
211 unsigned int n_center, n_side, n_corner;
212 if (_n_channels == 2)
213 {
214 n_corner = 0;
215 n_side = _n_channels;
216 n_center = _n_channels - n_side - n_corner;
217 }
218 else if (_n_channels > 2 && (_ny == 1 || _nx == 1))
219 {
220 n_corner = 0;
221 n_side = 2;
222 n_center = _n_channels - n_side - n_corner;
223 }
224 else
225 {
226 n_corner = 4;
227 n_side = 2 * (_ny - 2) + 2 * (_nx - 2);
228 n_center = _n_channels - n_side - n_corner;
229 }
230
231 const unsigned int points_per_level =
232 n_corner * points_per_corner + n_side * points_per_side + n_center * points_per_center;
233 const unsigned int elems_per_level =
234 n_corner * elems_per_corner + n_side * elems_per_side + n_center * elems_per_center;
235
236 // Pin centers are generated for 2x2 and larger quad assemblies.
237 std::vector<Point> pin_centers;
238 if (n_pins > 0)
240
241 const unsigned int pin_points =
242 n_pins > 0 ? (_n_cells + 1) * (_num_sectors + 1) * pin_centers.size() : 0;
243 const unsigned int pin_elems = n_pins > 0 ? _n_cells * _num_sectors * pin_centers.size() : 0;
244
245 mesh_base->reserve_nodes(points_per_level * (_n_cells + 1) + pin_points);
246 mesh_base->reserve_elem(elems_per_level * _n_cells + pin_elems);
247
248 // Build an array of points arranged in a circle on the xy-plane. The last and first node overlap.
249 const Real radius = _pin_diameter / 2.0;
250 std::array<Point, theta_res + 1> circle_points;
251 {
252 Real theta = 0;
253 for (unsigned int i = 0; i < theta_res + 1; i++)
254 {
255 circle_points[i](0) = radius * std::cos(theta);
256 circle_points[i](1) = radius * std::sin(theta);
257 theta += 2 * M_PI / theta_res;
258 }
259 }
260
261 // Define "quadrant center" reference points. These will be the centers of the 4 circles that
262 // represent the fuel pins. These centers are offset slightly so that in the final mesh, there is
263 // a tiny gap between neighboring subchannel cells. That allows us to easily map a solution to
264 // this detailed mesh with a nearest-neighbor search.
265 const Real shrink_factor = 0.99999;
266 std::array<Point, 4> quadrant_centers;
267 quadrant_centers[0] = Point(_pitch * 0.5 * shrink_factor, _pitch * 0.5 * shrink_factor, 0);
268 quadrant_centers[1] = Point(-_pitch * 0.5 * shrink_factor, _pitch * 0.5 * shrink_factor, 0);
269 quadrant_centers[2] = Point(-_pitch * 0.5 * shrink_factor, -_pitch * 0.5 * shrink_factor, 0);
270 quadrant_centers[3] = Point(_pitch * 0.5 * shrink_factor, -_pitch * 0.5 * shrink_factor, 0);
271
272 const unsigned int m = theta_res / 4;
273 // Build an array of points that represent a cross-section of a center subchannel cell. The points
274 // are ordered in this fashion:
275 // 4 3
276 // 6 5 2 1
277 // 0
278 // 7 8 * *
279 // 9 *
280 std::array<Point, points_per_center> center_points;
281 {
282 unsigned int start;
283 for (unsigned int i = 0; i < 4; i++)
284 {
285 if (i == 0)
286 start = 3 * m;
287 if (i == 1)
288 start = 4 * m;
289 if (i == 2)
290 start = 1 * m;
291 if (i == 3)
292 start = 2 * m;
293 for (unsigned int ii = 0; ii < points_per_quad; ii++)
294 {
295 auto c_pt = circle_points[start - ii];
296 center_points[i * points_per_quad + ii + 1] = quadrant_centers[i] + c_pt;
297 }
298 }
299 }
300
301 // Build an array of points that represent a cross-section of a top left corner subchannel cell.
302 // The points are ordered in this fashion:
303 // 5 4
304 //
305 // 0
306 // 2 3
307 // 6 1
308 std::array<Point, points_per_corner> tl_corner_points;
309 {
310 for (unsigned int ii = 0; ii < points_per_quad; ii++)
311 {
312 auto c_pt = circle_points[2 * m - ii];
313 tl_corner_points[ii + 1] = quadrant_centers[3] + c_pt;
314 }
315 tl_corner_points[points_per_quad + 1] = Point(_pitch * 0.5 * shrink_factor, _side_gap, 0);
316 tl_corner_points[points_per_quad + 2] = Point(-_side_gap, _side_gap, 0);
317 tl_corner_points[points_per_quad + 3] = Point(-_side_gap, -_pitch * 0.5 * shrink_factor, 0);
318 }
319
320 // Build an array of points that represent a cross-section of a top right corner subchannel cell.
321 // The points are ordered in this fashion:
322 // 6 5
323 //
324 // 0
325 // 1 2
326 // 3 4
327 std::array<Point, points_per_corner> tr_corner_points;
328 {
329 for (unsigned int ii = 0; ii < points_per_quad; ii++)
330 {
331 auto c_pt = circle_points[m - ii];
332 tr_corner_points[ii + 1] = quadrant_centers[2] + c_pt;
333 }
334 tr_corner_points[points_per_quad + 1] = Point(_side_gap, -_pitch * 0.5 * shrink_factor, 0);
335 tr_corner_points[points_per_quad + 2] = Point(_side_gap, _side_gap, 0);
336 tr_corner_points[points_per_quad + 3] = Point(-_pitch * 0.5 * shrink_factor, _side_gap, 0);
337 }
338
339 // Build an array of points that represent a cross-section of a bottom left corner subchannel
340 // cell. The points are ordered in this fashion:
341 // 4 3
342 // 2 1
343 // 0
344 //
345 // 5 6
346 std::array<Point, points_per_corner> bl_corner_points;
347 {
348 for (unsigned int ii = 0; ii < points_per_quad; ii++)
349 {
350 auto c_pt = circle_points[3 * m - ii];
351 bl_corner_points[ii + 1] = quadrant_centers[0] + c_pt;
352 }
353 bl_corner_points[points_per_quad + 1] = Point(-_side_gap, _pitch * 0.5 * shrink_factor, 0);
354 bl_corner_points[points_per_quad + 2] = Point(-_side_gap, -_side_gap, 0);
355 bl_corner_points[points_per_quad + 3] = Point(_pitch * 0.5 * shrink_factor, -_side_gap, 0);
356 }
357
358 // Build an array of points that represent a cross-section of a bottom right corner subchannel
359 // cell. The points are ordered in this fashion:
360 // 1 6
361 // 3 2
362 // 0
363 //
364 // 4 5
365 std::array<Point, points_per_corner> br_corner_points;
366 {
367 for (unsigned int ii = 0; ii < points_per_quad; ii++)
368 {
369 auto c_pt = circle_points[4 * m - ii];
370 br_corner_points[ii + 1] = quadrant_centers[1] + c_pt;
371 }
372 br_corner_points[points_per_quad + 1] = Point(-_pitch * 0.5 * shrink_factor, -_side_gap, 0);
373 br_corner_points[points_per_quad + 2] = Point(_side_gap, -_side_gap, 0);
374 br_corner_points[points_per_quad + 3] = Point(_side_gap, _pitch * 0.5 * shrink_factor, 0);
375 }
376
377 // Build an array of points that represent a cross-section of a top side subchannel cell. The
378 // points are ordered in this fashion:
379 // 8 7
380 //
381 // 0
382 // 1 2 5 6
383 // 3 4
384 std::array<Point, points_per_side> top_points;
385 {
386 for (unsigned int ii = 0; ii < points_per_quad; ii++)
387 {
388 auto c_pt = circle_points[m - ii];
389 top_points[ii + 1] = quadrant_centers[2] + c_pt;
390 }
391 for (unsigned int ii = 0; ii < points_per_quad; ii++)
392 {
393 auto c_pt = circle_points[2 * m - ii];
394 top_points[points_per_quad + ii + 1] = quadrant_centers[3] + c_pt;
395 }
396 top_points[2 * points_per_quad + 1] = Point(_pitch * 0.5 * shrink_factor, _side_gap, 0);
397 top_points[2 * points_per_quad + 2] = Point(-_pitch * 0.5 * shrink_factor, _side_gap, 0);
398 }
399
400 // Build an array of points that represent a cross-section of a left side subchannel cell. The
401 // points are ordered in this fashion:
402 // 7 6
403 // 5 4
404 // 0
405 // 2 3
406 // 8 1
407 std::array<Point, points_per_side> left_points;
408 {
409 for (unsigned int ii = 0; ii < points_per_quad; ii++)
410 {
411 auto c_pt = circle_points[2 * m - ii];
412 left_points[ii + 1] = quadrant_centers[3] + c_pt;
413 }
414 for (unsigned int ii = 0; ii < points_per_quad; ii++)
415 {
416 auto c_pt = circle_points[3 * m - ii];
417 left_points[points_per_quad + ii + 1] = quadrant_centers[0] + c_pt;
418 }
419 left_points[2 * points_per_quad + 1] = Point(-_side_gap, _pitch * 0.5 * shrink_factor, 0);
420 left_points[2 * points_per_quad + 2] = Point(-_side_gap, -_pitch * 0.5 * shrink_factor, 0);
421 }
422
423 // Build an array of points that represent a cross-section of a bottom side subchannel cell. The
424 // points are ordered in this fashion:
425 // 4 3
426 // 6 5 2 1
427 // 0
428 //
429 // 7 8
430 std::array<Point, points_per_side> bottom_points;
431 {
432 for (unsigned int ii = 0; ii < points_per_quad; ii++)
433 {
434 auto c_pt = circle_points[3 * m - ii];
435 bottom_points[ii + 1] = quadrant_centers[0] + c_pt;
436 }
437 for (unsigned int ii = 0; ii < points_per_quad; ii++)
438 {
439 auto c_pt = circle_points[4 * m - ii];
440 bottom_points[points_per_quad + ii + 1] = quadrant_centers[1] + c_pt;
441 }
442 bottom_points[2 * points_per_quad + 1] = Point(-_pitch * 0.5 * shrink_factor, -_side_gap, 0);
443 bottom_points[2 * points_per_quad + 2] = Point(_pitch * 0.5 * shrink_factor, -_side_gap, 0);
444 }
445
446 // Build an array of points that represent a cross-section of a right side subchannel cell. The
447 // points are ordered in this fashion:
448 // 1 8
449 // 3 2
450 // 0
451 // 4 5
452 // 6 7
453 std::array<Point, points_per_side> right_points;
454 {
455 for (unsigned int ii = 0; ii < points_per_quad; ii++)
456 {
457 auto c_pt = circle_points[4 * m - ii];
458 right_points[ii + 1] = quadrant_centers[1] + c_pt;
459 }
460 for (unsigned int ii = 0; ii < points_per_quad; ii++)
461 {
462 auto c_pt = circle_points[m - ii];
463 right_points[points_per_quad + ii + 1] = quadrant_centers[2] + c_pt;
464 }
465 right_points[2 * points_per_quad + 1] = Point(_side_gap, -_pitch * 0.5 * shrink_factor, 0);
466 right_points[2 * points_per_quad + 2] = Point(_side_gap, _pitch * 0.5 * shrink_factor, 0);
467 }
468
469 if (_n_channels == 2)
470 {
471 // Special handling for the smallest line meshes, which contain only side subchannels.
472 unsigned int node_id = 0;
473 const Real offset_x = (_nx - 1) * _pitch / 2.0;
474 const Real offset_y = (_ny - 1) * _pitch / 2.0;
475 for (unsigned int iy = 0; iy < _ny; iy++)
476 {
477 Point y0 = {0, _pitch * iy - offset_y, 0};
478 for (unsigned int ix = 0; ix < _nx; ix++)
479 {
480 Point x0 = {_pitch * ix - offset_x, 0, 0};
481 for (auto z : _z_grid)
482 {
483 Point z0{0, 0, z};
484 if (_nx == 1 && iy == 0) // vertical orientation
485 for (unsigned int i = 0; i < points_per_side; i++)
486 mesh_base->add_point(bottom_points[i] + x0 + y0 + z0, node_id++);
487 if (_nx == 1 && iy == 1) // vertical orientation
488 for (unsigned int i = 0; i < points_per_side; i++)
489 mesh_base->add_point(top_points[i] + x0 + y0 + z0, node_id++);
490 if (_ny == 1 && ix == 0) // horizontal orientation
491 for (unsigned int i = 0; i < points_per_side; i++)
492 mesh_base->add_point(left_points[i] + x0 + y0 + z0, node_id++);
493 if (_ny == 1 && ix == 1) // horizontal orientation
494 for (unsigned int i = 0; i < points_per_side; i++)
495 mesh_base->add_point(right_points[i] + x0 + y0 + z0, node_id++);
496 }
497 }
498 }
499 }
500 else if (_n_channels > 2 && (_ny == 1 || _nx == 1))
501 {
502 // Line meshes larger than 2 channels have two side subchannels and center subchannels between
503 // them.
504 unsigned int node_id = 0;
505 const Real offset_x = (_nx - 1) * _pitch / 2.0;
506 const Real offset_y = (_ny - 1) * _pitch / 2.0;
507 for (unsigned int iy = 0; iy < _ny; iy++)
508 {
509 Point y0 = {0, _pitch * iy - offset_y, 0};
510 for (unsigned int ix = 0; ix < _nx; ix++)
511 {
512 Point x0 = {_pitch * ix - offset_x, 0, 0};
513 for (auto z : _z_grid)
514 {
515 Point z0{0, 0, z};
516 if (_nx == 1)
517 {
518 if (iy == 0)
519 for (unsigned int i = 0; i < points_per_side; i++)
520 mesh_base->add_point(bottom_points[i] + x0 + y0 + z0, node_id++);
521 else if (iy == _ny - 1)
522 for (unsigned int i = 0; i < points_per_side; i++)
523 mesh_base->add_point(top_points[i] + x0 + y0 + z0, node_id++);
524 else
525 for (unsigned int i = 0; i < points_per_center; i++)
526 mesh_base->add_point(center_points[i] + x0 + y0 + z0, node_id++);
527 }
528 else if (_ny == 1)
529 {
530 if (ix == 0)
531 for (unsigned int i = 0; i < points_per_side; i++)
532 mesh_base->add_point(left_points[i] + x0 + y0 + z0, node_id++);
533 else if (ix == _nx - 1)
534 for (unsigned int i = 0; i < points_per_side; i++)
535 mesh_base->add_point(right_points[i] + x0 + y0 + z0, node_id++);
536 else
537 for (unsigned int i = 0; i < points_per_center; i++)
538 mesh_base->add_point(center_points[i] + x0 + y0 + z0, node_id++);
539 }
540 }
541 }
542 }
543 }
544 else
545 {
546 // General 2D quad assemblies contain corner, side, and center subchannels.
547 unsigned int node_id = 0;
548 const Real offset_x = (_nx - 1) * _pitch / 2.0;
549 const Real offset_y = (_ny - 1) * _pitch / 2.0;
550 for (unsigned int iy = 0; iy < _ny; iy++)
551 {
552 Point y0 = {0, _pitch * iy - offset_y, 0};
553 for (unsigned int ix = 0; ix < _nx; ix++)
554 {
555 Point x0 = {_pitch * ix - offset_x, 0, 0};
556 if (ix == 0 && iy == 0) // Bottom Left corner
557 for (auto z : _z_grid)
558 {
559 Point z0{0, 0, z};
560 for (unsigned int i = 0; i < points_per_corner; i++)
561 mesh_base->add_point(bl_corner_points[i] + x0 + y0 + z0, node_id++);
562 }
563 else if (ix == _nx - 1 && iy == 0) // Bottom right corner
564 for (auto z : _z_grid)
565 {
566 Point z0{0, 0, z};
567 for (unsigned int i = 0; i < points_per_corner; i++)
568 mesh_base->add_point(br_corner_points[i] + x0 + y0 + z0, node_id++);
569 }
570 else if (ix == 0 && iy == _ny - 1) // top Left corner
571 for (auto z : _z_grid)
572 {
573 Point z0{0, 0, z};
574 for (unsigned int i = 0; i < points_per_corner; i++)
575 mesh_base->add_point(tl_corner_points[i] + x0 + y0 + z0, node_id++);
576 }
577 else if (ix == _nx - 1 && iy == _ny - 1) // top right corner
578 for (auto z : _z_grid)
579 {
580 Point z0{0, 0, z};
581 for (unsigned int i = 0; i < points_per_corner; i++)
582 mesh_base->add_point(tr_corner_points[i] + x0 + y0 + z0, node_id++);
583 }
584 else if (ix == 0 && (iy != _ny - 1 || iy != 0)) // left side
585 for (auto z : _z_grid)
586 {
587 Point z0{0, 0, z};
588 for (unsigned int i = 0; i < points_per_side; i++)
589 mesh_base->add_point(left_points[i] + x0 + y0 + z0, node_id++);
590 }
591 else if (ix == _nx - 1 && (iy != _ny - 1 || iy != 0)) // right side
592 for (auto z : _z_grid)
593 {
594 Point z0{0, 0, z};
595 for (unsigned int i = 0; i < points_per_side; i++)
596 mesh_base->add_point(right_points[i] + x0 + y0 + z0, node_id++);
597 }
598 else if (iy == 0 && (ix != _nx - 1 || ix != 0)) // bottom side
599 for (auto z : _z_grid)
600 {
601 Point z0{0, 0, z};
602 for (unsigned int i = 0; i < points_per_side; i++)
603 mesh_base->add_point(bottom_points[i] + x0 + y0 + z0, node_id++);
604 }
605 else if (iy == _ny - 1 && (ix != _nx - 1 || ix != 0)) // top side
606 for (auto z : _z_grid)
607 {
608 Point z0{0, 0, z};
609 for (unsigned int i = 0; i < points_per_side; i++)
610 mesh_base->add_point(top_points[i] + x0 + y0 + z0, node_id++);
611 }
612 else // center
613 for (auto z : _z_grid)
614 {
615 Point z0{0, 0, z};
616 for (unsigned int i = 0; i < points_per_center; i++)
617 mesh_base->add_point(center_points[i] + x0 + y0 + z0, node_id++);
618 }
619 }
620 }
621 }
622
623 if (_n_channels == 2)
624 {
625 // Build elements for the smallest line meshes.
626 for (unsigned int iy = 0; iy < _ny; iy++)
627 for (unsigned int ix = 0; ix < _nx; ix++)
628 {
629 const unsigned int i_ch = _nx * iy + ix;
630 for (unsigned int iz = 0; iz < _n_cells; iz++)
631 for (unsigned int i = 0; i < elems_per_side; i++)
632 {
633 Elem * elem = mesh_base->add_elem(std::make_unique<Prism6>());
634 elem->subdomain_id() = _subchannel_block_id;
635 elem->set_id(_elem_id++);
636 // index of the central node at base of cell
637 const unsigned int indx1 =
638 iz * points_per_side + points_per_side * (_n_cells + 1) * i_ch;
639 const unsigned int indx2 =
640 (iz + 1) * points_per_side + points_per_side * (_n_cells + 1) * i_ch;
641 const unsigned int elems_per_channel = elems_per_side;
642 elem->set_node(0, mesh_base->node_ptr(indx1));
643 elem->set_node(1, mesh_base->node_ptr(indx1 + i + 1));
644 elem->set_node(2,
645 i != elems_per_channel - 1 ? mesh_base->node_ptr(indx1 + i + 2)
646 : mesh_base->node_ptr(indx1 + 1));
647 elem->set_node(3, mesh_base->node_ptr(indx2));
648 elem->set_node(4, mesh_base->node_ptr(indx2 + i + 1));
649 elem->set_node(5,
650 i != elems_per_channel - 1 ? mesh_base->node_ptr(indx2 + i + 2)
651 : mesh_base->node_ptr(indx2 + 1));
652
653 if (iz == 0)
654 boundary_info.add_side(elem, 0, 0);
655 if (iz == _n_cells - 1)
656 boundary_info.add_side(elem, 4, 1);
657 }
658 }
659 }
660 else if (_n_channels > 2 && (_ny == 1 || _nx == 1))
661 {
662 // Build elements for 1xN and Nx1 line meshes.
663 unsigned int number_of_corner = 0;
664 unsigned int number_of_side = 0;
665 unsigned int number_of_center = 0;
666 unsigned int elems_per_channel = 0;
667 unsigned int points_per_channel = 0;
668 for (unsigned int iy = 0; iy < _ny; iy++)
669 for (unsigned int ix = 0; ix < _nx; ix++)
670 {
671 const unsigned int i_ch = _nx * iy + ix;
672 const auto subch_type = getSubchannelType(i_ch);
673 if (subch_type == EChannelType::CORNER)
674 {
675 number_of_side++;
676 elems_per_channel = elems_per_side;
677 points_per_channel = points_per_side;
678 }
679 else if (subch_type == EChannelType::EDGE)
680 {
681 number_of_center++;
682 elems_per_channel = elems_per_center;
683 points_per_channel = points_per_center;
684 }
685
686 for (unsigned int iz = 0; iz < _n_cells; iz++)
687 {
688 const unsigned int elapsed_points =
689 number_of_corner * points_per_corner * (_n_cells + 1) +
690 number_of_side * points_per_side * (_n_cells + 1) +
691 number_of_center * points_per_center * (_n_cells + 1) -
692 points_per_channel * (_n_cells + 1);
693 // index of the central node at base of cell
694 const unsigned int indx1 = iz * points_per_channel + elapsed_points;
695 const unsigned int indx2 = (iz + 1) * points_per_channel + elapsed_points;
696
697 for (unsigned int i = 0; i < elems_per_channel; i++)
698 {
699 Elem * elem = mesh_base->add_elem(std::make_unique<Prism6>());
700 elem->subdomain_id() = _subchannel_block_id;
701 elem->set_id(_elem_id++);
702 elem->set_node(0, mesh_base->node_ptr(indx1));
703 elem->set_node(1, mesh_base->node_ptr(indx1 + i + 1));
704 elem->set_node(2,
705 i != elems_per_channel - 1 ? mesh_base->node_ptr(indx1 + i + 2)
706 : mesh_base->node_ptr(indx1 + 1));
707 elem->set_node(3, mesh_base->node_ptr(indx2));
708 elem->set_node(4, mesh_base->node_ptr(indx2 + i + 1));
709 elem->set_node(5,
710 i != elems_per_channel - 1 ? mesh_base->node_ptr(indx2 + i + 2)
711 : mesh_base->node_ptr(indx2 + 1));
712
713 if (iz == 0)
714 boundary_info.add_side(elem, 0, 0);
715 if (iz == _n_cells - 1)
716 boundary_info.add_side(elem, 4, 1);
717 }
718 }
719 }
720 }
721 else
722 {
723 // Build elements for general 2D quad assemblies.
724 unsigned int number_of_corner = 0;
725 unsigned int number_of_side = 0;
726 unsigned int number_of_center = 0;
727 unsigned int elems_per_channel = 0;
728 unsigned int points_per_channel = 0;
729 for (unsigned int iy = 0; iy < _ny; iy++)
730 for (unsigned int ix = 0; ix < _nx; ix++)
731 {
732 const unsigned int i_ch = _nx * iy + ix;
733 const auto subch_type = getSubchannelType(i_ch);
734 if (subch_type == EChannelType::CORNER)
735 {
736 number_of_corner++;
737 elems_per_channel = elems_per_corner;
738 points_per_channel = points_per_corner;
739 }
740 else if (subch_type == EChannelType::EDGE)
741 {
742 number_of_side++;
743 elems_per_channel = elems_per_side;
744 points_per_channel = points_per_side;
745 }
746 else
747 {
748 number_of_center++;
749 elems_per_channel = elems_per_center;
750 points_per_channel = points_per_center;
751 }
752
753 for (unsigned int iz = 0; iz < _n_cells; iz++)
754 {
755 const unsigned int elapsed_points =
756 number_of_corner * points_per_corner * (_n_cells + 1) +
757 number_of_side * points_per_side * (_n_cells + 1) +
758 number_of_center * points_per_center * (_n_cells + 1) -
759 points_per_channel * (_n_cells + 1);
760 // index of the central node at base of cell
761 const unsigned int indx1 = iz * points_per_channel + elapsed_points;
762 const unsigned int indx2 = (iz + 1) * points_per_channel + elapsed_points;
763
764 for (unsigned int i = 0; i < elems_per_channel; i++)
765 {
766 Elem * elem = mesh_base->add_elem(std::make_unique<Prism6>());
767 elem->subdomain_id() = _subchannel_block_id;
768 elem->set_id(_elem_id++);
769 elem->set_node(0, mesh_base->node_ptr(indx1));
770 elem->set_node(1, mesh_base->node_ptr(indx1 + i + 1));
771 elem->set_node(2,
772 i != elems_per_channel - 1 ? mesh_base->node_ptr(indx1 + i + 2)
773 : mesh_base->node_ptr(indx1 + 1));
774 elem->set_node(3, mesh_base->node_ptr(indx2));
775 elem->set_node(4, mesh_base->node_ptr(indx2 + i + 1));
776 elem->set_node(5,
777 i != elems_per_channel - 1 ? mesh_base->node_ptr(indx2 + i + 2)
778 : mesh_base->node_ptr(indx2 + 1));
779
780 if (iz == 0)
781 boundary_info.add_side(elem, 0, 0);
782 if (iz == _n_cells - 1)
783 boundary_info.add_side(elem, 4, 1);
784 }
785 }
786 }
787 }
788
789 if (n_pins > 0)
790 for (auto & ctr : pin_centers)
791 generatePin(mesh_base, ctr);
792
793 boundary_info.sideset_name(0) = "inlet";
794 boundary_info.sideset_name(1) = "outlet";
795 mesh_base->set_subdomain_name(_subchannel_block_id, "subchannel", true);
796 if (n_pins > 0)
797 mesh_base->set_subdomain_name(_pin_block_id, "fuel_pins", true);
798 mesh_base->prepare_for_use();
799
800 return mesh_base;
801}
Point center
registerMooseObjectRenamed("SubChannelApp", SCMDetailedQuadSubChannelMeshGenerator, "06/30/2027 24:00", SCMDetailedQuadAssemblyMeshGenerator)
registerMooseObject("SubChannelApp", SCMDetailedQuadAssemblyMeshGenerator)
void ErrorVector unsigned int
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
static InputParameters validParams()
std::unique_ptr< MeshBase > buildMeshBaseObject(unsigned int dim=libMesh::invalid_uint)
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
static void generatePinCenters(unsigned int nx, unsigned int ny, Real pitch, Real elev, std::vector< Point > &pin_centers)
Generate pin centers.
Mesh generator that builds a detailed 3D mesh representing quadrilateral subchannels and pins.
std::vector< std::vector< Real > > _subchannel_position
x,y coordinates of the subchannel centroids
const Real _unheated_length_entry
unheated length of the fuel Pin at the entry of the assembly
const Real _heated_length
heated length of the fuel Pin
SCMDetailedQuadAssemblyMeshGenerator(const InputParameters &parameters)
const unsigned int _nx
Number of subchannels in the x direction.
const Real _pitch
Distance between the neighbor fuel pins, pitch.
const Real _side_gap
The side gap, not to be confused with the gap between pins, this refers to the gap next to the duct o...
unsigned int _n_channels
Total number of subchannels.
virtual std::unique_ptr< MeshBase > generate() override
EChannelType getSubchannelType(unsigned int index) const
returns the type of the subchannel given the index
const unsigned int _num_sectors
Number of azimuthal sectors used to discretize each circular pin cross section.
const unsigned int _ny
Number of subchannels in the y direction.
void generatePin(std::unique_ptr< MeshBase > &mesh_base, const Point &center)
Generate one detailed fuel pin volume centered at the supplied point.
std::vector< Real > _z_grid
axial location of nodes
const unsigned int _n_cells
Number of cells in the axial direction.
dof_id_type _elem_id
Counter for element numbering.
const unsigned int _subchannel_block_id
Subchannel subdomain ID.
std::vector< EChannelType > _subch_type
Subchannel type.
const Real _unheated_length_exit
unheated length of the fuel Pin at the exit of the assembly
const Real radius