Skip to content
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

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions armi/reactor/assemblies.py
Original file line number Diff line number Diff line change
Expand Up @@ -1239,9 +1239,8 @@ def rotate(self, rad):

Parameters
----------
rad: float
rad : float
number (in radians) specifying the angle of counter clockwise rotation

"""
for b in self:
b.rotate(rad)
Expand All @@ -1250,6 +1249,15 @@ def isOnWhichSymmetryLine(self):
grid = self.parent.spatialGrid
return grid.overlapsWhichSymmetryLine(self.spatialLocator.getCompleteIndices())

def orientBlocks(self, cornersUp):
"""Add special grids to the blocks inside this Assembly, respecting their orientation."""
for b in self:
if b.spatialGrid is None:
try:
b.autoCreateSpatialGrids(cornersUp)
except (ValueError, NotImplementedError) as e:
runLog.warning(str(e), single=True)

Comment on lines +1232 to +1240
Copy link
Contributor

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 a cornerUp flag that is hex specific?

Copy link
Member Author

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?

Copy link
Contributor

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"


class HexAssembly(Assembly):
"""Placeholder, so users can explicitly define a hex-based Assembly."""
Expand Down
27 changes: 17 additions & 10 deletions armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ def getSmearDensity(self, cold=True):

return smearDensity

def autoCreateSpatialGrids(self):
def autoCreateSpatialGrids(self, cornersUp=True):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reservations about putting cornersUp switch in the generic Block.autoCreateSpatialGrids

"""
Creates a spatialGrid for a Block.

Expand Down Expand Up @@ -2317,7 +2317,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).
Expand All @@ -2326,6 +2326,11 @@ 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
Expand All @@ -2339,7 +2344,7 @@ def autoCreateSpatialGrids(self):
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:
Expand All @@ -2349,29 +2354,31 @@ 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, cornersUp=cornersUp
)
john-science marked this conversation as resolved.
Show resolved Hide resolved
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(
numLocations, self.getNumPins()
)
)

i = 0
# set the spatial position of the sub-block components
spatialLocators = grids.MultiIndexLocation(grid=grid)
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])

# 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller for this method in Assembly.orientBlocks only calls this if HexBlock.spatialGrid is None. Do we need to have this check here?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 (if self.spatialGrid is None) up to the top of the method in a different PR, by the way. But we have like 6 unit tests designed specifically for this check to work this way that will break. So I didn't want to muddy the waters and make this PR less clear for you to read. So I'll do it as it's own stand-alone PR.)

for c in self:
Expand Down
10 changes: 2 additions & 8 deletions armi/reactor/blueprints/blockBlueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
from armi import getPluginManagerOrFail, runLog
from armi.materials.material import Material
from armi.reactor import blocks
from armi.reactor.composites import Composite
from armi.reactor import parameters
from armi.reactor.flags import Flags
from armi.reactor.blueprints import componentBlueprint
from armi.reactor.components.component import Component
from armi.reactor.composites import Composite
from armi.reactor.converters import blockConverters
from armi.settings.fwSettings import globalSettings
from armi.reactor.flags import Flags


def _configureGeomOptions():
Expand Down Expand Up @@ -240,11 +239,6 @@ def construct(
b.verifyBlockDims()
b.spatialGrid = spatialGrid

if b.spatialGrid is None and cs[globalSettings.CONF_BLOCK_AUTO_GRID]:
drewj-tp marked this conversation as resolved.
Show resolved Hide resolved
try:
b.autoCreateSpatialGrids()
except (ValueError, NotImplementedError) as e:
runLog.warning(str(e), single=True)
return b

def _getBlockwiseMaterialModifierOptions(
Expand Down
4 changes: 4 additions & 0 deletions armi/reactor/cores.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block.spatialGrid should be the opposite orientation of the grid on which its parent assembly lies

Suggested change
a.orientBlocks(cornersUp=cornerUp)
a.orientBlocks(cornersUp=not cornerUp)


ring, _loc = self.spatialGrid.getRingPos(
a.spatialLocator.getCompleteIndices()
)
Expand Down
2 changes: 1 addition & 1 deletion armi/reactor/grids/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def getLocalCoordinates(self, nativeCoords=False):
"""Return the coordinates of the center of the mesh cell here in cm."""
if self.grid is None:
raise ValueError(
"Cannot get local coordinates of {} because grid is None.".format(self)
f"Cannot get local coordinates of {self} because grid is None."
)
return self.grid.getCoordinates(self.indices, nativeCoords=nativeCoords)

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 @@ -17,13 +17,13 @@

import numpy as np

from armi.reactor.grids.grid import Grid
from armi.reactor.grids.locations import (
IJKType,
LocationBase,
IndexLocation,
LocationBase,
MultiIndexLocation,
)
from armi.reactor.grids.grid import Grid

# data structure for database-serialization of grids
GridParameters = collections.namedtuple(
Expand Down
87 changes: 79 additions & 8 deletions armi/reactor/grids/tests/test_grids.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -238,8 +238,8 @@ 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):
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))
Expand All @@ -261,6 +261,82 @@ def test_positions(self):
assert_allclose(grid.getCoordinates((1, 0, 0)), iDirection)
assert_allclose(grid.getCoordinates((0, 1, 0)), jDirection)

