From 9a510ca3a330c04471fa49f1cf7bf48d5f646f2b Mon Sep 17 00:00:00 2001 From: HunterPSmith <101363496+HunterPSmith@users.noreply.github.com> Date: Tue, 9 Aug 2022 15:04:47 -0700 Subject: [PATCH] Updating stationary block methods (#665) Updated the way that ARMI handles stationary blocks. ARMI now has a global setting called stationaryBlockFlags which can be set by the user to identify flags for stationary blocks. By default, GRID_PLATE has a stationary flag, unless set by user in the input file. When a reactor object is initialized, a reactor attribute called stationaryBlockFlagsList is generated which contains a list of the flags in the reactor that are considered stationary. ARMI now operates on stationaryBlockFlagsList in order to control stationary blocks. --- armi/bookkeeping/historyTracker.py | 6 +- .../physics/fuelCycle/fuelHandlerInterface.py | 5 +- armi/physics/fuelCycle/fuelHandlers.py | 138 ++----- .../fuelCycle/tests/test_fuelHandlers.py | 366 +++++++++++------- armi/reactor/reactors.py | 18 +- armi/reactor/tests/test_reactors.py | 1 - armi/settings/fwSettings/globalSettings.py | 12 +- armi/tests/test_mpiFeatures.py | 2 +- doc/release/0.2.rst | 4 +- 9 files changed, 276 insertions(+), 276 deletions(-) diff --git a/armi/bookkeeping/historyTracker.py b/armi/bookkeeping/historyTracker.py index bf97c413c..c721e5215 100644 --- a/armi/bookkeeping/historyTracker.py +++ b/armi/bookkeeping/historyTracker.py @@ -295,7 +295,11 @@ def writeAssemHistory(self, a, fName=""): params = self.getTrackedParams() blocks = [ - b for bi, b in enumerate(a) if bi not in self.cs["stationaryBlocks"] + b + for b in a + if not any( + b.hasFlags(sbf) for sbf in self.r.core.stationaryBlockFlagsList + ) ] blockHistories = dbi.getHistories(blocks, params) diff --git a/armi/physics/fuelCycle/fuelHandlerInterface.py b/armi/physics/fuelCycle/fuelHandlerInterface.py index 153549c68..068b3c08d 100644 --- a/armi/physics/fuelCycle/fuelHandlerInterface.py +++ b/armi/physics/fuelCycle/fuelHandlerInterface.py @@ -13,15 +13,12 @@ # limitations under the License. """A place for the FuelHandler's Interface""" -import logging +from armi import runLog from armi import interfaces from armi.utils import plotting -from armi.physics.fuelCycle import fuelHandlers from armi.physics.fuelCycle import fuelHandlerFactory -runLog = logging.getLogger(__name__) - class FuelHandlerInterface(interfaces.Interface): """ diff --git a/armi/physics/fuelCycle/fuelHandlers.py b/armi/physics/fuelCycle/fuelHandlers.py index 91316331b..94acf6aae 100644 --- a/armi/physics/fuelCycle/fuelHandlers.py +++ b/armi/physics/fuelCycle/fuelHandlers.py @@ -29,18 +29,17 @@ import os import re import warnings -import logging 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.physics.fuelCycle.fuelHandlerFactory import fuelHandlerFactory from armi.physics.fuelCycle.fuelHandlerInterface import FuelHandlerInterface -runLog = logging.getLogger(__name__) - class FuelHandler: """ @@ -1160,25 +1159,49 @@ def swapAssemblies(self, a1, a2): a1.moveTo(a2.spatialLocator) a2.moveTo(oldA1Location) - self._swapFluxParam(a1, a2) - def _transferStationaryBlocks(self, assembly1, assembly2): """ Exchange the stationary blocks (e.g. grid plate) between the moving assemblies These blocks in effect are not moved at all. """ - for index in self.cs["stationaryBlocks"]: - # this block swap is designed to ensure that all blocks have the - # correct parents and children structure at the end of the swaps. - tempBlock1 = assembly1[index] - assembly1.remove(tempBlock1) + # grab stationary block flags + sBFList = self.r.core.stationaryBlockFlagsList + + # identify stationary blocks for assembly 1 + a1StationaryBlocks = [ + [block, block.spatialLocator.k] + for block in assembly1 + if any(block.hasFlags(sbf) for sbf in sBFList) + ] + # identify stationary blocks for assembly 2 + a2StationaryBlocks = [ + [block, block.spatialLocator.k] + for block in assembly2 + if any(block.hasFlags(sbf) for sbf in sBFList) + ] - tempBlock2 = assembly2[index] - assembly2.remove(tempBlock2) + # check for any inconsistencies in stationary blocks and ensure alignment + if [block[1] for block in a1StationaryBlocks] != [ + block[1] for block in a2StationaryBlocks + ]: + raise ValueError( + """Different number and/or locations of stationary blocks + between {} (Stationary Blocks: {}) and {} (Stationary Blocks: {}).""".format( + assembly1, a1StationaryBlocks, assembly2, a2StationaryBlocks + ) + ) - assembly1.insert(index, tempBlock2) - assembly2.insert(index, tempBlock1) + # swap stationary blocks + for (assem1Block, assem1BlockIndex), (assem2Block, assem2BlockIndex) in zip( + a1StationaryBlocks, a2StationaryBlocks + ): + # remove stationary blocks + assembly1.remove(assem1Block) + assembly2.remove(assem2Block) + # insert stationary blocks + assembly1.insert(assem1BlockIndex, assem2Block) + assembly2.insert(assem2BlockIndex, assem1Block) def dischargeSwap(self, incoming, outgoing): r""" @@ -1225,93 +1248,6 @@ def dischargeSwap(self, incoming, outgoing): incoming.p.multiplicity = 1 self.r.core.add(incoming, loc) - self._swapFluxParam(incoming, outgoing) - - def _swapFluxParam(self, incoming, outgoing): - """ - Set the flux and power params of the new blocks to that of the old and vice versa. - - This is essential for getting loosely-coupled flux-averaged cross sections from things like - :py:class:`armi.physics.neutronics.crossSectionGroupManager.BlockCollectionAverageFluxWeighted` - - Parameters - ---------- - incoming, outgoing : Assembly - Assembly objects to be swapped - """ - # Find the block-based mesh points for each assembly - meshIn = self.r.core.findAllAxialMeshPoints([incoming], False) - meshOut = self.r.core.findAllAxialMeshPoints([outgoing], False) - - # If the assembly mesh points don't match, the swap won't be easy - if meshIn != meshOut: - runLog.debug( - "{0} and {1} have different meshes, resampling.".format( - incoming, outgoing - ) - ) - - # grab the current values for incoming and outgoing - fluxIn = [b.p.flux for b in incoming] - mgFluxIn = [b.p.mgFlux for b in incoming] - powerIn = [b.p.power for b in incoming] - fluxOut = [b.p.flux for b in outgoing] - mgFluxOut = [b.p.mgFlux for b in outgoing] - powerOut = [b.p.power for b in outgoing] - - # resample incoming to outgoing, and vice versa - fluxOutNew = resampleStepwise(meshIn, fluxIn, meshOut) - mgFluxOutNew = resampleStepwise(meshIn, mgFluxIn, meshOut) - powerOutNew = resampleStepwise(meshIn, powerIn, meshOut, avg=False) - fluxInNew = resampleStepwise(meshOut, fluxOut, meshIn) - mgFluxInNew = resampleStepwise(meshOut, mgFluxOut, meshIn) - powerInNew = resampleStepwise(meshOut, powerOut, meshIn, avg=False) - - # load the new outgoing values into place - for b, flux, mgFlux, power in zip( - outgoing, fluxOutNew, mgFluxOutNew, powerOutNew - ): - b.p.flux = flux - b.p.mgFlux = mgFlux - b.p.power = power - b.p.pdens = power / b.getVolume() - - # load the new incoming values into place - for b, flux, mgFlux, power in zip( - incoming, fluxInNew, mgFluxInNew, powerInNew - ): - b.p.flux = flux - b.p.mgFlux = mgFlux - b.p.power = power - b.p.pdens = power / b.getVolume() - - return - - # Since the axial mesh points match, do the simple swap - for bi, (bIncoming, bOutgoing) in enumerate(zip(incoming, outgoing)): - if bi in self.cs["stationaryBlocks"]: - # stationary blocks are already swapped - continue - - incomingFlux = bIncoming.p.flux - incomingMgFlux = bIncoming.p.mgFlux - incomingPower = bIncoming.p.power - outgoingFlux = bOutgoing.p.flux - outgoingMgFlux = bOutgoing.p.mgFlux - outgoingPower = bOutgoing.p.power - - if outgoingFlux > 0.0: - bIncoming.p.flux = outgoingFlux - bIncoming.p.mgFlux = outgoingMgFlux - bIncoming.p.power = outgoingPower - bIncoming.p.pdens = outgoingPower / bIncoming.getVolume() - - if incomingFlux > 0.0: - bOutgoing.p.flux = incomingFlux - bOutgoing.p.mgFlux = incomingMgFlux - bOutgoing.p.power = incomingPower - bOutgoing.p.pdens = incomingPower / bOutgoing.getVolume() - def swapCascade(self, assemList): """ Perform swaps on a list of assemblies. diff --git a/armi/physics/fuelCycle/tests/test_fuelHandlers.py b/armi/physics/fuelCycle/tests/test_fuelHandlers.py index b7e64b22c..6b314c210 100644 --- a/armi/physics/fuelCycle/tests/test_fuelHandlers.py +++ b/armi/physics/fuelCycle/tests/test_fuelHandlers.py @@ -582,168 +582,238 @@ def test_buildEqRingSchedule(self): locSchedule = fh.buildEqRingSchedule([2, 1]) self.assertEqual(locSchedule, ["002-001", "002-002", "001-001"]) - def test_swapFluxParamSameLength(self): + def test_transferStationaryBlocks(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 + Test the _transferStationaryBlocks method . """ + # grab stationary block flags + sBFList = self.r.core.stationaryBlockFlagsList + # 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 + 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._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_swapFluxParamDifferentLengths(self): + 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) + + def test_transferIncompatibleStationaryBlocks(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. + 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 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) + 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 - # grab the power after the swap - power1f = [b.p.power for b in a1.getBlocks()] - power2f = [b.p.power for b in a2.getBlocks()] + # try to swap stationary blocks between assembly 1 and 2 + fh = fuelHandlers.FuelHandler(self.o) + with self.assertRaises(ValueError): + fh._transferStationaryBlocks(a1, a2) - # validate the swap worked - self.assertEqual(len(a1.getBlocks()), 5) - self.assertEqual(len(a2.getBlocks()), 6) + # re-initialize assemblies + self.setUp() + assems = self.r.core.getAssemblies(Flags.FUEL) + 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: + 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 - self.assertEqual(power1f, [0, 3000, 0, 0, 0]) - self.assertEqual(power2f, [0, 0, 0, 0, 0, 0]) + # try to swap stationary blocks between assembly 1 and 2 + with self.assertRaises(ValueError): + fh._transferStationaryBlocks(a1, a2) - # 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) + def test_dischargeSwap(self): + """ + Test the dischargeSwap method. + """ + # 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.core.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) + + 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.core.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 - for i, b in enumerate(a2.getBlocks()): - self.assertAlmostEqual(b.p.pdens, 0, msg=i) + # try to discharge assembly 1 and replace with assembly 2 + fh = fuelHandlers.FuelHandler(self.o) + with self.assertRaises(ValueError): + fh.dischargeSwap(a2, a1) - # validate the flux after the swap - for b in a1.getBlocks(): - self.assertEqual(b.p.flux, 50000000000.0) + # re-initialize assemblies + self.setUp() + a1 = self.r.core.getAssemblies(Flags.FUEL)[0] + a2 = self.r.core.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: + 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 - for b in a2.getBlocks(): - self.assertEqual(b.p.flux, 50000000000.0) + # try to discharge assembly 1 and replace with assembly 2 + with self.assertRaises(ValueError): + fh.dischargeSwap(a2, a1) class TestFuelPlugin(unittest.TestCase): diff --git a/armi/reactor/reactors.py b/armi/reactor/reactors.py index e006334cc..a5333e5ea 100644 --- a/armi/reactor/reactors.py +++ b/armi/reactor/reactors.py @@ -2301,19 +2301,13 @@ def processLoading(self, cs, dbLoad: bool = False): self.getNuclideCategories() - # some blocks will not move in the core like grid plates... Find them and fix them in place - stationaryBlocks = [] - # look for blocks that should not be shuffled in an assembly. It is assumed that the - # reference assembly has all the fixed block information and it is the same for all assemblies - for i, b in enumerate(self.refAssem): - if b.hasFlags(Flags.GRID_PLATE): - stationaryBlocks.append(i) - # TODO: remove hard-coded assumption of grid plates (T3019) - runLog.extra( - "Detected a grid plate {}. Adding to stationary blocks".format(b) - ) + # Generate list of flags that are to be stationary during assembly shuffling + stationaryBlockFlags = [] + + for stationaryBlockFlagString in cs["stationaryBlockFlags"]: + stationaryBlockFlags.append(Flags.fromString(stationaryBlockFlagString)) - cs["stationaryBlocks"] = stationaryBlocks + self.stationaryBlockFlagsList = stationaryBlockFlags # Perform initial zoning task self.buildZones(cs) diff --git a/armi/reactor/tests/test_reactors.py b/armi/reactor/tests/test_reactors.py index 66d2eb0d6..06e22571f 100644 --- a/armi/reactor/tests/test_reactors.py +++ b/armi/reactor/tests/test_reactors.py @@ -178,7 +178,6 @@ def loadTestReactor( runLog.setVerbosity("error") newSettings = {} - newSettings["stationaryBlocks"] = [] cs = cs.modified(newSettings=newSettings) settings.setMasterCs(cs) diff --git a/armi/settings/fwSettings/globalSettings.py b/armi/settings/fwSettings/globalSettings.py index dd549f532..3ab8caeb1 100644 --- a/armi/settings/fwSettings/globalSettings.py +++ b/armi/settings/fwSettings/globalSettings.py @@ -87,7 +87,7 @@ CONF_SKIP_CYCLES = "skipCycles" CONF_SMALL_RUN = "smallRun" CONF_REALLY_SMALL_RUN = "reallySmallRun" -CONF_STATIONARY_BLOCKS = "stationaryBlocks" +CONF_STATIONARY_BLOCK_FLAGS = "stationaryBlockFlags" CONF_TARGET_K = "targetK" # lots of things use this CONF_TRACK_ASSEMS = "trackAssems" CONF_VERBOSITY = "verbosity" @@ -655,11 +655,11 @@ def defineSettings() -> List[setting.Setting]: description="Clean up files at the beginning of each cycle (BOC)", ), setting.Setting( - CONF_STATIONARY_BLOCKS, - default=[], - label="Stationary Blocks", - description="Blocks with these indices (int values) will not move in " - "moves", + CONF_STATIONARY_BLOCK_FLAGS, + default=["GRID_PLATE"], + label="stationary Block Flags", + description="Blocks with these flags will not move in moves. " + "Used for fuel management.", ), setting.Setting( CONF_TARGET_K, diff --git a/armi/tests/test_mpiFeatures.py b/armi/tests/test_mpiFeatures.py index ea7831e4d..49f203a1a 100644 --- a/armi/tests/test_mpiFeatures.py +++ b/armi/tests/test_mpiFeatures.py @@ -175,7 +175,7 @@ def test_distributeSettings(self): # remove values that are *expected to be* different... # crossSectionControl is removed because unittest is being mean about # comparing dicts... - for key in ["stationaryBlocks", "verbosity", "crossSectionControl"]: + for key in ["stationaryBlockFlags", "verbosity", "crossSectionControl"]: if key in original: del original[key] if key in current: diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst index c07182147..ba573610a 100644 --- a/doc/release/0.2.rst +++ b/doc/release/0.2.rst @@ -14,9 +14,11 @@ What's new in ARMI #. Minor code re-org, moving math utilities into their own module. #. Removed the ``PyYaml`` dependency. #. Removed all bare ``import armi`` statements, for code clarity. +#. Replaced setting ``stationaryBlocks`` (now deprecated) with ``stationaryBlockFlags`` setting (`PR#665 `_) #. Renamed control rod assembly parameters for generic implementations of control rod handling. #. Changed the default Git branch name to ``main``. #. Changed the default value of the ``trackAssems`` setting to ``False``. +#. Removed ``_swapFluxParam`` method (`PR#665 `_) #. Added new ``UserPlugin`` functionality. #. Made the min/max temperatures of ``Material`` curves discoverable. #. Introduction of axial expansion changer to enable component-level axial expansion of assemblies. @@ -29,10 +31,8 @@ What's new in ARMI Bug fixes --------- -#. Supporting swapping assemblies with different block meshes in ``_swapFluxParam``. #. Fixed issues finding ``ISOXX`` files cross-platform. #. Fixed issue with docs not correctly loading their Git submodule in TOX. -#. Fixed issue with ``_swapFluxParams`` failing for assemblies with different blocks (`#566 `_) #. Multiple bug fixes in ``growToFullCore``. #. ``Block.getWettedPerim`` was moved to ``HexBlock``, as that was more accurate. #. ``pathTools.cleanPath()`` is not much more linear, and handles the MPI use-case better.