-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixing sub-block grids #1947
base: main
Are you sure you want to change the base?
Fixing sub-block grids #1947
Changes from 17 commits
0a3764c
f19ea49
b79e2e7
ac34bb3
b244c8a
55c6715
dc73342
1942ed1
3facbc8
37dd62e
0d55251
2f8a350
349e406
7d8ab19
6141cc7
e8182dd
4f8242a
86516c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -292,7 +292,7 @@ def getSmearDensity(self, cold=True): | |
|
||
return smearDensity | ||
|
||
def autoCreateSpatialGrids(self): | ||
def autoCreateSpatialGrids(self, cornersUp=True): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same reservations about putting |
||
""" | ||
Creates a spatialGrid for a Block. | ||
|
||
|
@@ -2340,7 +2340,7 @@ def getSymmetryFactor(self): | |
return 2.0 | ||
return 1.0 | ||
|
||
def autoCreateSpatialGrids(self): | ||
def autoCreateSpatialGrids(self, cornersUp=True): | ||
""" | ||
Given a block without a spatialGrid, create a spatialGrid and give its children the | ||
corresponding spatialLocators (if it is a simple block). | ||
|
@@ -2349,20 +2349,27 @@ def autoCreateSpatialGrids(self): | |
to 1 or N but no other multiplicities. Also, this should only happen when N fits exactly | ||
into a given number of hex rings. Otherwise, do not create a grid for this block. | ||
|
||
Parameters | ||
---------- | ||
cornersUp: bool | ||
Should the hexagons of this grid be corners up (as opposed to flats-up)? | ||
|
||
Notes | ||
----- | ||
If the Block meets all the conditions, we gather all components to either be a | ||
multiIndexLocation containing all of the pin positions, or the locator is the center (0,0). | ||
When a hex grid has another hex grid nested inside it, the nested grid has the opposite | ||
orientation (corners vs flats up). This method takes care of that. | ||
|
||
Also, this only works on blocks that have 'flat side up'. | ||
If components inside this block are multiplicity 1, they get a single locator at the center | ||
of the grid cell. If the multiplicity is greater than 1, all the components are added to a | ||
multiIndexLocation on the hex grid. | ||
|
||
Raises | ||
------ | ||
ValueError | ||
If the multiplicities of the block are not only 1 or N or if generated ringNumber leads | ||
to more positions than necessary. | ||
""" | ||
# Check multiplicities... | ||
# Check multiplicities | ||
mults = {c.getDimension("mult") for c in self.iterComponents()} | ||
|
||
if len(mults) != 2 or 1 not in mults: | ||
|
@@ -2372,29 +2379,42 @@ def autoCreateSpatialGrids(self): | |
) | ||
) | ||
|
||
ringNumber = hexagon.numRingsToHoldNumCells(self.getNumPins()) | ||
# For the below to work, there must not be multiple wire or multiple clad types. | ||
# 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) | ||
# build the grid, from pitch and orientation | ||
grid = grids.HexGrid.fromPitch( | ||
self.getPinPitch(cold=True), numRings=0, cornersUp=True | ||
self.getPinPitch(cold=True), | ||
numRings=0, | ||
armiObject=self, | ||
cornersUp=cornersUp, | ||
) | ||
|
||
# build the sub-grid, with opposite orientation to the block | ||
subGrid = grids.HexGrid.fromPitch( | ||
self.getPinPitch(cold=True), | ||
numRings=0, | ||
armiObject=self, | ||
cornersUp=not cornersUp, | ||
) | ||
spatialLocators = grids.MultiIndexLocation(grid=self.spatialGrid) | ||
|
||
ringNumber = hexagon.numRingsToHoldNumCells(self.getNumPins()) | ||
numLocations = 0 | ||
for ring in range(ringNumber): | ||
numLocations = numLocations + hexagon.numPositionsInRing(ring + 1) | ||
|
||
if numLocations != self.getNumPins(): | ||
raise ValueError( | ||
"Cannot create spatialGrid, number of locations in rings{} not equal to pin number{}".format( | ||
"Cannot create spatialGrid, number of locations in rings {} not equal to pin number {}".format( | ||
numLocations, self.getNumPins() | ||
) | ||
) | ||
|
||
i = 0 | ||
# set the spatial position of the sub-block components | ||
spatialLocators = grids.MultiIndexLocation(grid=subGrid) | ||
for ring in range(ringNumber): | ||
for pos in range(grid.getPositionsInRing(ring + 1)): | ||
i, j = grid.getIndicesFromRingAndPos(ring + 1, pos + 1) | ||
spatialLocators.append(grid[i, j, 0]) | ||
for pos in range(subGrid.getPositionsInRing(ring + 1)): | ||
i, j = subGrid.getIndicesFromRingAndPos(ring + 1, pos + 1) | ||
spatialLocators.append(subGrid[i, j, 0]) | ||
|
||
# finally, fill the spatial grid, and put the sub-block components on it | ||
if self.spatialGrid is None: | ||
self.spatialGrid = grid | ||
Comment on lines
2418
to
2419
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The caller for this method in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a public method. People can call it multiple times, any time they want, downstream in their plugins. So, this method would cause havoc if we let people call it multiple times in a row. (I am considering moving this check ( |
||
for c in self: | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -563,11 +563,15 @@ def add(self, a, spatialLocator=None): | |||||
"".format(aName, self.assembliesByName[aName], a, self.r.p.maxAssemNum) | ||||||
) | ||||||
raise RuntimeError("Core already contains an assembly with the same name.") | ||||||
|
||||||
self.assembliesByName[aName] = a | ||||||
for b in a: | ||||||
self.blocksByName[b.getName()] = b | ||||||
|
||||||
if self.geomType == geometry.GeomType.HEX: | ||||||
cornerUp = self.spatialGrid.cornersUp | ||||||
a.orientBlocks(cornersUp=cornerUp) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
ring, _loc = self.spatialGrid.getRingPos( | ||||||
a.spatialLocator.getCompleteIndices() | ||||||
) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,6 +158,10 @@ def __setstate__(self, state: Dict): | |
def isAxialOnly(self) -> bool: | ||
"""Indicate to parts of ARMI if this Grid handles only axial cells.""" | ||
|
||
@property | ||
def cornersUp(self) -> bool: | ||
return False | ||
|
||
Comment on lines
+161
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
@abstractmethod | ||
def __len__(self) -> int: | ||
"""Number of items in the grid.""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,8 @@ | |
"""Tests for grids.""" | ||
from io import BytesIO | ||
import math | ||
import unittest | ||
import pickle | ||
import unittest | ||
|
||
import numpy as np | ||
from numpy.testing import assert_allclose, assert_array_equal | ||
|
@@ -238,8 +238,9 @@ def test_ringPosFromIndicesIncorrect(self): | |
class TestHexGrid(unittest.TestCase): | ||
"""A set of tests for the Hexagonal Grid.""" | ||
|
||
def test_positions(self): | ||
grid = grids.HexGrid.fromPitch(1.0) | ||
def test_getCoordinatesFlatsUp(self): | ||
"""Test getCoordinates() for flats up hex grids.""" | ||
grid = grids.HexGrid.fromPitch(1.0, cornersUp=False) | ||
self.assertAlmostEqual(grid.pitch, 1.0) | ||
side = 1.0 / math.sqrt(3) | ||
assert_allclose(grid.getCoordinates((0, 0, 0)), (0.0, 0.0, 0.0)) | ||
|
@@ -261,6 +262,86 @@ def test_positions(self): | |
assert_allclose(grid.getCoordinates((1, 0, 0)), iDirection) | ||
assert_allclose(grid.getCoordinates((0, 1, 0)), jDirection) | ||
|
||
def test_getCoordinatesCornersUp(self): | ||
"""Test getCoordinates() for corners up hex grids.""" | ||
grid = grids.HexGrid.fromPitch(1.0, cornersUp=True) | ||
self.assertAlmostEqual(grid.pitch, 1.0) | ||
side = 1.0 / math.sqrt(3) | ||
assert_allclose(grid.getCoordinates((0, 0, 0)), (0.0, 0.0, 0.0)) | ||
assert_allclose(grid.getCoordinates((1, 0, 0)), (0.5, 1.5 * side, 0.0)) | ||
assert_allclose(grid.getCoordinates((-1, 0, 0)), (-0.5, -1.5 * side, 0.0)) | ||
assert_allclose(grid.getCoordinates((0, 1, 0)), (-0.5, 1.5 * side, 0.0)) | ||
assert_allclose(grid.getCoordinates((1, -1, 0)), (1, 0.0, 0.0)) | ||
john-science marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
unitSteps = grid.reduce()[0] | ||
iDirection = tuple(direction[0] for direction in unitSteps) | ||
jDirection = tuple(direction[1] for direction in unitSteps) | ||
for directionVector in (iDirection, jDirection): | ||
self.assertAlmostEqual( | ||
(sum(val**2 for val in directionVector)) ** 0.5, | ||
1.0, | ||
msg=f"Direction vector {directionVector} should have " | ||
"magnitude 1 for pitch 1.", | ||
) | ||
assert_allclose(grid.getCoordinates((1, 0, 0)), iDirection) | ||
assert_allclose(grid.getCoordinates((0, 1, 0)), jDirection) | ||
|
||
def test_getLocalCoordinatesHex(self): | ||
"""Test getLocalCoordinates() is different for corners up vs flats up hex grids.""" | ||
grid0 = grids.HexGrid.fromPitch(1.0, cornersUp=True) | ||
grid1 = grids.HexGrid.fromPitch(1.0, cornersUp=False) | ||
for i in range(3): | ||
for j in range(3): | ||
if i == 0 and j == 0: | ||
continue | ||
coords0 = grid0[i, j, 0].getLocalCoordinates() | ||
coords1 = grid1[i, j, 0].getLocalCoordinates() | ||
self.assertNotEqual(coords0[0], coords1[0], msg=f"X @ ({i}, {j})") | ||
self.assertNotEqual(coords0[1], coords1[1], msg=f"Y @ ({i}, {j})") | ||
self.assertEqual(coords0[2], coords1[2], msg=f"Z @ ({i}, {j})") | ||
|
||
def test_getLocalCoordinatesCornersUp(self): | ||
"""Test getLocalCoordinates() for corners up hex grids.""" | ||
# validate the first ring of a corners-up hex grid | ||
grid = grids.HexGrid.fromPitch(1.0, cornersUp=True) | ||
vals = [] | ||
for pos in range(grid.getPositionsInRing(2)): | ||
i, j = grid.getIndicesFromRingAndPos(2, pos + 1) | ||
vals.append(grid[i, j, 0].getLocalCoordinates()) | ||
|
||
# short in Y | ||
maxY = max(v[1] for v in vals) | ||
minY = min(v[1] for v in vals) | ||
self.assertLess(maxY, 1) | ||
self.assertGreater(minY, -1) | ||
Comment on lines
+315
to
+316
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Recommend a stronger check for asserting max and min y locations are +/- |
||
|
||
# long in X | ||
maxX = max(v[0] for v in vals) | ||
minX = min(v[0] for v in vals) | ||
self.assertAlmostEqual(maxX, 1) | ||
self.assertAlmostEqual(minX, -1) | ||
|
||
def test_getLocalCoordinatesFlatsUp(self): | ||
"""Test getLocalCoordinates() for flats up hex grids.""" | ||
# validate the first ring of a flats-up hex grid | ||
grid = grids.HexGrid.fromPitch(1.0, cornersUp=False) | ||
vals = [] | ||
for pos in range(grid.getPositionsInRing(2)): | ||
i, j = grid.getIndicesFromRingAndPos(2, pos + 1) | ||
vals.append(grid[i, j, 0].getLocalCoordinates()) | ||
|
||
# long in Y | ||
maxY = max(v[1] for v in vals) | ||
minY = min(v[1] for v in vals) | ||
self.assertAlmostEqual(maxY, 1) | ||
self.assertAlmostEqual(minY, -1) | ||
|
||
# short in X | ||
maxX = max(v[0] for v in vals) | ||
minX = min(v[0] for v in vals) | ||
self.assertLess(maxX, 1) | ||
self.assertGreater(minX, -1) | ||
Comment on lines
+342
to
+343
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same suggestion to check max and min x positions are +/- |
||
|
||
def test_neighbors(self): | ||
grid = grids.HexGrid.fromPitch(1.0) | ||
neighbs = grid.getNeighboringCellIndices(0, 0, 0) | ||
|
@@ -837,8 +918,3 @@ def test_getLocations(self): | |
self.assertEqual(x, 0.0) | ||
self.assertEqual(y, 0.0) | ||
self.assertEqual(z, count + 0.5) | ||
|
||
|
||
if __name__ == "__main__": | ||
# import sys;sys.argv = ["", "TestHexGrid.testPositions"] | ||
unittest.main() |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -70,6 +70,10 @@ def add(self, assem, loc=None): | |||||
else: | ||||||
loc = self._getNextLocation() | ||||||
|
||||||
# orient the blocks to match this grid | ||||||
cornerUp = self.spatialGrid.cornersUp | ||||||
assem.orientBlocks(cornerUp) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
super().add(assem, loc) | ||||||
|
||||||
def getAssembly(self, name): | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this live on the
HexAssembly
because we have acornerUp
flag that is hex specific?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All assemblies have a
.autoCreateSpatialGrids()
method though, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocks yes, but the
cornersUp
switch is hex specific. What does this mean for a cartesian grid? I get that it's good OOP practice to have the same signatures for overridden methods, but this feels in the vein of "everything is hexagonal in ARMI"