Skip to content

Commit

Permalink
Merge 8005d88 into 6163e94
Browse files Browse the repository at this point in the history
  • Loading branch information
john-science committed Feb 22, 2024
2 parents 6163e94 + 8005d88 commit 8137451
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 115 deletions.
2 changes: 1 addition & 1 deletion armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2286,7 +2286,7 @@ def autoCreateSpatialGrids(self):
# note that it's the pointed end of the cell hexes that are up (but the
# macro shape of the pins forms a hex with a flat top fitting in the assembly)
grid = grids.HexGrid.fromPitch(
self.getPinPitch(cold=True), numRings=0, pointedEndUp=True
self.getPinPitch(cold=True), numRings=0, cornersUp=True
)
spatialLocators = grids.MultiIndexLocation(grid=self.spatialGrid)
numLocations = 0
Expand Down
2 changes: 1 addition & 1 deletion armi/reactor/blueprints/gridBlueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def _constructSpatialGrid(self):
spatialGrid = grids.HexGrid.fromPitch(
pitch,
numRings=maxIndex + 2,
pointedEndUp=geom == geometry.HEX_CORNERS_UP,
cornersUp=geom == geometry.HEX_CORNERS_UP,
)
elif geom == geometry.CARTESIAN:
# if full core or not cut-off, bump the first assembly from the center of
Expand Down
223 changes: 164 additions & 59 deletions armi/reactor/grids/hexagonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

