-
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
Conversation
I have proof that this WAS a bug, but this PR fixes it. I added a bunch of unit tests in this PR, but they all pass here, however, if you copy one of the new ones to ARMI __________________________________ TestHexBlockOrientation.test_validateFlatsUp __________________________________
Traceback (most recent call last):
File "Python3.9.7\lib\unittest\case.py", line 59, in testPartExecutor
yield
File "Python3.9.7\lib\unittest\case.py", line 592, in run
self._callTestMethod(testMethod)
File "Python3.9.7\lib\unittest\case.py", line 550, in _callTestMethod
method()
File "armi\armi\reactor\tests\test_blocks.py", line 2281, in test_validateFlatsUp
self.assertFalse(b.spatialGrid.cornersUp)
File "Python3.9.7\lib\unittest\case.py", line 674, in assertFalse
raise self.failureException(msg)
AssertionError: True is not false |
I haven't reviewed this, but I skimmed it and I think this solution is great! I had stopped working on #1648 because I ran into the exact problem that this PR addresses and I didn't know how we wanted to resolve it. This looks good to me 👍 |
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.
Thanks for digging into this @john-science. I think this is the most feasible way for us to solve the problem.
I'm going to provide some context as to why we need to loop over every block in every assembly during Core.add
to solve this problem.
- When we make a block from blueprints, we have not fully created the assembly, let alone the grid for that assembly.
- It is possible to use the same assembly design in two different grids, so we can't set the orientation in the "cached" assembly stored on the blueprint object during constructing assemblies
- We don't really know the orientation of a block in an assembly until that assembly is fully realized and added to a thing (core, sfp, etc.)
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) | ||
|
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 a cornerUp
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"
if self.spatialGrid is None: | ||
self.spatialGrid = grid |
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.
The caller for this method in Assembly.orientBlocks
only calls this if HexBlock.spatialGrid is None
. Do we need to have this check here?
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.
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.)
Co-authored-by: Drew Johnson <anjohnson@terrapower.com>
Talked with @john-science and I wanted to clarify something that I missed in the initial review
A hexagonal block on a flats-up core grid should have a corners up pin grid if it intends to tightly pack pins into the block. If you look at the problem plot from #1421 it looks like the pins are in a flats up grid because they form the shape of a flats up hexagon. But! The individual pins are on a corners up grid if you draw a tiny hexagon around each lattice site. Or the ability to walk from neighbor to neighbor in the x-direction does not require moving in the y-direction. |
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.
My main concern is that Block.spatialGrid
should be the grid on which its children live. I added some suggestions to help communicate this and I hope I caught enough locations so accepting the changes will still let unit tests pass.
But I believe our desired outcome is
b.spatialGrid.cornersUp != b.parent.parent.spatialGrid.cornersUp
armi/reactor/blocks.py
Outdated
grid = grids.HexGrid.fromPitch( | ||
self.getPinPitch(cold=True), numRings=0, cornersUp=cornersUp | ||
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, |
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.
This is a deviation from how ARMI has previously reported the pin locations. On main, the following takes place
>>> r = armi.init(fName="armi/tests/tutorials/anl-afci-177.yaml").r
>>> a = r.core.getFirstAssembly(Flags.FUEL)
>>> b = a.getFirstBlock(Flags.FUEL)
>>> fuel = b.getChildrenWithFlags(Flags.FUEL)[0]
>>> fuel.spatialLocator.grid is b.spatialGrid
True
The docs for Composite.spatialGrid
and Composite.spatialLocator
could be better, but they allude to this fact
armi/armi/reactor/composites.py
Lines 322 to 325 in 01b0ade
spatialGrid : grids.Grid | |
The spatial grid that this object contains | |
spatialLocator : grids.LocationBase | |
The location of this object in its parent grid, or global space |
So Composite.spatialGrid
is the sub-grid, or the grid on which the children live. Core.spatialGrid
is the grid for all the assemblies, Assembly.spatialGrid
for blocks, Block.spatialGrid
for components.
I think the negation needs to go up a level higher:
HexBlock.autoCreateSpatialGrid
makes theHexBlock.spatialGrid
in the orientation prescribed bycornersUp
- All the child components live on the grid defined by
HexBlock.spatialGrid
- When we add the
HexAssembly
to aCore.spatialGrid
(or SFP) we need to invert the orientation with e.g.,
def add(self, hexAssem):
blockGridCornersUp = not self.spatialGrid.cornersUp
for b in hexAssem:
b.autoCreateSpatialGrid(..., cornersUp=blockGridCornersUp)
@property | ||
def cornersUp(self) -> bool: | ||
return False | ||
|
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.
CartesianGrid.cornersUp
doesn't make sense to me. This seems like putting hex specific logic onto the base class just to unify the API.
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) | ||
|
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"
def autoCreateSpatialGrids(self): | ||
def autoCreateSpatialGrids(self, cornersUp=True): |
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.
Same reservations about putting cornersUp
switch in the generic Block.autoCreateSpatialGrids
self.assertLess(maxY, 1) | ||
self.assertGreater(minY, -1) |
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.
Recommend a stronger check for asserting max and min y locations are +/- pitch * cos(60)
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 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
a.orientBlocks(cornersUp=cornerUp) | |
a.orientBlocks(cornersUp=not cornerUp) |
|
||
# grab a pinned fuel block, and verify it is corners up | ||
b = r.core.getFirstBlock(Flags.FUEL) | ||
self.assertTrue(b.spatialGrid.cornersUp) |
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.
This test reactor has a corners up core grid so Block.spatialGrid
should be flats up
self.assertTrue(b.spatialGrid.cornersUp) | |
self.assertFalse(b.spatialGrid.cornersUp) |
b = r.core.getFirstBlock(Flags.FUEL) | ||
self.assertTrue(b.spatialGrid.cornersUp) | ||
|
||
# for a flats up sub-grid, the hex centroids should stretch more in Y than X |
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.
# for a flats up sub-grid, the hex centroids should stretch more in Y than X | |
# for a flats up block-grid, the hex centroids should stretch more in Y than X |
|
||
# grab a pinned fuel block, and verify it is flats up | ||
b = r.core.getFirstBlock(Flags.FUEL) | ||
self.assertFalse(b.spatialGrid.cornersUp) |
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.
Block.spatialGrid
in a flats up core should be corners up
self.assertFalse(b.spatialGrid.cornersUp) | |
self.assertTrue(b.spatialGrid.cornersUp) |
b = r.core.getFirstBlock(Flags.FUEL) | ||
self.assertFalse(b.spatialGrid.cornersUp) | ||
|
||
# for a corners up sub-grid, the hex centroids should stretch more in X than Y |
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.
# for a corners up sub-grid, the hex centroids should stretch more in X than Y | |
# for a corners up block-grid, the hex centroids should stretch more in X than Y |
@drewj-tp I need to disambiguate. You say:
But below that you have 4 concerns I see:
Do I have that right? |
Correct. I should be clearer on concern level and preference.
If we are doing Under the assumption the user wants to pack some number of pin-like objects into a hexagon, the grid of the hexagonal lattice should be 30-degrees rotated from the orientation of the block. A user should still be allowed to explicitly create a flats up pin grid in a flats up core grid via blueprints.
Almost. all(child.spatialLocator.grid is parent.spatialGrid for child in parent) == True That is how I believe ARMI does it already and it makes sense to me. This is also a must have in that I think deviations are a backwards incompatible change that I don't believe is warranted.
Want yes. Must have, no. We already have a lot of hex specific stuff on base classes. Why stop now.
Want, yes. Must have, no. We have a lot of hex specific stuff in methods that should be generic. Why stop now. |
What is the change?
It appears there was a bug where Components inside a Block (like fuel pins) would have the wrong location if they were on a Hex grid that was corners-side up. The pins would be incorrectly rotated to the "corners up" grid rotation, even they should be flats-up.
The change I made to fix the issue:
autoCreateSpatialGrid()
)Why is the change being made?
Because fixing bugs is good.
Checklist
doc
folder.pyproject.toml
.