https://mooseframework.inl.gov
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 #include "RotationMatrix.h"
14 
16 const Real HexagonalLatticeUtils::SIN60 = std::sqrt(3.0) / 2.0;
17 const unsigned int HexagonalLatticeUtils::NUM_SIDES = 6;
18 
19 HexagonalLatticeUtils::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 
98 unsigned int
99 HexagonalLatticeUtils::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 
109 unsigned int
110 HexagonalLatticeUtils::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 
119 unsigned int
120 HexagonalLatticeUtils::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 
138 Real
140 {
141  return pitch / std::sqrt(3.0);
142 }
143 
144 Real
146 {
147  Real side = hexagonSide(pitch);
148  return 3 * SIN60 * side * side;
149 }
150 
151 Real
153 {
154  return hexagonArea(pitch) * _wire_pitch;
155 }
156 
157 Real
158 HexagonalLatticeUtils::hexagonPitch(const Real volume) const
159 {
160  Real area = volume / _wire_pitch;
161  return std::sqrt(area / SIN60);
162 }
163 
164 Real
166 {
167  return 0.5 * triangleHeight(side) * side;
168 }
169 
170 Real
172 {
173  return SIN60 * side;
174 }
175 
176 Real
177 HexagonalLatticeUtils::triangleSide(const Real height) const
178 {
179  return height / SIN60;
180 }
181 
182 Real
184 {
185  return triangleArea(side) * _wire_pitch;
186 }
187 
188 Real
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())
200  return channel_type::interior;
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)
209  return channel_type::corner;
210  else
211  return channel_type::edge;
212 }
213 
214 Real
216  const channel_type::ChannelTypeEnum & channel) const
217 {
218  switch (channel)
219  {
222  case channel_type::edge:
226  default:
227  mooseError("Unhandled ChannelTypeEnum in HexagonalLattice!");
228  }
229 }
230 
231 Real
233 {
234  switch (channel)
235  {
237  return _interior_Dh;
238  case channel_type::edge:
239  return _edge_Dh;
241  return _corner_Dh;
242  default:
243  mooseError("Unhandled ChannelTypeEnum in HexagonalLattice!");
244  }
245 }
246 
247 Real
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 
264 Real
266 {
268 }
269 
270 void
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
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)) /
354  (_duct_corners[n](_ix) - _duct_corners[c](_ix));
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 
360 void
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 
373 void
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.
382  _Dh = 4 * _flow_volume / _wetted_area;
383 }
384 
385 void
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 
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 
407 unsigned int
409 {
410  return (ring * 2 - 1) * NUM_SIDES;
411 }
412 
413 void
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 
426  _n_edge_channels = 0;
428 
429  if (_n_rings > 1)
430  {
432  _n_edge_pins = (_n_rings - 2) * NUM_SIDES;
434 
436 
437  for (unsigned int i = 1; i < _n_rings; ++i)
439  }
440 
442 }
443 
444 void
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 
453 void
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 
464 void
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 
554 const 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 
566 const std::vector<Point>
567 HexagonalLatticeUtils::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 
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 
590 const std::vector<Point>
591 HexagonalLatticeUtils::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 
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 
613 Point
614 HexagonalLatticeUtils::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 
624 unsigned int
625 HexagonalLatticeUtils::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 
649 unsigned int
650 HexagonalLatticeUtils::closestPinIndex(const Point & point) const
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 
678 unsigned int
679 HexagonalLatticeUtils::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  }
695  case channel_type::edge:
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 
719  mooseError(
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 
730 bool
731 HexagonalLatticeUtils::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 
744 void
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 
784 void
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 
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 
879  }
880 
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 
895  }
896 }
897 
898 unsigned int
899 HexagonalLatticeUtils::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 
911 Real
912 HexagonalLatticeUtils::distanceFromGap(const Point & pt, const unsigned int gap_index) const
913 {
914  auto p = _gap_points[gap_index];
915  return geom_utils::projectedDistanceFromLine(pt, p[0], p[1], _axis);
916 }
917 
918 unsigned int
919 HexagonalLatticeUtils::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 
940 void
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 
961 unsigned int
962 HexagonalLatticeUtils::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 
971 unsigned int
972 HexagonalLatticeUtils::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 
981 unsigned int
982 HexagonalLatticeUtils::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 
992 bool
993 HexagonalLatticeUtils::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 _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.
const RealTensorValue _rotation_matrix
Rotation matrix to apply the rotation why.
unsigned int _n_interior_gaps
Number of gaps that touch an interior channel.
const unsigned int _n_rings
Total number of rings of pins.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sin(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tan
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)
GenericRealTensorValue< is_ad > rotVec2DToX(const GenericRealVectorValue< is_ad > &vec)
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.
const Real _rotation_around_axis
Rotation around the axis to apply to the lattice.
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
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
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) ...
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template cos(_arg) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(cos
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.
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)
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
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.
const Real pi
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.