Skip to content

Commit

Permalink
Better usage of Component.p.pinPercentBu in finding max burnup block …
Browse files Browse the repository at this point in the history
…for rotation
  • Loading branch information
drewj-tp committed Oct 31, 2024
1 parent f069641 commit 6cbba96
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 12 deletions.
4 changes: 2 additions & 2 deletions armi/physics/fuelCycle/hexAssemblyFuelMgmtUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import numpy as np

from armi import runLog
from armi.physics.fuelCycle.utils import maxBurnupFuelPinLocation
from armi.physics.fuelCycle.utils import maxBurnupFuelPinLocation, maxBurnupBlock
from armi.utils.mathematics import findClosest

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -89,7 +89,7 @@ def getOptimalAssemblyOrientation(a: "HexAssembly", aPrev: "HexAssembly") -> int
assumption in that most fuel assemblies have similar layouts so it's plausible
that if ``a`` has a fuel pin at ``(1, 0, 0)``, so does ``aPrev``.
"""
maxBuBlock = max(a, key=lambda b: b.p.percentBuMax)
maxBuBlock = maxBurnupBlock(a)
if maxBuBlock.spatialGrid is None:
raise ValueError(
f"Block {maxBuBlock} in {a} does not have a spatial grid. Cannot rotate."
Expand Down
28 changes: 18 additions & 10 deletions armi/physics/fuelCycle/tests/test_assemblyRotationAlgorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@ class TestOptimalAssemblyRotation(FuelHandlerTestHelper):
def prepShuffledAssembly(a: HexAssembly, percentBuMaxPinLocation: int):
"""Prepare the assembly that will be shuffled and rotated."""
for b in a.getChildrenWithFlags(Flags.FUEL):
# Fake some maximum burnup
b.p.percentBuMax = 5
# Fake enough information to build a spatial grid
b.getPinPitch = mock.Mock(return_value=1.1)
b.autoCreateSpatialGrids()
b.p.percentBuMaxPinLocation = percentBuMaxPinLocation
for c in b.getChildrenWithFlags(Flags.FUEL):
mult = c.getDimension("mult")
if mult <= percentBuMaxPinLocation:
continue
burnups = np.ones(mult, dtype=float)
burnups[percentBuMaxPinLocation] *= 2
c.p.pinPercentBu = burnups

@staticmethod
def prepPreviousAssembly(a: HexAssembly, pinPowers: list[float]):
Expand All @@ -79,7 +83,7 @@ def test_maxBurnupAtCenterNoRotation(self):
"""If max burnup pin is at the center, no rotation is suggested."""
# Fake a higher power towards the center
powers = np.arange(self.N_PINS)[::-1]
self.prepShuffledAssembly(self.assembly, percentBuMaxPinLocation=1)
self.prepShuffledAssembly(self.assembly, percentBuMaxPinLocation=0)
self.prepPreviousAssembly(self.assembly, powers)
rot = getOptimalAssemblyOrientation(self.assembly, self.assembly)
self.assertEqual(rot, 0)
Expand All @@ -95,22 +99,26 @@ def test_oppositeRotation(self):
:tests: R_ARMI_ROTATE_HEX_BURNUP
:acceptance_criteria: After rotating a hexagonal assembly, confirm the pin with the highest burnup is
in the same sector as pin with the lowest power in the high burnup pin's ring.
Notes
-----
Note: use zero-indexed pin location not pin ID to assign burnups and powers. Since
we have a single component, ``Block.p.linPowByPin[i] <-> Component.p.pinPercentBu[i]``
"""
shuffledAssembly = self.assembly
previousAssembly = copy.deepcopy(shuffledAssembly)
for startPin, oppositePin in ((2, 5), (3, 6), (4, 7), (5, 2), (6, 3), (7, 4)):
for startPin, oppositePin in ((1, 4), (2, 5), (3, 6), (4, 1), (5, 2), (6, 3)):
powers = np.ones(self.N_PINS)
powers[startPin - 1] *= 2
powers[oppositePin - 1] = 0
powers[oppositePin] = 0
self.prepShuffledAssembly(shuffledAssembly, startPin)
self.prepPreviousAssembly(previousAssembly, powers)
rot = getOptimalAssemblyOrientation(shuffledAssembly, previousAssembly)
# 180 degrees is three 60 degree rotations
self.assertEqual(rot, 3, msg=f"{startPin=} :: {oppositePin=}")

def test_noGridOnShuffledBlock(self):
"""Require a spatial grid on the shuffled block."""
with self.assertRaisesRegex(ValueError, "spatial grid"):
def test_noBlocksWithBurnup(self):
"""Require at least one block to have burnup."""
with self.assertRaisesRegex(ValueError, "No blocks with burnup found"):
getOptimalAssemblyOrientation(self.assembly, self.assembly)

def test_mismatchPinPowersAndLocations(self):
Expand Down
20 changes: 20 additions & 0 deletions armi/physics/fuelCycle/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,23 @@ def getMaxBurnupLocationFromChildren(
if maxLocation is not None:
return maxLocation
raise ValueError("No burnups found!")


def maxBurnupBlock(a: typing.Iterable["Block"]) -> "Block":
"""Find the block that contains the pin with the highest burnup."""
maxBlock = None
maxBurnup = 0
for b in a:
maxCompBu = 0
for c in b:
if not np.any(c.p.pinPercentBu):
continue
compBu = c.p.pinPercentBu.max()
if compBu > maxCompBu:
maxCompBu = compBu
if maxCompBu > maxBurnup:
maxBurnup = maxCompBu
maxBlock = b
if maxBlock is not None:
return maxBlock
raise ValueError(f"No blocks with burnup found")

0 comments on commit 6cbba96

Please sign in to comment.