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

Fixing sub-block grids #1947

wants to merge 18 commits into from

Conversation

john-science
Copy link
Member

@john-science john-science commented Oct 10, 2024

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:

  • Move adding a spatial grid to Components in a Block until the Assembly containing those Blocks is added to the Core or SFP
  • calculate "Is this a corners up grid?" and pass that into the method that adds the spatial grid to the Blocks (autoCreateSpatialGrid())

Why is the change being made?

Because fixing bugs is good.


Checklist

  • The release notes have been updated if necessary.
  • The documentation is still up-to-date in the doc folder.
  • The dependencies are still up-to-date in pyproject.toml.

@john-science john-science added the bug Something is wrong: Highest Priority label Oct 10, 2024
@john-science
Copy link
Member Author

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 main, it fails:

__________________________________ 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

@mgjarrett
Copy link
Contributor

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 👍

Copy link
Contributor

@drewj-tp drewj-tp left a 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.

  1. When we make a block from blueprints, we have not fully created the assembly, let alone the grid for that assembly.
  2. 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
  3. 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.)

armi/reactor/spentFuelPool.py Outdated Show resolved Hide resolved
armi/reactor/tests/test_blocks.py Outdated Show resolved Hide resolved
Comment on lines +1252 to +1260
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)

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"

armi/reactor/blueprints/blockBlueprint.py Show resolved Hide resolved
Comment on lines 2382 to 2383
if self.spatialGrid is None:
self.spatialGrid = grid
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.)

armi/reactor/blocks.py Outdated Show resolved Hide resolved
armi/reactor/grids/tests/test_grids.py Outdated Show resolved Hide resolved
armi/reactor/grids/tests/test_grids.py Outdated Show resolved Hide resolved
armi/reactor/grids/tests/test_grids.py Outdated Show resolved Hide resolved
armi/reactor/grids/tests/test_grids.py Show resolved Hide resolved
@drewj-tp
Copy link
Contributor

Talked with @john-science and I wanted to clarify something that I missed in the initial review

The pins would be incorrectly rotated to the "corners up" grid rotation, even for flats-up grids

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.

Copy link
Contributor

@drewj-tp drewj-tp left a 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

Comment on lines 2383 to 2395
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,
Copy link
Contributor

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

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:

  1. HexBlock.autoCreateSpatialGrid makes the HexBlock.spatialGrid in the orientation prescribed by cornersUp
  2. All the child components live on the grid defined by HexBlock.spatialGrid
  3. When we add the HexAssembly to a Core.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)

Comment on lines +161 to +164
@property
def cornersUp(self) -> bool:
return False

Copy link
Contributor

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.

Comment on lines +1252 to +1260
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)

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"

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

Comment on lines +315 to +316
self.assertLess(maxY, 1)
self.assertGreater(minY, -1)
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)

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)


# grab a pinned fuel block, and verify it is corners up
b = r.core.getFirstBlock(Flags.FUEL)
self.assertTrue(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.

This test reactor has a corners up core grid so Block.spatialGrid should be flats up

Suggested change
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
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
# 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)
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)

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
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
# 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

@john-science john-science marked this pull request as draft October 16, 2024 18:39
@john-science
Copy link
Member Author

@drewj-tp I need to disambiguate. You say:

My main concern is that Block.spatialGrid should be the grid on which its children live.

But below that you have 4 concerns I see:

  1. You want b.spatialGrid.cornersUp != b.parent.parent.spatialGrid.cornersUp
  2. You want b.spatialGrid.cornersUp == list(b.getChildren())[0].spatialGrid.cornersUp
  3. You don't want the base Grid class to have a .cornersUp() method.
  4. You don't want to pass cornersUp into autoCreateSpatialGrid.

Do I have that right?

@drewj-tp
Copy link
Contributor

@drewj-tp I need to disambiguate. You say:

My main concern is that Block.spatialGrid should be the grid on which its children live.

But below that you have 4 concerns I see:

Correct. I should be clearer on concern level and preference.

  1. You want b.spatialGrid.cornersUp != b.parent.parent.spatialGrid.cornersUp

If we are doing HexBlock.autoCreateSpatialGrid yes, this is a must have.

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.

image

A user should still be allowed to explicitly create a flats up pin grid in a flats up core grid via blueprints.

  1. You want b.spatialGrid.cornersUp == list(b.getChildren())[0].spatialGrid.cornersUp

Almost. Composite.spatialGrid indicates this attribute is the grid for children of the composite. Core.spatialGrid is the grid on which assemblies live. The Composite.spatialLocator tells us where this object lives in it's parents grid. So

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.

  1. You don't want the base Grid class to have a .cornersUp() method.

Want yes. Must have, no. We already have a lot of hex specific stuff on base classes. Why stop now.

  1. You don't want to pass cornersUp into autoCreateSpatialGrid.

Want, yes. Must have, no. We have a lot of hex specific stuff in methods that should be generic. Why stop now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something is wrong: Highest Priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants