From 76e36f32d14f314064d259b10ec86204f8cc5b1f Mon Sep 17 00:00:00 2001 From: zachmprince Date: Mon, 14 Oct 2024 16:29:15 -0600 Subject: [PATCH 01/26] Moving pin flux parameters to component level --- armi/physics/neutronics/parameters.py | 76 +++++++++++++++------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/armi/physics/neutronics/parameters.py b/armi/physics/neutronics/parameters.py index 78268b8ba..980cbeeae 100644 --- a/armi/physics/neutronics/parameters.py +++ b/armi/physics/neutronics/parameters.py @@ -21,6 +21,7 @@ from armi.physics.neutronics.settings import CONF_DPA_PER_FLUENCE from armi.reactor import parameters from armi.reactor.blocks import Block +from armi.reactor.components import Component from armi.reactor.parameters import ParamLocation from armi.reactor.parameters.parameterDefinitions import isNumpyArray from armi.reactor.reactors import Core @@ -29,7 +30,11 @@ def getNeutronicsParameterDefinitions(): """Return ParameterDefinitionCollections for each appropriate ArmiObject.""" - return {Block: _getNeutronicsBlockParams(), Core: _getNeutronicsCoreParams()} + return { + Block: _getNeutronicsBlockParams(), + Component: _getNeutronicsComponentParams(), + Core: _getNeutronicsCoreParams(), + } def _getNeutronicsBlockParams(): @@ -157,39 +162,6 @@ def _getNeutronicsBlockParams(): default=None, ) - # Not anointing the pin fluxes as a MG quantity, since it has an extra dimension, which - # could lead to issues, depending on how the multiGroupQuantities category gets used - pb.defParam( - "pinMgFluxes", - units=f"n/{units.CM}^2/{units.SECONDS}", - description=""" - The block-level pin multigroup fluxes. pinMgFluxes[i, g] represents the flux in group g - for pin i. Flux units are the standard n/cm^2/s. The "ARMI pin ordering" is used, which - is counter-clockwise from 3 o'clock. - """, - categories=[parameters.Category.pinQuantities], - saveToDB=True, - default=None, - ) - - pb.defParam( - "pinMgFluxesAdj", - units=units.UNITLESS, - description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", - categories=[parameters.Category.pinQuantities], - saveToDB=False, - default=None, - ) - - pb.defParam( - "pinMgFluxesGamma", - units=f"#/{units.CM}^2/{units.SECONDS}", - description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", - categories=[parameters.Category.pinQuantities, parameters.Category.gamma], - saveToDB=False, - default=None, - ) - pb.defParam( "axialPowerProfile", units=f"{units.WATTS}/{units.CM}^3", @@ -745,6 +717,42 @@ def _getNeutronicsBlockParams(): return pDefs +def _getNeutronicsComponentParams(): + pDefs = parameters.ParameterDefinitionCollection() + with pDefs.createBuilder() as pb: + + pb.defParam( + "pinMgFluxes", + units=f"n/{units.CM}^2/{units.SECONDS}", + description=""" + The component-level pin multigroup fluxes. pinMgFluxes[i, g] represents the flux in group g + for pin i. Flux units are the standard n/cm^2/s. The "ARMI pin ordering" is used, which + is counter-clockwise from 3 o'clock. + """, + categories=[parameters.Category.pinQuantities], + saveToDB=True, + default=None, + ) + + pb.defParam( + "pinMgFluxesAdj", + units=units.UNITLESS, + description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", + categories=[parameters.Category.pinQuantities], + saveToDB=False, + default=None, + ) + + pb.defParam( + "pinMgFluxesGamma", + units=f"#/{units.CM}^2/{units.SECONDS}", + description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", + categories=[parameters.Category.pinQuantities, parameters.Category.gamma], + saveToDB=False, + default=None, + ) + + def _getNeutronicsCoreParams(): pDefs = parameters.ParameterDefinitionCollection() From 80b673e3cf69e65fb95a1101527387b3ad87451b Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 15 Oct 2024 15:20:37 -0700 Subject: [PATCH 02/26] add pinPercentBu component param --- armi/reactor/components/componentParameters.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 14292b126..abb8a6623 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -16,6 +16,7 @@ from armi.reactor import parameters from armi.reactor.parameters import ParamLocation from armi.utils import units +from armi.reactor.parameters.parameterDefinitions import isNumpyArray def getComponentParameterDefinitions(): @@ -138,6 +139,16 @@ def _assignTDFrac(self, val): default=1, setter=_assignTDFrac, ) + + pb.defParam( + "pinPercentBu", + setter=isNumpyArray("pinPercentBu"), + units=f"{units.PERCENT_FIMA}", + description="Pin-wise burnup as a percentage of initial (heavy) metal atoms.", + location=ParamLocation.AVERAGE, + saveToDB=True, + ) + return pDefs From cf7ea6c4dde11b168bc4ed99963785fb9e0461fd Mon Sep 17 00:00:00 2001 From: zachmprince Date: Wed, 16 Oct 2024 08:56:58 -0600 Subject: [PATCH 03/26] Revert "Moving pin flux parameters to component level" This reverts commit 76e36f32d14f314064d259b10ec86204f8cc5b1f. --- armi/physics/neutronics/parameters.py | 76 ++++++++++++--------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/armi/physics/neutronics/parameters.py b/armi/physics/neutronics/parameters.py index 980cbeeae..78268b8ba 100644 --- a/armi/physics/neutronics/parameters.py +++ b/armi/physics/neutronics/parameters.py @@ -21,7 +21,6 @@ from armi.physics.neutronics.settings import CONF_DPA_PER_FLUENCE from armi.reactor import parameters from armi.reactor.blocks import Block -from armi.reactor.components import Component from armi.reactor.parameters import ParamLocation from armi.reactor.parameters.parameterDefinitions import isNumpyArray from armi.reactor.reactors import Core @@ -30,11 +29,7 @@ def getNeutronicsParameterDefinitions(): """Return ParameterDefinitionCollections for each appropriate ArmiObject.""" - return { - Block: _getNeutronicsBlockParams(), - Component: _getNeutronicsComponentParams(), - Core: _getNeutronicsCoreParams(), - } + return {Block: _getNeutronicsBlockParams(), Core: _getNeutronicsCoreParams()} def _getNeutronicsBlockParams(): @@ -162,6 +157,39 @@ def _getNeutronicsBlockParams(): default=None, ) + # Not anointing the pin fluxes as a MG quantity, since it has an extra dimension, which + # could lead to issues, depending on how the multiGroupQuantities category gets used + pb.defParam( + "pinMgFluxes", + units=f"n/{units.CM}^2/{units.SECONDS}", + description=""" + The block-level pin multigroup fluxes. pinMgFluxes[i, g] represents the flux in group g + for pin i. Flux units are the standard n/cm^2/s. The "ARMI pin ordering" is used, which + is counter-clockwise from 3 o'clock. + """, + categories=[parameters.Category.pinQuantities], + saveToDB=True, + default=None, + ) + + pb.defParam( + "pinMgFluxesAdj", + units=units.UNITLESS, + description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", + categories=[parameters.Category.pinQuantities], + saveToDB=False, + default=None, + ) + + pb.defParam( + "pinMgFluxesGamma", + units=f"#/{units.CM}^2/{units.SECONDS}", + description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", + categories=[parameters.Category.pinQuantities, parameters.Category.gamma], + saveToDB=False, + default=None, + ) + pb.defParam( "axialPowerProfile", units=f"{units.WATTS}/{units.CM}^3", @@ -717,42 +745,6 @@ def _getNeutronicsBlockParams(): return pDefs -def _getNeutronicsComponentParams(): - pDefs = parameters.ParameterDefinitionCollection() - with pDefs.createBuilder() as pb: - - pb.defParam( - "pinMgFluxes", - units=f"n/{units.CM}^2/{units.SECONDS}", - description=""" - The component-level pin multigroup fluxes. pinMgFluxes[i, g] represents the flux in group g - for pin i. Flux units are the standard n/cm^2/s. The "ARMI pin ordering" is used, which - is counter-clockwise from 3 o'clock. - """, - categories=[parameters.Category.pinQuantities], - saveToDB=True, - default=None, - ) - - pb.defParam( - "pinMgFluxesAdj", - units=units.UNITLESS, - description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", - categories=[parameters.Category.pinQuantities], - saveToDB=False, - default=None, - ) - - pb.defParam( - "pinMgFluxesGamma", - units=f"#/{units.CM}^2/{units.SECONDS}", - description="should be a blank 3-D array, but re-defined later (nPins x ng x nAxialSegments)", - categories=[parameters.Category.pinQuantities, parameters.Category.gamma], - saveToDB=False, - default=None, - ) - - def _getNeutronicsCoreParams(): pDefs = parameters.ParameterDefinitionCollection() From 87a1c2ac0139e88a59169a17b59abaa421a2f114 Mon Sep 17 00:00:00 2001 From: zachmprince Date: Wed, 16 Oct 2024 12:33:48 -0600 Subject: [PATCH 04/26] Adding component method to retrieve pin fluxes --- armi/reactor/components/component.py | 76 +++++++++++++++++++++++++-- armi/reactor/tests/test_components.py | 42 +++++++++++++++ 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index e00af7860..c3f1f473b 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -21,6 +21,7 @@ import re import numpy as np +from typing import Optional from armi import materials from armi import runLog @@ -31,6 +32,7 @@ from armi.nucDirectory import nuclideBases from armi.reactor import composites from armi.reactor import flags +from armi.reactor import grids from armi.reactor import parameters from armi.reactor.components import componentParameters from armi.utils import densityTools @@ -722,9 +724,9 @@ def setNumberDensity(self, nucName, val): self.p.numberDensities[nucName] = val self.p.assigned = parameters.SINCE_ANYTHING # necessary for syncMpiState - parameters.ALL_DEFINITIONS[ - "numberDensities" - ].assigned = parameters.SINCE_ANYTHING + parameters.ALL_DEFINITIONS["numberDensities"].assigned = ( + parameters.SINCE_ANYTHING + ) def setNumberDensities(self, numberDensities): """ @@ -1262,6 +1264,74 @@ def getIntegratedMgFlux(self, adjoint=False, gamma=False): return pinFluxes[self.p.pinNum - 1] * self.getVolume() + def getPinMgFluxes( + self, adjoint: Optional[bool] = False, gamma: Optional[bool] = False + ) -> np.ndarray: + """Retrieves the pin multigroup fluxes for the component. + + Parameters + ---------- + adjoint : bool, optional + Return adjoint flux instead of real + gamma : bool, optional + Whether to return the neutron flux or the gamma flux. + + Returns + ------- + np.ndarray + A ``(N, nGroup)`` array of pin multigroup fluxes, where ``N`` is the + equivalent to the multiplicity of the component (``self.p.mult``) + and ``nGroup`` is the number of energy groups of the flux. + + Raises + ------ + ValueError + If the location(s) of the component are not aligned with pin indices + from the block. This would happen if this component is not actually + a pin. + """ + # Get the (i, j, k) location of all pins from the parent block + # FIXME: This should be changed to just using Block.getPinLocations once Drew's PR is merged + indicesAll = [] + for clad in self.parent.getChildrenWithFlags(flags.Flags.CLAD): + if isinstance(clad.spatialLocator, grids.MultiIndexLocation): + indicesAll.extend(clad.spatialLocator.indices) + else: + indicesAll.append(clad.spatialLocator.indices) + + # Retrieve the indices of this component + indices = self.spatialLocator.indices + if not isinstance(indices, list): + indices = [indices] + + # Map this component's indices to block's pin indices + getIndex = lambda ind: next( + (i for i, indA in enumerate(indicesAll) if np.all(ind == indA)), + None, + ) + indexMap = list(map(getIndex, indices)) + print(len(indices)) + print(len(indicesAll)) + if None in indexMap: + msg = f"Failed to retrieve pin indices for component {self}." + runLog.error(msg) + raise ValueError(msg) + + # Get the parameter name we are trying to retrieve + if gamma: + if adjoint: + raise ValueError("Adjoint gamma flux is currently unsupported.") + else: + param = "pinMgFluxesGamma" + else: + if adjoint: + param = "pinMgFluxesAdj" + else: + param = "pinMgFluxes" + + # Return pin fluxes + return self.parent.p.get(param)[indexMap] + def density(self) -> float: """Returns the mass density of the object in g/cc.""" density = composites.Composite.density(self) diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 46b936091..a8351870c 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -15,12 +15,15 @@ """Tests functionalities of components within ARMI.""" import copy import math +import numpy as np +from numpy.testing import assert_equal import unittest from armi.materials import air, alloy200 from armi.materials.material import Material from armi.reactor import components from armi.reactor import flags +from armi.reactor.blocks import Block from armi.reactor.components import ( Component, UnshapedComponent, @@ -1812,3 +1815,42 @@ def test_finalizeLoadDBAdjustsTD(self): comp.p.theoreticalDensityFrac = tdFrac comp.finalizeLoadingFromDB() self.assertEqual(comp.material.getTD(), tdFrac) + + +class TestPinQuantities(unittest.TestCase): + """Test methods that involve retrieval of pin quantities.""" + + def setUp(self): + self.r = loadTestReactor()[1] + + def test_getPinMgFluxes(self): + """Test proper retrieval of pin multigroup flux for fuel component.""" + # Get a fuel block and its fuel component from the core + fuelBlock: Block = self.r.core.getFirstBlock(flags.Flags.FUEL) + fuelComponent: Component = fuelBlock.getComponent(flags.Flags.FUEL) + numPins = int(fuelComponent.p.mult) + self.assertGreater(numPins, 1) + + # Set pin fluxes at block level + fuelBlock.initializePinLocations() + pinMgFluxes = np.random.rand(numPins, 33) + pinMgFluxesAdj = np.random.rand(numPins, 33) + pinMgFluxesGamma = np.random.rand(numPins, 33) + fuelBlock.setPinMgFluxes(pinMgFluxes) + fuelBlock.setPinMgFluxes(pinMgFluxesAdj, adjoint=True) + fuelBlock.setPinMgFluxes(pinMgFluxesGamma, gamma=True) + + # Retrieve from component to ensure they match + simPinMgFluxes = fuelComponent.getPinMgFluxes() + simPinMgFluxesAdj = fuelComponent.getPinMgFluxes(adjoint=True) + simPinMgFluxesGamma = fuelComponent.getPinMgFluxes(gamma=True) + assert_equal(pinMgFluxes, simPinMgFluxes) + assert_equal(pinMgFluxesAdj, simPinMgFluxesAdj) + assert_equal(pinMgFluxesGamma, simPinMgFluxesGamma) + + # Get a coolant block and replace the spatialLocator in the component to test exception raised + coolantBlock: Block = self.r.core.getFirstBlock(flags.Flags.CONTROL) + coolantComponent: Component = coolantBlock.getComponent(flags.Flags.CONTROL) + coolantComponent.spatialLocator = fuelComponent.spatialLocator + with self.assertRaises(ValueError): + coolantComponent.getPinMgFluxes() From 6b69a6b345a84344fbee92ad794073b01b0a2e41 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 16 Oct 2024 15:38:09 -0700 Subject: [PATCH 05/26] add c.p.molesHmBOL and populate it --- armi/reactor/blocks.py | 3 +++ armi/reactor/components/componentParameters.py | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 9210ca5bb..61fd94331 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -774,6 +774,9 @@ def completeInitialLoading(self, bolBlock=None): self.p.puFrac = ( self.getPuMoles() / self.p.molesHmBOL if self.p.molesHmBOL > 0.0 else 0.0 ) + ## populate molesHmBOL on components within the block as well + for c in self.getChildren(): + c.p.molesHmBOL = c.getHMMoles() try: # non-pinned reactors (or ones without cladding) will not use smear density diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index abb8a6623..7c43e92de 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -145,8 +145,12 @@ def _assignTDFrac(self, val): setter=isNumpyArray("pinPercentBu"), units=f"{units.PERCENT_FIMA}", description="Pin-wise burnup as a percentage of initial (heavy) metal atoms.", - location=ParamLocation.AVERAGE, - saveToDB=True, + ) + + pb.defParam( + "molesHmBOL", + units=f"{units.MOLES}", + description="Total number of moles of heavy metal at BOL.", ) return pDefs From 7041ee46ac5c09e1f0d6512bb079e8dec58eeb77 Mon Sep 17 00:00:00 2001 From: aalberti Date: Fri, 18 Oct 2024 13:40:40 -0700 Subject: [PATCH 06/26] add c.p.puFrac calc, move b.getPuMoles up the composite tree, add a pu frac comp params --- armi/reactor/blocks.py | 13 +------------ armi/reactor/components/componentParameters.py | 14 ++++++++++++++ armi/reactor/composites.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 61fd94331..8c2bc2c62 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -777,6 +777,7 @@ def completeInitialLoading(self, bolBlock=None): ## populate molesHmBOL on components within the block as well for c in self.getChildren(): c.p.molesHmBOL = c.getHMMoles() + c.p.puFrac = self.getPuMoles() / c.p.molesHmBOL if c.p.molesHmBOL > 0.0 else 0.0 try: # non-pinned reactors (or ones without cladding) will not use smear density @@ -1710,18 +1711,6 @@ def getBoronMassEnrich(self): return 0.0 return b10 / total - def getPuMoles(self): - """Returns total number of moles of Pu isotopes.""" - nucNames = [nuc.name for nuc in elements.byZ[94].nuclides] - puN = sum(self.getNuclideNumberDensities(nucNames)) - - return ( - puN - / units.MOLES_PER_CC_TO_ATOMS_PER_BARN_CM - * self.getVolume() - * self.getSymmetryFactor() - ) - def getUraniumMassEnrich(self): """Returns U-235 mass fraction assuming U-235 and U-238 only.""" u5 = self.getMass("U235") diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 7c43e92de..df66df264 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -153,6 +153,20 @@ def _assignTDFrac(self, val): description="Total number of moles of heavy metal at BOL.", ) + pb.defParam( + "pinPuFrac", + setter=isNumpyArray("pinPuFrac"), + units=units.UNITLESS, + description="Current pin-wise Pu fraction. Calculated as the ratio of Pu mass to total HM mass.", + ) + + pb.defParam( + "puFrac", + default = 0.0, + units=units.UNITLESS, + description="Current average Pu fraction. Calculated as the ratio of Pu mass to total HM mass.", + ) + return pDefs diff --git a/armi/reactor/composites.py b/armi/reactor/composites.py index 3ae44b786..bbcf6cfb0 100644 --- a/armi/reactor/composites.py +++ b/armi/reactor/composites.py @@ -3118,6 +3118,18 @@ def getBoundingCircleOuterDiameter(self, Tc=None, cold=False): getter = operator.methodcaller("getBoundingCircleOuterDiameter", Tc, cold) return sum(map(getter, self)) + def getPuMoles(self): + """Returns total number of moles of Pu isotopes.""" + nucNames = [nuc.name for nuc in elements.byZ[94].nuclides] + puN = sum(self.getNuclideNumberDensities(nucNames)) + + return ( + puN + / units.MOLES_PER_CC_TO_ATOMS_PER_BARN_CM + * self.getVolume() + * self.getSymmetryFactor() + ) + class StateRetainer: """ From 33ec45bd58f773c6eecf45b48fcd3c71f7b16896 Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 22 Oct 2024 16:35:03 +0000 Subject: [PATCH 07/26] add defaults to pin level params --- armi/reactor/components/componentParameters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index df66df264..7d0f959bf 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -145,6 +145,7 @@ def _assignTDFrac(self, val): setter=isNumpyArray("pinPercentBu"), units=f"{units.PERCENT_FIMA}", description="Pin-wise burnup as a percentage of initial (heavy) metal atoms.", + default=None, ) pb.defParam( @@ -158,6 +159,7 @@ def _assignTDFrac(self, val): setter=isNumpyArray("pinPuFrac"), units=units.UNITLESS, description="Current pin-wise Pu fraction. Calculated as the ratio of Pu mass to total HM mass.", + default=None, ) pb.defParam( From 98c13f50e74b3607c3bb063c11ea9a166e83ccc9 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 23 Oct 2024 17:14:19 +0000 Subject: [PATCH 08/26] comment out print statements --- armi/reactor/components/component.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index c3f1f473b..68e05d64a 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -1310,8 +1310,8 @@ def getPinMgFluxes( None, ) indexMap = list(map(getIndex, indices)) - print(len(indices)) - print(len(indicesAll)) + # print(len(indices)) + # print(len(indicesAll)) if None in indexMap: msg = f"Failed to retrieve pin indices for component {self}." runLog.error(msg) From 29f587848815fa7359cf831bf32d865af4bb867a Mon Sep 17 00:00:00 2001 From: aalberti Date: Fri, 25 Oct 2024 22:36:10 +0000 Subject: [PATCH 09/26] make history tracker respect control assemblies --- armi/bookkeeping/historyTracker.py | 54 ++++++++++--------- armi/bookkeeping/tests/test_historyTracker.py | 4 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/armi/bookkeeping/historyTracker.py b/armi/bookkeeping/historyTracker.py index c25f6eb0c..512c5e16c 100644 --- a/armi/bookkeeping/historyTracker.py +++ b/armi/bookkeeping/historyTracker.py @@ -67,7 +67,7 @@ def getHistoryParams(self): See :ref:`detail-assems`. """ -from typing import Tuple +from typing import TYPE_CHECKING import traceback from armi import interfaces @@ -79,6 +79,10 @@ def getHistoryParams(self): ORDER = 2 * interfaces.STACK_ORDER.BEFORE + interfaces.STACK_ORDER.BOOKKEEPING +if TYPE_CHECKING: + from armi.reactor.blocks import Block + from armi.reactor.assemblies import Assembly + def describeInterfaces(cs): """Function for exposing interface(s) to other code.""" @@ -120,6 +124,8 @@ class HistoryTrackerInterface(interfaces.Interface): name = "history" + DETAILED_ASSEMBLY_FLAGS = [Flags.FUEL, Flags.CONTROL] + def __init__(self, r, cs): """ HistoryTracker that uses the database to look up parameter history rather than @@ -149,7 +155,7 @@ def interactBOC(self, cycle=None): """Look for any new assemblies that are asked for and add them to tracking.""" self.addDetailAssemsByAssemNums() if self.cs["detailAllAssems"]: - self.addAllFuelAssems() + self.addAllDetailedAssems() def interactEOL(self): """Generate the history reports.""" @@ -174,16 +180,16 @@ def addDetailAssembliesBOL(self): self.addDetailAssembly(a) if self.cs["detailAllAssems"]: - self.addAllFuelAssems() + self.addAllDetailedAssems() # This also gets called at BOC but we still # do it here for operators that do not call BOC. self.addDetailAssemsByAssemNums() - def addAllFuelAssems(self): - """Add all fuel assems as detail assems.""" + def addAllDetailedAssems(self): + """Add all assems who have the DETAILED_ASSEMBLY_FLAGS as detail assems.""" for a in self.r.core: - if a.hasFlags(Flags.FUEL): + if a.hasFlags(self.DETAILED_ASSEMBLY_FLAGS): self.addDetailAssembly(a) def addDetailAssemsByAssemNums(self): @@ -239,13 +245,13 @@ def getTrackedParams(self): trackedParams.add(newParam) return sorted(trackedParams) - def addDetailAssembly(self, a): + def addDetailAssembly(self, a: "Assembly"): """Track the name of assemblies that are flagged for detailed treatment.""" aName = a.getName() if aName not in self.detailAssemblyNames: self.detailAssemblyNames.append(aName) - def getDetailAssemblies(self): + def getDetailAssemblies(self) -> list["Assembly"]: """Returns the assemblies that have been signaled as detail assemblies.""" assems = [] if not self.detailAssemblyNames: @@ -262,7 +268,7 @@ def getDetailAssemblies(self): ) return assems - def getDetailBlocks(self): + def getDetailBlocks(self) -> list["Block"]: """Get all blocks in all detail assemblies.""" return [block for a in self.getDetailAssemblies() for block in a] @@ -284,7 +290,7 @@ def filterTimeIndices(self, timeIndices, boc=False, moc=False, eoc=False): return filtered - def writeAssemHistory(self, a, fName=""): + def writeAssemHistory(self, a: "Assembly", fName: str = ""): """Write the assembly history report to a text file.""" fName = fName or self._getAssemHistoryFileName(a) dbi = self.getInterface("database") @@ -376,7 +382,7 @@ def unloadBlockHistoryVals(self): """Remove all cached db reads.""" self._preloadedBlockHistory = None - def getBlockHistoryVal(self, name: str, paramName: str, ts: Tuple[int, int]): + def getBlockHistoryVal(self, name: str, paramName: str, ts: tuple[int, int]): """ Use the database interface to return the parameter values for the supplied block names, and timesteps. @@ -425,28 +431,28 @@ def getBlockHistoryVal(self, name: str, paramName: str, ts: Tuple[int, int]): raise return val - def _isCurrentTimeStep(self, ts: Tuple[int, int]): + def _isCurrentTimeStep(self, ts: tuple[int, int]) -> bool: """Return True if the timestep requested is the current time step.""" return ts == (self.r.p.cycle, self.r.p.timeNode) - def _databaseHasDataForTimeStep(self, ts): + def _databaseHasDataForTimeStep(self, ts) -> bool: """Return True if the database has data for the requested time step.""" dbi = self.getInterface("database") return ts in dbi.database.genTimeSteps() - def getTimeSteps(self, a=None): - r""" - Return list of time steps values (in years) that are available. + def getTimeSteps(self, a: "Assembly" = None) -> list[float]: + """ + Given a fuel assembly, return list of time steps values (in years) that are available. Parameters ---------- - a : Assembly object, optional - An assembly object designated a detail assem. If passed, only timesteps + a + A fuel assembly that has been designated a detail assem. If passed, only timesteps where this assembly is in the core will be tracked. Returns ------- - timeSteps : list + timeSteps times in years that are available in the history See Also @@ -465,15 +471,13 @@ def getTimeSteps(self, a=None): return timeInYears @staticmethod - def _getBlockInAssembly(a): - """Get a representative fuel block from an assembly.""" + def _getBlockInAssembly(a: "Assembly") -> "Block": + """Get a representative fuel block from a fuel assembly.""" b = a.getFirstBlock(Flags.FUEL) if not b: - # there is a problem, it doesn't look like we have a fueled assembly - # but that is all we track... what is it? Throw an error - runLog.warning("Assembly {} does not contain fuel".format(a)) + runLog.error("Assembly {} does not contain fuel".format(a)) for b in a: - runLog.warning("Block {}".format(b)) + runLog.error("Block {}".format(b)) raise RuntimeError( "A tracked assembly does not contain fuel and has caused this error, see the details in stdout." ) diff --git a/armi/bookkeeping/tests/test_historyTracker.py b/armi/bookkeeping/tests/test_historyTracker.py index 3a18f7ac3..0b615a37d 100644 --- a/armi/bookkeeping/tests/test_historyTracker.py +++ b/armi/bookkeeping/tests/test_historyTracker.py @@ -231,8 +231,8 @@ def test_historyReport(self): # test that detailAssemblyNames() is working self.assertEqual(len(history.detailAssemblyNames), 1) - history.addAllFuelAssems() - self.assertEqual(len(history.detailAssemblyNames), 51) + history.addAllDetailedAssems() + self.assertEqual(len(history.detailAssemblyNames), 54) def test_getBlockInAssembly(self): history = self.o.getInterface("history") From 67ced7907930ccc6bf3f562421b5aeef2be39a51 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 30 Oct 2024 19:11:46 +0000 Subject: [PATCH 10/26] start rm'img things --- armi/reactor/blocks.py | 56 ------------------------------------------ 1 file changed, 56 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 6df44ca47..574e59af4 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -1428,62 +1428,6 @@ def updateComponentDims(self): except NotImplementedError: runLog.warning("{0} has no updatedDims method -- skipping".format(c)) - def breakFuelComponentsIntoIndividuals(self): - """ - Split block-level components (in fuel blocks) into pin-level components. - - The fuel component will be broken up according to its multiplicity. - - Order matters! The first pin component will be located at a particular (x, y), which - will be used in the fluxRecon module to determine the interpolated flux. - - The fuel will become fuel001 through fuel169 if there are 169 pins. - """ - fuels = self.getChildrenWithFlags(Flags.FUEL) - if len(fuels) != 1: - runLog.error( - "This block contains {0} fuel components: {1}".format(len(fuels), fuels) - ) - raise RuntimeError( - "Cannot break {0} into multiple fuel components b/c there is not a single fuel" - " component.".format(self) - ) - - fuel = fuels[0] - fuelFlags = fuel.p.flags - nPins = self.getNumPins() - runLog.info( - "Creating {} individual {} components on {}".format(nPins, fuel, self) - ) - - # Handle all other components that may be linked to the fuel multiplicity - # by unlinking them and setting them directly. - # TODO: What about other (actual) dimensions? This is a limitation in that only fuel - # components are duplicated, and not the entire pin. It is also a reasonable assumption with - # current/historical usage of ARMI. - for comp, dim in self.getComponentsThatAreLinkedTo(fuel, "mult"): - comp.setDimension(dim, nPins) - - # finish the first pin as a single pin - fuel.setDimension("mult", 1) - fuel.setName("fuel001") - fuel.p.pinNum = 1 - - # create all the new pin components and add them to the block with 'fuel001' names - for i in range(nPins - 1): - # wow, only use of a non-deepcopy - newC = copy.copy(fuel) - newC.setName("fuel{0:03d}".format(i + 2)) # start with 002. - newC.p.pinNum = i + 2 - self.add(newC) - - # update moles at BOL for each pin - self.p.molesHmBOLByPin = [] - for pin in self.iterComponents(Flags.FUEL): - # Update the fuel component flags to be the same as before the split (i.e., DEPLETABLE) - pin.p.flags = fuelFlags - self.p.molesHmBOLByPin.append(pin.getHMMoles()) - pin.p.massHmBOL /= nPins def getIntegratedMgFlux(self, adjoint=False, gamma=False): """ From 829b40b28194fcd0c64d50fbaac96aa7928e4f73 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 30 Oct 2024 22:07:36 +0000 Subject: [PATCH 11/26] rm call to non-existent method --- armi/physics/fuelCycle/tests/test_assemblyRotationAlgorithms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/armi/physics/fuelCycle/tests/test_assemblyRotationAlgorithms.py b/armi/physics/fuelCycle/tests/test_assemblyRotationAlgorithms.py index c750717a1..bbbdec230 100644 --- a/armi/physics/fuelCycle/tests/test_assemblyRotationAlgorithms.py +++ b/armi/physics/fuelCycle/tests/test_assemblyRotationAlgorithms.py @@ -37,7 +37,6 @@ def test_buReducingAssemblyRotation(self): # 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 From 7c32499b36fd06fb954d1ce6fa12e67a84041807 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 30 Oct 2024 22:12:51 +0000 Subject: [PATCH 12/26] continue rm'img things --- armi/reactor/tests/test_blocks.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index 07f7eb09b..34bc61451 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -1739,14 +1739,6 @@ def _testDimensionsAreLinked(self): self.block.getComponent(Flags.INTERCOOLANT).getDimension("ip"), ) - def test_breakFuelComponentsIntoIndividuals(self): - fuel = self.block.getComponent(Flags.FUEL) - mult = fuel.getDimension("mult") - self.assertGreater(mult, 1.0) - self.block.completeInitialLoading() - self.block.breakFuelComponentsIntoIndividuals() - self.assertEqual(fuel.getDimension("mult"), 1.0) - def test_pinMgFluxes(self): """ Test setting/getting of pin-wise fluxes. From 5319cb7f66388262376fede61981bd892a1d2e09 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 30 Oct 2024 22:30:13 +0000 Subject: [PATCH 13/26] fix black and ruff --- armi/reactor/components/component.py | 6 +++--- armi/reactor/components/componentParameters.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index 68e05d64a..7ea21626e 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -724,9 +724,9 @@ def setNumberDensity(self, nucName, val): self.p.numberDensities[nucName] = val self.p.assigned = parameters.SINCE_ANYTHING # necessary for syncMpiState - parameters.ALL_DEFINITIONS["numberDensities"].assigned = ( - parameters.SINCE_ANYTHING - ) + parameters.ALL_DEFINITIONS[ + "numberDensities" + ].assigned = parameters.SINCE_ANYTHING def setNumberDensities(self, numberDensities): """ diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 7d0f959bf..ce3abdcb9 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -164,7 +164,7 @@ def _assignTDFrac(self, val): pb.defParam( "puFrac", - default = 0.0, + default=0.0, units=units.UNITLESS, description="Current average Pu fraction. Calculated as the ratio of Pu mass to total HM mass.", ) From 1408ac6c14b6979ed0cd83df6a8941a0b0abe91e Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 30 Oct 2024 22:32:20 +0000 Subject: [PATCH 14/26] apparently "git add ." missed this.... --- armi/reactor/blocks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index bebde05c9..12ac8d6ec 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -29,7 +29,6 @@ from armi import nuclideBases from armi import runLog from armi.bookkeeping import report -from armi.nucDirectory import elements from armi.nuclearDataIO import xsCollections from armi.physics.neutronics import GAMMA from armi.physics.neutronics import NEUTRON @@ -784,7 +783,9 @@ def completeInitialLoading(self, bolBlock=None): ## populate molesHmBOL on components within the block as well for c in self.getChildren(): c.p.molesHmBOL = c.getHMMoles() - c.p.puFrac = self.getPuMoles() / c.p.molesHmBOL if c.p.molesHmBOL > 0.0 else 0.0 + c.p.puFrac = ( + self.getPuMoles() / c.p.molesHmBOL if c.p.molesHmBOL > 0.0 else 0.0 + ) try: # non-pinned reactors (or ones without cladding) will not use smear density @@ -1434,7 +1435,6 @@ def updateComponentDims(self): except NotImplementedError: runLog.warning("{0} has no updatedDims method -- skipping".format(c)) - def getIntegratedMgFlux(self, adjoint=False, gamma=False): """ Return the volume integrated multigroup neutron tracklength in [n-cm/s]. From fd4e9ff38f51fd18bce17e96474da41ce6723ca5 Mon Sep 17 00:00:00 2001 From: aalberti Date: Thu, 31 Oct 2024 01:59:34 +0000 Subject: [PATCH 15/26] fix unit tests --- armi/bookkeeping/db/tests/test_comparedb3.py | 2 +- armi/reactor/blocks.py | 13 ++++++------- armi/reactor/components/componentParameters.py | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/armi/bookkeeping/db/tests/test_comparedb3.py b/armi/bookkeeping/db/tests/test_comparedb3.py index a19b94950..e5fd17f15 100644 --- a/armi/bookkeeping/db/tests/test_comparedb3.py +++ b/armi/bookkeeping/db/tests/test_comparedb3.py @@ -181,7 +181,7 @@ def test_compareDatabaseSim(self): dbs[1]._fullPath, timestepCompare=[(0, 0), (0, 1)], ) - self.assertEqual(len(diffs.diffs), 477) + self.assertEqual(len(diffs.diffs), 501) # Cycle length is only diff (x3) self.assertEqual(diffs.nDiffs(), 3) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 12ac8d6ec..05722cdf1 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -780,12 +780,6 @@ def completeInitialLoading(self, bolBlock=None): self.p.puFrac = ( self.getPuMoles() / self.p.molesHmBOL if self.p.molesHmBOL > 0.0 else 0.0 ) - ## populate molesHmBOL on components within the block as well - for c in self.getChildren(): - c.p.molesHmBOL = c.getHMMoles() - c.p.puFrac = ( - self.getPuMoles() / c.p.molesHmBOL if c.p.molesHmBOL > 0.0 else 0.0 - ) try: # non-pinned reactors (or ones without cladding) will not use smear density @@ -799,9 +793,14 @@ def completeInitialLoading(self, bolBlock=None): for child in self: hmMass = child.getHMMass() * sf massHmBOL += hmMass - # Components have a massHmBOL parameter but not every composite will + # Components have the following parameters but not every composite will + # massHmBOL, molesHmBOL, puFrac if isinstance(child, components.Component): child.p.massHmBOL = hmMass + child.p.molesHmBOL = child.getHMMoles() + child.p.puFrac = ( + self.getPuMoles() / child.p.molesHmBOL if child.p.molesHmBOL > 0.0 else 0.0 + ) self.p.massHmBOL = massHmBOL diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index ce3abdcb9..33808d85d 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -151,6 +151,7 @@ def _assignTDFrac(self, val): pb.defParam( "molesHmBOL", units=f"{units.MOLES}", + default=0.0, description="Total number of moles of heavy metal at BOL.", ) From f7bf4a53ee4d48f301b6f05b3aa5d3126cdc5dba Mon Sep 17 00:00:00 2001 From: aalberti Date: Thu, 31 Oct 2024 02:04:29 +0000 Subject: [PATCH 16/26] ugh. why is black kicking my butt today --- armi/reactor/blocks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 05722cdf1..df048229a 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -799,7 +799,9 @@ def completeInitialLoading(self, bolBlock=None): child.p.massHmBOL = hmMass child.p.molesHmBOL = child.getHMMoles() child.p.puFrac = ( - self.getPuMoles() / child.p.molesHmBOL if child.p.molesHmBOL > 0.0 else 0.0 + self.getPuMoles() / child.p.molesHmBOL + if child.p.molesHmBOL > 0.0 + else 0.0 ) self.p.massHmBOL = massHmBOL From ac34e2ba49ca3bd142c9e7bac49146aab96c38b0 Mon Sep 17 00:00:00 2001 From: John Stilley <1831479+john-science@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:16:50 -0700 Subject: [PATCH 17/26] Update armi/reactor/components/component.py --- armi/reactor/components/component.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index 7ea21626e..a948f0377 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -1310,8 +1310,6 @@ def getPinMgFluxes( None, ) indexMap = list(map(getIndex, indices)) - # print(len(indices)) - # print(len(indicesAll)) if None in indexMap: msg = f"Failed to retrieve pin indices for component {self}." runLog.error(msg) From ba0acf3b4884607d3491aede78948491fae6868f Mon Sep 17 00:00:00 2001 From: zachmprince Date: Fri, 1 Nov 2024 14:29:01 -0600 Subject: [PATCH 18/26] Using one-block reactor for component flux test --- armi/reactor/tests/test_components.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index a8351870c..aeb0f893d 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -18,6 +18,7 @@ import numpy as np from numpy.testing import assert_equal import unittest +from unittest.mock import Mock from armi.materials import air, alloy200 from armi.materials.material import Material @@ -1821,7 +1822,9 @@ class TestPinQuantities(unittest.TestCase): """Test methods that involve retrieval of pin quantities.""" def setUp(self): - self.r = loadTestReactor()[1] + self.r = loadTestReactor( + inputFileName="smallestTestReactor/armiRunSmallest.yaml" + )[1] def test_getPinMgFluxes(self): """Test proper retrieval of pin multigroup flux for fuel component.""" @@ -1848,9 +1851,8 @@ def test_getPinMgFluxes(self): assert_equal(pinMgFluxesAdj, simPinMgFluxesAdj) assert_equal(pinMgFluxesGamma, simPinMgFluxesGamma) - # Get a coolant block and replace the spatialLocator in the component to test exception raised - coolantBlock: Block = self.r.core.getFirstBlock(flags.Flags.CONTROL) - coolantComponent: Component = coolantBlock.getComponent(flags.Flags.CONTROL) - coolantComponent.spatialLocator = fuelComponent.spatialLocator + # Mock the spatial locator of the component to raise error + fuelComponent.spatialLocator = Mock() + fuelComponent.spatialLocator.indices = [np.array([111, 111, 111])] with self.assertRaises(ValueError): - coolantComponent.getPinMgFluxes() + fuelComponent.getPinMgFluxes() From 9f7a1e025d59d423c005efb593e0ff13ff4e0e68 Mon Sep 17 00:00:00 2001 From: Tony Alberti Date: Tue, 5 Nov 2024 11:52:27 -0800 Subject: [PATCH 19/26] No need to recast strings to strings Co-authored-by: Drew Johnson --- armi/reactor/components/componentParameters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 33808d85d..9746bcb20 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -143,14 +143,14 @@ def _assignTDFrac(self, val): pb.defParam( "pinPercentBu", setter=isNumpyArray("pinPercentBu"), - units=f"{units.PERCENT_FIMA}", + units=units.PERCENT_FIMA, description="Pin-wise burnup as a percentage of initial (heavy) metal atoms.", default=None, ) pb.defParam( "molesHmBOL", - units=f"{units.MOLES}", + units=units.MOLES, default=0.0, description="Total number of moles of heavy metal at BOL.", ) From 937057cb664ffc971bf525f0062156bc8fa30e05 Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 5 Nov 2024 19:59:58 +0000 Subject: [PATCH 20/26] org imports --- armi/reactor/tests/test_components.py | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index aeb0f893d..182e66453 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -15,40 +15,39 @@ """Tests functionalities of components within ARMI.""" import copy import math +import unittest + import numpy as np from numpy.testing import assert_equal -import unittest -from unittest.mock import Mock from armi.materials import air, alloy200 from armi.materials.material import Material -from armi.reactor import components -from armi.reactor import flags +from armi.reactor import components, flags from armi.reactor.blocks import Block from armi.reactor.components import ( - Component, - UnshapedComponent, - NullComponent, Circle, + Component, + ComponentType, + Cube, + DerivedShape, + DifferentialRadialSegment, + Helix, Hexagon, - HoledHexagon, HexHoledCircle, + HoledHexagon, HoledRectangle, HoledSquare, - Helix, - Sphere, - Cube, + NullComponent, + RadialSegment, Rectangle, SolidRectangle, + Sphere, Square, Triangle, - RadialSegment, - DifferentialRadialSegment, - DerivedShape, + UnshapedComponent, UnshapedVolumetricComponent, - ComponentType, + materials, ) -from armi.reactor.components import materials from armi.reactor.tests.test_reactors import loadTestReactor From e657ce92a2737939c9352363f38f49448b7ff6fb Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 5 Nov 2024 20:36:44 +0000 Subject: [PATCH 21/26] resolve fixme --- armi/reactor/components/component.py | 11 ++--------- armi/reactor/tests/test_components.py | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index a948f0377..d1b4abfac 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -32,7 +32,6 @@ from armi.nucDirectory import nuclideBases from armi.reactor import composites from armi.reactor import flags -from armi.reactor import grids from armi.reactor import parameters from armi.reactor.components import componentParameters from armi.utils import densityTools @@ -1291,13 +1290,7 @@ def getPinMgFluxes( a pin. """ # Get the (i, j, k) location of all pins from the parent block - # FIXME: This should be changed to just using Block.getPinLocations once Drew's PR is merged - indicesAll = [] - for clad in self.parent.getChildrenWithFlags(flags.Flags.CLAD): - if isinstance(clad.spatialLocator, grids.MultiIndexLocation): - indicesAll.extend(clad.spatialLocator.indices) - else: - indicesAll.append(clad.spatialLocator.indices) + indicesAll = self.parent.getPinLocations() # Retrieve the indices of this component indices = self.spatialLocator.indices @@ -1306,7 +1299,7 @@ def getPinMgFluxes( # Map this component's indices to block's pin indices getIndex = lambda ind: next( - (i for i, indA in enumerate(indicesAll) if np.all(ind == indA)), + (i for i, indA in enumerate(indicesAll) if np.all(ind == indA.indices)), None, ) indexMap = list(map(getIndex, indices)) diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 182e66453..74dcb6c82 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -1851,7 +1851,7 @@ def test_getPinMgFluxes(self): assert_equal(pinMgFluxesGamma, simPinMgFluxesGamma) # Mock the spatial locator of the component to raise error - fuelComponent.spatialLocator = Mock() + fuelComponent.spatialLocator = unittest.mock.Mock() fuelComponent.spatialLocator.indices = [np.array([111, 111, 111])] with self.assertRaises(ValueError): fuelComponent.getPinMgFluxes() From fe246d9456d1bea5a78eae54f3c1a0f56baf0dbb Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 5 Nov 2024 20:43:21 +0000 Subject: [PATCH 22/26] beef up assertions --- armi/reactor/tests/test_components.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 74dcb6c82..823e3d6dc 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -1831,7 +1831,7 @@ def test_getPinMgFluxes(self): fuelBlock: Block = self.r.core.getFirstBlock(flags.Flags.FUEL) fuelComponent: Component = fuelBlock.getComponent(flags.Flags.FUEL) numPins = int(fuelComponent.p.mult) - self.assertGreater(numPins, 1) + self.assertEqual(numPins, 169) # Set pin fluxes at block level fuelBlock.initializePinLocations() @@ -1853,5 +1853,8 @@ def test_getPinMgFluxes(self): # Mock the spatial locator of the component to raise error fuelComponent.spatialLocator = unittest.mock.Mock() fuelComponent.spatialLocator.indices = [np.array([111, 111, 111])] - with self.assertRaises(ValueError): + with self.assertRaisesRegex( + ValueError, + expected_regex=f"Failed to retrieve pin indices for component {fuelComponent}", + ): fuelComponent.getPinMgFluxes() From bb03ca2737139ce9e633a7b1f9016d4022f4ddca Mon Sep 17 00:00:00 2001 From: zachmprince Date: Tue, 5 Nov 2024 15:18:12 -0700 Subject: [PATCH 23/26] Addressing reviewer comments for component pin mg fluxes --- armi/reactor/components/component.py | 26 ++++++++++++++++---------- armi/reactor/tests/test_components.py | 21 ++++++++++++++++++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/armi/reactor/components/component.py b/armi/reactor/components/component.py index d1b4abfac..5a4da2f3b 100644 --- a/armi/reactor/components/component.py +++ b/armi/reactor/components/component.py @@ -32,6 +32,7 @@ from armi.nucDirectory import nuclideBases from armi.reactor import composites from armi.reactor import flags +from armi.reactor import grids from armi.reactor import parameters from armi.reactor.components import componentParameters from armi.utils import densityTools @@ -1290,19 +1291,18 @@ def getPinMgFluxes( a pin. """ # Get the (i, j, k) location of all pins from the parent block - indicesAll = self.parent.getPinLocations() + indicesAll = { + (loc.i, loc.j): i for i, loc in enumerate(self.parent.getPinLocations()) + } # Retrieve the indices of this component - indices = self.spatialLocator.indices - if not isinstance(indices, list): - indices = [indices] + if isinstance(self.spatialLocator, grids.MultiIndexLocation): + indices = [(loc.i, loc.j) for loc in self.spatialLocator] + else: + indices = [(self.spatialLocator.i, self.spatialLocator.j)] # Map this component's indices to block's pin indices - getIndex = lambda ind: next( - (i for i, indA in enumerate(indicesAll) if np.all(ind == indA.indices)), - None, - ) - indexMap = list(map(getIndex, indices)) + indexMap = list(map(indicesAll.get, indices)) if None in indexMap: msg = f"Failed to retrieve pin indices for component {self}." runLog.error(msg) @@ -1321,7 +1321,13 @@ def getPinMgFluxes( param = "pinMgFluxes" # Return pin fluxes - return self.parent.p.get(param)[indexMap] + try: + return self.parent.p[param][indexMap] + except Exception as ee: + msg = f"Failure getting {param} from {self} via parent {self.parent}" + runLog.error(msg) + runLog.error(ee) + raise ValueError(msg) from ee def density(self) -> float: """Returns the mass density of the object in g/cc.""" diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 823e3d6dc..e096c99c6 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -1851,10 +1851,25 @@ def test_getPinMgFluxes(self): assert_equal(pinMgFluxesGamma, simPinMgFluxesGamma) # Mock the spatial locator of the component to raise error - fuelComponent.spatialLocator = unittest.mock.Mock() - fuelComponent.spatialLocator.indices = [np.array([111, 111, 111])] + with unittest.mock.patch.object(fuelComponent, "spatialLocator") as mockLocator: + mockLocator.i = 111 + mockLocator.j = 111 + with self.assertRaisesRegex( + ValueError, + f"Failed to retrieve pin indices for component {fuelComponent}", + ): + fuelComponent.getPinMgFluxes() + + # Check assertion for adjoint gamma flux + with self.assertRaisesRegex( + ValueError, "Adjoint gamma flux is currently unsupported." + ): + fuelComponent.getPinMgFluxes(adjoint=True, gamma=True) + + # Check assertion for not-found parameter + fuelBlock.p.pinMgFluxes = None with self.assertRaisesRegex( ValueError, - expected_regex=f"Failed to retrieve pin indices for component {fuelComponent}", + f"Failure getting pinMgFluxes from {fuelComponent} via parent {fuelBlock}", ): fuelComponent.getPinMgFluxes() From 1f869aacdef3a079d50eb73fe97b28f24ad53a85 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 6 Nov 2024 01:59:47 +0000 Subject: [PATCH 24/26] release notes --- doc/release/0.4.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index edda7276f..f1931032a 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -27,6 +27,7 @@ New Features #. Improve efficiency of reaction rate calculations. (`PR#1887 `_) #. Adding new options for simplifying 1D cross section modeling. (`PR#1949 `_) #. Updating ``copyOrWarn`` and ``getFileSHA1Hash`` to support directories. (`PR#1984 `_) +#. Add a method getPinMgFluxes to retrieve the pin-wise multigroup fluxes from a Block. (`PR#1990 `_) #. TBD API Changes @@ -51,6 +52,9 @@ API Changes #. Removing setting ``mpiTasksPerNode`` and renaming ``numProcessors`` to ``nTasks``. (`PR#1958 `_) #. Changing ``synDbAfterWrite`` default to ``True``. (`PR#1968 `_) #. Removing unused class ``SmartList``. (`PR#1992 `_) +#. History Tracker: "detail assemblies" are now fuel and control assemblies. (`PR#1990 `_) +#. Removing ``Blocks.py::block::breakFuelComponentsIntoIndividuals``. (`PR#1990 `_) +#. Move getPuMoles from blocks.py up to composites.py. (`PR#1990 `_) #. TBD Bug Fixes From 9f7168d5015bcdfc7a38833f775f8184f1c9a731 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 6 Nov 2024 02:23:25 +0000 Subject: [PATCH 25/26] fix linting --- armi/reactor/components/componentParameters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 1ed7a0431..0a6f1a460 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -17,7 +17,6 @@ from armi.reactor.parameters import ParamLocation from armi.reactor.parameters.parameterDefinitions import isNumpyArray from armi.utils import units -from armi.reactor.parameters.parameterDefinitions import isNumpyArray def getComponentParameterDefinitions(): From df62010ac913d585b06c7bb9d4754033907d5352 Mon Sep 17 00:00:00 2001 From: aalberti Date: Thu, 14 Nov 2024 11:15:37 -0800 Subject: [PATCH 26/26] fix unit test --- armi/bookkeeping/db/tests/test_comparedb3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armi/bookkeeping/db/tests/test_comparedb3.py b/armi/bookkeeping/db/tests/test_comparedb3.py index 3d56ee022..4eb5531d8 100644 --- a/armi/bookkeeping/db/tests/test_comparedb3.py +++ b/armi/bookkeeping/db/tests/test_comparedb3.py @@ -181,7 +181,7 @@ def test_compareDatabaseSim(self): dbs[1]._fullPath, timestepCompare=[(0, 0), (0, 1)], ) - self.assertEqual(len(diffs.diffs), 480) + self.assertEqual(len(diffs.diffs), 504) # Cycle length is only diff (x3) self.assertEqual(diffs.nDiffs(), 3)