From 76e36f32d14f314064d259b10ec86204f8cc5b1f Mon Sep 17 00:00:00 2001 From: zachmprince Date: Mon, 14 Oct 2024 16:29:15 -0600 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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) From 642f0f356764f242729d22cc6ec30d5d75498663 Mon Sep 17 00:00:00 2001 From: aalberti Date: Mon, 18 Nov 2024 18:11:29 +0000 Subject: [PATCH 27/37] mv b.p.percentBuMin internal --- armi/reactor/blockParameters.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/armi/reactor/blockParameters.py b/armi/reactor/blockParameters.py index 820ebac9f..ab65301a0 100644 --- a/armi/reactor/blockParameters.py +++ b/armi/reactor/blockParameters.py @@ -151,13 +151,6 @@ def getBlockParameterDefinitions(): location=ParamLocation.MAX, ) - pb.defParam( - "percentBuMin", - units=units.PERCENT_FIMA, - description="Minimum percentage of the initial heavy metal atoms that have been fissioned", - location=ParamLocation.MAX, - ) - pb.defParam( "residence", units=units.DAYS, From 9aa6895bb36be05982624da1177459957b68e8b4 Mon Sep 17 00:00:00 2001 From: Tony Alberti Date: Mon, 18 Nov 2024 11:00:58 -0800 Subject: [PATCH 28/37] Apply suggestions from code review Co-authored-by: John Stilley <1831479+john-science@users.noreply.github.com> --- armi/reactor/tests/test_components.py | 3 ++- doc/release/0.4.rst | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 6ee93e486..b16cc2265 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -22,7 +22,8 @@ from armi.materials import air, alloy200 from armi.materials.material import Material -from armi.reactor import components, flags +from armi.reactor import components +from armi.reactor import flags from armi.reactor.blocks import Block from armi.reactor.components import ( Circle, diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index d4d5cb16e..ca8d1ecdc 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -28,7 +28,7 @@ New Features #. Adding support for ENDF/B-VII.1-based MC2-3 libraries. (`PR#1982 `_) #. 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 `_) +#. Adding a method ``getPinMgFluxes`` to get pin-wise multigroup fluxes from a Block. (`PR#1990 `_) #. Exposing ``detailedNDens`` to components. (`PR#1954 `_) #. TBD @@ -56,7 +56,7 @@ API Changes #. Removing unused class ``SmartList``. (`PR#1992 `_) #. `nuclideBases.byMcc3ID` and `getMcc3Id()` return IDs consistent with ENDF/B-VII.1. (`PR#1982 `_) #. History Tracker: "detail assemblies" are now fuel and control assemblies. (`PR#1990 `_) -#. Removing ``Blocks.py::block::breakFuelComponentsIntoIndividuals``. (`PR#1990 `_) +#. Removing ``Block.breakFuelComponentsIntoIndividuals()``. (`PR#1990 `_) #. Move getPuMoles from blocks.py up to composites.py. (`PR#1990 `_) #. TBD From 8c94b3200134732ce306bd43828b2fd9b4e8cd98 Mon Sep 17 00:00:00 2001 From: aalberti Date: Mon, 18 Nov 2024 23:20:35 +0000 Subject: [PATCH 29/37] add testing for Block.completeInitialLoading --- armi/reactor/blocks.py | 8 +++-- armi/reactor/tests/test_blocks.py | 51 ++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 930567074..8d1aec5e5 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -793,15 +793,19 @@ def completeInitialLoading(self, bolBlock=None): massHmBOL = 0.0 sf = self.getSymmetryFactor() for child in self: + # multiplying by sf ends up cancelling out the symmetry factor used in + # Component.getMass(). So massHmBOL does not respect the symmetry factor. hmMass = child.getHMMass() * sf massHmBOL += hmMass # 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() + # to stay consistent with massHmBOL, molesHmBOL and puFrac should be + # independent of sf. As such, the need to be scaled by 1/sf. + child.p.molesHmBOL = child.getHMMoles() / sf child.p.puFrac = ( - self.getPuMoles() / child.p.molesHmBOL + self.getPuMoles() / sf / child.p.molesHmBOL if child.p.molesHmBOL > 0.0 else 0.0 ) diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index 2e5fcf6a2..26fe6629a 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -40,7 +40,7 @@ from armi.reactor.tests.test_reactors import loadTestReactor from armi.reactor.tests.test_reactors import TEST_ROOT from armi.tests import ISOAA_PATH -from armi.utils import hexagon, units +from armi.utils import hexagon, units, densityTools from armi.utils.directoryChangers import TemporaryDirectoryChanger from armi.utils.units import MOLES_PER_CC_TO_ATOMS_PER_BARN_CM from armi.utils.units import ASCII_LETTER_A, ASCII_LETTER_Z, ASCII_LETTER_a @@ -1199,7 +1199,19 @@ def test_adjustDensity(self): self.assertAlmostEqual(mass2 - mass1, massDiff) - def test_completeInitialLoading(self): + @patch.object(blocks.HexBlock, "getSymmetryFactor") + def test_completeInitialLoading(self, mock_sf): + """Ensure that some BOL block and component params are populated properly. + + Notes + ----- + - When checking component-level BOL params, puFrac is skipped due to 1) there's no Pu in the block, and 2) + getPuMoles is functionally identical to getHMMoles (just limits nuclides from heavy metal to just Pu). + - getSymmetryFactor is mocked to return 3. This indicates that the block is in the center-most assembly. + Providing this mock ensures that symmetry factors are tested as well (otherwise it's just a factor of 1 + and it is a less robust test). + """ + mock_sf.return_value = 3 area = self.block.getArea() height = 2.0 self.block.setHeight(height) @@ -1216,10 +1228,39 @@ def test_completeInitialLoading(self): self.block.completeInitialLoading() + sf = self.block.getSymmetryFactor() cur = self.block.p.molesHmBOL - ref = self.block.getHMDens() / MOLES_PER_CC_TO_ATOMS_PER_BARN_CM * height * area - places = 6 - self.assertAlmostEqual(cur, ref, places=places) + ref = ( + self.block.getHMDens() + / MOLES_PER_CC_TO_ATOMS_PER_BARN_CM + * height + * area + * sf + ) + self.assertAlmostEqual(cur, ref, places=12) + + for c in self.block: + nucs = c.getNuclides() + hmNucs = [nuc for nuc in nucs if nucDir.isHeavyMetal(nuc)] + hmNDens = {hmNuc: c.getNumberDensity(hmNuc) for hmNuc in hmNucs} + hmMass = densityTools.calculateMassDensity(hmNDens) * c.getVolume() + if hmMass: + # hmMass does not need to account for sf since what's calculated in blocks.completeInitialLoading + # ends up cancelling out sf + self.assertAlmostEqual(c.p.massHmBOL, hmMass, places=12) + # since sf is cancelled out in massHmBOL, there needs to be a factor 1/sf here to cancel out the + # factor of sf in getHMMoles. + self.assertAlmostEqual( + c.p.molesHmBOL, + sum(ndens for ndens in hmNDens.values()) + / units.MOLES_PER_CC_TO_ATOMS_PER_BARN_CM + * c.getVolume() + / sf, + places=12, + ) + else: + self.assertEqual(c.p.massHmBOL, 0.0) + self.assertEqual(c.p.molesHmBOL, 0.0) def test_add(self): numComps = len(self.block.getComponents()) From 9c9fbf344bc6b431a15456560a385878cb04f0de Mon Sep 17 00:00:00 2001 From: aalberti Date: Mon, 18 Nov 2024 23:31:47 +0000 Subject: [PATCH 30/37] add test for total block HmBOL mass --- armi/reactor/tests/test_blocks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index 26fe6629a..8205bf1d7 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -1239,11 +1239,13 @@ def test_completeInitialLoading(self, mock_sf): ) self.assertAlmostEqual(cur, ref, places=12) + totalHMMass = 0.0 for c in self.block: nucs = c.getNuclides() hmNucs = [nuc for nuc in nucs if nucDir.isHeavyMetal(nuc)] hmNDens = {hmNuc: c.getNumberDensity(hmNuc) for hmNuc in hmNucs} hmMass = densityTools.calculateMassDensity(hmNDens) * c.getVolume() + totalHMMass += hmMass if hmMass: # hmMass does not need to account for sf since what's calculated in blocks.completeInitialLoading # ends up cancelling out sf @@ -1262,6 +1264,8 @@ def test_completeInitialLoading(self, mock_sf): self.assertEqual(c.p.massHmBOL, 0.0) self.assertEqual(c.p.molesHmBOL, 0.0) + self.assertAlmostEqual(self.block.p.massHmBOL, totalHMMass) + def test_add(self): numComps = len(self.block.getComponents()) From 89514da51241fe38a6ffcdf9b14cb4cf14386446 Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 19 Nov 2024 00:08:58 +0000 Subject: [PATCH 31/37] move pin-specific params internal --- armi/reactor/components/componentParameters.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 0a6f1a460..9a9246e4d 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -154,14 +154,6 @@ def _assignTDFrac(self, val): setter=_assignTDFrac, ) - pb.defParam( - "pinPercentBu", - setter=isNumpyArray("pinPercentBu"), - units=units.PERCENT_FIMA, - description="Pin-wise burnup as a percentage of initial (heavy) metal atoms.", - default=None, - ) - pb.defParam( "molesHmBOL", units=units.MOLES, @@ -169,14 +161,6 @@ 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.", - default=None, - ) - pb.defParam( "puFrac", default=0.0, From b128d5f5866b76a92a2709ebe2e389e7e4a8e9bc Mon Sep 17 00:00:00 2001 From: aalberti Date: Tue, 19 Nov 2024 19:34:48 +0000 Subject: [PATCH 32/37] move pinPercentBu back for incoming assembly rotation work --- armi/reactor/components/componentParameters.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 9a9246e4d..704872fee 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -85,6 +85,14 @@ def getComponentParameterDefinitions(): default=0.0, ) + pb.defParam( + "pinPercentBu", + setter=isNumpyArray("pinPercentBu"), + units=units.PERCENT_FIMA, + description="Pin-wise burnup as a percentage of initial (heavy) metal atoms.", + default=None, + ) + pb.defParam( "buRate", units=f"{units.PERCENT_FIMA}/{units.DAYS}", From 648c95314b027877d7c13304a5796860f4f11433 Mon Sep 17 00:00:00 2001 From: Tony Alberti Date: Tue, 19 Nov 2024 15:40:46 -0800 Subject: [PATCH 33/37] Update doc/release/0.4.rst Co-authored-by: John Stilley <1831479+john-science@users.noreply.github.com> --- doc/release/0.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index ca8d1ecdc..584642205 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -57,7 +57,7 @@ API Changes #. `nuclideBases.byMcc3ID` and `getMcc3Id()` return IDs consistent with ENDF/B-VII.1. (`PR#1982 `_) #. History Tracker: "detail assemblies" are now fuel and control assemblies. (`PR#1990 `_) #. Removing ``Block.breakFuelComponentsIntoIndividuals()``. (`PR#1990 `_) -#. Move getPuMoles from blocks.py up to composites.py. (`PR#1990 `_) +#. Moving ``getPuMoles`` from blocks.py up to composites.py. (`PR#1990 `_) #. TBD Bug Fixes From 8cb2c9381f533fdbdb28691515bff004fd2e9055 Mon Sep 17 00:00:00 2001 From: aalberti Date: Wed, 20 Nov 2024 19:53:42 +0000 Subject: [PATCH 34/37] update release notes --- doc/release/0.5.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/release/0.5.rst b/doc/release/0.5.rst index 6ebbc39ca..8ae4f1a95 100644 --- a/doc/release/0.5.rst +++ b/doc/release/0.5.rst @@ -51,6 +51,7 @@ New Features #. Adding new options for simplifying 1D cross section modeling. (`PR#1949 `_) #. Adding ``--skip-inspection`` flag to ``CompareCases`` CLI. (`PR#1842 `_) #. Exposing ``detailedNDens`` to components. (`PR#1954 `_) +#. Adding a method ``getPinMgFluxes`` to get pin-wise multigroup fluxes from a Block. (`PR#1990 `_) API Changes ----------- @@ -75,6 +76,9 @@ API Changes #. Removing method ``SkippingXsGen_BuChangedLessThanTolerance``. (`PR#1845 `_) #. Removing setting ``autoGenerateBlockGrids``. (`PR#1947 `_) #. Removing setting ``mpiTasksPerNode`` and renaming ``numProcessors`` to ``nTasks``. (`PR#1958 `_) +#. History Tracker: "detail assemblies" are now fuel and control assemblies. (`PR#1990 `_) +#. Removing ``Block.breakFuelComponentsIntoIndividuals()``. (`PR#1990 `_) +#. Moving ``getPuMoles`` from blocks.py up to composites.py. (`PR#1990 `_) Bug Fixes --------- From 35adca0c0d9b4a627dcf71e310d11a38abfa4cc5 Mon Sep 17 00:00:00 2001 From: aalberti Date: Thu, 21 Nov 2024 21:04:29 +0000 Subject: [PATCH 35/37] add pinNDens param, numpy32bit setter, update pinNDens with changeNDensByFactor --- .../reactor/components/componentParameters.py | 13 +++++++++- armi/reactor/composites.py | 3 +++ .../parameters/parameterDefinitions.py | 24 +++++++++++++++++++ armi/reactor/tests/test_components.py | 2 ++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 704872fee..a19ad850c 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -15,7 +15,7 @@ """Component parameter definitions.""" from armi.reactor import parameters from armi.reactor.parameters import ParamLocation -from armi.reactor.parameters.parameterDefinitions import isNumpyArray +from armi.reactor.parameters.parameterDefinitions import isNumpyArray, isNumpyArray32Bit from armi.utils import units @@ -78,6 +78,17 @@ def getComponentParameterDefinitions(): default=None, ) + pb.defParam( + "pinNDens", + setter=isNumpyArray32Bit("pinNDens"), + units=f"#/(bn*{units.CM})", + description="Pin-wise number densities of each nuclide.", + location=ParamLocation.AVERAGE, + saveToDB=True, + categories=["depletion", parameters.Category.pinQuantities], + default=None, + ) + pb.defParam( "percentBu", units=f"{units.PERCENT_FIMA}", diff --git a/armi/reactor/composites.py b/armi/reactor/composites.py index 2e57617dd..f61ad5580 100644 --- a/armi/reactor/composites.py +++ b/armi/reactor/composites.py @@ -1578,6 +1578,9 @@ def changeNDensByFactor(self, factor): # Update detailedNDens if self.p.detailedNDens is not None: self.p.detailedNDens *= factor + # Update pinNDens + if self.p.pinNDens is not None: + self.p.pinNDens *= factor def clearNumberDensities(self): """ diff --git a/armi/reactor/parameters/parameterDefinitions.py b/armi/reactor/parameters/parameterDefinitions.py index 17c024e7d..911cdbf4d 100644 --- a/armi/reactor/parameters/parameterDefinitions.py +++ b/armi/reactor/parameters/parameterDefinitions.py @@ -223,6 +223,30 @@ def setParameter(selfObj, value): return setParameter +def isNumpyArray32Bit(paramStr: str): + """Helper meta-function to create a method that sets a Parameter value to a 32 bit float NumPy array. + + Parameters + ---------- + paramStr + Name of the Parameter we want to set. + + Returns + ------- + function + A setter method on the Parameter class to force the value to be a 32 bit NumPy array. + """ + + def setParameter(selfObj, value): + if value is None: + # allow default of None to exist + setattr(selfObj, "_p_" + paramStr, value) + else: + # force to 32 bit + setattr(selfObj, "_p_" + paramStr, np.array(value, dtype=np.float32)) + + return setParameter + @functools.total_ordering class Parameter: diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index b16cc2265..7ce082a54 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -702,10 +702,12 @@ def test_changeNumberDensities(self): """Test that demonstates that the number densities on a component can be modified.""" self.component.p.numberDensities = {"NA23": 1.0} self.component.p.detailedNDens = [1.0] + self.component.p.pinNDens = [1.0] self.assertEqual(self.component.getNumberDensity("NA23"), 1.0) self.component.changeNDensByFactor(3.0) self.assertEqual(self.component.getNumberDensity("NA23"), 3.0) self.assertEqual(self.component.p.detailedNDens[0], 3.0) + self.assertEqual(self.component.p.pinNDens[0], 3.0) def test_fuelMass(self): nominalMass = self.component.getMass() From 938bf83a72093836faf222404827408ae8b5e127 Mon Sep 17 00:00:00 2001 From: aalberti Date: Thu, 21 Nov 2024 21:28:35 +0000 Subject: [PATCH 36/37] run black --- armi/reactor/parameters/parameterDefinitions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/armi/reactor/parameters/parameterDefinitions.py b/armi/reactor/parameters/parameterDefinitions.py index 911cdbf4d..f18ce417f 100644 --- a/armi/reactor/parameters/parameterDefinitions.py +++ b/armi/reactor/parameters/parameterDefinitions.py @@ -223,6 +223,7 @@ def setParameter(selfObj, value): return setParameter + def isNumpyArray32Bit(paramStr: str): """Helper meta-function to create a method that sets a Parameter value to a 32 bit float NumPy array. From 223e5a6fcb381f7c9323fd9112ecb3cbc2ed99cc Mon Sep 17 00:00:00 2001 From: Tony Alberti Date: Thu, 21 Nov 2024 13:51:48 -0800 Subject: [PATCH 37/37] Apply suggestions from code review Co-authored-by: Drew Johnson --- armi/reactor/components/componentParameters.py | 6 +++--- armi/reactor/parameters/parameterDefinitions.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index a19ad850c..548ec7940 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -15,7 +15,7 @@ """Component parameter definitions.""" from armi.reactor import parameters from armi.reactor.parameters import ParamLocation -from armi.reactor.parameters.parameterDefinitions import isNumpyArray, isNumpyArray32Bit +from armi.reactor.parameters.parameterDefinitions import isNumpyArray, isNumpyF32Array from armi.utils import units @@ -80,8 +80,8 @@ def getComponentParameterDefinitions(): pb.defParam( "pinNDens", - setter=isNumpyArray32Bit("pinNDens"), - units=f"#/(bn*{units.CM})", + setter=isNumpyF32Array("pinNDens"), + units=f"atoms/(bn*{units.CM})", description="Pin-wise number densities of each nuclide.", location=ParamLocation.AVERAGE, saveToDB=True, diff --git a/armi/reactor/parameters/parameterDefinitions.py b/armi/reactor/parameters/parameterDefinitions.py index f18ce417f..4063a2cf3 100644 --- a/armi/reactor/parameters/parameterDefinitions.py +++ b/armi/reactor/parameters/parameterDefinitions.py @@ -224,7 +224,7 @@ def setParameter(selfObj, value): return setParameter -def isNumpyArray32Bit(paramStr: str): +def isNumpyF32Array(paramStr: str): """Helper meta-function to create a method that sets a Parameter value to a 32 bit float NumPy array. Parameters