Source code for armi.physics.fuelCycle.tests.test_fuelHandlers

# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Tests some capabilities of the fuel handling machine.

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
"""
import collections
import copy
import os
import unittest
from unittest.mock import patch

import numpy as np

from armi.physics.fuelCycle import fuelHandlers, settings
from armi.physics.fuelCycle.settings import (
    CONF_ASSEM_ROTATION_STATIONARY,
    CONF_ASSEMBLY_ROTATION_ALG,
    CONF_PLOT_SHUFFLE_ARROWS,
    CONF_RUN_LATTICE_BEFORE_SHUFFLING,
)
from armi.physics.neutronics.crossSectionGroupManager import CrossSectionGroupManager
from armi.physics.neutronics.latticePhysics.latticePhysicsInterface import (
    LatticePhysicsInterface,
)
from armi.reactor import assemblies, blocks, components, grids
from armi.reactor.flags import Flags
from armi.reactor.tests import test_reactors
from armi.reactor.zones import Zone
from armi.settings import caseSettings
from armi.tests import ArmiTestHelper
from armi.tests import mockRunLogs
from armi.tests import TEST_ROOT
from armi.utils import directoryChangers


[docs]class FuelHandlerTestHelper(ArmiTestHelper): @classmethod def setUpClass(cls): # prepare the input files. This is important so the unit tests run from wherever # they need to run from. cls.directoryChanger = directoryChangers.DirectoryChanger( TEST_ROOT, dumpOnException=False ) cls.directoryChanger.open() @classmethod def tearDownClass(cls): cls.directoryChanger.close() def setUp(self): """ Build a dummy reactor without using input files. There are some igniters and feeds but none of these have any number densities. """ self.o, self.r = test_reactors.loadTestReactor( self.directoryChanger.destination, customSettings={"nCycles": 3, "trackAssems": True}, ) blockList = self.r.core.getBlocks() for bi, b in enumerate(blockList): b.p.flux = 5e10 if b.isFuel(): b.p.percentBu = 30.0 * bi / len(blockList) self.nfeed = len(self.r.core.getAssemblies(Flags.FEED)) self.nigniter = len(self.r.core.getAssemblies(Flags.IGNITER)) self.nSfp = len(self.r.sfp) # generate a reactor with assemblies # generate components with materials nPins = 271 fuelDims = {"Tinput": 273.0, "Thot": 273.0, "od": 1.0, "id": 0.0, "mult": nPins} fuel = components.Circle("fuel", "UZr", **fuelDims) cladDims = {"Tinput": 273.0, "Thot": 273.0, "od": 1.1, "id": 1.0, "mult": nPins} clad = components.Circle("clad", "HT9", **cladDims) interDims = { "Tinput": 273.0, "Thot": 273.0, "op": 16.8, "ip": 16.0, "mult": 1.0, } interSodium = components.Hexagon("interCoolant", "Sodium", **interDims) # generate a block self.block = blocks.HexBlock("TestHexBlock") self.block.setType("fuel") self.block.setHeight(10.0) self.block.add(fuel) self.block.add(clad) self.block.add(interSodium) # generate an assembly self.assembly = assemblies.HexAssembly("TestAssemblyType") self.assembly.spatialGrid = grids.axialUnitGrid(1) for _ in range(1): self.assembly.add(copy.deepcopy(self.block)) # copy the assembly to make a list of assemblies and have a reference assembly self.aList = [] for _ in range(6): self.aList.append(copy.deepcopy(self.assembly)) self.refAssembly = copy.deepcopy(self.assembly) self.directoryChanger.open() def tearDown(self): # clean up the test self.block = None self.assembly = None self.aList = None self.refAssembly = None self.r = None self.o = None self.directoryChanger.close()
[docs]class MockLatticePhysicsInterface(LatticePhysicsInterface): """A mock lattice physics interface that does nothing for interactBOC.""" name = "MockLatticePhysicsInterface" def _getExecutablePath(self): return "/mock/"
[docs] def interactBOC(self, cycle=None): pass
[docs]class MockXSGM(CrossSectionGroupManager): """A mock cross section group manager that does nothing for interactBOC."""
[docs] def interactBOC(self, cycle=None): pass
[docs]class TestFuelHandler(FuelHandlerTestHelper):
[docs] def test_interactBOC(self): # set up mock interface self.o.addInterface(MockLatticePhysicsInterface(self.r, self.o.cs)) self.o.removeInterface(interfaceName="xsGroups") self.o.addInterface(MockXSGM(self.r, self.o.cs)) # adjust case settings self.o.cs[CONF_RUN_LATTICE_BEFORE_SHUFFLING] = True # run fhi.interactBOC fhi = self.o.getInterface("fuelHandler") with mockRunLogs.BufferLog() as mock: fhi.interactBOC() self.assertIn( "lattice physics before fuel management due to the", mock._outputStream )
[docs] 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)
[docs] @patch("armi.physics.fuelCycle.fuelHandlers.FuelHandler.chooseSwaps") def test_outage(self, mockChooseSwaps): # mock up a fuel handler fh = fuelHandlers.FuelHandler(self.o) mockChooseSwaps.return_value = list(self.r.core.getAssemblies()) # edge case: cannot perform two outages on the same FuelHandler fh.moved = [self.r.core.getFirstAssembly()] with self.assertRaises(ValueError): fh.outage(factor=1.0) # edge case: fail if the shuffle file is missing fh.moved = [] self.o.cs = self.o.cs.modified( newSettings={"explicitRepeatShuffles": "fakePath"} ) with self.assertRaises(RuntimeError): fh.outage(factor=1.0) # a successful run fh.moved = [] self.o.cs = self.o.cs.modified( newSettings={ "explicitRepeatShuffles": "", "fluxRecon": True, CONF_ASSEMBLY_ROTATION_ALG: "simpleAssemblyRotation", } ) fh.outage(factor=1.0) self.assertEqual(len(fh.moved), 0)
[docs] def test_isAssemblyInAZone(self): # build a fuel handler fh = fuelHandlers.FuelHandler(self.o) # test the default value if there are no zones a = self.r.core.getFirstAssembly() self.assertTrue(fh.isAssemblyInAZone(None, a)) # If our assembly isn't in one of the supplied zones z = Zone("test_isAssemblyInAZone") self.assertFalse(fh.isAssemblyInAZone([z], a)) # If our assembly IS in one of the supplied zones z.addLoc(a.getLocation()) self.assertTrue(fh.isAssemblyInAZone([z], a))
[docs] def test_width(self): """Tests the width capability of findAssembly.""" fh = fuelHandlers.FuelHandler(self.o) assemsByRing = collections.defaultdict(list) for a in self.r.core.getAssemblies(): assemsByRing[a.spatialLocator.getRingPos()[0]].append(a) # instantiate reactor power. more power in more outer rings for ring, power in zip(range(1, 8), range(10, 80, 10)): aList = assemsByRing[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), )
[docs] def test_findMany(self): """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), )
[docs] def test_findInSFP(self): """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), )
[docs] def test_findByCoords(self): fh = fuelHandlers.FuelHandler(self.o) assem = fh.findAssembly(coords=(0, 0)) self.o.r.core.sortAssemsByRing() self.assertIs(assem, self.o.r.core[0])
[docs] 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)
[docs] def runShuffling(self, fh): """Shuffle fuel and write out a SHUFFLES.txt file.""" fh.attachReactor(self.o, self.r) # so we don't overwrite the version-controlled armiRun-SHUFFLES.txt self.o.cs.caseTitle = "armiRun2" fh.interactBOL() for cycle in range(3): self.r.p.cycle = cycle fh.cycle = cycle fh.manageFuel(cycle) for a in self.r.sfp.getChildren(): self.assertEqual(a.getLocation(), "SFP") for b in self.r.core.getBlocks(Flags.FUEL): self.assertGreater(b.p.kgHM, 0.0, "b.p.kgHM not populated!") self.assertGreater(b.p.kgFis, 0.0, "b.p.kgFis not populated!") fh.interactEOL()
[docs] def test_repeatShuffles(self): """Loads the ARMI test reactor with a custom shuffle logic file and shuffles assemblies twice. .. test:: Execute user-defined shuffle operations based on a reactor model. :id: T_ARMI_SHUFFLE :tests: R_ARMI_SHUFFLE .. test:: Move an assembly from one position in the core to another. :id: T_ARMI_SHUFFLE_MOVE0 :tests: R_ARMI_SHUFFLE_MOVE Notes ----- The custom shuffle logic is executed by :py:meth:`armi.physics.fuelCycle.fuelHandlerInterface.FuelHandlerInterface.manageFuel` in :py:meth:`armi.physics.fuelCycle.tests.test_fuelHandlers.TestFuelHandler.runShuffling`. There are two primary assertions: spent fuel pool assemblies are in the correct location and the assemblies were shuffled into their correct locations. This process is repeated twice to ensure repeatability. """ # check labels before shuffling: for a in self.r.sfp.getChildren(): self.assertEqual(a.getLocation(), "SFP") # do some shuffles fh = self.r.o.getInterface("fuelHandler") self.runShuffling(fh) # changes caseTitle # Make sure the generated shuffles file matches the tracked one. This will need to be # updated if/when more assemblies are added to the test reactor but must be done carefully. # Do not blindly rebaseline this file. self.compareFilesLineByLine("armiRun-SHUFFLES.txt", "armiRun2-SHUFFLES.txt") # store locations of each assembly firstPassResults = {} for a in self.r.core.getAssemblies(): firstPassResults[a.getLocation()] = a.getName() self.assertNotIn(a.getLocation(), ["SFP", "LoadQueue", "ExCore"]) # reset core to BOL state # reset assembly counter to get the same assem nums. self.setUp() newSettings = {CONF_PLOT_SHUFFLE_ARROWS: True} # now repeat shuffles newSettings["explicitRepeatShuffles"] = "armiRun-SHUFFLES.txt" self.o.cs = self.o.cs.modified(newSettings=newSettings) fh = self.r.o.getInterface("fuelHandler") self.runShuffling(fh) # make sure the shuffle was repeated perfectly. for a in self.r.core.getAssemblies(): self.assertEqual(a.getName(), firstPassResults[a.getLocation()]) for a in self.r.sfp.getChildren(): self.assertEqual(a.getLocation(), "SFP") # Do some cleanup, since the fuelHandler Interface has code that gets # around the TempDirectoryChanger os.remove("armiRun2-SHUFFLES.txt") os.remove("armiRun2.shuffles_0.png") os.remove("armiRun2.shuffles_1.png") os.remove("armiRun2.shuffles_2.png")
[docs] 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], "002-001") 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], "005-003") self.assertEqual(sfpMove[4], "A0073") # name of assem in SFP # make sure we fail hard if the file doesn't exist with self.assertRaises(RuntimeError): fh.readMoves("totall_fictional_file.txt")
[docs] def test_processMoveList(self): fh = fuelHandlers.FuelHandler(self.o) moves = fh.readMoves("armiRun-SHUFFLES.txt") ( loadChains, loopChains, _, _, loadNames, _, ) = fh.processMoveList(moves[2]) self.assertIn("A0073", loadNames) self.assertIn(None, loadNames) self.assertNotIn("SFP", loadChains) self.assertNotIn("LoadQueue", loadChains) self.assertFalse(loopChains)
[docs] def test_getFactorList(self): fh = fuelHandlers.FuelHandler(self.o) factors, _ = fh.getFactorList(0) self.assertIn("eqShuffles", factors)
[docs] def test_linPowByPin(self): _fh = fuelHandlers.FuelHandler(self.o) _hist = self.o.getInterface("history") newSettings = {CONF_ASSEM_ROTATION_STATIONARY: 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.linPowByPin = [1, 2, 3] self.assertEqual(type(b.p.linPowByPin), np.ndarray) b.p.linPowByPin = np.array([1, 2, 3]) self.assertEqual(type(b.p.linPowByPin), np.ndarray)
[docs] def test_linPowByPinNeutron(self): _fh = fuelHandlers.FuelHandler(self.o) _hist = self.o.getInterface("history") newSettings = {CONF_ASSEM_ROTATION_STATIONARY: 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.linPowByPinNeutron = [1, 2, 3] self.assertEqual(type(b.p.linPowByPinNeutron), np.ndarray) b.p.linPowByPinNeutron = np.array([1, 2, 3]) self.assertEqual(type(b.p.linPowByPinNeutron), np.ndarray)
[docs] def test_linPowByPinGamma(self): _fh = fuelHandlers.FuelHandler(self.o) _hist = self.o.getInterface("history") newSettings = {CONF_ASSEM_ROTATION_STATIONARY: 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)
[docs] def test_transferStationaryBlocks(self): """Test the _transferStationaryBlocks method. .. test:: User-specified blocks can remain in place during shuffling :id: T_ARMI_SHUFFLE_STATIONARY0 :tests: R_ARMI_SHUFFLE_STATIONARY """ # grab stationary block flags sBFList = self.r.core.stationaryBlockFlagsList # grab the assemblies assems = self.r.core.getAssemblies(Flags.FUEL) # grab two arbitrary assemblies a1 = assems[1] a2 = assems[2] # grab the stationary blocks pre swap a1PreSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a1 if any(block.hasFlags(sbf) for sbf in sBFList) ] a2PreSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a2 if any(block.hasFlags(sbf) for sbf in sBFList) ] # swap the stationary blocks fh = fuelHandlers.FuelHandler(self.o) fh._transferStationaryBlocks(a1, a2) # grab the stationary blocks post swap a1PostSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a1 if any(block.hasFlags(sbf) for sbf in sBFList) ] a2PostSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a2 if any(block.hasFlags(sbf) for sbf in sBFList) ] # validate the stationary blocks have swapped locations and are aligned self.assertEqual(a1PostSwapStationaryBlocks, a2PreSwapStationaryBlocks) self.assertEqual(a2PostSwapStationaryBlocks, a1PreSwapStationaryBlocks)
[docs] def test_transferDifferentNumberStationaryBlocks(self): """ Test the _transferStationaryBlocks method for the case where the input assemblies have different numbers of stationary blocks. """ # grab stationary block flags sBFList = self.r.core.stationaryBlockFlagsList # grab the assemblies assems = self.r.core.getAssemblies(Flags.FUEL) # grab two arbitrary assemblies a1 = assems[1] a2 = assems[2] # change a block in assembly 1 to be flagged as a stationary block for block in a1: if not any(block.hasFlags(sbf) for sbf in sBFList): a1[block.spatialLocator.k].setType( a1[block.spatialLocator.k].p.type, sBFList[0] ) self.assertTrue(any(block.hasFlags(sbf) for sbf in sBFList)) break # try to swap stationary blocks between assembly 1 and 2 fh = fuelHandlers.FuelHandler(self.o) with self.assertRaises(ValueError): fh._transferStationaryBlocks(a1, a2)
[docs] def test_transferUnalignedLocationStationaryBlocks(self): """ Test the _transferStationaryBlocks method for the case where the input assemblies have unaligned locations of stationary blocks. """ # grab stationary block flags sBFList = self.r.core.stationaryBlockFlagsList # grab the assemblies assems = self.r.core.getAssemblies(Flags.FUEL) # grab two arbitrary assemblies a1 = assems[1] a2 = assems[2] # move location of a stationary flag in assembly 1 for block in a1: if any(block.hasFlags(sbf) for sbf in sBFList): # change flag of first identified stationary block to fuel a1[block.spatialLocator.k].setType( a1[block.spatialLocator.k].p.type, Flags.FUEL ) self.assertTrue(a1[block.spatialLocator.k].hasFlags(Flags.FUEL)) # change next or previous block flag to stationary flag try: a1[block.spatialLocator.k + 1].setType( a1[block.spatialLocator.k + 1].p.type, sBFList[0] ) self.assertTrue( any( a1[block.spatialLocator.k + 1].hasFlags(sbf) for sbf in sBFList ) ) except: # noqa: bare-except a1[block.spatialLocator.k - 1].setType( a1[block.spatialLocator.k - 1].p.type, sBFList[0] ) self.assertTrue( any( a1[block.spatialLocator.k - 1].hasFlags(sbf) for sbf in sBFList ) ) break # try to swap stationary blocks between assembly 1 and 2 fh = fuelHandlers.FuelHandler(self.o) with self.assertRaises(ValueError): fh._transferStationaryBlocks(a1, a2)
[docs] def test_transferIncompatibleHeightStationaryBlocks(self): """ Test the _transferStationaryBlocks method for the case where the total height of the stationary blocks is unequal between input assemblies. """ # grab stationary block flags sBFList = self.r.core.stationaryBlockFlagsList # grab the assemblies assems = self.r.core.getAssemblies(Flags.FUEL) # grab two arbitrary assemblies a1 = assems[1] a2 = assems[2] # change height of a stationary block in assembly 1 for block in a1: if any(block.hasFlags(sbf) for sbf in sBFList): # change height of first identified stationary block nomHeight = block.getHeight() a1[block.spatialLocator.k].setHeight(nomHeight - 1e-5) # try to swap stationary blocks between assembly 1 and 2 fh = fuelHandlers.FuelHandler(self.o) with mockRunLogs.BufferLog() as mock: fh._transferStationaryBlocks(a1, a2) self.assertIn("top elevation of stationary", mock.getStdout())
[docs] def test_dischargeSwap(self): """Remove an assembly from the core and replace it with one from the SFP. .. test:: Move an assembly from one position in the core to another. :id: T_ARMI_SHUFFLE_MOVE1 :tests: R_ARMI_SHUFFLE_MOVE .. test:: User-specified blocks can remain in place during shuffling :id: T_ARMI_SHUFFLE_STATIONARY1 :tests: R_ARMI_SHUFFLE_STATIONARY """ # grab stationary block flags sBFList = self.r.core.stationaryBlockFlagsList # grab an arbitrary fuel assembly from the core and from the SFP a1 = self.r.core.getAssemblies(Flags.FUEL)[0] a2 = self.r.sfp.getChildren(Flags.FUEL)[0] # grab the stationary blocks pre swap a1PreSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a1 if any(block.hasFlags(sbf) for sbf in sBFList) ] a2PreSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a2 if any(block.hasFlags(sbf) for sbf in sBFList) ] # test discharging assembly 1 and replacing with assembly 2 fh = fuelHandlers.FuelHandler(self.o) fh.dischargeSwap(a2, a1) self.assertTrue(a1.getLocation() in a1.NOT_IN_CORE) self.assertTrue(a2.getLocation() not in a2.NOT_IN_CORE) # grab the stationary blocks post swap a1PostSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a1 if any(block.hasFlags(sbf) for sbf in sBFList) ] a2PostSwapStationaryBlocks = [ [block.getName(), block.spatialLocator.k] for block in a2 if any(block.hasFlags(sbf) for sbf in sBFList) ] # validate the stationary blocks have swapped locations correctly and are aligned self.assertEqual(a1PostSwapStationaryBlocks, a2PreSwapStationaryBlocks) self.assertEqual(a2PostSwapStationaryBlocks, a1PreSwapStationaryBlocks)
[docs] def test_dischargeSwapIncompatibleStationaryBlocks(self): """ Test the _transferStationaryBlocks method for the case where the input assemblies have different numbers as well as unaligned locations of stationary blocks. """ # grab stationary block flags sBFList = self.r.core.stationaryBlockFlagsList # grab an arbitrary fuel assembly from the core and from the SFP a1 = self.r.core.getAssemblies(Flags.FUEL)[0] a2 = self.r.sfp.getChildren(Flags.FUEL)[0] # change a block in assembly 1 to be flagged as a stationary block for block in a1: if not any(block.hasFlags(sbf) for sbf in sBFList): a1[block.spatialLocator.k].setType( a1[block.spatialLocator.k].p.type, sBFList[0] ) self.assertTrue(any(block.hasFlags(sbf) for sbf in sBFList)) break # try to discharge assembly 1 and replace with assembly 2 fh = fuelHandlers.FuelHandler(self.o) with self.assertRaises(ValueError): fh.dischargeSwap(a2, a1) # re-initialize assemblies self.setUp() a1 = self.r.core.getAssemblies(Flags.FUEL)[0] a2 = self.r.sfp.getChildren(Flags.FUEL)[0] # move location of a stationary flag in assembly 1 for block in a1: if any(block.hasFlags(sbf) for sbf in sBFList): # change flag of first identified stationary block to fuel a1[block.spatialLocator.k].setType( a1[block.spatialLocator.k].p.type, Flags.FUEL ) self.assertTrue(a1[block.spatialLocator.k].hasFlags(Flags.FUEL)) # change next or previous block flag to stationary flag try: a1[block.spatialLocator.k + 1].setType( a1[block.spatialLocator.k + 1].p.type, sBFList[0] ) self.assertTrue( any( a1[block.spatialLocator.k + 1].hasFlags(sbf) for sbf in sBFList ) ) except: # noqa: bare-except a1[block.spatialLocator.k - 1].setType( a1[block.spatialLocator.k - 1].p.type, sBFList[0] ) self.assertTrue( any( a1[block.spatialLocator.k - 1].hasFlags(sbf) for sbf in sBFList ) ) break # try to discharge assembly 1 and replace with assembly 2 with self.assertRaises(ValueError): fh.dischargeSwap(a2, a1)
[docs]class TestFuelPlugin(unittest.TestCase): """Tests that make sure the plugin is being discovered well."""
[docs] def test_settingsAreDiscovered(self): cs = caseSettings.Settings() nm = settings.CONF_CIRCULAR_RING_ORDER self.assertEqual(cs[nm], "angle") setting = cs.getSetting(nm) self.assertIn("distance", setting.options)
[docs]def addSomeDetailAssemblies(hist, assems): for a in assems: hist.detailAssemblyNames.append(a.getName())