LCOV - code coverage report
Current view: top level - src/utils - HexagonalLatticeUtils.C (source / functions) Hit Total Coverage
Test: idaholab/moose reactor: #31405 (292dce) with base fef103 Lines: 418 490 85.3 %
Date: 2025-09-04 07:56: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         201 : 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         201 :                                              const Real rotation_around_axis)
      27         201 :   : _bundle_pitch(bundle_inner_flat_to_flat),
      28         201 :     _pin_pitch(pin_pitch),
      29         201 :     _pin_diameter(pin_diameter),
      30         201 :     _wire_diameter(wire_diameter),
      31         201 :     _wire_pitch(wire_pitch),
      32         201 :     _n_rings(n_rings),
      33         201 :     _axis(axis),
      34         201 :     _rotation_around_axis(rotation_around_axis),
      35         201 :     _rotation_matrix(
      36         201 :         RotationMatrix::rotVec2DToX(Point(std::cos(_rotation_around_axis * libMesh::pi / 180),
      37         201 :                                           -std::sin(_rotation_around_axis * libMesh::pi / 180),
      38             :                                           0.))),
      39         201 :     _bundle_side_length(hexagonSide(_bundle_pitch)),
      40         201 :     _pin_area(M_PI * _pin_diameter * _pin_diameter / 4.0),
      41         201 :     _pin_circumference(M_PI * _pin_diameter),
      42         201 :     _wire_area(M_PI * _wire_diameter * _wire_diameter / 4.0),
      43         201 :     _wire_circumference(M_PI * _wire_diameter),
      44         201 :     _pin_surface_area_per_pitch(M_PI * _pin_diameter * _wire_pitch),
      45         201 :     _pin_volume_per_pitch(_pin_area * _wire_pitch),
      46         201 :     _P_over_D(_pin_pitch / _pin_diameter),
      47         201 :     _L_over_D(_wire_pitch / _pin_diameter)
      48             : {
      49         201 :   auto idx = geom_utils::projectedIndices(_axis);
      50         201 :   _ix = idx.first;
      51         201 :   _iy = idx.second;
      52             : 
      53             :   // object is not tested and probably won't work if axis != 2
      54         201 :   if (_axis != 2)
      55           0 :     mooseError("The HexagonalLatticeUtils is currently limited to 'axis = 2'!");
      56             : 
      57         201 :   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         201 :   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         201 :   _unit_translation_x = {0.0, -SIN60, -SIN60, 0.0, SIN60, SIN60};
      67         201 :   _unit_translation_y = {1.0, COS60, -COS60, -1.0, -COS60, COS60};
      68             : 
      69             :   // Use rotation matrix on unit translation vectors
      70         201 :   if (rotation_around_axis != 0)
      71         161 :     for (const auto i : make_range(6))
      72             :     {
      73             :       const Point rotated =
      74         138 :           _rotation_matrix * Point(_unit_translation_x[i], _unit_translation_y[i], 0);
      75         138 :       _unit_translation_x[i] = rotated(0);
      76         138 :       _unit_translation_y[i] = rotated(1);
      77             :     }
      78             : 
      79             :   // compute number of each pin and channel and channel type (interior, edge channel)
      80         201 :   computePinAndChannelTypes();
      81             : 
      82         201 :   _corner_edge_length = (_bundle_side_length - _n_edge_channels / NUM_SIDES * _pin_pitch) / 2.0;
      83             : 
      84         201 :   computePinBundleSpacing();
      85         201 :   computeWireVolumeAndAreaPerPitch();
      86         201 :   computeFlowVolumes();
      87         201 :   computeWettedAreas();
      88         201 :   computeHydraulicDiameters();
      89         201 :   computePinAndDuctCoordinates();
      90         201 :   computeChannelPinIndices();
      91         201 :   computeGapIndices();
      92             : 
      93         201 :   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         201 : }
      97             : 
      98             : unsigned int
      99  6849863215 : HexagonalLatticeUtils::pins(const unsigned int n) const
     100             : {
     101  6849863215 :   if (n <= 0)
     102             :     return 0;
     103  6849863214 :   else if (n == 1)
     104             :     return 1;
     105             :   else
     106  6849808980 :     return NUM_SIDES * (n - 1);
     107             : }
     108             : 
     109             : unsigned int
     110       53916 : HexagonalLatticeUtils::totalPins(const unsigned int n) const
     111             : {
     112             :   unsigned int total = 0;
     113  1277569005 :   for (unsigned int i = 1; i <= n; ++i)
     114  1277515089 :     total += pins(i);
     115             : 
     116       53916 :   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       28620 : HexagonalLatticeUtils::hexagonSide(const Real pitch) const
     140             : {
     141       28620 :   return pitch / std::sqrt(3.0);
     142             : }
     143             : 
     144             : Real
     145         201 : HexagonalLatticeUtils::hexagonArea(const Real pitch) const
     146             : {
     147         201 :   Real side = hexagonSide(pitch);
     148         201 :   return 3 * SIN60 * side * side;
     149             : }
     150             : 
     151             : Real
     152         201 : HexagonalLatticeUtils::hexagonVolume(const Real pitch) const
     153             : {
     154         201 :   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         201 : HexagonalLatticeUtils::triangleArea(const Real side) const
     166             : {
     167         201 :   return 0.5 * triangleHeight(side) * side;
     168             : }
     169             : 
     170             : Real
     171         402 : HexagonalLatticeUtils::triangleHeight(const Real side) const
     172             : {
     173         402 :   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         201 : HexagonalLatticeUtils::triangleVolume(const Real side) const
     184             : {
     185         201 :   return triangleArea(side) * _wire_pitch;
     186             : }
     187             : 
     188             : Real
     189         755 : HexagonalLatticeUtils::pinRadius() const
     190             : {
     191         755 :   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         201 : HexagonalLatticeUtils::computePinAndDuctCoordinates()
     272             : {
     273         201 :   Real corner_shiftx[] = {COS60, -COS60, -1, -COS60, COS60, 1};
     274         201 :   Real corner_shifty[] = {SIN60, SIN60, 0, -SIN60, -SIN60, 0};
     275             : 
     276         201 :   Real edge_shiftx[] = {-1, -COS60, COS60, 1, COS60, -COS60};
     277         201 :   Real edge_shifty[] = {0, -SIN60, -SIN60, 0, SIN60, SIN60};
     278             : 
     279             :   // Use rotation matrix on shift vectors
     280         201 :   if (_rotation_around_axis != 0)
     281         161 :     for (const auto i : make_range(6))
     282             :     {
     283         138 :       Point rotated = _rotation_matrix * Point(corner_shiftx[i], corner_shifty[i], 0);
     284         138 :       corner_shiftx[i] = rotated(0);
     285         138 :       corner_shifty[i] = rotated(1);
     286         138 :       rotated = _rotation_matrix * Point(edge_shiftx[i], edge_shifty[i], 0);
     287         138 :       edge_shiftx[i] = rotated(0);
     288         138 :       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         201 :   _pin_centers.push_back(center);
     294             : 
     295         472 :   for (unsigned int i = 2; i <= _n_rings; ++i)
     296             :   {
     297         271 :     auto n_total_in_ring = pins(i);
     298         271 :     auto increment = i - 1;
     299             : 
     300             :     unsigned int d = 0;
     301             : 
     302        2527 :     for (const auto j : make_range(n_total_in_ring))
     303             :     {
     304        2256 :       unsigned int side = j / increment;
     305             : 
     306        2256 :       if (d == increment)
     307             :         d = 0;
     308             : 
     309        2256 :       Point center = geom_utils::projectPoint(corner_shiftx[side] * _pin_pitch * (i - 1),
     310        2256 :                                               corner_shifty[side] * _pin_pitch * (i - 1),
     311        2256 :                                               _axis);
     312             : 
     313             :       // additional shift for the edge sides
     314        2256 :       if (d != 0)
     315             :       {
     316         630 :         center(_ix) += edge_shiftx[side] * _pin_pitch * d;
     317         630 :         center(_iy) += edge_shifty[side] * _pin_pitch * d;
     318             :       }
     319             : 
     320        2256 :       _pin_centers.push_back(center);
     321             : 
     322        2256 :       d += 1;
     323             :     }
     324             :   }
     325             : 
     326             :   // compute corners of the hexagonal prisms that enclose each pin
     327         201 :   _pin_centered_corner_coordinates.resize(_n_pins);
     328         201 :   auto side = hexagonSide(_pin_pitch);
     329        2658 :   for (const auto pin : make_range(_n_pins))
     330             :   {
     331       17199 :     for (const auto i : make_range(NUM_SIDES))
     332             :     {
     333       14742 :       Point translation = geom_utils::projectPoint(
     334       14742 :           _unit_translation_x[i] * side, _unit_translation_y[i] * side, _axis);
     335             : 
     336       14742 :       _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         201 :   Real l = _bundle_side_length;
     342        1407 :   for (const auto i : make_range(NUM_SIDES))
     343             :   {
     344        1206 :     Point corner = geom_utils::projectPoint(corner_shiftx[i] * l, corner_shifty[i] * l, _axis);
     345        1206 :     _duct_corners.push_back(corner);
     346             :   }
     347             : 
     348             :   // compute the equations (a*x + b*y + c) defining each duct wall
     349        1407 :   for (const auto i : make_range(NUM_SIDES))
     350             :   {
     351             :     auto c = i;
     352        1206 :     unsigned int n = i == 5 ? 0 : i + 1;
     353        1206 :     Real slope = (_duct_corners[n](_iy) - _duct_corners[c](_iy)) /
     354        1206 :                  (_duct_corners[n](_ix) - _duct_corners[c](_ix));
     355        1206 :     std::vector<Real> coeffs = {-slope, 1.0, slope * _duct_corners[c](_ix) - _duct_corners[c](_iy)};
     356        1206 :     _duct_coeffs.push_back(coeffs);
     357        1206 :   }
     358         201 : }
     359             : 
     360             : void
     361         201 : HexagonalLatticeUtils::computeWettedAreas()
     362             : {
     363         201 :   Real rod_area_per_pitch = _pin_surface_area_per_pitch + _wire_surface_area_per_pitch;
     364             : 
     365         201 :   _interior_wetted_area = 0.5 * rod_area_per_pitch;
     366         201 :   _edge_wetted_area = 0.5 * rod_area_per_pitch + _pin_pitch * _wire_pitch;
     367             : 
     368         201 :   Real h = (_bundle_side_length - _n_edge_channels / NUM_SIDES * _pin_pitch) / 2.0;
     369         201 :   _corner_wetted_area = rod_area_per_pitch / 6.0 + 2 * h * _wire_pitch;
     370         201 :   _wetted_area = _n_pins * rod_area_per_pitch + 6.0 * _bundle_side_length * _wire_pitch;
     371         201 : }
     372             : 
     373             : void
     374         201 : 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         201 :   _interior_Dh = 4 * _interior_flow_volume / _interior_wetted_area;
     380         201 :   _edge_Dh = 4 * _edge_flow_volume / _edge_wetted_area;
     381         201 :   _corner_Dh = 4 * _corner_flow_volume / _corner_wetted_area;
     382         201 :   _Dh = 4 * _flow_volume / _wetted_area;
     383         201 : }
     384             : 
     385             : void
     386         201 : 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         201 :   Real rod_volume_per_pitch = _pin_volume_per_pitch + _wire_volume_per_pitch;
     393             : 
     394         201 :   Real l = _pin_bundle_spacing + pinRadius();
     395             : 
     396         201 :   _interior_volume = triangleVolume(_pin_pitch);
     397         201 :   _edge_volume = _pin_pitch * l * _wire_pitch;
     398         201 :   _corner_volume = l * _corner_edge_length * _wire_pitch;
     399             : 
     400         201 :   _interior_flow_volume = _interior_volume - 0.5 * rod_volume_per_pitch;
     401         201 :   _edge_flow_volume = _edge_volume - 0.5 * rod_volume_per_pitch;
     402         201 :   _corner_flow_volume = _corner_volume - rod_volume_per_pitch / 6.0;
     403             : 
     404         201 :   _flow_volume = hexagonVolume(_bundle_pitch) - _n_pins * rod_volume_per_pitch;
     405         201 : }
     406             : 
     407             : unsigned int
     408         542 : HexagonalLatticeUtils::interiorChannels(const unsigned int ring)
     409             : {
     410         542 :   return (ring * 2 - 1) * NUM_SIDES;
     411             : }
     412             : 
     413             : void
     414         201 : HexagonalLatticeUtils::computePinAndChannelTypes()
     415             : {
     416         201 :   _n_pins = 0;
     417         673 :   for (unsigned int i = _n_rings; i > 0; --i)
     418         472 :     _n_pins += pins(i);
     419             : 
     420             :   // the one-ring case
     421         201 :   _n_interior_pins = _n_pins;
     422         201 :   _n_edge_pins = 0;
     423         201 :   _n_corner_pins = 0;
     424             : 
     425         201 :   _n_corner_channels = NUM_SIDES;
     426         201 :   _n_edge_channels = 0;
     427         201 :   _n_interior_channels = 0;
     428             : 
     429         201 :   if (_n_rings > 1)
     430             :   {
     431         191 :     _n_corner_pins = NUM_SIDES;
     432         191 :     _n_edge_pins = (_n_rings - 2) * NUM_SIDES;
     433         191 :     _n_interior_pins -= _n_corner_pins + _n_edge_pins;
     434             : 
     435         191 :     _n_edge_channels = _n_edge_pins + NUM_SIDES;
     436             : 
     437         462 :     for (unsigned int i = 1; i < _n_rings; ++i)
     438         271 :       _n_interior_channels += interiorChannels(i);
     439             :   }
     440             : 
     441         201 :   _n_channels = _n_interior_channels + _n_edge_channels + _n_corner_channels;
     442         201 : }
     443             : 
     444             : void
     445         201 : HexagonalLatticeUtils::computePinBundleSpacing()
     446             : {
     447             :   // hexagonal flat-to-flat that *just* encloses the pins (NOT including the wire)
     448         201 :   Real pin_h = 2.0 * (_n_rings - 1) * triangleHeight(_pin_pitch) + _pin_diameter;
     449             : 
     450         201 :   _pin_bundle_spacing = (_bundle_pitch - pin_h) / 2.0;
     451         201 : }
     452             : 
     453             : void
     454         201 : HexagonalLatticeUtils::computeWireVolumeAndAreaPerPitch()
     455             : {
     456             :   unsigned int n_wire_coils = 1;
     457             :   Real wire_length_per_coil =
     458         201 :       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         201 :   _wire_volume_per_pitch = _wire_area * wire_length;
     461         201 :   _wire_surface_area_per_pitch = _wire_circumference * wire_length;
     462         201 : }
     463             : 
     464             : void
     465         201 : HexagonalLatticeUtils::computeChannelPinIndices()
     466             : {
     467         201 :   _interior_channel_pin_indices.resize(_n_interior_channels);
     468         201 :   _edge_channel_pin_indices.resize(_n_edge_channels);
     469         201 :   _corner_channel_pin_indices.resize(_n_corner_channels);
     470             : 
     471             :   // 3 pins per interior channel
     472        3087 :   for (auto & i : _interior_channel_pin_indices)
     473        2886 :     i.resize(3);
     474             : 
     475             :   // 2 pins per edge channel
     476        1827 :   for (auto & i : _edge_channel_pin_indices)
     477        1626 :     i.resize(2);
     478             : 
     479             :   // 1 pin per corner channel
     480        1407 :   for (auto & i : _corner_channel_pin_indices)
     481        1206 :     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         201 :   starts.resize(_n_rings);
     486         673 :   for (const auto i : make_range(_n_rings))
     487             :   {
     488         472 :     starts[i].resize(NUM_SIDES);
     489         472 :     starts[i][0] = (i == 0) ? 0 : totalPins(i);
     490             : 
     491        2832 :     for (unsigned int j = 1; j < NUM_SIDES; ++j)
     492        2360 :       starts[i][j] = starts[i][j - 1] + i;
     493             :   }
     494             : 
     495             :   unsigned int c = 0;
     496         472 :   for (const auto i : make_range(_n_rings - 1))
     497             :   {
     498         271 :     auto channels = interiorChannels(i + 1);
     499         271 :     unsigned int channels_per_sector = channels / NUM_SIDES;
     500             : 
     501        1897 :     for (const auto j : make_range(NUM_SIDES))
     502             :     {
     503        1626 :       auto prev_inner = starts[i][j];
     504        1626 :       auto prev_outer = starts[i + 1][j];
     505             : 
     506        4512 :       for (const auto k : make_range(channels_per_sector))
     507             :       {
     508        2886 :         bool downward = k % 2 == 0;
     509             : 
     510        2886 :         if (downward)
     511             :         {
     512        2256 :           _interior_channel_pin_indices[c][0] = prev_inner;
     513        2256 :           _interior_channel_pin_indices[c][1] = prev_outer;
     514        2256 :           _interior_channel_pin_indices[c][2] = ++prev_outer;
     515             :         }
     516             :         else
     517             :         {
     518         630 :           _interior_channel_pin_indices[c][0] = prev_outer;
     519         630 :           _interior_channel_pin_indices[c][1] = ++prev_inner;
     520         630 :           _interior_channel_pin_indices[c][2] = prev_inner - 1;
     521             :         }
     522             : 
     523        2886 :         if (j == 5) // last sector
     524             :         {
     525         481 :           if (k == channels_per_sector - 1)
     526             :           {
     527         271 :             _interior_channel_pin_indices[c][2] = starts[i + 1][0];
     528         271 :             _interior_channel_pin_indices[c][0] = starts[i][0];
     529             :           }
     530             : 
     531         481 :           if (k == channels_per_sector - 2 && i > 0)
     532          80 :             _interior_channel_pin_indices[c][1] = starts[i][0];
     533             :         }
     534             : 
     535        2886 :         c++;
     536             :       }
     537             :     }
     538             :   }
     539             : 
     540        1827 :   for (const auto i : make_range(_n_edge_channels))
     541             :   {
     542        1626 :     _edge_channel_pin_indices[i][0] = starts[_n_rings - 1][0] + i;
     543        1626 :     _edge_channel_pin_indices[i][1] = _edge_channel_pin_indices[i][0] + 1;
     544             : 
     545        1626 :     if (i == _n_edge_channels - 1)
     546         191 :       _edge_channel_pin_indices[i][1] = _edge_channel_pin_indices[0][0];
     547             :   }
     548             : 
     549        1407 :   for (const auto i : make_range(_n_corner_channels))
     550        1206 :     _corner_channel_pin_indices[i][0] =
     551        1206 :         totalPins(_n_rings - 1) + i * (_n_edge_channels / NUM_SIDES);
     552         201 : }
     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       28017 : HexagonalLatticeUtils::pinIndex(const Point & point) const
     626             : {
     627       28017 :   auto side = hexagonSide(_pin_pitch);
     628             : 
     629      440065 :   for (const auto i : make_range(_n_pins))
     630             :   {
     631      431532 :     const auto & center = _pin_centers[i];
     632      431532 :     Real dx = center(_ix) - point(_ix);
     633      431532 :     Real dy = center(_iy) - point(_iy);
     634      431532 :     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      431532 :     if (distance_from_pin > side)
     639      410859 :       continue;
     640             : 
     641       20673 :     auto corners = _pin_centered_corner_coordinates[i];
     642       20673 :     if (geom_utils::pointInPolygon(point, corners, _axis))
     643             :       return i;
     644       20673 :   }
     645             : 
     646        8533 :   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         893 : 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         893 :   const auto ring_index = ringIndex(pin_index);
     752         893 :   row_index = _n_rings - ring_index;
     753         893 :   within_row_index = _n_rings - 1;
     754             : 
     755         893 :   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        3572 :   for (const auto local_i : make_range(pin_index - firstPinInRing(ring_index)))
     760             :   {
     761        2679 :     if (local_i < n_pin_sectors)
     762         874 :       within_row_index--;
     763        1805 :     else if (local_i < 3 * n_pin_sectors)
     764        1235 :       row_index++;
     765         570 :     else if (local_i < 4 * n_pin_sectors)
     766         361 :       within_row_index++;
     767         209 :     else if (local_i < 5 * n_pin_sectors)
     768             :     {
     769         190 :       row_index--;
     770         190 :       within_row_index++;
     771             :     }
     772             :     else
     773             :     {
     774          19 :       row_index--;
     775          19 :       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         893 : }
     783             : 
     784             : void
     785         201 : HexagonalLatticeUtils::computeGapIndices()
     786             : {
     787             :   std::set<std::pair<int, int>> indices;
     788        3087 :   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        2886 :     indices.insert(gap0);
     795        2886 :     indices.insert(gap1);
     796        2886 :     indices.insert(gap2);
     797             :   }
     798             : 
     799        5343 :   for (const auto & it : indices)
     800        5142 :     _gap_indices.push_back({it.first, it.second});
     801             : 
     802         201 :   _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         201 :   int n_edge_gaps = _n_rings;
     807         201 :   int pin = totalPins(_n_rings - 1);
     808        1407 :   for (const auto i : make_range(NUM_SIDES))
     809             :   {
     810        4038 :     for (const auto j : make_range(_n_rings))
     811        2832 :       _gap_indices.push_back({pin + j, -(i + 1)});
     812             : 
     813        1206 :     pin += n_edge_gaps - 1;
     814             :   }
     815             : 
     816             :   // fix the last gap to use the first pin
     817         201 :   _gap_indices.back() = {totalPins(_n_rings - 1), -int(NUM_SIDES)};
     818         201 :   _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        3087 :   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        2886 :     int loc_gap0 = globalGapIndex(gap0);
     829        2886 :     int loc_gap1 = globalGapIndex(gap1);
     830        2886 :     int loc_gap2 = globalGapIndex(gap2);
     831        5772 :     _local_to_global_gaps.push_back({loc_gap0, loc_gap1, loc_gap2});
     832             :   }
     833             : 
     834         201 :   int gap = _gap_indices.size() - _n_rings * NUM_SIDES;
     835        1827 :   for (const auto i : make_range(_n_edge_channels))
     836             :   {
     837        1626 :     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        1626 :     int loc_gap0 = globalGapIndex(gap0);
     840        3252 :     _local_to_global_gaps.push_back({loc_gap0, gap + 1, gap});
     841             : 
     842        1626 :     if ((i + 1) % (_n_rings - 1) == 0)
     843        1146 :       gap += 2;
     844             :     else
     845             :       gap += 1;
     846             :   }
     847             : 
     848         201 :   int n_interior_gaps = _gap_indices.size() - _n_rings * NUM_SIDES - 1;
     849         201 :   n_edge_gaps = _n_rings * NUM_SIDES;
     850         402 :   _local_to_global_gaps.push_back({n_interior_gaps + n_edge_gaps, n_interior_gaps + 1});
     851         201 :   gap = n_interior_gaps + _n_rings;
     852        1206 :   for (unsigned int i = 1; i < _n_corner_channels; ++i)
     853             :   {
     854        2010 :     _local_to_global_gaps.push_back({gap, gap + 1});
     855        1005 :     gap += _n_rings;
     856             :   }
     857             : 
     858         201 :   _gap_points.resize(_n_gaps);
     859             : 
     860             :   // For each gap, get two points on the gap
     861        5343 :   for (const auto i : make_range(_n_interior_gaps))
     862             :   {
     863        5142 :     const auto & pins = _gap_indices[i];
     864        5142 :     Point pt1(_pin_centers[pins.first]);
     865        5142 :     Point pt2(_pin_centers[pins.second]);
     866        5142 :     _gap_centers.push_back(0.5 * (pt2 + pt1));
     867             : 
     868        5142 :     _gap_points[i] = {pt1, pt2};
     869             : 
     870             :     // for the last gap in the ring, we need to swap the ordering of pins
     871        5142 :     if (lastGapInRing(i))
     872             :     {
     873             :       Point tmp = pt1;
     874             :       pt1 = pt2;
     875             :       pt2 = tmp;
     876             :     }
     877             : 
     878       10284 :     _gap_unit_normals.push_back(geom_utils::projectedUnitNormal(pt1, pt2, _axis));
     879             :   }
     880             : 
     881         201 :   Real d = _pin_bundle_spacing + pinRadius();
     882        3033 :   for (unsigned int i = _n_interior_gaps; i < _n_gaps; ++i)
     883             :   {
     884        2832 :     const auto & pins = _gap_indices[i];
     885        2832 :     int side = std::abs(pins.second) - 1;
     886             : 
     887        2832 :     const auto & pt1 = _pin_centers[pins.first];
     888             :     const Point pt2 =
     889        2832 :         pt1 + Point(d * _unit_translation_x[side], d * _unit_translation_y[side], 0.0);
     890        2832 :     _gap_centers.push_back(0.5 * (pt2 + pt1));
     891             : 
     892        2832 :     _gap_points[i] = {pt1, pt2};
     893             : 
     894        5664 :     _gap_unit_normals.push_back(geom_utils::projectedUnitNormal(pt1, pt2, _axis));
     895             :   }
     896         201 : }
     897             : 
     898             : unsigned int
     899       10284 : HexagonalLatticeUtils::globalGapIndex(const std::pair<int, int> & local_gap) const
     900             : {
     901      280146 :   for (const auto i : index_range(_gap_indices))
     902             :   {
     903      280146 :     const auto gap = _gap_indices[i];
     904      280146 :     if (gap.first == local_gap.first && gap.second == local_gap.second)
     905       10284 :       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       11788 : HexagonalLatticeUtils::firstPinInRing(const unsigned int ring) const
     963             : {
     964             :   mooseAssert(ring > 0, "Ring indexing starts at 1");
     965       11788 :   if (ring == 1)
     966             :     return 0;
     967             :   else
     968       11692 :     return totalPins(ring - 1);
     969             : }
     970             : 
     971             : unsigned int
     972       12814 : HexagonalLatticeUtils::lastPinInRing(const unsigned int ring) const
     973             : {
     974             :   mooseAssert(ring > 0, "Ring indexing starts at 1");
     975       12814 :   if (ring == 1)
     976             :     return 0;
     977             :   else
     978       11920 :     return totalPins(ring) - 1;
     979             : }
     980             : 
     981             : unsigned int
     982         893 : HexagonalLatticeUtils::ringIndex(const unsigned int pin) const
     983             : {
     984        1919 :   for (const auto i : make_range(_n_rings))
     985             :   {
     986        1919 :     if (pin <= lastPinInRing(i + 1))
     987         893 :       return i + 1;
     988             :   }
     989           0 :   mooseError("Should not reach here. Pin index: ", pin, " for ", _n_rings, " rings.");
     990             : }
     991             : 
     992             : bool
     993        5232 : HexagonalLatticeUtils::lastGapInRing(const unsigned int gap_index) const
     994             : {
     995        5232 :   if (gap_index >= _n_interior_gaps)
     996             :     return false;
     997             : 
     998        5196 :   const auto & pins = _gap_indices[gap_index];
     999             : 
    1000       15814 :   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       10892 :     int first_pin = firstPinInRing(i);
    1006       10892 :     int last_pin = lastPinInRing(i);
    1007             : 
    1008       10892 :     if (pins.first == first_pin || pins.second == first_pin)
    1009             :       one_is_first_pin = true;
    1010             : 
    1011       10892 :     if (pins.first == last_pin || pins.second == last_pin)
    1012             :       one_is_last_pin = true;
    1013             : 
    1014       10892 :     if (one_is_first_pin && one_is_last_pin)
    1015             :       return true;
    1016             :   }
    1017             : 
    1018             :   return false;
    1019             : }

Generated by: LCOV version 1.14