Skip to content

Commit

Permalink
Moving hexAssem tests into their own module
Browse files Browse the repository at this point in the history
  • Loading branch information
john-science committed Nov 11, 2022
1 parent 1a46e74 commit 848310a
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 180 deletions.
17 changes: 3 additions & 14 deletions armi/physics/fuelCycle/fuelHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@

import numpy


from armi import runLog
from armi.utils.customExceptions import InputError
from armi.reactor.flags import Flags
from armi.utils.mathematics import findClosest, resampleStepwise
from armi.utils.mathematics import resampleStepwise
from armi.physics.fuelCycle.fuelHandlerFactory import fuelHandlerFactory
from armi.physics.fuelCycle.fuelHandlerInterface import FuelHandlerInterface

Expand Down Expand Up @@ -600,7 +599,7 @@ def getParamWithBlockLevelMax(a, paramName):
return minDiff[1]

if not minDiff[1]:
# warning("can't find assembly in targetRing %d with close %s to %s" % (targetRing,param,compareTo),'findAssembly')
# can't find assembly in targetRing with close param to compareTo
pass

if findMany:
Expand Down Expand Up @@ -647,7 +646,6 @@ def _getAssembliesInRings(
-------
assemblyList : list
List of assemblies in each ring of the ringList. [[a1,a2,a3],[a4,a5,a6,a7],...]
"""
assemblyList = [[] for _i in range(len(ringList))] # empty lists for each ring
if exclusions is None:
Expand All @@ -674,9 +672,7 @@ def _getAssembliesInRings(
assemListTmp2.append(a)
# make the list of lists of assemblies
assemblyList[i] = assemListTmp2

else:

if ringList[0] == "SFP":
# kind of a hack for now. Need the capability.
assemList = self.r.core.sfp.getChildren()
Expand Down Expand Up @@ -875,7 +871,6 @@ def repeatShufflePattern(self, explicitRepeatShuffles):
processMoveList : Converts a stored list of moves into a functional list of assemblies to swap
makeShuffleReport : Creates the file that is processed here
"""

# read moves file
moves = self.readMoves(explicitRepeatShuffles)
# get the correct cycle number
Expand Down Expand Up @@ -924,7 +919,6 @@ def readMoves(fname):
repeatShufflePattern : reads this file and repeats the shuffling
outage : creates the moveList in the first place.
makeShuffleReport : writes the file that is read here.
"""
try:
f = open(fname)
Expand Down Expand Up @@ -1037,9 +1031,7 @@ def trackChain(moveList, startingAt, alreadyDone=None):
See Also
--------
repeatShufflePattern
mcnpInterface.getMoveCards
processMoveList
"""
if alreadyDone is None:
alreadyDone = []
Expand Down Expand Up @@ -1157,13 +1149,10 @@ def processMoveList(self, moveList):
--------
makeShuffleReport : writes the file that is being processed
repeatShufflePattern : uses this to repeat shuffles
"""
alreadyDone = []
loadChains = [] # moves that have discharges
loadChargeTypes = (
[]
) # the assembly types (strings) that should be used in a load chain.
loadChargeTypes = [] # the assembly types (str) to be used in a load chain.
loopChains = [] # moves that don't have discharges
enriches = [] # enrichments of each loadChain
loadNames = [] # assembly name of each load assembly (to read from SFP)
Expand Down
133 changes: 70 additions & 63 deletions armi/physics/fuelCycle/hexAssemblyFuelMgmtUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@
"""
TODO: JOHN
TODO: pylint armi\physics\fuelCycle\hexAssemblyFuelMgmtUtils.py | grep -v consider-using-f-stri | grep -v missing-raises-doc
(Do it, the code was a mess to start.)
"""
import math

import numpy

from armi import runLog
from armi.reactor.flags import Flags
from armi.utils.mathematics import findClosest


def buReducingAssemblyRotation(fh):
Expand All @@ -40,7 +46,6 @@ def buReducingAssemblyRotation(fh):
aNow = fh.r.core.getAssemblyWithStringLocation(aPrev.lastLocationLabel)
# no point in rotation if there's no pin detail
if aNow in hist.getDetailAssemblies():

rot = getOptimalAssemblyOrientation(aNow, aPrev)
aNow.rotatePins(rot) # rot = integer between 0 and 5
numRotated += 1
Expand Down Expand Up @@ -424,68 +429,6 @@ def buildConvergentRingSchedule(chargeRing, dischargeRing=1, coarseFactor=0.0):
return convergent, conWidths


def buildEqRingSchedule(core, ringSchedule, circularRingOrder):
r"""
Expands simple ringSchedule input into full-on location schedule
Parameters
----------
core : Core object
Fully initialized Core object, for a hex assembly reactor.
ringSchedule : list
List of ring bounds that is required to be an even number of entries. These
entries then are used in a from - to approach to add the rings. The from ring will
always be included.
circularRingOrder : str
From the circularRingOrder setting. Valid values include angle and distanceSmart,
anything else will
Returns
-------
locationSchedule : list
"""

def squaredDistanceFromOrigin(a):
origin = numpy.array([0.0, 0.0, 0.0])
p = numpy.array(a.spatialLocator.getLocalCoordinates())
return ((p - origin) ** 2).sum()

def assemAngle(a):
x, y, _ = a.spatialLocator.getLocalCoordinates()
return math.atan2(y, x)

# start by expanding the user-input eqRingSchedule list into a list containing
# all the rings as it goes.
ringList = _buildEqRingScheduleHelper(ringSchedule, core.getNumRings())

# now build the locationSchedule ring by ring using this ringSchedule
lastRing = 0
locationSchedule = []
for ring in ringList:
assemsInRing = core.getAssembliesInRing(ring, typeSpec=Flags.FUEL)
if circularRingOrder == "angle":
sorter = lambda a: assemAngle(a)
elif circularRingOrder == "distanceSmart":
if lastRing == ring + 1:
# converging. Put things on the outside first.
sorter = lambda a: -squaredDistanceFromOrigin(a)
else:
# diverging. Put things on the inside first.
sorter = lambda a: squaredDistanceFromOrigin(a)
else:
# purely based on distance. Can mix things up in convergent-divergent cases. Prefer distanceSmart
sorter = lambda a: squaredDistanceFromOrigin(a)

assemsInRing = sorted(assemsInRing, key=sorter)
for a in assemsInRing:
locationSchedule.append(a.getLocation())
lastRing = ring

return locationSchedule


def _buildEqRingScheduleHelper(ringSchedule, numRings):
r"""
turns ringScheduler into explicit list of rings
Expand Down Expand Up @@ -550,3 +493,67 @@ def _buildEqRingScheduleHelper(ringSchedule, numRings):
lastRing = ring

return newList


def buildEqRingSchedule(core, ringSchedule, circularRingOrder):
r"""
Expands simple ringSchedule input into full-on location schedule
Parameters
----------
core : Core object
Fully initialized Core object, for a hex assembly reactor.
ringSchedule : list
List of ring bounds that is required to be an even number of entries. These
entries then are used in a from - to approach to add the rings. The from ring will
always be included.
circularRingOrder : str
From the circularRingOrder setting. Valid values include angle and distanceSmart,
anything else will
Returns
-------
locationSchedule : list
"""

def squaredDistanceFromOrigin(
a,
): # TODO: JOHN Move out of function, to be testable.
origin = numpy.array([0.0, 0.0, 0.0])
p = numpy.array(a.spatialLocator.getLocalCoordinates())
return ((p - origin) ** 2).sum()

def assemAngle(a): # TODO: JOHN Move out of function, to be testable.
x, y, _ = a.spatialLocator.getLocalCoordinates()
return math.atan2(y, x)

# start by expanding the user-input eqRingSchedule list into a list containing
# all the rings as it goes.
ringList = _buildEqRingScheduleHelper(ringSchedule, core.getNumRings())

# now build the locationSchedule ring by ring using this ringSchedule
lastRing = 0
locationSchedule = []
for ring in ringList:
assemsInRing = core.getAssembliesInRing(ring, typeSpec=Flags.FUEL)
if circularRingOrder == "angle":
sorter = lambda a: assemAngle(a)
elif circularRingOrder == "distanceSmart":
if lastRing == ring + 1:
# converging. Put things on the outside first.
sorter = lambda a: -squaredDistanceFromOrigin(a)
else:
# diverging. Put things on the inside first.
sorter = lambda a: squaredDistanceFromOrigin(a)
else:
# purely based on distance. Can mix things up in convergent-divergent cases. Prefer distanceSmart
sorter = lambda a: squaredDistanceFromOrigin(a)

assemsInRing = sorted(assemsInRing, key=sorter)
for a in assemsInRing:
locationSchedule.append(a.getLocation())
lastRing = ring

return locationSchedule
107 changes: 4 additions & 103 deletions armi/physics/fuelCycle/tests/test_fuelHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
This test is high enough level that it requires input files to be present. The ones to use
are called armiRun.yaml which is located in armi.tests
"""
# pylint: disable=missing-function-docstring,missing-class-docstring,abstract-method,protected-access
# pylint: disable=missing-function-docstring,missing-class-docstring,protected-access,invalid-name,no-self-use,no-method-argument,import-outside-toplevel
import collections
import copy
import os
import unittest

import numpy as np
Expand All @@ -34,7 +33,7 @@
from armi.utils import directoryChangers


class TestFuelHandler(ArmiTestHelper):
class FuelHandlerTestHelper(ArmiTestHelper):
@classmethod
def setUpClass(cls):
# prepare the input files. This is important so the unit tests run from wherever
Expand Down Expand Up @@ -119,6 +118,8 @@ def tearDown(self):

self.directoryChanger.close()


class TestFuelHandler(FuelHandlerTestHelper):
def test_findHighBu(self):
loc = self.r.core.spatialGrid.getLocatorFromRingAndPos(5, 4)
a = self.r.core.childrenByLocator[loc]
Expand Down Expand Up @@ -333,25 +334,6 @@ def runShuffling(self, fh):
self.assertEqual(a.getLocation(), "SFP")
fh.interactEOL()

def test_buildEqRingScheduleHelper(self):
fh = fuelHandlers.FuelHandler(self.o)

ringList1 = [1, 5]
buildRing1 = fh.buildEqRingScheduleHelper(ringList1)
self.assertEqual(buildRing1, [1, 2, 3, 4, 5])

ringList2 = [1, 5, 9, 6]
buildRing2 = fh.buildEqRingScheduleHelper(ringList2)
self.assertEqual(buildRing2, [1, 2, 3, 4, 5, 9, 8, 7, 6])

ringList3 = [9, 5, 3, 4, 1, 2]
buildRing3 = fh.buildEqRingScheduleHelper(ringList3)
self.assertEqual(buildRing3, [9, 8, 7, 6, 5, 3, 4, 1, 2])

ringList4 = [2, 5, 1, 1]
buildRing1 = fh.buildEqRingScheduleHelper(ringList4)
self.assertEqual(buildRing1, [2, 3, 4, 5, 1])

def test_repeatShuffles(self):
r"""
Builds a dummy core. Does some shuffles. Repeats the shuffles. Checks that it was a perfect repeat.
Expand Down Expand Up @@ -447,19 +429,6 @@ def test_getFactorList(self):
factors, _ = fh.getFactorList(0)
self.assertIn("eqShuffles", factors)

def test_simpleAssemblyRotation(self):
fh = fuelHandlers.FuelHandler(self.o)
newSettings = {"assemblyRotationStationary": True}
self.o.cs = self.o.cs.modified(newSettings=newSettings)
hist = self.o.getInterface("history")
assems = hist.o.r.core.getAssemblies(Flags.FUEL)[:5]
addSomeDetailAssemblies(hist, assems)
b = self.o.r.core.getFirstBlock(Flags.FUEL)
rotNum = b.getRotationNum()
fh.simpleAssemblyRotation()
fh.simpleAssemblyRotation()
self.assertEqual(b.getRotationNum(), rotNum + 2)

def test_linPowByPin(self):
_fh = fuelHandlers.FuelHandler(self.o)
_hist = self.o.getInterface("history")
Expand Down Expand Up @@ -502,74 +471,6 @@ def test_linPowByPinGamma(self):
b.p.linPowByPinGamma = np.array([1, 2, 3])
self.assertEqual(type(b.p.linPowByPinGamma), np.ndarray)

def test_buReducingAssemblyRotation(self):
fh = fuelHandlers.FuelHandler(self.o)
hist = self.o.getInterface("history")
newSettings = {"assemblyRotationStationary": True}
self.o.cs = self.o.cs.modified(newSettings=newSettings)
assem = self.o.r.core.getFirstAssembly(Flags.FUEL)

# apply dummy pin-level data to allow intelligent rotation
for b in assem.getBlocks(Flags.FUEL):
b.breakFuelComponentsIntoIndividuals()
b.initializePinLocations()
b.p.percentBuMaxPinLocation = 10
b.p.percentBuMax = 5
b.p.linPowByPin = list(reversed(range(b.getNumPins())))

addSomeDetailAssemblies(hist, [assem])
rotNum = b.getRotationNum()
fh.buReducingAssemblyRotation()
self.assertNotEqual(b.getRotationNum(), rotNum)

def test_buildRingSchedule(self):
fh = fuelHandlers.FuelHandler(self.o)

# simple divergent
schedule, widths = fh.buildRingSchedule(1, 9)
self.assertEqual(schedule, [9, 8, 7, 6, 5, 4, 3, 2, 1])

# simple with no jumps
schedule, widths = fh.buildRingSchedule(9, 1, jumpRingTo=1)
self.assertEqual(schedule, [1, 2, 3, 4, 5, 6, 7, 8, 9])

# simple with 1 jump
schedule, widths = fh.buildRingSchedule(9, 1, jumpRingFrom=6)
self.assertEqual(schedule, [5, 4, 3, 2, 1, 6, 7, 8, 9])
self.assertEqual(widths, [0, 0, 0, 0, 0, 0, 0, 0, 0])

# 1 jump plus auto-correction to core size
schedule, widths = fh.buildRingSchedule(1, 17, jumpRingFrom=5)
self.assertEqual(schedule, [6, 7, 8, 9, 5, 4, 3, 2, 1])
self.assertEqual(widths, [0, 0, 0, 0, 0, 0, 0, 0, 0])

# crash on invalid jumpring
with self.assertRaises(ValueError):
schedule, widths = fh.buildRingSchedule(1, 17, jumpRingFrom=0)

# test 4: Mid way jumping
schedule, widths = fh.buildRingSchedule(1, 9, jumpRingTo=6, jumpRingFrom=3)
self.assertEqual(schedule, [9, 8, 7, 4, 5, 6, 3, 2, 1])

def test_buildConvergentRingSchedule(self):
fh = fuelHandlers.FuelHandler(self.o)
schedule, widths = fh.buildConvergentRingSchedule(17, 1)
self.assertEqual(schedule, [1, 17])
self.assertEqual(widths, [16, 1])

def test_buildEqRingSchedule(self):
fh = fuelHandlers.FuelHandler(self.o)
locSchedule = fh.buildEqRingSchedule([2, 1])
self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])

fh.cs["circularRingOrder"] = "distanceSmart"
locSchedule = fh.buildEqRingSchedule([2, 1])
self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])

fh.cs["circularRingOrder"] = "somethingCrazy"
locSchedule = fh.buildEqRingSchedule([2, 1])
self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])

def test_transferStationaryBlocks(self):
"""
Test the _transferStationaryBlocks method .
Expand Down
Loading

0 comments on commit 848310a

Please sign in to comment.