https://mooseframework.inl.gov
Loading...
Searching...
No Matches
HexagonalLatticeUtils.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 "MooseUtils.h"
12#include "GeometryUtils.h"
13#include "RotationMatrix.h"
14
16const Real HexagonalLatticeUtils::SIN60 = std::sqrt(3.0) / 2.0;
17const unsigned int HexagonalLatticeUtils::NUM_SIDES = 6;
18
19HexagonalLatticeUtils::HexagonalLatticeUtils(const Real bundle_inner_flat_to_flat,
20 const Real pin_pitch,
21 const Real pin_diameter,
22 const Real wire_diameter,
23 const Real wire_pitch,
24 const unsigned int n_rings,
25 const unsigned int axis,
26 const Real rotation_around_axis)
27 : _bundle_pitch(bundle_inner_flat_to_flat),
28 _pin_pitch(pin_pitch),
29 _pin_diameter(pin_diameter),
30 _wire_diameter(wire_diameter),
31 _wire_pitch(wire_pitch),
32 _n_rings(n_rings),
33 _axis(axis),
34 _rotation_around_axis(rotation_around_axis),
35 _rotation_matrix(
36 RotationMatrix::rotVec2DToX(Point(std::cos(_rotation_around_axis * libMesh::pi / 180),
37 -std::sin(_rotation_around_axis * libMesh::pi / 180),
38 0.))),
39 _bundle_side_length(hexagonSide(_bundle_pitch)),
40 _pin_area(M_PI * _pin_diameter * _pin_diameter / 4.0),
41 _pin_circumference(M_PI * _pin_diameter),
42 _wire_area(M_PI * _wire_diameter * _wire_diameter / 4.0),
43 _wire_circumference(M_PI * _wire_diameter),
44 _pin_surface_area_per_pitch(M_PI * _pin_diameter * _wire_pitch),
45 _pin_volume_per_pitch(_pin_area * _wire_pitch),
46 _P_over_D(_pin_pitch / _pin_diameter),
47 _L_over_D(_wire_pitch / _pin_diameter)
48{
50 _ix = idx.first;
51 _iy = idx.second;
52
53 // object is not tested and probably won't work if axis != 2
54 if (_axis != 2)
55 mooseError("The HexagonalLatticeUtils is currently limited to 'axis = 2'!");
56
58 mooseError("Pin pitch of " + std::to_string(_pin_pitch) + " cannot fit pins of diameter " +
59 std::to_string(_pin_diameter) + "!");
60
62 mooseError("Wire diameter of " + std::to_string(_wire_diameter) +
63 " cannot fit between pin-pin space of " +
64 std::to_string(_pin_pitch - _pin_diameter) + "!");
65
66 _unit_translation_x = {0.0, -SIN60, -SIN60, 0.0, SIN60, SIN60};
67 _unit_translation_y = {1.0, COS60, -COS60, -1.0, -COS60, COS60};
68
69 // Use rotation matrix on unit translation vectors
70 if (rotation_around_axis != 0)
71 for (const auto i : make_range(6))
72 {
73 const Point rotated =
75 _unit_translation_x[i] = rotated(0);
76 _unit_translation_y[i] = rotated(1);
77 }
78
79 // compute number of each pin and channel and channel type (interior, edge channel)
81
83
92
94 mooseError("Specified bundle pitch " + std::to_string(_bundle_pitch) +
95 " is too small to fit the given pins and wire wrap!");
96}
97
98unsigned int
99HexagonalLatticeUtils::pins(const unsigned int n) const
100{
101 if (n <= 0)
102 return 0;
103 else if (n == 1)
104 return 1;
105 else
106 return NUM_SIDES * (n - 1);
107}
108
109unsigned int
110HexagonalLatticeUtils::totalPins(const unsigned int n) const
111{
112 unsigned int total = 0;
113 for (unsigned int i = 1; i <= n; ++i)
114 total += pins(i);
115
116 return total;
117}
118
119unsigned int
120HexagonalLatticeUtils::rings(const unsigned int n) const
121{
122 auto remaining = n;
123 unsigned int i = 0;
124
125 while (remaining > 0)
126 {
127 i += 1;
128 remaining -= pins(i);
129 }
130
131 if (n != totalPins(i))
132 mooseError("Number of pins " + std::to_string(n) +
133 " not evenly divisible in a hexagonal lattice!");
134
135 return i;
136}
137
138Real
140{
141 return pitch / std::sqrt(3.0);
142}
143
144Real
146{
147 Real side = hexagonSide(pitch);
148 return 3 * SIN60 * side * side;
149}
150
151Real
153{
154 return hexagonArea(pitch) * _wire_pitch;
155}
156
157Real
159{
160 Real area = volume / _wire_pitch;
161 return std::sqrt(area / SIN60);
162}
163
164Real
166{
167 return 0.5 * triangleHeight(side) * side;
168}
169
170Real
172{
173 return SIN60 * side;
174}
175
176Real
178{
179 return height / SIN60;
180}
181
182Real
184{
185 return triangleArea(side) * _wire_pitch;
186}
187
188Real
190{
191 return _pin_diameter / 2.0;
192}
193
196{
197 const Real distance_to_wall = minDuctWallDistance(p);
198
199 if (distance_to_wall > _pin_bundle_spacing + pinRadius())
201
202 // the point is in a corner channel if the right triangle formed by the point,
203 // the corner, and the duct wall has a wall length less than the wall length
204 // of a corner channel
205 const Real distance_to_corner = minDuctCornerDistance(p);
206 Real l = std::sqrt(distance_to_corner * distance_to_corner - distance_to_wall * distance_to_wall);
207
208 if (l < _corner_edge_length)
210 else
211 return channel_type::edge;
212}
213
214Real
216 const channel_type::ChannelTypeEnum & channel) const
217{
218 switch (channel)
219 {
226 default:
227 mooseError("Unhandled ChannelTypeEnum in HexagonalLattice!");
228 }
229}
230
231Real
233{
234 switch (channel)
235 {
237 return _interior_Dh;
239 return _edge_Dh;
241 return _corner_Dh;
242 default:
243 mooseError("Unhandled ChannelTypeEnum in HexagonalLattice!");
244 }
245}
246
247Real
249{
250 Real distance = std::numeric_limits<Real>::max();
251 for (const auto i : make_range(NUM_SIDES))
252 {
253 Real a = _duct_coeffs[i][0];
254 Real b = _duct_coeffs[i][1];
255 Real c = _duct_coeffs[i][2];
256
257 Real d = std::abs(a * p(_ix) + b * p(_iy) + c) / std::sqrt(a * a + b * b);
258 distance = std::min(d, distance);
259 }
260
261 return distance;
262}
263
264Real
269
270void
272{
273 Real corner_shiftx[] = {COS60, -COS60, -1, -COS60, COS60, 1};
274 Real corner_shifty[] = {SIN60, SIN60, 0, -SIN60, -SIN60, 0};
275
276 Real edge_shiftx[] = {-1, -COS60, COS60, 1, COS60, -COS60};
277 Real edge_shifty[] = {0, -SIN60, -SIN60, 0, SIN60, SIN60};
278
279 // Use rotation matrix on shift vectors
280 if (_rotation_around_axis != 0)
281 for (const auto i : make_range(6))
282 {
283 Point rotated = _rotation_matrix * Point(corner_shiftx[i], corner_shifty[i], 0);
284 corner_shiftx[i] = rotated(0);
285 corner_shifty[i] = rotated(1);
286 rotated = _rotation_matrix * Point(edge_shiftx[i], edge_shifty[i], 0);
287 edge_shiftx[i] = rotated(0);
288 edge_shifty[i] = rotated(1);
289 }
290
291 // compute coordinates of the pin centers relative to the bundle's center
292 Point center(0, 0, 0);
293 _pin_centers.push_back(center);
294
295 for (unsigned int i = 2; i <= _n_rings; ++i)
296 {
297 auto n_total_in_ring = pins(i);
298 auto increment = i - 1;
299
300 unsigned int d = 0;
301
302 for (const auto j : make_range(n_total_in_ring))
303 {
304 unsigned int side = j / increment;
305
306 if (d == increment)
307 d = 0;
308
309 Point center = geom_utils::projectPoint(corner_shiftx[side] * _pin_pitch * (i - 1),
310 corner_shifty[side] * _pin_pitch * (i - 1),
311 _axis);
312
313 // additional shift for the edge sides
314 if (d != 0)
315 {
316 center(_ix) += edge_shiftx[side] * _pin_pitch * d;
317 center(_iy) += edge_shifty[side] * _pin_pitch * d;
318 }
319
320 _pin_centers.push_back(center);
321
322 d += 1;
323 }
324 }
325
326 // compute corners of the hexagonal prisms that enclose each pin
328 auto side = hexagonSide(_pin_pitch);
329 for (const auto pin : make_range(_n_pins))
330 {
331 for (const auto i : make_range(NUM_SIDES))
332 {
333 Point translation = geom_utils::projectPoint(
334 _unit_translation_x[i] * side, _unit_translation_y[i] * side, _axis);
335
336 _pin_centered_corner_coordinates[pin].push_back(translation + _pin_centers[pin]);
337 }
338 }
339
340 // compute coordinates of duct corners relative to the bundle's center
341 Real l = _bundle_side_length;
342 for (const auto i : make_range(NUM_SIDES))
343 {
344 Point corner = geom_utils::projectPoint(corner_shiftx[i] * l, corner_shifty[i] * l, _axis);
345 _duct_corners.push_back(corner);
346 }
347
348 // compute the equations (a*x + b*y + c) defining each duct wall
349 for (const auto i : make_range(NUM_SIDES))
350 {
351 auto c = i;
352 unsigned int n = i == 5 ? 0 : i + 1;
353 Real slope = (_duct_corners[n](_iy) - _duct_corners[c](_iy)) /
355 std::vector<Real> coeffs = {-slope, 1.0, slope * _duct_corners[c](_ix) - _duct_corners[c](_iy)};
356 _duct_coeffs.push_back(coeffs);
357 }
358}
359
360void
362{
364
365 _interior_wetted_area = 0.5 * rod_area_per_pitch;
366 _edge_wetted_area = 0.5 * rod_area_per_pitch + _pin_pitch * _wire_pitch;
367
369 _corner_wetted_area = rod_area_per_pitch / 6.0 + 2 * h * _wire_pitch;
370 _wetted_area = _n_pins * rod_area_per_pitch + 6.0 * _bundle_side_length * _wire_pitch;
371}
372
373void
375{
376 // Because the hydraulic diameter should be different for a bundle with an
377 // infinite wire pitch vs. a finite wire pitch (because finite = more wire spooled in),
378 // we compute hydraulic diameters based on volumes and areas.
383}
384
385void
387{
388 // It is assumed that in the interior and edge channels, that a wire is present half
389 // of the time (because those channels contain half a pin), while in the corner channels
390 // that a wire is present one sixth of the time (because those channels contain one sixth
391 // of a pin).
392 Real rod_volume_per_pitch = _pin_volume_per_pitch + _wire_volume_per_pitch;
393
394 Real l = _pin_bundle_spacing + pinRadius();
395
399
400 _interior_flow_volume = _interior_volume - 0.5 * rod_volume_per_pitch;
401 _edge_flow_volume = _edge_volume - 0.5 * rod_volume_per_pitch;
402 _corner_flow_volume = _corner_volume - rod_volume_per_pitch / 6.0;
403
404 _flow_volume = hexagonVolume(_bundle_pitch) - _n_pins * rod_volume_per_pitch;
405}
406
407unsigned int
409{
410 return (ring * 2 - 1) * NUM_SIDES;
411}
412
413void
415{
416 _n_pins = 0;
417 for (unsigned int i = _n_rings; i > 0; --i)
418 _n_pins += pins(i);
419
420 // the one-ring case
422 _n_edge_pins = 0;
423 _n_corner_pins = 0;
424
428
429 if (_n_rings > 1)
430 {
434
436
437 for (unsigned int i = 1; i < _n_rings; ++i)
439 }
440
442}
443
444void
446{
447 // hexagonal flat-to-flat that *just* encloses the pins (NOT including the wire)
448 Real pin_h = 2.0 * (_n_rings - 1) * triangleHeight(_pin_pitch) + _pin_diameter;
449
450 _pin_bundle_spacing = (_bundle_pitch - pin_h) / 2.0;
451}
452
453void
455{
456 unsigned int n_wire_coils = 1;
457 Real wire_length_per_coil =
458 std::sqrt(_wire_pitch * _wire_pitch + std::pow(M_PI * (_pin_diameter + _wire_diameter), 2.0));
459 Real wire_length = wire_length_per_coil * n_wire_coils;
460 _wire_volume_per_pitch = _wire_area * wire_length;
462}
463
464void
466{
470
471 // 3 pins per interior channel
472 for (auto & i : _interior_channel_pin_indices)
473 i.resize(3);
474
475 // 2 pins per edge channel
476 for (auto & i : _edge_channel_pin_indices)
477 i.resize(2);
478
479 // 1 pin per corner channel
480 for (auto & i : _corner_channel_pin_indices)
481 i.resize(1);
482
483 // for each ring of pins, and for each sector, get the "first" pin index in that ring
484 std::vector<std::vector<unsigned int>> starts;
485 starts.resize(_n_rings);
486 for (const auto i : make_range(_n_rings))
487 {
488 starts[i].resize(NUM_SIDES);
489 starts[i][0] = (i == 0) ? 0 : totalPins(i);
490
491 for (unsigned int j = 1; j < NUM_SIDES; ++j)
492 starts[i][j] = starts[i][j - 1] + i;
493 }
494
495 unsigned int c = 0;
496 for (const auto i : make_range(_n_rings - 1))
497 {
498 auto channels = interiorChannels(i + 1);
499 unsigned int channels_per_sector = channels / NUM_SIDES;
500
501 for (const auto j : make_range(NUM_SIDES))
502 {
503 auto prev_inner = starts[i][j];
504 auto prev_outer = starts[i + 1][j];
505
506 for (const auto k : make_range(channels_per_sector))
507 {
508 bool downward = k % 2 == 0;
509
510 if (downward)
511 {
512 _interior_channel_pin_indices[c][0] = prev_inner;
513 _interior_channel_pin_indices[c][1] = prev_outer;
514 _interior_channel_pin_indices[c][2] = ++prev_outer;
515 }
516 else
517 {
518 _interior_channel_pin_indices[c][0] = prev_outer;
519 _interior_channel_pin_indices[c][1] = ++prev_inner;
520 _interior_channel_pin_indices[c][2] = prev_inner - 1;
521 }
522
523 if (j == 5) // last sector
524 {
525 if (k == channels_per_sector - 1)
526 {
527 _interior_channel_pin_indices[c][2] = starts[i + 1][0];
528 _interior_channel_pin_indices[c][0] = starts[i][0];
529 }
530
531 if (k == channels_per_sector - 2 && i > 0)
532 _interior_channel_pin_indices[c][1] = starts[i][0];
533 }
534
535 c++;
536 }
537 }
538 }
539
540 for (const auto i : make_range(_n_edge_channels))
541 {
542 _edge_channel_pin_indices[i][0] = starts[_n_rings - 1][0] + i;
544
545 if (i == _n_edge_channels - 1)
547 }
548
549 for (const auto i : make_range(_n_corner_channels))
552}
553
554const std::vector<Point>
556 const unsigned int interior_channel_id) const
557{
558 std::vector<Point> corners;
559 auto pin_indices = _interior_channel_pin_indices[interior_channel_id];
560 for (const auto & pin : pin_indices)
561 corners.push_back(_pin_centers[pin]);
562
563 return corners;
564}
565
566const std::vector<Point>
567HexagonalLatticeUtils::edgeChannelCornerCoordinates(const unsigned int edge_channel_id) const
568{
569 std::vector<Point> corners;
570
571 auto pin_indices = _edge_channel_pin_indices[edge_channel_id];
572
573 const Point & pin1 = _pin_centers[pin_indices[0]];
574 const Point & pin2 = _pin_centers[pin_indices[1]];
575
576 Real d = pinBundleSpacing() + pinRadius();
577
578 corners.push_back(pin1);
579 corners.push_back(pin2);
580
581 unsigned int sector = edge_channel_id / (_n_rings - 1);
582 corners.push_back(pin2 +
583 Point(d * _unit_translation_x[sector], d * _unit_translation_y[sector], 0.0));
584 corners.push_back(pin1 +
585 Point(d * _unit_translation_x[sector], d * _unit_translation_y[sector], 0.0));
586
587 return corners;
588}
589
590const std::vector<Point>
591HexagonalLatticeUtils::cornerChannelCornerCoordinates(const unsigned int corner_channel_id) const
592{
593 std::vector<Point> corners;
594
595 auto pin_indices = _corner_channel_pin_indices[corner_channel_id];
596 const Point & pin = _pin_centers[pin_indices[0]];
597 corners.push_back(pin);
598
599 Real d = pinBundleSpacing() + pinRadius();
600
601 unsigned int side1 = corner_channel_id == 0 ? NUM_SIDES - 1 : corner_channel_id - 1;
602 unsigned int side2 = corner_channel_id;
603
604 corners.push_back(pin +
605 Point(d * _unit_translation_x[side1], d * _unit_translation_y[side1], 0.0));
606 corners.push_back(_duct_corners[corner_channel_id]);
607 corners.push_back(pin +
608 Point(d * _unit_translation_x[side2], d * _unit_translation_y[side2], 0.0));
609
610 return corners;
611}
612
613Point
614HexagonalLatticeUtils::channelCentroid(const std::vector<Point> & pins) const
615{
616 int n_pins = pins.size();
617 Point centroid(0.0, 0.0, 0.0);
618 for (const auto & p : pins)
619 centroid += p / n_pins;
620
621 return centroid;
622}
623
624unsigned int
625HexagonalLatticeUtils::pinIndex(const Point & point) const
626{
627 auto side = hexagonSide(_pin_pitch);
628
629 for (const auto i : make_range(_n_pins))
630 {
631 const auto & center = _pin_centers[i];
632 Real dx = center(_ix) - point(_ix);
633 Real dy = center(_iy) - point(_iy);
634 Real distance_from_pin = std::sqrt(dx * dx + dy * dy);
635
636 // if we're outside the circumference of the hexagon, we're certain not
637 // within the hexagon for this pin
638 if (distance_from_pin > side)
639 continue;
640
641 auto corners = _pin_centered_corner_coordinates[i];
642 if (geom_utils::pointInPolygon(point, corners, _axis))
643 return i;
644 }
645
646 return _n_pins;
647}
648
649unsigned int
651{
652 // If within the lattice, you must consider all pins. If outside, outer ring suffices
653 unsigned int start_index = 0;
654 if (!insideLattice(point))
655 start_index = firstPinInRing(_n_rings);
656
657 // Classic minimum search. If more performance is required for a large lattice we may consider
658 // using a KD-Tree instead or checking which ring the point is part of before examining each pin
659 auto min_distance = std::numeric_limits<Real>::max();
660 unsigned int index_min = 0;
661 for (unsigned int i = start_index; i < _n_pins; ++i)
662 {
663 const auto & center = _pin_centers[i];
664 Real dx = center(_ix) - point(_ix);
665 Real dy = center(_iy) - point(_iy);
666 Real distance_from_pin = std::sqrt(dx * dx + dy * dy);
667
668 if (distance_from_pin < min_distance)
669 {
670 min_distance = distance_from_pin;
671 index_min = i;
672 }
673 }
674
675 return index_min;
676}
677
678unsigned int
679HexagonalLatticeUtils::channelIndex(const Point & point) const
680{
681 auto channel = channelType(point);
682
683 switch (channel)
684 {
686 {
687 for (const auto i : make_range(_n_interior_channels))
688 {
689 auto corners = interiorChannelCornerCoordinates(i);
690 if (geom_utils::pointInPolygon(point, corners, _axis))
691 return i;
692 }
693 break;
694 }
696 {
697 for (const auto i : make_range(_n_edge_channels))
698 {
699 auto corners = edgeChannelCornerCoordinates(i);
700 if (geom_utils::pointInPolygon(point, corners, _axis))
701 return i + _n_interior_channels;
702 }
703 break;
704 }
706 {
707 for (const auto i : make_range(_n_corner_channels))
708 {
709 auto corners = cornerChannelCornerCoordinates(i);
710 if (geom_utils::pointInPolygon(point, corners, _axis))
712 }
713 break;
714 }
715 default:
716 mooseError("Unhandled ChannelTypeEnum!");
717 }
718
720 "Point (" + std::to_string(point(0)) + ", " + std::to_string(point(1)) + ", " +
721 std::to_string(point(2)) +
722 ") is not in any channel! This can sometimes happen "
723 "due to:\n\n a) Points in the mesh actually being outside the domain specified with the "
724 "HexagonalLatticeUtils.\n b) Small floating point errors - we recommend using a CONSTANT "
725 "MONOMIAL variable with all related objects.\nYou can also try slightly decreasing the pin "
726 "diameter and/or "
727 "increasing the bundle pitch.");
728}
729
730bool
731HexagonalLatticeUtils::insideLattice(const Point & point) const
732{
733 std::vector<Point> lattice_corners(6);
734 auto side = hexagonSide(_bundle_pitch);
735 for (const auto i : make_range(NUM_SIDES))
736 {
737 Point translation = geom_utils::projectPoint(
738 _unit_translation_x[i] * side, _unit_translation_y[i] * side, _axis);
739 lattice_corners[i] = translation;
740 }
741 return geom_utils::pointInPolygon(point, lattice_corners, _axis);
742}
743
744void
746 unsigned int & row_index,
747 unsigned int & within_row_index) const
748{
749
750 // First compute the ring on which the pin is
751 const auto ring_index = ringIndex(pin_index);
752 row_index = _n_rings - ring_index;
753 within_row_index = _n_rings - 1;
754
755 const auto n_pin_sectors = pins(ring_index) / 6;
756 // Loop around the center until we reach the pin_index
757 // We start on the diagonal at the top
758
759 for (const auto local_i : make_range(pin_index - firstPinInRing(ring_index)))
760 {
761 if (local_i < n_pin_sectors)
762 within_row_index--;
763 else if (local_i < 3 * n_pin_sectors)
764 row_index++;
765 else if (local_i < 4 * n_pin_sectors)
766 within_row_index++;
767 else if (local_i < 5 * n_pin_sectors)
768 {
769 row_index--;
770 within_row_index++;
771 }
772 else
773 {
774 row_index--;
775 within_row_index--;
776 }
777 }
778
779 // Underflow (row_index is invalid_uint) will also fail this assert
780 mooseAssert(row_index < 2 * _n_rings - 1, "Inverse indexing failed");
781 mooseAssert(within_row_index < 2 * _n_rings - 1, "Inverse indexing failed");
782}
783
784void
786{
787 std::set<std::pair<int, int>> indices;
788 for (const auto & pins : _interior_channel_pin_indices)
789 {
790 std::pair<int, int> gap0 = {std::min(pins[0], pins[1]), std::max(pins[0], pins[1])};
791 std::pair<int, int> gap1 = {std::min(pins[0], pins[2]), std::max(pins[0], pins[2])};
792 std::pair<int, int> gap2 = {std::min(pins[2], pins[1]), std::max(pins[2], pins[1])};
793
794 indices.insert(gap0);
795 indices.insert(gap1);
796 indices.insert(gap2);
797 }
798
799 for (const auto & it : indices)
800 _gap_indices.push_back({it.first, it.second});
801
802 _n_interior_gaps = _gap_indices.size();
803
804 // add the gaps along the peripheral channels; -1 indicates side 1, -2 indicates side 2,
805 // and so on
806 int n_edge_gaps = _n_rings;
807 int pin = totalPins(_n_rings - 1);
808 for (const auto i : make_range(NUM_SIDES))
809 {
810 for (const auto j : make_range(_n_rings))
811 _gap_indices.push_back({pin + j, -(i + 1)});
812
813 pin += n_edge_gaps - 1;
814 }
815
816 // fix the last gap to use the first pin
817 _gap_indices.back() = {totalPins(_n_rings - 1), -int(NUM_SIDES)};
818 _n_gaps = _gap_indices.size();
819
820 // for each channel, determine which gaps are on that channel to find the local-to-global
821 // indexing
822 for (const auto & pins : _interior_channel_pin_indices)
823 {
824 std::pair<int, int> gap0 = {std::min(pins[0], pins[1]), std::max(pins[0], pins[1])};
825 std::pair<int, int> gap1 = {std::min(pins[1], pins[2]), std::max(pins[1], pins[2])};
826 std::pair<int, int> gap2 = {std::min(pins[2], pins[0]), std::max(pins[2], pins[0])};
827
828 int loc_gap0 = globalGapIndex(gap0);
829 int loc_gap1 = globalGapIndex(gap1);
830 int loc_gap2 = globalGapIndex(gap2);
831 _local_to_global_gaps.push_back({loc_gap0, loc_gap1, loc_gap2});
832 }
833
834 int gap = _gap_indices.size() - _n_rings * NUM_SIDES;
835 for (const auto i : make_range(_n_edge_channels))
836 {
837 const auto & pins = _edge_channel_pin_indices[i];
838 std::pair<int, int> gap0 = {std::min(pins[0], pins[1]), std::max(pins[0], pins[1])};
839 int loc_gap0 = globalGapIndex(gap0);
840 _local_to_global_gaps.push_back({loc_gap0, gap + 1, gap});
841
842 if ((i + 1) % (_n_rings - 1) == 0)
843 gap += 2;
844 else
845 gap += 1;
846 }
847
848 int n_interior_gaps = _gap_indices.size() - _n_rings * NUM_SIDES - 1;
849 n_edge_gaps = _n_rings * NUM_SIDES;
850 _local_to_global_gaps.push_back({n_interior_gaps + n_edge_gaps, n_interior_gaps + 1});
851 gap = n_interior_gaps + _n_rings;
852 for (unsigned int i = 1; i < _n_corner_channels; ++i)
853 {
854 _local_to_global_gaps.push_back({gap, gap + 1});
855 gap += _n_rings;
856 }
857
858 _gap_points.resize(_n_gaps);
859
860 // For each gap, get two points on the gap
861 for (const auto i : make_range(_n_interior_gaps))
862 {
863 const auto & pins = _gap_indices[i];
864 Point pt1(_pin_centers[pins.first]);
865 Point pt2(_pin_centers[pins.second]);
866 _gap_centers.push_back(0.5 * (pt2 + pt1));
867
868 _gap_points[i] = {pt1, pt2};
869
870 // for the last gap in the ring, we need to swap the ordering of pins
871 if (lastGapInRing(i))
872 {
873 Point tmp = pt1;
874 pt1 = pt2;
875 pt2 = tmp;
876 }
877
878 _gap_unit_normals.push_back(geom_utils::projectedUnitNormal(pt1, pt2, _axis));
879 }
880
881 Real d = _pin_bundle_spacing + pinRadius();
882 for (unsigned int i = _n_interior_gaps; i < _n_gaps; ++i)
883 {
884 const auto & pins = _gap_indices[i];
885 int side = std::abs(pins.second) - 1;
886
887 const auto & pt1 = _pin_centers[pins.first];
888 const Point pt2 =
889 pt1 + Point(d * _unit_translation_x[side], d * _unit_translation_y[side], 0.0);
890 _gap_centers.push_back(0.5 * (pt2 + pt1));
891
892 _gap_points[i] = {pt1, pt2};
893
894 _gap_unit_normals.push_back(geom_utils::projectedUnitNormal(pt1, pt2, _axis));
895 }
896}
897
898unsigned int
899HexagonalLatticeUtils::globalGapIndex(const std::pair<int, int> & local_gap) const
900{
901 for (const auto i : index_range(_gap_indices))
902 {
903 const auto gap = _gap_indices[i];
904 if (gap.first == local_gap.first && gap.second == local_gap.second)
905 return i;
906 }
907
908 mooseError("Failed to find local gap in global gap array!");
909}
910
911Real
912HexagonalLatticeUtils::distanceFromGap(const Point & pt, const unsigned int gap_index) const
913{
914 auto p = _gap_points[gap_index];
916}
917
918unsigned int
919HexagonalLatticeUtils::gapIndex(const Point & point) const
920{
921 const auto & channel_index = channelIndex(point);
922 const auto & gap_indices = _local_to_global_gaps[channel_index];
923
924 Real distance = std::numeric_limits<Real>::max();
925 unsigned int index = 0;
926 for (const auto i : index_range(gap_indices))
927 {
928 Real distance_from_gap = distanceFromGap(point, gap_indices[i]);
929
930 if (distance_from_gap < distance)
931 {
932 distance = distance_from_gap;
933 index = gap_indices[i];
934 }
935 }
936
937 return index;
938}
939
940void
942 unsigned int & index,
943 Real & distance) const
944{
945 const auto & channel_index = channelIndex(point);
946 const auto & gap_indices = _local_to_global_gaps[channel_index];
947
948 distance = std::numeric_limits<Real>::max();
949 for (const auto i : index_range(gap_indices))
950 {
951 Real distance_from_gap = distanceFromGap(point, gap_indices[i]);
952
953 if (distance_from_gap < distance)
954 {
955 distance = distance_from_gap;
956 index = gap_indices[i];
957 }
958 }
959}
960
961unsigned int
962HexagonalLatticeUtils::firstPinInRing(const unsigned int ring) const
963{
964 mooseAssert(ring > 0, "Ring indexing starts at 1");
965 if (ring == 1)
966 return 0;
967 else
968 return totalPins(ring - 1);
969}
970
971unsigned int
972HexagonalLatticeUtils::lastPinInRing(const unsigned int ring) const
973{
974 mooseAssert(ring > 0, "Ring indexing starts at 1");
975 if (ring == 1)
976 return 0;
977 else
978 return totalPins(ring) - 1;
979}
980
981unsigned int
982HexagonalLatticeUtils::ringIndex(const unsigned int pin) const
983{
984 for (const auto i : make_range(_n_rings))
985 {
986 if (pin <= lastPinInRing(i + 1))
987 return i + 1;
988 }
989 mooseError("Should not reach here. Pin index: ", pin, " for ", _n_rings, " rings.");
990}
991
992bool
993HexagonalLatticeUtils::lastGapInRing(const unsigned int gap_index) const
994{
995 if (gap_index >= _n_interior_gaps)
996 return false;
997
998 const auto & pins = _gap_indices[gap_index];
999
1000 for (unsigned int i = 2; i <= _n_rings; ++i)
1001 {
1002 bool one_is_first_pin = false;
1003 bool one_is_last_pin = false;
1004
1005 int first_pin = firstPinInRing(i);
1006 int last_pin = lastPinInRing(i);
1007
1008 if (pins.first == first_pin || pins.second == first_pin)
1009 one_is_first_pin = true;
1010
1011 if (pins.first == last_pin || pins.second == last_pin)
1012 one_is_last_pin = true;
1013
1014 if (one_is_first_pin && one_is_last_pin)
1015 return true;
1016 }
1017
1018 return false;
1019}
const Real p
void mooseError(Args &&... args)
Point center
void ErrorVector unsigned int
const Real _bundle_side_length
Side length of duct.
unsigned int _n_corner_channels
Total number of corner channels.
unsigned int _n_pins
Total number of pins.
Real pinBundleSpacing() const
Get the distance between the outermost pins and the duct walls.
const Real _pin_volume_per_pitch
Single-pin volume per wire pitch.
Real _wire_volume_per_pitch
Single-wire volume per wire pitch.
std::vector< std::vector< unsigned int > > _interior_channel_pin_indices
Pin indices forming the corner of each interior channel.
void gapIndexAndDistance(const Point &point, unsigned int &index, Real &distance) const
Get the gap index and distance to that gap for a given point.
std::vector< Point > _pin_centers
Pin center coordinates.
std::vector< Real > _unit_translation_y
(unitless) y-translations to apply to move from a center point to a side of a hexagon
Real _wetted_area
Wetted area of entire bundle per wire pitch.
Real hexagonPitch(const Real volume) const
Get the pitch of a hexagonal prism based on its per-wire-pitch volume.
unsigned int rings(const unsigned int n) const
Get the number of rings for a specified number of pins.
void computeWettedAreas()
Compute the wetted areas for each channel type.
unsigned int _n_interior_gaps
Number of gaps that touch an interior channel.
std::vector< Real > _unit_translation_x
(unitless) x-translations to apply to move from a center point to a side of a hexagon
unsigned int closestPinIndex(const Point &point) const
Get the closest pin index given a point outside the lattice.
void computeChannelPinIndices()
Get the pin indices that form the corners of each channel type.
Real _Dh
Bundle-wide hydraulic diameter.
static const unsigned int NUM_SIDES
Number of sides in a hexagon.
Real _flow_volume
Bundle-wide flow volume per wire pitch.
std::vector< std::vector< Real > > _duct_coeffs
Coefficients in the line equations defining each duct wall.
Real _wire_surface_area_per_pitch
Wire surface area per wire pitch.
Real distanceFromGap(const Point &pt, const unsigned int gap_index) const
Distance from a point and a gap.
unsigned int pinIndex(const Point &point) const
Get the pin index given a point.
unsigned int _n_channels
Total number of channels.
Real _edge_wetted_area
Wetted area of an edge channel per wire pitch.
Real _edge_flow_volume
Flow volume of an edge channel per wire pitch.
void computeFlowVolumes()
Compute the flow volumes for each channel type.
Real _corner_volume
Total volume of a corner channel per wire pitch.
const Real _wire_pitch
Wire pitch.
Real _interior_Dh
Hydraulic diameter of interior channel.
const std::vector< Point > cornerChannelCornerCoordinates(const unsigned int corner_channel_id) const
Get the corner coordinates of a corner channel given an ID (relative to the start of the corner chann...
Real _edge_Dh
Hydraulic diameter of edge channel.
Real triangleArea(const Real side) const
Get the area of an equilateral triangle with given side length.
const Real _wire_area
Wire cross-sectional area.
Real _corner_wetted_area
Wetted area of a corner channel per wire pitch.
Real hexagonSide(const Real pitch) const
Get the side length of a hexagon with given flat-to-flat distance (pitch)
Real triangleVolume(const Real side) const
Get the volume of an equilateral triangle prism per wire pitch.
const std::vector< Point > interiorChannelCornerCoordinates(const unsigned int interior_channel_id) const
Get the corner coordinates of an interior channel given an ID (relative to the start of the interior ...
const Real _pin_diameter
Pin diameter.
const Real _pin_surface_area_per_pitch
Pin surface area per wire pitch.
void get2DInputPatternIndex(const unsigned int pin_index, unsigned int &row_index, unsigned int &within_row_index) const
Conversion from lattice pin indexing to the 2D input file index.
void computePinAndDuctCoordinates()
Compute the pin center coordinates and the duct corner coordinates.
unsigned int _iy
Index representing "second" coordinate of 2-D plane.
unsigned int lastPinInRing(const unsigned int ring) const
Get the index of the "last" pin in a ring.
const unsigned int _n_rings
Total number of rings of pins.
Real _corner_flow_volume
Flow volume of a corner channel per wire pitch.
const RealTensorValue _rotation_matrix
Rotation matrix to apply the rotation why.
unsigned int interiorChannels(const unsigned int ring)
Get the number of interior channels between ring and ring - 1 (0 indexing)
Real _interior_wetted_area
Wetted area of an interior channel per wire pitch.
std::vector< std::vector< int > > _local_to_global_gaps
Local-to-global gap indexing, ordered by channel ID.
const Real _bundle_pitch
Bundle pitch (distance across bundle measured flat-to-flat on the inside of the duct)
const std::vector< Point > edgeChannelCornerCoordinates(const unsigned int edge_channel_id) const
Get the corner coordinates of an edge channel given an ID (relative to the start of the edge channels...
Real _pin_bundle_spacing
Spacing between the duct inner wall and the pin surface.
unsigned int _ix
Index representing "first" coordinate of 2-D plane.
Point channelCentroid(const std::vector< Point > &corners) const
Get the centroid of a channel given the corner coordinates.
void computeHydraulicDiameters()
Compute the hydraulic diameters for each channel type.
std::vector< std::vector< unsigned int > > _corner_channel_pin_indices
Pin indices forming one of the corners of each corner channel.
Real hexagonArea(const Real pitch) const
Get the area of a hexagon with given flat-to-flat distance (pitch)
void computeWireVolumeAndAreaPerPitch()
Compute the volume and surface area const occupied by the wire in one with pitch.
std::vector< std::vector< Point > > _gap_points
Two points on each gap, in order to compute distance-from-gap calculations.
unsigned int _n_edge_channels
Total number of edge channels.
void computePinBundleSpacing()
Compute the spacing between the outer pins and the duct inner walls.
bool lastGapInRing(const unsigned int gap_index) const
Whether this gap is the "last" in the ring, i.e.
std::vector< std::vector< unsigned int > > _edge_channel_pin_indices
Pin indices forming two of the corners of each edge channel.
Real pinRadius() const
Get the pin outer radius.
const Real _wire_diameter
Wire diameter.
Real channelHydraulicDiameter(const channel_type::ChannelTypeEnum &channel) const
Get the hydraulic diameter of a channel.
unsigned int firstPinInRing(const unsigned int ring) const
Get the index of the "first" pin in a ring.
std::vector< std::pair< int, int > > _gap_indices
Gap indices, connecting two pins or one pin and a side, ordered by global gap ID.
std::vector< std::vector< Point > > _pin_centered_corner_coordinates
Corner coordinates of a hexagon surrounding each pin.
unsigned int channelIndex(const Point &point) const
Get the channel index given a point.
HexagonalLatticeUtils(const Real bundle_inner_flat_to_flat, const Real pin_pitch, const Real pin_diameter, const Real wire_diameter, const Real wire_pitch, const unsigned int n_rings, const unsigned int axis, const Real rotation_around_axis=0)
Real _corner_Dh
Hydraulic diameter of corner channel.
Real _interior_flow_volume
Flow volume of an interior channel per wire pitch.
void computeGapIndices()
Determine the global gap indices, sorted first by lower pin ID and next by higher pin ID.
std::vector< Point > _duct_corners
Six corner coordinates for the ducts.
bool insideLattice(const Point &point) const
Whether the point is inside the lattice.
Real _edge_volume
Total volume of an edge channel per wire pitch.
const Real _pin_pitch
Pin pitch.
Real channelSpecificSurfaceArea(const channel_type::ChannelTypeEnum &channel) const
Get the specific surface area of a channel.
unsigned int globalGapIndex(const std::pair< int, int > &local_gap) const
Get the global gap index from the local gap index.
unsigned int totalPins(const unsigned int n) const
Get the total number of pins for all rings.
const Real _rotation_around_axis
Rotation around the axis to apply to the lattice.
unsigned int _n_interior_pins
Number of interior pins.
Real minDuctWallDistance(const Point &p) const
Get the minimum distance from a point to the duct inner surface.
const unsigned int _axis
Vertical axis of the bundle along which the pins are aligned.
unsigned int _n_edge_pins
Number of edge pins.
Real hexagonVolume(const Real side) const
Get the volume of a hexagonal prism per wire pitch.
Real triangleHeight(const Real side) const
Get the height of an equilateral triangle with given side length.
channel_type::ChannelTypeEnum channelType(const Point &p) const
Get the channel type (interior, edge, corner) given a point.
unsigned int _n_corner_pins
Number of corner pins.
const Real _wire_circumference
Wire circumference.
unsigned int gapIndex(const Point &point) const
Get the index for the gap closest to the point.
unsigned int _n_interior_channels
Total number of interior channels.
Real minDuctCornerDistance(const Point &p) const
Get the minimum distance from a point to the duct corners.
Real triangleSide(const Real height) const
Get the side of an equilateral triangle with given height.
Real _corner_edge_length
Half the distance for which a corner channel is in contact with the duct.
void computePinAndChannelTypes()
Compute the number of pins and channels and their types for the lattice.
unsigned int pins(const unsigned int n) const
Get the number of pins in a given ring.
Real _interior_volume
Total volume of an interior channel per wire pitch.
unsigned int ringIndex(const unsigned int pin) const
Get the index of the ring for a certain pin index.
ChannelTypeEnum
Type of subchannel.
std::pair< unsigned int, unsigned int > projectedIndices(const unsigned int axis)
bool pointInPolygon(const libMesh::Point &point, const std::vector< libMesh::Point > &corners, const unsigned int axis)
libMesh::Point projectPoint(const libMesh::Real x0, const libMesh::Real x1, const unsigned int axis)
libMesh::Real minDistanceToPoints(const libMesh::Point &pt, const std::vector< libMesh::Point > &candidates, const unsigned int axis)
libMesh::Real projectedDistanceFromLine(libMesh::Point pt, libMesh::Point line0, libMesh::Point line1, const unsigned int axis)
libMesh::Point projectedUnitNormal(libMesh::Point pt1, libMesh::Point pt2, const unsigned int axis)
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
Real distance(const Point &p)