COS30 = sqrt(3) / 2.0
SIN30 = 1.0 / 2.0
# going CCW from "position 1" (top right)
# going counter-clockwise from "position 1" (top right)
TRIANGLES_IN_HEXAGON = numpy.array(
[
(+COS30, SIN30),
Expand All @@ -44,7 +44,7 @@


class HexGrid(StructuredGrid):
"""
r"""
Has 6 neighbors in plane.
It is recommended to use :meth:`fromPitch` rather than calling the ``__init__``
Expand All @@ -63,25 +63,59 @@ class HexGrid(StructuredGrid):
Notes
-----
In an axial plane (i, j) are as follows (second one is pointedEndUp)::
( 0, 1)
(-1, 1) ( 1, 0)
( 0, 0)
(-1, 0) ( 1,-1)
( 0,-1)
In an axial plane (i, j) are as follows (flats up)::
_____
/ \
_____/ 0,1 \_____
/ \ / \
/ -1,1 \_____/ 1,0 \
\ / \ /
\_____/ 0,0 \_____/
/ \ / \
/ -1,0 \_____/ 1,-1 \
\ / \ /
\_____/ 0,-1 \_____/
\ /
\_____/
In an axial plane (i, j) are as follows (corners up)::
/ \ / \
/ \ / \
| 0,1 | 1,0 |
| | |
/ \ / \ / \
/ \ / \ / \
| -1,1 | 0,0 | 1,-1 |
| | | |
\ / \ / \ /
\ / \ / \ /
| -1,0 | 0,-1 |
| | |
\ / \ /
\ / \ /
Basic hexagon geometry::
- pitch = sqrt(3) * side
- long diagonal = 2 * side
- Area = (sqrt(3) / 4) * side^2
- perimeter = 6 * side
"""

( 0, 1) ( 1, 0)
(-1, 1) ( 0, 0) ( 1,-1)
@property
def cornersUp(self) -> bool:
"""
Check whether the hexagonal grid is "corners up" or "flats up".
(-1, 0) ( 0,-1)
"""
See the armi.reactor.grids.HexGrid class documentation for an
illustration of the two types of grid indexing.
"""
return self._unitSteps[0][1] != 0.0

@staticmethod
def fromPitch(pitch, numRings=25, armiObject=None, pointedEndUp=False, symmetry=""):
def fromPitch(pitch, numRings=25, armiObject=None, cornersUp=False, symmetry=""):
"""
Build a finite step-based 2-D hex grid from a hex pitch in cm.
Expand Down Expand Up @@ -116,9 +150,9 @@ def fromPitch(pitch, numRings=25, armiObject=None, pointedEndUp=False, symmetry=
armiObject : ArmiObject, optional
The object that this grid is anchored to (i.e. the reactor for a grid of
assemblies)
pointedEndUp : bool, optional
Rotate the hexagons 30 degrees so that the pointed end faces up instead of
the flat.
cornersUp : bool, optional
Rotate the hexagons 30 degrees so that the corners point up instead of
the flat faces.
symmetry : string, optional
A string representation of the symmetry options for the grid.
Expand All @@ -127,27 +161,15 @@ def fromPitch(pitch, numRings=25, armiObject=None, pointedEndUp=False, symmetry=
HexGrid
A functional hexagonal grid object.
"""
side = hexagon.side(pitch)
if pointedEndUp:
# rotated 30 degrees CCW from normal
# increases in i move you in x and y
# increases in j also move you in x and y
unitSteps = (
(pitch / 2.0, -pitch / 2.0, 0),
(1.5 * side, 1.5 * side, 0),
(0, 0, 0),
)
else:
# x direction is only a function of i because j-axis is vertical.
# y direction is a function of both.
unitSteps = ((1.5 * side, 0.0, 0.0), (pitch / 2.0, pitch, 0.0), (0, 0, 0))
unitSteps = HexGrid._getRawUnitSteps(pitch, cornersUp)

return HexGrid(
hex = HexGrid(
unitSteps=unitSteps,
unitStepLimits=((-numRings, numRings), (-numRings, numRings), (0, 1)),
armiObject=armiObject,
symmetry=symmetry,
)
return hex

@property
def pitch(self) -> float:
Expand Down Expand Up @@ -221,7 +243,7 @@ def getNeighboringCellIndices(
Return the indices of the immediate neighbors of a mesh point in the plane.
Note that these neighbors are ordered counter-clockwise beginning from the
30 or 60 degree direction. Exact direction is dependent on pointedEndUp arg.
30 or 60 degree direction. Exact direction is dependent on cornersUp arg.
"""
return [
(i + 1, j, k),
Expand All @@ -248,20 +270,42 @@ def getLabel(self, indices):

@staticmethod
def _indicesAndEdgeFromRingAndPos(ring, position):
"""Given the ring and position, return the (I,J) coordinates, and which edge the grid
cell is on.
Parameters
----------
ring : int
Starting with 1 (not zero), the ring of the grid cell.
position : int
Starting with 1 (not zero), the position of the grid cell, in the ring.
Returns
-------
(int, int, int) : I coordinate, J coordinate, which edge of the hex ring
Notes
-----
- Edge indicates which edge of the ring in which the hexagon resides.
- Edge 0 is the NE edge, edge 1 is the N edge, etc.
- Offset is (0-based) index of the hexagon in that edge. For instance,
ring 3, pos 12 resides in edge 5 at index 1; it is the second hexagon
in ring 3, edge 5.
"""
# The inputs start counting at 1, but the grid starts counting at zero.
ring = ring - 1
pos = position - 1

# Handle the center grid cell.
if ring == 0:
if pos != 0:
raise ValueError(f"Position in center ring must be 1, not {position}")
return 0, 0, 0

# Edge indicates which edge of the ring in which the hexagon resides.
# Edge 0 is the NE edge, edge 1 is the N edge, etc.
# Offset is (0-based) index of the hexagon in that edge. For instance,
# ring 3, pos 12 resides in edge 5 at index 1; it is the second hexagon
# in ring 3, edge 5.
edge, offset = divmod(pos, ring) # = pos//ring, pos%ring
# find the edge and offset (pos//ring or pos%ring)
edge, offset = divmod(pos, ring)

# find (I,J) based on the ring, edge, and offset
if edge == 0:
i = ring - offset
j = offset
Expand All @@ -270,9 +314,9 @@ def _indicesAndEdgeFromRingAndPos(ring, position):
j = ring
elif edge == 2:
i = -ring
j = -offset + ring
j = ring - offset
elif edge == 3:
i = -ring + offset
i = offset - ring
j = -offset
elif edge == 4:
i = offset
Expand All @@ -281,13 +325,47 @@ def _indicesAndEdgeFromRingAndPos(ring, position):
i = ring
j = offset - ring
else:
raise ValueError(
"Edge {} is invalid. From ring {}, pos {}".format(edge, ring, pos)
)
raise ValueError(f"Edge {edge} is invalid. From ring {ring}, pos {pos}")

return i, j, edge

@staticmethod
def getIndicesFromRingAndPos(ring: int, pos: int) -> IJType:
r"""Given the ring and position, return the (I,J) coordinates in the hex grid.
Parameters
----------
ring : int
Starting with 1 (not zero), the ring of the grid cell.
position : int
Starting with 1 (not zero), the position of the grid cell, in the ring.
Returns
-------
(int, int) : I coordinate, J coordinate
Notes
-----
In an axial plane, the (ring, position) coordinates are as follows::
Flat-to-Flat Corners Up
_____
/ \ / \ / \
_____/ 2,2 \_____ / \ / \
/ \ / \ | 2,2 | 2,1 |
/ 2,3 \_____/ 2,1 \ | | |
\ / \ / / \ / \ / \
\_____/ 1,1 \_____/ / \ / \ / \
/ \ / \ | 2,3 | 1,1 | 2,6 |
/ 2,4 \_____/ 2,6 \ | | | |
\ / \ / \ / \ / \ /
\_____/ 2,5 \_____/ \ / \ / \ /
\ / | 2,4 | 2,5 |
\_____/ | | |
\ / \ /
\ / \ /
"""
i, j, _edge = HexGrid._indicesAndEdgeFromRingAndPos(ring, pos)
return i, j

Expand Down Expand Up @@ -341,16 +419,16 @@ def overlapsWhichSymmetryLine(self, indices: IJType) -> Optional[int]:
return symmetryLine

def getSymmetricEquivalents(self, indices: IJKType) -> List[IJType]:
"""Retrieve the equivalent indices; return them as-is if this is full core, but
return the symmetric equivalent if this is a 1/3-core grid.
"""Retrieve the equivalent indices. If full core return nothing, if 1/3-core grid,
return the symmetric equivalents, if any other grid, raise an error.
.. impl:: Equivalent contents in 1/3-core geometries are retrievable.
:id: I_ARMI_GRID_EQUIVALENTS
:implements: R_ARMI_GRID_EQUIVALENTS
This method takes in (I,J,K) indices, and if this ``HexGrid`` is full core,
it returns them as-is. If this ``HexGrid`` is 1/3-core, this method will
return the 1/3-core symmetric equivalent. If this grid is any other kind,
it returns nothing. If this ``HexGrid`` is 1/3-core, this method will return
the 1/3-core symmetric equivalent of just (I,J). If this grid is any other kind,
this method will just return an error; a hexagonal grid with any other
symmetry is probably an error.
"""
Expand All @@ -370,9 +448,7 @@ def getSymmetricEquivalents(self, indices: IJKType) -> List[IJType]:

@staticmethod
def _getSymmetricIdenticalsThird(indices) -> List[IJType]:
"""This works by rotating the indices by 120 degrees twice,
counterclockwise.
"""
"""This works by rotating the indices by 120 degrees twice, counterclockwise."""
i, j = indices[:2]
if i == 0 and j == 0:
return []
Expand All @@ -390,12 +466,42 @@ def triangleCoords(self, indices: IJKType) -> numpy.ndarray:
scale = self.pitch / 3.0
return xy + scale * TRIANGLES_IN_HEXAGON

@staticmethod
def _getRawUnitSteps(pitch, cornersUp=False):
"""Get the raw unit steps (ignore step dimensions), for a hex grid.
Parameters
----------
pitch : float
The short diameter of the hexagons (flat to flat).
cornersUp : bool, optional
If True, the hexagons have a corner pointing in the Y direction. Default: False
Returns
-------
tuple : The full 3D set of derivatives of X,Y,Z in terms of i,j,k.
"""
side = hexagon.side(pitch)
if cornersUp:
# rotated 30 degrees counter-clockwise from normal
# increases in i moves you in x and y
# increases in j also moves you in x and y
unitSteps = (
(pitch / 2.0, -pitch / 2.0, 0),
(1.5 * side, 1.5 * side, 0),
(0, 0, 0),
)
else:
# x direction is only a function of i because j-axis is vertical.
# y direction is a function of both.
unitSteps = ((1.5 * side, 0.0, 0.0), (pitch / 2.0, pitch, 0.0), (0, 0, 0))

return unitSteps

def changePitch(self, newPitchCm: float):
"""Change the hex pitch."""
side = hexagon.side(newPitchCm)
self._unitSteps = numpy.array(
((1.5 * side, 0.0, 0.0), (newPitchCm / 2.0, newPitchCm, 0.0), (0, 0, 0))
)[self._stepDims]
unitSteps = numpy.array(HexGrid._getRawUnitSteps(newPitchCm, self.cornersUp))
self._unitSteps = unitSteps[self._stepDims]

def locatorInDomain(self, locator, symmetryOverlap: Optional[bool] = False) -> bool:
# This will include the "top" 120-degree symmetry lines. This is to support
Expand Down Expand Up @@ -470,8 +576,7 @@ def generateSortedHexLocationList(self, nLocs: int):

return locList[:nLocs]

# TODO: this is only used by testing and another method that just needs the count of assemblies
# in a ring, not the actual positions
# TODO: This is only used by the old GUI code, and should be moved there.
def allPositionsInThird(self, ring, includeEdgeAssems=False):
"""
Returns a list of all the positions in a ring (in the first third).
Expand Down
4 changes: 2 additions & 2 deletions armi/reactor/grids/structuredGrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class StructuredGrid(Grid):
variety of geometries, including hexagonal and Cartesian. The tuples are not
vectors in the direction of the translation, but rather grouped by direction. If
the bounds argument is described for a direction, the bounds will be used rather
than the unit step information. The default of (0, 0, 0) makes all dimensions
than the unit step information. The default of (0, 0, 0) makes all dimensions
insensitive to indices since the coordinates are calculated by the dot product
of this and the indices. With this default, any dimension that is desired to
change with indices should be defined with bounds. RZtheta grids are created
Expand All @@ -73,7 +73,7 @@ class StructuredGrid(Grid):
grids to be finite so we can populate them with SpatialLocator objects.
offset : 3-tuple, optional
Offset in cm for each axis. By default the center of the (0,0,0)-th object is in
the center of the grid. Offsets can move it so that the (0,0,0)-th object can
the center of the grid. Offsets can move it so that the (0,0,0)-th object can
be fully within a quadrant (i.e. in a Cartesian grid).
armiObject : ArmiObject, optional
The ArmiObject that this grid describes. For example if it's a 1-D assembly
Expand Down

0 comments on commit 8137451

Please sign in to comment.