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