LCOV - code coverage report
Current view: top level - src/utils - HexagonalLatticeUtils.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #32971 (54bef8) with base c6cf66 Lines: 418 490 85.3 %
Date: 2026-05-29 20:39:24 Functions: 37 45 82.2 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      15             : const Real HexagonalLatticeUtils::COS60 = 0.5;
      16             : const Real HexagonalLatticeUtils::SIN60 = std::sqrt(3.0) / 2.0;
      17             : const unsigned int HexagonalLatticeUtils::NUM_SIDES = 6;
      18             : 
      19         115 : 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         115 :                                              const Real rotation_around_axis)
      27         115 :   : _bundle_pitch(bundle_inner_flat_to_flat),
      28         115 :     _pin_pitch(pin_pitch),
      29         115 :     _pin_diameter(pin_diameter),
      30         115 :     _wire_diameter(wire_diameter),
      31         115 :     _wire_pitch(wire_pitch),
      32         115 :     _n_rings(n_rings),
      33         115 :     _axis(axis),
      34         115 :     _rotation_around_axis(rotation_around_axis),
      35         115 :     _rotation_matrix(
      36         115 :         RotationMatrix::rotVec2DToX(Point(std::cos(_rotation_around_axis * libMesh::pi / 180),
      37         115 :                                           -std::sin(_rotation_around_axis * libMesh::pi / 180),
      38             :                                           0.))),
      39         115 :     _bundle_side_length(hexagonSide(_bundle_pitch)),
      40         115 :     _pin_area(M_PI * _pin_diameter * _pin_diameter / 4.0),
      41         115 :     _pin_circumference(M_PI * _pin_diameter),
      42         115 :     _wire_area(M_PI * _wire_diameter * _wire_diameter / 4.0),
      43         115 :     _wire_circumference(M_PI * _wire_diameter),
      44         115 :     _pin_surface_area_per_pitch(M_PI * _pin_diameter * _wire_pitch),
      45         115 :     _pin_volume_per_pitch(_pin_area * _wire_pitch),
      46         115 :     _P_over_D(_pin_pitch / _pin_diameter),
      47         115 :     _L_over_D(_wire_pitch / _pin_diameter)
      48             : {
      49         115 :   auto idx = geom_utils::projectedIndices(_axis);
      50         115 :   _ix = idx.first;
      51         115 :   _iy = idx.second;
      52             : 
      53             :   // object is not tested and probably won't work if axis != 2
      54         115 :   if (_axis != 2)
      55           0 :     mooseError("The HexagonalLatticeUtils is currently limited to 'axis = 2'!");
      56             : 
      57         115 :   if (_pin_pitch < _pin_diameter)
      58           0 :     mooseError("Pin pitch of " + std::to_string(_pin_pitch) + " cannot fit pins of diameter " +
      59           0 :                std::to_string(_pin_diameter) + "!");
      60             : 
      61         115 :   if (_pin_pitch - _pin_diameter < _wire_diameter)
      62           0 :     mooseError("Wire diameter of " + std::to_string(_wire_diameter) +
      63           0 :                " cannot fit between pin-pin space of " +
      64           0 :                std::to_string(_pin_pitch - _pin_diameter) + "!");
      65             : 
      66         115 :   _unit_translation_x = {0.0, -SIN60, -SIN60, 0.0, SIN60, SIN60};
      67         115 :   _unit_translation_y = {1.0, COS60, -COS60, -1.0, -COS60, COS60};
      68             : 
      69             :   // Use rotation matrix on unit translation vectors
      70         115 :   if (rotation_around_axis != 0)
      71          91 :     for (const auto i : make_range(6))
      72             :     {
      73             :       const Point rotated =
      74          78 :           _rotation_matrix * Point(_unit_translation_x[i], _unit_translation_y[i], 0);
      75          78 :       _unit_translation_x[i] = rotated(0);
      76          78 :       _unit_translation_y[i] = rotated(1);
      77             :     }
      78             : 
      79             :   // compute number of each pin and channel and channel type (interior, edge channel)
      80         115 :   computePinAndChannelTypes();
      81             : 
      82         115 :   _corner_edge_length = (_bundle_side_length - _n_edge_channels / NUM_SIDES * _pin_pitch) / 2.0;
      83             : 
      84         115 :   computePinBundleSpacing();
      85         115 :   computeWireVolumeAndAreaPerPitch();
      86         115 :   computeFlowVolumes();
      87         115 :   computeWettedAreas();
      88         115 :   computeHydraulicDiameters();
      89         115 :   computePinAndDuctCoordinates();
      90         115 :   computeChannelPinIndices();
      91         115 :   computeGapIndices();
      92             : 
      93         115 :   if (_pin_bundle_spacing < _wire_diameter)
      94           0 :     mooseError("Specified bundle pitch " + std::to_string(_bundle_pitch) +
      95             :                " is too small to fit the given pins and wire wrap!");
      96         115 : }
      97             : 
      98             : unsigned int
      99  6849815643 : HexagonalLatticeUtils::pins(const unsigned int n) const
     100             : {
     101  6849815643 :   if (n <= 0)
     102             :     return 0;
     103  6849815642 :   else if (n == 1)
     104             :     return 1;
     105             :   else
     106  6849779906 :     return NUM_SIDES * (n - 1);
     107             : }
     108             : 
     109             : unsigned int
     110       35604 : HexagonalLatticeUtils::totalPins(const unsigned int n) const
     111             : {
     112             :   unsigned int total = 0;
     113  1277504027 :   for (unsigned int i = 1; i <= n; ++i)
     114  1277468423 :     total += pins(i);
     115             : 
     116       35604 :   return total;
     117             : }
     118             : 
     119             : unsigned int
     120           5 : HexagonalLatticeUtils::rings(const unsigned int n) const
     121             : {
     122             :   auto remaining = n;
     123             :   unsigned int i = 0;
     124             : 
     125  5572346283 :   while (remaining > 0)
     126             :   {
     127  5572346278 :     i += 1;
     128  5572346278 :     remaining -= pins(i);
     129             :   }
     130             : 
     131           5 :   if (n != totalPins(i))
     132           2 :     mooseError("Number of pins " + std::to_string(n) +
     133             :                " not evenly divisible in a hexagonal lattice!");
     134             : 
     135           4 :   return i;
     136             : }
     137             : 
     138             : Real
     139       20454 : HexagonalLatticeUtils::hexagonSide(const Real pitch) const
     140             : {
     141       20454 :   return pitch / std::sqrt(3.0);
     142             : }
     143             : 
     144             : Real
     145         115 : HexagonalLatticeUtils::hexagonArea(const Real pitch) const
     146             : {
     147         115 :   Real side = hexagonSide(pitch);
     148         115 :   return 3 * SIN60 * side * side;
     149             : }
     150             : 
     151             : Real
     152         115 : HexagonalLatticeUtils::hexagonVolume(const Real pitch) const
     153             : {
     154         115 :   return hexagonArea(pitch) * _wire_pitch;
     155             : }
     156             : 
     157             : Real
     158           0 : HexagonalLatticeUtils::hexagonPitch(const Real volume) const
     159             : {
     160           0 :   Real area = volume / _wire_pitch;
     161           0 :   return std::sqrt(area / SIN60);
     162             : }
     163             : 
     164             : Real
     165         115 : HexagonalLatticeUtils::triangleArea(const Real side) const
     166             : {
     167         115 :   return 0.5 * triangleHeight(side) * side;
     168             : }
     169             : 
     170             : Real
     171         230 : HexagonalLatticeUtils::triangleHeight(const Real side) const
     172             : {
     173         230 :   return SIN60 * side;
     174             : }
     175             : 
     176             : Real
     177           0 : HexagonalLatticeUtils::triangleSide(const Real height) const
     178             : {
     179           0 :   return height / SIN60;
     180             : }
     181             : 
     182             : Real
     183         115 : HexagonalLatticeUtils::triangleVolume(const Real side) const
     184             : {
     185         115 :   return triangleArea(side) * _wire_pitch;
     186             : }
     187             : 
     188             : Real
     189         583 : HexagonalLatticeUtils::pinRadius() const
     190             : {
     191         583 :   return _pin_diameter / 2.0;
     192             : }
     193             : 
     194             : channel_type::ChannelTypeEnum
     195         136 : HexagonalLatticeUtils::channelType(const Point & p) const
     196             : {
     197         136 :   const Real distance_to_wall = minDuctWallDistance(p);
     198             : 
     199         136 :   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          58 :   const Real distance_to_corner = minDuctCornerDistance(p);
     206          58 :   Real l = std::sqrt(distance_to_corner * distance_to_corner - distance_to_wall * distance_to_wall);
     207             : 
     208          58 :   if (l < _corner_edge_length)
     209             :     return channel_type::corner;
     210             :   else
     211          38 :     return channel_type::edge;
     212             : }
     213             : 
     214             : Real
     215           0 : HexagonalLatticeUtils::channelSpecificSurfaceArea(
     216             :     const channel_type::ChannelTypeEnum & channel) const
     217             : {
     218           0 :   switch (channel)
     219             :   {
     220           0 :     case channel_type::interior:
     221           0 :       return _interior_wetted_area / _interior_volume;
     222           0 :     case channel_type::edge:
     223           0 :       return _edge_wetted_area / _edge_volume;
     224           0 :     case channel_type::corner:
     225           0 :       return _corner_wetted_area / _corner_volume;
     226           0 :     default:
     227           0 :       mooseError("Unhandled ChannelTypeEnum in HexagonalLattice!");
     228             :   }
     229             : }
     230             : 
     231             : Real
     232           0 : HexagonalLatticeUtils::channelHydraulicDiameter(const channel_type::ChannelTypeEnum & channel) const
     233             : {
     234           0 :   switch (channel)
     235             :   {
     236           0 :     case channel_type::interior:
     237           0 :       return _interior_Dh;
     238           0 :     case channel_type::edge:
     239           0 :       return _edge_Dh;
     240           0 :     case channel_type::corner:
     241           0 :       return _corner_Dh;
     242           0 :     default:
     243           0 :       mooseError("Unhandled ChannelTypeEnum in HexagonalLattice!");
     244             :   }
     245             : }
     246             : 
     247             : Real
     248         136 : HexagonalLatticeUtils::minDuctWallDistance(const Point & p) const
     249             : {
     250         136 :   Real distance = std::numeric_limits<Real>::max();
     251         952 :   for (const auto i : make_range(NUM_SIDES))
     252             :   {
     253         816 :     Real a = _duct_coeffs[i][0];
     254         816 :     Real b = _duct_coeffs[i][1];
     255         816 :     Real c = _duct_coeffs[i][2];
     256             : 
     257         816 :     Real d = std::abs(a * p(_ix) + b * p(_iy) + c) / std::sqrt(a * a + b * b);
     258         816 :     distance = std::min(d, distance);
     259             :   }
     260             : 
     261         136 :   return distance;
     262             : }
     263             : 
     264             : Real
     265          58 : HexagonalLatticeUtils::minDuctCornerDistance(const Point & p) const
     266             : {
     267          58 :   return geom_utils::minDistanceToPoints(p, _duct_corners, _axis);
     268             : }
     269             : 
     270             : void
     271         115 : HexagonalLatticeUtils::computePinAndDuctCoordinates()
     272             : {
     273         115 :   Real corner_shiftx[] = {COS60, -COS60, -1, -COS60, COS60, 1};
     274         115 :   Real corner_shifty[] = {SIN60, SIN60, 0, -SIN60, -SIN60, 0};
     275             : 
     276         115 :   Real edge_shiftx[] = {-1, -COS60, COS60, 1, COS60, -COS60};
     277         115 :   Real edge_shifty[] = {0, -SIN60, -SIN60, 0, SIN60, SIN60};
     278             : 
     279             :   // Use rotation matrix on shift vectors
     280         115 :   if (_rotation_around_axis != 0)
     281          91 :     for (const auto i : make_range(6))
     282             :     {
     283          78 :       Point rotated = _rotation_matrix * Point(corner_shiftx[i], corner_shifty[i], 0);
     284          78 :       corner_shiftx[i] = rotated(0);
     285          78 :       corner_shifty[i] = rotated(1);
     286          78 :       rotated = _rotation_matrix * Point(edge_shiftx[i], edge_shifty[i], 0);
     287          78 :       edge_shiftx[i] = rotated(0);
     288          78 :       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         115 :   _pin_centers.push_back(center);
     294             : 
     295         266 :   for (unsigned int i = 2; i <= _n_rings; ++i)
     296             :   {
     297         151 :     auto n_total_in_ring = pins(i);
     298         151 :     auto increment = i - 1;
     299             : 
     300             :     unsigned int d = 0;
     301             : 
     302        1411 :     for (const auto j : make_range(n_total_in_ring))
     303             :     {
     304        1260 :       unsigned int side = j / increment;
     305             : 
     306        1260 :       if (d == increment)
     307             :         d = 0;
     308             : 
     309        1260 :       Point center = geom_utils::projectPoint(corner_shiftx[side] * _pin_pitch * (i - 1),
     310        1260 :                                               corner_shifty[side] * _pin_pitch * (i - 1),
     311        1260 :                                               _axis);
     312             : 
     313             :       // additional shift for the edge sides
     314        1260 :       if (d != 0)
     315             :       {
     316         354 :         center(_ix) += edge_shiftx[side] * _pin_pitch * d;
     317         354 :         center(_iy) += edge_shifty[side] * _pin_pitch * d;
     318             :       }
     319             : 
     320        1260 :       _pin_centers.push_back(center);
     321             : 
     322        1260 :       d += 1;
     323             :     }
     324             :   }
     325             : 
     326             :   // compute corners of the hexagonal prisms that enclose each pin
     327         115 :   _pin_centered_corner_coordinates.resize(_n_pins);
     328         115 :   auto side = hexagonSide(_pin_pitch);
     329        1490 :   for (const auto pin : make_range(_n_pins))
     330             :   {
     331        9625 :     for (const auto i : make_range(NUM_SIDES))
     332             :     {
     333        8250 :       Point translation = geom_utils::projectPoint(
     334        8250 :           _unit_translation_x[i] * side, _unit_translation_y[i] * side, _axis);
     335             : 
     336        8250 :       _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         115 :   Real l = _bundle_side_length;
     342         805 :   for (const auto i : make_range(NUM_SIDES))
     343             :   {
     344         690 :     Point corner = geom_utils::projectPoint(corner_shiftx[i] * l, corner_shifty[i] * l, _axis);
     345         690 :     _duct_corners.push_back(corner);
     346             :   }
     347             : 
     348             :   // compute the equations (a*x + b*y + c) defining each duct wall
     349         805 :   for (const auto i : make_range(NUM_SIDES))
     350             :   {
     351             :     auto c = i;
     352         690 :     unsigned int n = i == 5 ? 0 : i + 1;
     353         690 :     Real slope = (_duct_corners[n](_iy) - _duct_corners[c](_iy)) /
     354         690 :                  (_duct_corners[n](_ix) - _duct_corners[c](_ix));
     355         690 :     std::vector<Real> coeffs = {-slope, 1.0, slope * _duct_corners[c](_ix) - _duct_corners[c](_iy)};
     356         690 :     _duct_coeffs.push_back(coeffs);
     357         690 :   }
     358         115 : }
     359             : 
     360             : void
     361         115 : HexagonalLatticeUtils::computeWettedAreas()
     362             : {
     363         115 :   Real rod_area_per_pitch = _pin_surface_area_per_pitch + _wire_surface_area_per_pitch;
     364             : 
     365         115 :   _interior_wetted_area = 0.5 * rod_area_per_pitch;
     366         115 :   _edge_wetted_area = 0.5 * rod_area_per_pitch + _pin_pitch * _wire_pitch;
     367             : 
     368         115 :   Real h = (_bundle_side_length - _n_edge_channels / NUM_SIDES * _pin_pitch) / 2.0;
     369         115 :   _corner_wetted_area = rod_area_per_pitch / 6.0 + 2 * h * _wire_pitch;
     370         115 :   _wetted_area = _n_pins * rod_area_per_pitch + 6.0 * _bundle_side_length * _wire_pitch;
     371         115 : }
     372             : 
     373             : void
     374         115 : HexagonalLatticeUtils::computeHydraulicDiameters()
     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.
     379         115 :   _interior_Dh = 4 * _interior_flow_volume / _interior_wetted_area;
     380         115 :   _edge_Dh = 4 * _edge_flow_volume / _edge_wetted_area;
     381         115 :   _corner_Dh = 4 * _corner_flow_volume / _corner_wetted_area;
     382         115 :   _Dh = 4 * _flow_volume / _wetted_area;
     383         115 : }
     384             : 
     385             : void
     386         115 : HexagonalLatticeUtils::computeFlowVolumes()
     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         115 :   Real rod_volume_per_pitch = _pin_volume_per_pitch + _wire_volume_per_pitch;
     393             : 
     394         115 :   Real l = _pin_bundle_spacing + pinRadius();
     395             : 
     396         115 :   _interior_volume = triangleVolume(_pin_pitch);
     397         115 :   _edge_volume = _pin_pitch * l * _wire_pitch;
     398         115 :   _corner_volume = l * _corner_edge_length * _wire_pitch;
     399             : 
     400         115 :   _interior_flow_volume = _interior_volume - 0.5 * rod_volume_per_pitch;
     401         115 :   _edge_flow_volume = _edge_volume - 0.5 * rod_volume_per_pitch;
     402         115 :   _corner_flow_volume = _corner_volume - rod_volume_per_pitch / 6.0;
     403             : 
     404         115 :   _flow_volume = hexagonVolume(_bundle_pitch) - _n_pins * rod_volume_per_pitch;
     405         115 : }
     406             : 
     407             : unsigned int
     408         302 : HexagonalLatticeUtils::interiorChannels(const unsigned int ring)
     409             : {
     410         302 :   return (ring * 2 - 1) * NUM_SIDES;
     411             : }
     412             : 
     413             : void
     414         115 : HexagonalLatticeUtils::computePinAndChannelTypes()
     415             : {
     416         115 :   _n_pins = 0;
     417         381 :   for (unsigned int i = _n_rings; i > 0; --i)
     418         266 :     _n_pins += pins(i);
     419             : 
     420             :   // the one-ring case
     421         115 :   _n_interior_pins = _n_pins;
     422         115 :   _n_edge_pins = 0;
     423         115 :   _n_corner_pins = 0;
     424             : 
     425         115 :   _n_corner_channels = NUM_SIDES;
     426         115 :   _n_edge_channels = 0;
     427         115 :   _n_interior_channels = 0;
     428             : 
     429         115 :   if (_n_rings > 1)
     430             :   {
     431         105 :     _n_corner_pins = NUM_SIDES;
     432         105 :     _n_edge_pins = (_n_rings - 2) * NUM_SIDES;
     433         105 :     _n_interior_pins -= _n_corner_pins + _n_edge_pins;
     434             : 
     435         105 :     _n_edge_channels = _n_edge_pins + NUM_SIDES;
     436             : 
     437         256 :     for (unsigned int i = 1; i < _n_rings; ++i)
     438         151 :       _n_interior_channels += interiorChannels(i);
     439             :   }
     440             : 
     441         115 :   _n_channels = _n_interior_channels + _n_edge_channels + _n_corner_channels;
     442         115 : }
     443             : 
     444             : void
     445         115 : HexagonalLatticeUtils::computePinBundleSpacing()
     446             : {
     447             :   // hexagonal flat-to-flat that *just* encloses the pins (NOT including the wire)
     448         115 :   Real pin_h = 2.0 * (_n_rings - 1) * triangleHeight(_pin_pitch) + _pin_diameter;
     449             : 
     450         115 :   _pin_bundle_spacing = (_bundle_pitch - pin_h) / 2.0;
     451         115 : }
     452             : 
     453             : void
     454         115 : HexagonalLatticeUtils::computeWireVolumeAndAreaPerPitch()
     455             : {
     456             :   unsigned int n_wire_coils = 1;
     457             :   Real wire_length_per_coil =
     458         115 :       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         115 :   _wire_volume_per_pitch = _wire_area * wire_length;
     461         115 :   _wire_surface_area_per_pitch = _wire_circumference * wire_length;
     462         115 : }
     463             : 
     464             : void
     465         115 : HexagonalLatticeUtils::computeChannelPinIndices()
     466             : {
     467         115 :   _interior_channel_pin_indices.resize(_n_interior_channels);
     468         115 :   _edge_channel_pin_indices.resize(_n_edge_channels);
     469         115 :   _corner_channel_pin_indices.resize(_n_corner_channels);
     470             : 
     471             :   // 3 pins per interior channel
     472        1729 :   for (auto & i : _interior_channel_pin_indices)
     473        1614 :     i.resize(3);
     474             : 
     475             :   // 2 pins per edge channel
     476        1021 :   for (auto & i : _edge_channel_pin_indices)
     477         906 :     i.resize(2);
     478             : 
     479             :   // 1 pin per corner channel
     480         805 :   for (auto & i : _corner_channel_pin_indices)
     481         690 :     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         115 :   starts.resize(_n_rings);
     486         381 :   for (const auto i : make_range(_n_rings))
     487             :   {
     488         266 :     starts[i].resize(NUM_SIDES);
     489         266 :     starts[i][0] = (i == 0) ? 0 : totalPins(i);
     490             : 
     491        1596 :     for (unsigned int j = 1; j < NUM_SIDES; ++j)
     492        1330 :       starts[i][j] = starts[i][j - 1] + i;
     493             :   }
     494             : 
     495             :   unsigned int c = 0;
     496         266 :   for (const auto i : make_range(_n_rings - 1))
     497             :   {
     498         151 :     auto channels = interiorChannels(i + 1);
     499         151 :     unsigned int channels_per_sector = channels / NUM_SIDES;
     500             : 
     501        1057 :     for (const auto j : make_range(NUM_SIDES))
     502             :     {
     503         906 :       auto prev_inner = starts[i][j];
     504         906 :       auto prev_outer = starts[i + 1][j];
     505             : 
     506        2520 :       for (const auto k : make_range(channels_per_sector))
     507             :       {
     508        1614 :         bool downward = k % 2 == 0;
     509             : 
     510        1614 :         if (downward)
     511             :         {
     512        1260 :           _interior_channel_pin_indices[c][0] = prev_inner;
     513        1260 :           _interior_channel_pin_indices[c][1] = prev_outer;
     514        1260 :           _interior_channel_pin_indices[c][2] = ++prev_outer;
     515             :         }
     516             :         else
     517             :         {
     518         354 :           _interior_channel_pin_indices[c][0] = prev_outer;
     519         354 :           _interior_channel_pin_indices[c][1] = ++prev_inner;
     520         354 :           _interior_channel_pin_indices[c][2] = prev_inner - 1;
     521             :         }
     522             : 
     523        1614 :         if (j == 5) // last sector
     524             :         {
     525         269 :           if (k == channels_per_sector - 1)
     526             :           {
     527         151 :             _interior_channel_pin_indices[c][2] = starts[i + 1][0];
     528         151 :             _interior_channel_pin_indices[c][0] = starts[i][0];
     529             :           }
     530             : 
     531         269 :           if (k == channels_per_sector - 2 && i > 0)
     532          46 :             _interior_channel_pin_indices[c][1] = starts[i][0];
     533             :         }
     534             : 
     535        1614 :         c++;
     536             :       }
     537             :     }
     538             :   }
     539             : 
     540        1021 :   for (const auto i : make_range(_n_edge_channels))
     541             :   {
     542         906 :     _edge_channel_pin_indices[i][0] = starts[_n_rings - 1][0] + i;
     543         906 :     _edge_channel_pin_indices[i][1] = _edge_channel_pin_indices[i][0] + 1;
     544             : 
     545         906 :     if (i == _n_edge_channels - 1)
     546         105 :       _edge_channel_pin_indices[i][1] = _edge_channel_pin_indices[0][0];
     547             :   }
     548             : 
     549         805 :   for (const auto i : make_range(_n_corner_channels))
     550         690 :     _corner_channel_pin_indices[i][0] =
     551         690 :         totalPins(_n_rings - 1) + i * (_n_edge_channels / NUM_SIDES);
     552         115 : }
     553             : 
     554             : const std::vector<Point>
     555         652 : HexagonalLatticeUtils::interiorChannelCornerCoordinates(
     556             :     const unsigned int interior_channel_id) const
     557             : {
     558             :   std::vector<Point> corners;
     559         652 :   auto pin_indices = _interior_channel_pin_indices[interior_channel_id];
     560        2608 :   for (const auto & pin : pin_indices)
     561        1956 :     corners.push_back(_pin_centers[pin]);
     562             : 
     563         652 :   return corners;
     564         652 : }
     565             : 
     566             : const std::vector<Point>
     567         166 : HexagonalLatticeUtils::edgeChannelCornerCoordinates(const unsigned int edge_channel_id) const
     568             : {
     569             :   std::vector<Point> corners;
     570             : 
     571         166 :   auto pin_indices = _edge_channel_pin_indices[edge_channel_id];
     572             : 
     573         166 :   const Point & pin1 = _pin_centers[pin_indices[0]];
     574         166 :   const Point & pin2 = _pin_centers[pin_indices[1]];
     575             : 
     576         166 :   Real d = pinBundleSpacing() + pinRadius();
     577             : 
     578         166 :   corners.push_back(pin1);
     579         166 :   corners.push_back(pin2);
     580             : 
     581         166 :   unsigned int sector = edge_channel_id / (_n_rings - 1);
     582         166 :   corners.push_back(pin2 +
     583         166 :                     Point(d * _unit_translation_x[sector], d * _unit_translation_y[sector], 0.0));
     584         166 :   corners.push_back(pin1 +
     585         166 :                     Point(d * _unit_translation_x[sector], d * _unit_translation_y[sector], 0.0));
     586             : 
     587         166 :   return corners;
     588         166 : }
     589             : 
     590             : const std::vector<Point>
     591          50 : HexagonalLatticeUtils::cornerChannelCornerCoordinates(const unsigned int corner_channel_id) const
     592             : {
     593             :   std::vector<Point> corners;
     594             : 
     595          50 :   auto pin_indices = _corner_channel_pin_indices[corner_channel_id];
     596          50 :   const Point & pin = _pin_centers[pin_indices[0]];
     597          50 :   corners.push_back(pin);
     598             : 
     599          50 :   Real d = pinBundleSpacing() + pinRadius();
     600             : 
     601          50 :   unsigned int side1 = corner_channel_id == 0 ? NUM_SIDES - 1 : corner_channel_id - 1;
     602             :   unsigned int side2 = corner_channel_id;
     603             : 
     604          50 :   corners.push_back(pin +
     605          50 :                     Point(d * _unit_translation_x[side1], d * _unit_translation_y[side1], 0.0));
     606          50 :   corners.push_back(_duct_corners[corner_channel_id]);
     607          50 :   corners.push_back(pin +
     608          50 :                     Point(d * _unit_translation_x[side2], d * _unit_translation_y[side2], 0.0));
     609             : 
     610          50 :   return corners;
     611          50 : }
     612             : 
     613             : Point
     614           0 : HexagonalLatticeUtils::channelCentroid(const std::vector<Point> & pins) const
     615             : {
     616           0 :   int n_pins = pins.size();
     617             :   Point centroid(0.0, 0.0, 0.0);
     618           0 :   for (const auto & p : pins)
     619             :     centroid += p / n_pins;
     620             : 
     621           0 :   return centroid;
     622             : }
     623             : 
     624             : unsigned int
     625       20109 : HexagonalLatticeUtils::pinIndex(const Point & point) const
     626             : {
     627       20109 :   auto side = hexagonSide(_pin_pitch);
     628             : 
     629      300109 :   for (const auto i : make_range(_n_pins))
     630             :   {
     631      294168 :     const auto & center = _pin_centers[i];
     632      294168 :     Real dx = center(_ix) - point(_ix);
     633      294168 :     Real dy = center(_iy) - point(_iy);
     634      294168 :     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      294168 :     if (distance_from_pin > side)
     639      279207 :       continue;
     640             : 
     641       14961 :     auto corners = _pin_centered_corner_coordinates[i];
     642       14961 :     if (geom_utils::pointInPolygon(point, corners, _axis))
     643             :       return i;
     644       14961 :   }
     645             : 
     646        5941 :   return _n_pins;
     647             : }
     648             : 
     649             : unsigned int
     650           0 : 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           0 :   if (!insideLattice(point))
     655           0 :     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           0 :   for (unsigned int i = start_index; i < _n_pins; ++i)
     662             :   {
     663           0 :     const auto & center = _pin_centers[i];
     664           0 :     Real dx = center(_ix) - point(_ix);
     665           0 :     Real dy = center(_iy) - point(_iy);
     666           0 :     Real distance_from_pin = std::sqrt(dx * dx + dy * dy);
     667             : 
     668           0 :     if (distance_from_pin < min_distance)
     669             :     {
     670             :       min_distance = distance_from_pin;
     671             :       index_min = i;
     672             :     }
     673             :   }
     674             : 
     675           0 :   return index_min;
     676             : }
     677             : 
     678             : unsigned int
     679          94 : HexagonalLatticeUtils::channelIndex(const Point & point) const
     680             : {
     681          94 :   auto channel = channelType(point);
     682             : 
     683          94 :   switch (channel)
     684             :   {
     685          54 :     case channel_type::interior:
     686             :     {
     687         652 :       for (const auto i : make_range(_n_interior_channels))
     688             :       {
     689         652 :         auto corners = interiorChannelCornerCoordinates(i);
     690         652 :         if (geom_utils::pointInPolygon(point, corners, _axis))
     691             :           return i;
     692         652 :       }
     693             :       break;
     694             :     }
     695          26 :     case channel_type::edge:
     696             :     {
     697         166 :       for (const auto i : make_range(_n_edge_channels))
     698             :       {
     699         166 :         auto corners = edgeChannelCornerCoordinates(i);
     700         166 :         if (geom_utils::pointInPolygon(point, corners, _axis))
     701          26 :           return i + _n_interior_channels;
     702         166 :       }
     703             :       break;
     704             :     }
     705          14 :     case channel_type::corner:
     706             :     {
     707          50 :       for (const auto i : make_range(_n_corner_channels))
     708             :       {
     709          50 :         auto corners = cornerChannelCornerCoordinates(i);
     710          50 :         if (geom_utils::pointInPolygon(point, corners, _axis))
     711          14 :           return i + _n_interior_channels + _n_edge_channels;
     712          50 :       }
     713             :       break;
     714             :     }
     715           0 :     default:
     716           0 :       mooseError("Unhandled ChannelTypeEnum!");
     717             :   }
     718             : 
     719           0 :   mooseError(
     720           0 :       "Point (" + std::to_string(point(0)) + ", " + std::to_string(point(1)) + ", " +
     721           0 :       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           0 : HexagonalLatticeUtils::insideLattice(const Point & point) const
     732             : {
     733           0 :   std::vector<Point> lattice_corners(6);
     734           0 :   auto side = hexagonSide(_bundle_pitch);
     735           0 :   for (const auto i : make_range(NUM_SIDES))
     736             :   {
     737           0 :     Point translation = geom_utils::projectPoint(
     738           0 :         _unit_translation_x[i] * side, _unit_translation_y[i] * side, _axis);
     739           0 :     lattice_corners[i] = translation;
     740             :   }
     741           0 :   return geom_utils::pointInPolygon(point, lattice_corners, _axis);
     742           0 : }
     743             : 
     744             : void
     745         423 : HexagonalLatticeUtils::get2DInputPatternIndex(const unsigned int pin_index,
     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         423 :   const auto ring_index = ringIndex(pin_index);
     752         423 :   row_index = _n_rings - ring_index;
     753         423 :   within_row_index = _n_rings - 1;
     754             : 
     755         423 :   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        1692 :   for (const auto local_i : make_range(pin_index - firstPinInRing(ring_index)))
     760             :   {
     761        1269 :     if (local_i < n_pin_sectors)
     762         414 :       within_row_index--;
     763         855 :     else if (local_i < 3 * n_pin_sectors)
     764         585 :       row_index++;
     765         270 :     else if (local_i < 4 * n_pin_sectors)
     766         171 :       within_row_index++;
     767          99 :     else if (local_i < 5 * n_pin_sectors)
     768             :     {
     769          90 :       row_index--;
     770          90 :       within_row_index++;
     771             :     }
     772             :     else
     773             :     {
     774           9 :       row_index--;
     775           9 :       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         423 : }
     783             : 
     784             : void
     785         115 : HexagonalLatticeUtils::computeGapIndices()
     786             : {
     787             :   std::set<std::pair<int, int>> indices;
     788        1729 :   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        1614 :     indices.insert(gap0);
     795        1614 :     indices.insert(gap1);
     796        1614 :     indices.insert(gap2);
     797             :   }
     798             : 
     799        2989 :   for (const auto & it : indices)
     800        2874 :     _gap_indices.push_back({it.first, it.second});
     801             : 
     802         115 :   _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         115 :   int n_edge_gaps = _n_rings;
     807         115 :   int pin = totalPins(_n_rings - 1);
     808         805 :   for (const auto i : make_range(NUM_SIDES))
     809             :   {
     810        2286 :     for (const auto j : make_range(_n_rings))
     811        1596 :       _gap_indices.push_back({pin + j, -(i + 1)});
     812             : 
     813         690 :     pin += n_edge_gaps - 1;
     814             :   }
     815             : 
     816             :   // fix the last gap to use the first pin
     817         115 :   _gap_indices.back() = {totalPins(_n_rings - 1), -int(NUM_SIDES)};
     818         115 :   _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        1729 :   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        1614 :     int loc_gap0 = globalGapIndex(gap0);
     829        1614 :     int loc_gap1 = globalGapIndex(gap1);
     830        1614 :     int loc_gap2 = globalGapIndex(gap2);
     831        3228 :     _local_to_global_gaps.push_back({loc_gap0, loc_gap1, loc_gap2});
     832             :   }
     833             : 
     834         115 :   int gap = _gap_indices.size() - _n_rings * NUM_SIDES;
     835        1021 :   for (const auto i : make_range(_n_edge_channels))
     836             :   {
     837         906 :     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         906 :     int loc_gap0 = globalGapIndex(gap0);
     840        1812 :     _local_to_global_gaps.push_back({loc_gap0, gap + 1, gap});
     841             : 
     842         906 :     if ((i + 1) % (_n_rings - 1) == 0)
     843         630 :       gap += 2;
     844             :     else
     845             :       gap += 1;
     846             :   }
     847             : 
     848         115 :   int n_interior_gaps = _gap_indices.size() - _n_rings * NUM_SIDES - 1;
     849         115 :   n_edge_gaps = _n_rings * NUM_SIDES;
     850         230 :   _local_to_global_gaps.push_back({n_interior_gaps + n_edge_gaps, n_interior_gaps + 1});
     851         115 :   gap = n_interior_gaps + _n_rings;
     852         690 :   for (unsigned int i = 1; i < _n_corner_channels; ++i)
     853             :   {
     854        1150 :     _local_to_global_gaps.push_back({gap, gap + 1});
     855         575 :     gap += _n_rings;
     856             :   }
     857             : 
     858         115 :   _gap_points.resize(_n_gaps);
     859             : 
     860             :   // For each gap, get two points on the gap
     861        2989 :   for (const auto i : make_range(_n_interior_gaps))
     862             :   {
     863        2874 :     const auto & pins = _gap_indices[i];
     864        2874 :     Point pt1(_pin_centers[pins.first]);
     865        2874 :     Point pt2(_pin_centers[pins.second]);
     866        2874 :     _gap_centers.push_back(0.5 * (pt2 + pt1));
     867             : 
     868        2874 :     _gap_points[i] = {pt1, pt2};
     869             : 
     870             :     // for the last gap in the ring, we need to swap the ordering of pins
     871        2874 :     if (lastGapInRing(i))
     872             :     {
     873             :       Point tmp = pt1;
     874             :       pt1 = pt2;
     875             :       pt2 = tmp;
     876             :     }
     877             : 
     878        5748 :     _gap_unit_normals.push_back(geom_utils::projectedUnitNormal(pt1, pt2, _axis));
     879             :   }
     880             : 
     881         115 :   Real d = _pin_bundle_spacing + pinRadius();
     882        1711 :   for (unsigned int i = _n_interior_gaps; i < _n_gaps; ++i)
     883             :   {
     884        1596 :     const auto & pins = _gap_indices[i];
     885        1596 :     int side = std::abs(pins.second) - 1;
     886             : 
     887        1596 :     const auto & pt1 = _pin_centers[pins.first];
     888             :     const Point pt2 =
     889        1596 :         pt1 + Point(d * _unit_translation_x[side], d * _unit_translation_y[side], 0.0);
     890        1596 :     _gap_centers.push_back(0.5 * (pt2 + pt1));
     891             : 
     892        1596 :     _gap_points[i] = {pt1, pt2};
     893             : 
     894        3192 :     _gap_unit_normals.push_back(geom_utils::projectedUnitNormal(pt1, pt2, _axis));
     895             :   }
     896         115 : }
     897             : 
     898             : unsigned int
     899        5748 : HexagonalLatticeUtils::globalGapIndex(const std::pair<int, int> & local_gap) const
     900             : {
     901      153822 :   for (const auto i : index_range(_gap_indices))
     902             :   {
     903      153822 :     const auto gap = _gap_indices[i];
     904      153822 :     if (gap.first == local_gap.first && gap.second == local_gap.second)
     905        5748 :       return i;
     906             :   }
     907             : 
     908           0 :   mooseError("Failed to find local gap in global gap array!");
     909             : }
     910             : 
     911             : Real
     912          31 : HexagonalLatticeUtils::distanceFromGap(const Point & pt, const unsigned int gap_index) const
     913             : {
     914          31 :   auto p = _gap_points[gap_index];
     915          62 :   return geom_utils::projectedDistanceFromLine(pt, p[0], p[1], _axis);
     916          31 : }
     917             : 
     918             : unsigned int
     919          11 : HexagonalLatticeUtils::gapIndex(const Point & point) const
     920             : {
     921          11 :   const auto & channel_index = channelIndex(point);
     922          11 :   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          42 :   for (const auto i : index_range(gap_indices))
     927             :   {
     928          31 :     Real distance_from_gap = distanceFromGap(point, gap_indices[i]);
     929             : 
     930          31 :     if (distance_from_gap < distance)
     931             :     {
     932             :       distance = distance_from_gap;
     933          19 :       index = gap_indices[i];
     934             :     }
     935             :   }
     936             : 
     937          11 :   return index;
     938             : }
     939             : 
     940             : void
     941           0 : HexagonalLatticeUtils::gapIndexAndDistance(const Point & point,
     942             :                                            unsigned int & index,
     943             :                                            Real & distance) const
     944             : {
     945           0 :   const auto & channel_index = channelIndex(point);
     946           0 :   const auto & gap_indices = _local_to_global_gaps[channel_index];
     947             : 
     948           0 :   distance = std::numeric_limits<Real>::max();
     949           0 :   for (const auto i : index_range(gap_indices))
     950             :   {
     951           0 :     Real distance_from_gap = distanceFromGap(point, gap_indices[i]);
     952             : 
     953           0 :     if (distance_from_gap < distance)
     954             :     {
     955           0 :       distance = distance_from_gap;
     956           0 :       index = gap_indices[i];
     957             :     }
     958             :   }
     959           0 : }
     960             : 
     961             : unsigned int
     962        6516 : HexagonalLatticeUtils::firstPinInRing(const unsigned int ring) const
     963             : {
     964             :   mooseAssert(ring > 0, "Ring indexing starts at 1");
     965        6516 :   if (ring == 1)
     966             :     return 0;
     967             :   else
     968        6470 :     return totalPins(ring - 1);
     969             : }
     970             : 
     971             : unsigned int
     972        7002 : HexagonalLatticeUtils::lastPinInRing(const unsigned int ring) const
     973             : {
     974             :   mooseAssert(ring > 0, "Ring indexing starts at 1");
     975        7002 :   if (ring == 1)
     976             :     return 0;
     977             :   else
     978        6578 :     return totalPins(ring) - 1;
     979             : }
     980             : 
     981             : unsigned int
     982         423 : HexagonalLatticeUtils::ringIndex(const unsigned int pin) const
     983             : {
     984         909 :   for (const auto i : make_range(_n_rings))
     985             :   {
     986         909 :     if (pin <= lastPinInRing(i + 1))
     987         423 :       return i + 1;
     988             :   }
     989           0 :   mooseError("Should not reach here. Pin index: ", pin, " for ", _n_rings, " rings.");
     990             : }
     991             : 
     992             : bool
     993        2964 : HexagonalLatticeUtils::lastGapInRing(const unsigned int gap_index) const
     994             : {
     995        2964 :   if (gap_index >= _n_interior_gaps)
     996             :     return false;
     997             : 
     998        2928 :   const auto & pins = _gap_indices[gap_index];
     999             : 
    1000        8864 :   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        6090 :     int first_pin = firstPinInRing(i);
    1006        6090 :     int last_pin = lastPinInRing(i);
    1007             : 
    1008        6090 :     if (pins.first == first_pin || pins.second == first_pin)
    1009             :       one_is_first_pin = true;
    1010             : 
    1011        6090 :     if (pins.first == last_pin || pins.second == last_pin)
    1012             :       one_is_last_pin = true;
    1013             : 
    1014        6090 :     if (one_is_first_pin && one_is_last_pin)
    1015             :       return true;
    1016             :   }
    1017             : 
    1018             :   return false;
    1019             : }

Generated by: LCOV version 1.14