def test_buildRingSchedule(self): fh = fuelHandlers.FuelHandler(self.o) schedule, widths = fh.buildRingSchedule(17, 1, jumpRingFrom=14) self.assertEqual( schedule, [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 14, 15, 16, 17] ) self.assertEqual(widths, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
def test_FindMany(self): r""" Tests the findMany and type aspects of the fuel handler """ fh = fuelHandlers.FuelHandler(self.o) igniters = fh.findAssembly(typeSpec=Flags.IGNITER | Flags.FUEL, findMany=True) feeds = fh.findAssembly(typeSpec=Flags.FEED | Flags.FUEL, findMany=True) fewFeeds = fh.findAssembly(typeSpec=Flags.FEED | Flags.FUEL, findMany=True, maxNumAssems=4) self.assertEqual( len(igniters), self.nigniter, "Found {0} igniters. Should have found {1}".format( len(igniters), self.nigniter), ) self.assertEqual( len(feeds), self.nfeed, "Found {0} feeds. Should have found {1}".format( len(igniters), self.nfeed), ) self.assertEqual( len(fewFeeds), 4, "Reduced findMany returned {0} assemblies instead of {1}" "".format(len(fewFeeds), 4), )
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_findInSFP(self): r""" Tests ability to pull from the spent fuel pool. """ fh = fuelHandlers.FuelHandler(self.o) spent = fh.findAssembly( findMany=True, findFromSfp=True, param="percentBu", compareTo=100, blockLevelMax=True, ) self.assertEqual( len(spent), self.nSfp, "Found {0} assems in SFP. Should have found {1}".format( len(spent), self.nSfp), ) burnups = [a.getMaxParam("percentBu") for a in spent] bu = spent[0].getMaxParam("percentBu") self.assertEqual( bu, max(burnups), "First assembly does not have the " "highest burnup ({0}). It has ({1})".format(max(burnups), bu), )
def test_findWithMinMax(self): """Test the complex min/max comparators.""" fh = fuelHandlers.FuelHandler(self.o) assem = fh.findAssembly( param="percentBu", compareTo=100, blockLevelMax=True, minParam="percentBu", minVal=("percentBu", 0.1), maxParam="percentBu", maxVal=20.0, ) # the burnup should be the maximum bu within # up to a burnup of 20%, which by the simple # dummy data layout should be the 2/3rd block in the blocklist bs = self.r.core.getBlocks(Flags.FUEL) lastB = None for b in bs: if b.p.percentBu > 20: break lastB = b expected = lastB.parent self.assertIs(assem, expected) # test the impossible: an block with burnup less than # 110% of its own burnup assem = fh.findAssembly( param="percentBu", compareTo=100, blockLevelMax=True, minParam="percentBu", minVal=("percentBu", 1.1), ) self.assertIsNone(assem)
def test_FindHighBu(self): a = self.r.core.whichAssemblyIsIn(5, 4) # set burnup way over 1.0, which is otherwise the highest bu in the core a[0].p.percentBu = 50 fh = fuelHandlers.FuelHandler(self.o) a1 = fh.findAssembly( param="percentBu", compareTo=100, blockLevelMax=True, typeSpec=None ) self.assertIs(a, a1)
def test_simpleAssemblyRotation(self): fh = fuelHandlers.FuelHandler(self.o) self.o.cs["assemblyRotationStationary"] = True 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_FindHighBu(self): loc = self.r.core.spatialGrid.getLocatorFromRingAndPos(5, 4) a = self.r.core.childrenByLocator[loc] # set burnup way over 1.0, which is otherwise the highest bu in the core a[0].p.percentBu = 50 fh = fuelHandlers.FuelHandler(self.o) a1 = fh.findAssembly( param="percentBu", compareTo=100, blockLevelMax=True, typeSpec=None ) self.assertIs(a, a1)
def test_swapFluxParamSameLength(self): """ Test the _swapFluxParams method for the usual case, where each of the input assembles have the same number of assemblies, on the same mesh """ # grab the assemblies assems = self.r.core.getAssemblies(Flags.FEED) self.assertEqual(len(assems), 14) for a in assems: self.assertEqual(len(a.getBlocks()), 5) # make two copies of an arbitraty assembly a1 = copy.deepcopy(list(assems)[1]) a2 = copy.deepcopy(list(assems)[1]) blocks1 = list(a1.getBlocks()) blocks2 = list(a2.getBlocks()) self.assertEqual(len(blocks1), 5) self.assertEqual(len(blocks2), 5) self.assertEqual(blocks1[3].p.height, 25) self.assertEqual(blocks2[3].p.height, 25) # 1. alter the values of a single block in assembly 2 b2 = list(blocks2)[1] b2.p.flux = b2.p.flux * 2 b2.p.power = 1000 b2.p.pdens = b2.p.power / b2.getVolume() # grab the power before the swap power1 = sum([b.p.power for b in a1.getBlocks()]) power2 = sum([b.p.power for b in a2.getBlocks()]) # 2. validate the situation is as you'd expect self.assertEqual(list(a1.getBlocks())[1].p.flux, 50000000000.0) self.assertEqual(list(a2.getBlocks())[1].p.flux, 100000000000.0) self.assertEqual(list(a1.getBlocks())[1].p.power, 0.0) self.assertEqual(list(a2.getBlocks())[1].p.power, 1000.0) self.assertEqual(list(a1.getBlocks())[1].p.pdens, 0.0) self.assertGreater(list(a2.getBlocks())[1].p.pdens, 0.0) # 3. do the swap fh = fuelHandlers.FuelHandler(self.o) fh._swapFluxParam(a1, a2) # 4. validate the swap worked self.assertEqual(list(a1.getBlocks())[1].p.flux, 100000000000.0) self.assertEqual(list(a2.getBlocks())[1].p.flux, 50000000000.0) self.assertEqual(list(a1.getBlocks())[1].p.power, 1000.0) self.assertEqual(list(a2.getBlocks())[1].p.power, 0.0) self.assertGreater(list(a1.getBlocks())[1].p.pdens, 0.0) self.assertEqual(list(a2.getBlocks())[1].p.pdens, 0.0) self.assertEqual(sum([b.p.power for b in a1.getBlocks()]), power2) self.assertEqual(sum([b.p.power for b in a2.getBlocks()]), power1)
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_linPowByPinGamma(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) b = assem.getBlocks(Flags.FUEL)[0] b.p.linPowByPinGamma = [1, 2, 3] self.assertEqual(type(b.p.linPowByPinGamma), np.ndarray) b.p.linPowByPinGamma = np.array([1, 2, 3]) self.assertEqual(type(b.p.linPowByPinGamma), np.ndarray)
def test_FindHighBu(self): a = self.r.core.whichAssemblyIsIn(5, 4) # set burnup way over 1.0, which is otherwise the highest bu in the core a[0].p.percentBu = 10 fh = fuelHandlers.FuelHandler(self.o) a1 = fh.findAssembly( param="percentBu", compareTo=100, blockLevelMax=True, typeSpec=None ) self.assertEqual( a, a1, "The high burnup assembly {0} is not the expected one {1}".format(a, a1), )
def test_processMoveList(self): fh = fuelHandlers.FuelHandler(self.o) moves = fh.readMoves("armiRun-SHUFFLES.txt") ( loadChains, loopChains, enriches, loadChargeTypes, loadNames, alreadyDone, ) = fh.processMoveList(moves[2]) self.assertIn("A0089", loadNames) self.assertIn(None, loadNames) self.assertNotIn("SFP", loadChains) self.assertNotIn("LoadQueue", loadChains) self.assertFalse(loopChains)
def test_buReducingAssemblyRotation(self): fh = fuelHandlers.FuelHandler(self.o) hist = self.o.getInterface("history") self.o.cs["assemblyRotationStationary"] = True 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_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_readMoves(self): """ Depends on the shuffleLogic created by repeatShuffles See Also -------- runShuffling : creates the shuffling file to be read in. """ numblocks = len(self.r.core.getFirstAssembly()) fh = fuelHandlers.FuelHandler(self.o) moves = fh.readMoves("armiRun-SHUFFLES.txt") self.assertEqual(len(moves), 3) firstMove = moves[1][0] self.assertEqual(firstMove[0], "A2001") self.assertEqual(firstMove[1], "SFP") self.assertEqual(len(firstMove[2]), numblocks) self.assertEqual(firstMove[3], "igniter fuel") self.assertEqual(firstMove[4], None) # check the move that came back out of the SFP sfpMove = moves[2][-2] self.assertEqual(sfpMove[0], "SFP") self.assertEqual(sfpMove[1], "A5003") self.assertEqual(sfpMove[4], "A0089") # name of assem in SFP
def fuelHandlerFactory(operator): """ Return an instantiated FuelHandler object based on user settings. The FuelHandler is expected to be a short-lived object that only lives for the cycle upon which it acts. At the next cycle, this factory will be called again to instantiate a new FuelHandler. """ cs = operator.cs fuelHandlerClassName = cs["fuelHandlerName"] fuelHandlerModulePath = cs["shuffleLogic"] if not fuelHandlerClassName: # User did not request a custom fuel handler. # This is code coupling that should be untangled. # Special case for equilibrium-mode shuffling if cs["eqDirect"] and cs["runType"].lower() == RunTypes.STANDARD.lower(): from terrapower.physics.neutronics.equilibrium import fuelHandler as efh return efh.EqDirectFuelHandler(operator) else: # give the default FuelHandler. This does not have an implemented outage, but # still offers moving capabilities. Useful when you just need to make explicit # moves but do not have a fully-defined fuel management input. return fuelHandlers.FuelHandler(operator) # User did request a custom fuel handler. We must go find and import it # from the input directory. with directoryChangers.DirectoryChanger(cs.inputDirectory, dumpOnException=False): try: module = pathTools.importCustomPyModule(fuelHandlerModulePath) if not hasattr(module, fuelHandlerClassName): raise KeyError( "The requested fuel handler object {0} is not " "found in the fuel management input file {1} from CWD {2}. " "Check input" "".format( fuelHandlerClassName, fuelHandlerModulePath, cs.inputDirectory ) ) # instantiate the custom object fuelHandlerCls = getattr(module, fuelHandlerClassName) fuelHandler = fuelHandlerCls(operator) # also get getFactorList function from module level if it's there. # This is a legacy input option, getFactorList should now generally # be an method of the FuelHandler object if hasattr(module, "getFactorList"): # staticmethod binds the provided getFactorList function to the # fuelHandler object without passing the implicit self argument. # The __get__ pulls the actual function out from the descriptor. fuelHandler.getFactorList = staticmethod(module.getFactorList).__get__( fuelHandlerCls ) except IOError: raise ValueError( "Either the file specified in the `shuffleLogic` setting ({}) or the " "fuel handler class name specified in the `fuelHandlerName` setting ({}) " "cannot be found. CWD is: {}. Update input.".format( fuelHandlerModulePath, fuelHandlerClassName, cs.inputDirectory ) ) return fuelHandler
def test_buildEqRingSchedule(self): fh = fuelHandlers.FuelHandler(self.o) locSchedule = fh.buildEqRingSchedule([2, 1]) self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"])
def test_Width(self): """Tests the width capability of findAssembly.""" fh = fuelHandlers.FuelHandler(self.o) # instantiate reactor power. more power in more outer rings for ring, power in zip(range(1, 8), range(10, 80, 10)): aList = self.r.core.whichAssemblyIsIn(ring) for a in aList: for b in a: b.p.power = power paramName = "power" # 1 ring outer and inner from ring 3 a = fh.findAssembly( targetRing=3, width=(1, 0), param=paramName, blockLevelMax=True, compareTo=100, ) ring = a.spatialLocator.getRingPos()[0] self.assertEqual( ring, 4, "The highest power ring returned is {0}. It should be {1}".format( ring, 4), ) a = fh.findAssembly(targetRing=3, width=(1, 0), param=paramName, blockLevelMax=True, compareTo=0) ring = a.spatialLocator.getRingPos()[0] self.assertEqual( ring, 2, "The lowest power ring returned is {0}. It should be {1}".format( ring, 2), ) # 2 rings outer from ring 3 a = fh.findAssembly( targetRing=3, width=(2, 1), param=paramName, blockLevelMax=True, compareTo=100, ) ring = a.spatialLocator.getRingPos()[0] self.assertEqual( ring, 5, "The highest power ring returned is {0}. It should be {1}".format( ring, 5), ) a = fh.findAssembly(targetRing=3, width=(2, 1), param=paramName, blockLevelMax=True, compareTo=0) ring = a.spatialLocator.getRingPos()[0] self.assertEqual( ring, 3, "The lowest power ring returned is {0}. It should be {1}".format( ring, 3), ) # 2 rings inner from ring 3 a = fh.findAssembly( targetRing=3, width=(2, -1), param=paramName, blockLevelMax=True, compareTo=100, ) ring = a.spatialLocator.getRingPos()[0] self.assertEqual( ring, 3, "The highest power ring returned is {0}. It should be {1}".format( ring, 3), ) a = fh.findAssembly( targetRing=3, width=(2, -1), param=paramName, blockLevelMax=True, compareTo=0, ) ring = a.spatialLocator.getRingPos()[0] self.assertEqual( ring, 1, "The lowest power ring returned is {0}. It should be {1}".format( ring, 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_swapFluxParamDifferentLengths(self): """ Test the _swapFluxParams method for the less common, and more complicated case, where the input assembles have different numbers of blocks, potentially on wildly different point meshes. """ # grab the assemblies assems = self.r.core.getAssemblies(Flags.FEED) # make two copies of an arbitraty assembly a1 = copy.deepcopy(list(assems)[1]) a2 = copy.deepcopy(list(assems)[1]) height2 = 25.0 self.assertEqual(list(a1.getBlocks())[3].p.height, height2) self.assertEqual(list(a2.getBlocks())[3].p.height, height2) # grab the blocks from the second assembly blocks2 = list(a2.getBlocks()) self.assertEqual(len(blocks2), 5) # grab a single block from the second assembly, to be altered b2 = list(blocks2)[1] self.assertEqual(b2.p.height, height2) self.assertEqual(b2.p.flux, 50000000000.0) self.assertIsNone(b2.p.mgFlux) self.assertEqual(b2.p.power, 0.0) self.assertEqual(b2.p.pdens, 0.0) volume2 = 6074.356 self.assertAlmostEqual(b2.getVolume(), volume2, delta=0.1) # split the the block into two of half the heights b20 = copy.deepcopy(b2) b21 = copy.deepcopy(b2) b20.setHeight(height2 / 2) b21.setHeight(height2 / 2) self.assertAlmostEqual(b20.getVolume(), volume2 / 2, delta=0.1) self.assertAlmostEqual(b21.getVolume(), volume2 / 2, delta=0.1) # give the two new (smaller) blocks some power/pdens b20.p.power = 1000 b21.p.power = 2000 b20.p.pdens = b20.p.power / b20.getVolume() b21.p.pdens = b21.p.power / b21.getVolume() self.assertEqual(b20.p.power, 1000.0) self.assertEqual(b21.p.power, 2000.0) self.assertAlmostEqual(b20.p.pdens, 0.3292, delta=0.1) self.assertAlmostEqual(b21.p.pdens, 0.6585, delta=0.1) # give the second assembly the new blocks a2.removeAll() a2.setChildren([blocks2[0]] + [b20, b21] + blocks2[2:]) # validate the situation is as you'd expect self.assertEqual(len(a1.getBlocks()), 5) self.assertEqual(len(a2.getBlocks()), 6) # validate the power before the swap power1 = [b.p.power for b in a1.getBlocks()] power2 = [b.p.power for b in a2.getBlocks()] self.assertEqual(power1, [0, 0, 0, 0, 0]) self.assertEqual(power2, [0, 1000, 2000, 0, 0, 0]) # validate the power density before the swap for b in a1.getBlocks(): self.assertEqual(b.p.pdens, 0.0) pdens2i = [0, 0.32925299379047496, 0.6585059875809499, 0, 0, 0] for i, b in enumerate(a2.getBlocks()): self.assertAlmostEqual(b.p.pdens, pdens2i[i], msg=i) # validate the flux before the swap for b in a1.getBlocks(): self.assertEqual(b.p.flux, 50000000000.0) for b in a2.getBlocks(): self.assertEqual(b.p.flux, 50000000000.0) # do the swap, using averages fh = fuelHandlers.FuelHandler(self.o) fh._swapFluxParam(a1, a2) # grab the power after the swap power1f = [b.p.power for b in a1.getBlocks()] power2f = [b.p.power for b in a2.getBlocks()] # validate the swap worked self.assertEqual(len(a1.getBlocks()), 5) self.assertEqual(len(a2.getBlocks()), 6) self.assertEqual(power1f, [0, 3000, 0, 0, 0]) self.assertEqual(power2f, [0, 0, 0, 0, 0, 0]) # validate the power density after the swap pdens1f = [0, 0.4938794906857124, 0, 0, 0] for i, b in enumerate(a1.getBlocks()): self.assertAlmostEqual(b.p.pdens, pdens1f[i], msg=i) for i, b in enumerate(a2.getBlocks()): self.assertAlmostEqual(b.p.pdens, 0, msg=i) # validate the flux after the swap for b in a1.getBlocks(): self.assertEqual(b.p.flux, 50000000000.0) for b in a2.getBlocks(): self.assertEqual(b.p.flux, 50000000000.0)
# configure ARMI configure(permissive=True) o, reactor = test_reactors.loadTestReactor( inputFileName="refTestCartesian.yaml") # Apply a dummy burnup distribution roughly in a cosine for b in reactor.core.getBlocks(Flags.FUEL): x, y, z = b.spatialLocator.getGlobalCoordinates() d = math.sqrt(x**2 + y**2) b.p.percentBu = 5 * math.cos(d * math.pi / 2 / 90) # show the initial burnup distribution plotting.plotFaceMap(reactor.core, param="percentBu") fuelHandler = fuelHandlers.FuelHandler(o) candidateAssems = reactor.core.getAssemblies(Flags.FUEL) criterion = lambda a: a.getMaxParam("percentBu") candidateAssems.sort(key=criterion) for num in range(12): # swap the 12 highest burnup assemblies with the 12 lowest burnup ones high = candidateAssems.pop() low = candidateAssems.pop(0) fuelHandler.swapAssemblies(high, low) # re-filter the remaining candidates for more complex selections candidateAssems = [ a for a in candidateAssems if a.getMaxParam("percentBu") < 4.0 ]
def test_findByCoords(self): fh = fuelHandlers.FuelHandler(self.o) assem = fh.findAssembly(coords=(0, 0)) self.assertIs(assem, self.o.r.core[0])
def test_getFactorList(self): fh = fuelHandlers.FuelHandler(self.o) factors, flags = fh.getFactorList(0) self.assertIn("eqShuffles", factors)