def test_getCoordinatesCornersUp(self):
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_getLocalCoordinates(self):
john-science marked this conversation as resolved.
Show resolved Hide resolved
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):
# validate the first ring of a corners-up hex gric
john-science marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend a stronger check for asserting max and min y locations are +/- pitch * cos(60)


# 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):
# validate the first ring of a flats-up hex gric
john-science marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same suggestion to check max and min x positions are +/- pitch * cos(60)


def test_neighbors(self):
grid = grids.HexGrid.fromPitch(1.0)
neighbs = grid.getNeighboringCellIndices(0, 0, 0)
Expand Down Expand Up @@ -837,8 +913,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()
4 changes: 4 additions & 0 deletions armi/reactor/spentFuelPool.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ def add(self, assem, loc=None):
else:
loc = self._getNextLocation()

# orient the blocks to match this grid
cornerUp = self.spatialGrid._unitSteps[0][1] == 0
john-science marked this conversation as resolved.
Show resolved Hide resolved
assem.orientBlocks(cornerUp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assem.orientBlocks(cornerUp)
assem.orientBlocks(not cornerUp)


super().add(assem, loc)

def getAssembly(self, name):
Expand Down
78 changes: 77 additions & 1 deletion armi/reactor/tests/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
from armi.reactor.components import basicShapes, complexShapes
from armi.reactor.flags import Flags
from armi.reactor.tests.test_assemblies import makeTestAssembly
from armi.tests import ISOAA_PATH, TEST_ROOT
from armi.reactor.tests.test_reactors import loadTestReactor, TEST_ROOT
from armi.tests import ISOAA_PATH
from armi.utils import hexagon, units
from armi.utils.directoryChangers import TemporaryDirectoryChanger
from armi.utils.units import MOLES_PER_CC_TO_ATOMS_PER_BARN_CM

NUM_PINS_IN_TEST_BLOCK = 217
Expand Down Expand Up @@ -2244,6 +2246,80 @@ def test_gridNotCreatedMultipleMultiplicities(self):
self.assertIsNone(self.HexBlock.spatialGrid)


class TestHexBlockOrientation(unittest.TestCase):
def setUp(self):
self.td = TemporaryDirectoryChanger()
self.td.__enter__()

def tearDown(self):
self.td.__exit__(None, None, None)

def test_validateCornersUp(self):
# load a corners up reactor
_o, r = loadTestReactor(
os.path.join(TEST_ROOT, "smallestTestReactor"),
inputFileName="armiRunSmallest.yaml",
)

# grab a pinned fuel block, and verify it is corners up
b = r.core.getFirstBlock(Flags.FUEL)
self.assertTrue(b.spatialGrid.cornersUp)

# for corners up, the hex centroids should stretch more in X than Y
maxX = -111
minX = 999
maxY = -111
minY = 999
for comp in b:
locs = comp.spatialLocator
if not isinstance(locs, grids.MultiIndexLocation):
locs = [locs]
for loc in locs:
x, y, _ = loc.getLocalCoordinates()
if x > maxX:
maxX = x
elif x < minX:
minX = x

if y > maxY:
maxY = y
elif y < minY:
minY = y

self.assertGreater(maxX - minX, maxY - minY)

def test_validateFlatsUp(self):
# load a flats up reactor
_o, r = loadTestReactor(TEST_ROOT, inputFileName="armiRun.yaml")

# grab a pinned fuel block, and verify it is flats up
b = r.core.getFirstBlock(Flags.FUEL)
self.assertFalse(b.spatialGrid.cornersUp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block.spatialGrid in a flats up core should be corners up

Suggested change
self.assertFalse(b.spatialGrid.cornersUp)
self.assertTrue(b.spatialGrid.cornersUp)


# for flats up, the hex centroids should stretch more in Y than X
maxX = -111
minX = 999
maxY = -111
minY = 999
for comp in b:
locs = comp.spatialLocator
if not isinstance(locs, grids.MultiIndexLocation):
locs = [locs]
for loc in locs:
x, y, _ = loc.getLocalCoordinates()
if x > maxX:
maxX = x
elif x < minX:
minX = x

if y > maxY:
maxY = y
elif y < minY:
minY = y
john-science marked this conversation as resolved.
Show resolved Hide resolved

self.assertGreater(maxY - minY, maxX - minX)


class ThRZBlock_TestCase(unittest.TestCase):
def setUp(self):
_ = settings.Settings()
Expand Down
1 change: 1 addition & 0 deletions doc/release/0.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ API Changes

Bug Fixes
---------
#. Fixed locations of pins in Blocks on flats-up grids. (`PR#1947 <https://github.com/terrapower/armi/pull/1947>`_)
#. Fixed ``DerivedShape.getArea`` for ``cold=True``. (`PR#1831 <https://github.com/terrapower/armi/pull/1831>`_)
#. Fixed error parsing command line integers in ``ReportsEntryPoint``. (`PR#1824 <https://github.com/terrapower/armi/pull/1824>`_)
#. Fixed ``PermissionError`` when using ``syncDbAfterWrite``. (`PR#1857 <https://github.com/terrapower/armi/pull/1857>`_)
Expand Down
Loading