Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding temperature dependence when generating cross sections #1987

Merged
merged 23 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion armi/bookkeeping/historyTracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def writeAssemHistory(self, a, fName=""):
out.write("\n\n\nAssembly info\n")
out.write("{0} {1}\n".format(a.getName(), a.getType()))
for b in blocks:
out.write('"{}" {} {}\n'.format(b.getType(), b.p.xsType, b.p.buGroup))
out.write('"{}" {} {}\n'.format(b.getType(), b.p.xsType, b.p.envGroup))

def preloadBlockHistoryVals(self, names, keys, timesteps):
"""
Expand Down
5 changes: 5 additions & 0 deletions armi/physics/neutronics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ def defineParameters():

return neutronicsParameters.getNeutronicsParameterDefinitions()

@staticmethod
@plugins.HOOKIMPL
def defineParameterRenames():
return {"buGroup": "envGroup", "buGroupNum": "envGroupNum"}

@staticmethod
@plugins.HOOKIMPL
def defineEntryPoints():
Expand Down
203 changes: 138 additions & 65 deletions armi/physics/neutronics/crossSectionGroupManager.py

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions armi/physics/neutronics/crossSectionSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,19 +250,21 @@ def __getitem__(self, xsID):
if xsID in self:
return dict.__getitem__(self, xsID)

# exact key not present so give lowest env group key, eg AA or BA as the source for
# settings since users do not typically provide all combinations of second chars explicitly
xsType = xsID[0]
buGroup = xsID[1]
envGroup = xsID[1]
existingXsOpts = [
xsOpt
for xsOpt in self.values()
if xsOpt.xsType == xsType and xsOpt.buGroup < buGroup
if xsOpt.xsType == xsType and xsOpt.envGroup < envGroup
]

if not any(existingXsOpts):
return self._getDefault(xsID)

else:
return sorted(existingXsOpts, key=lambda xsOpt: xsOpt.buGroup)[0]
return sorted(existingXsOpts, key=lambda xsOpt: xsOpt.envGroup)[0]

def setDefaults(self, blockRepresentation, validBlockTypes):
"""
Expand Down Expand Up @@ -509,7 +511,7 @@ def xsType(self):
return self.xsID[0]

@property
def buGroup(self):
def envGroup(self):
"""Return the single-char burnup group indicator."""
return self.xsID[1]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class LatticePhysicsWriter(interfaces.InputWriter):
DEPLETABLE = "Depletable" + 4 * _SPACE
UNDEPLETABLE = "Non-Depletable"
REPRESENTED = "Represented" + 2 * _SPACE
UNREPRESENTED = "Unrepresented"
INF_DILUTE = "Inf Dilute"

def __init__(
self,
Expand Down Expand Up @@ -300,7 +300,7 @@ def _getAllNuclidesByCategory(self, component=None):
if nucName in objNuclides:
nucCategory += self.REPRESENTED + self._SEPARATOR
else:
nucCategory += self.UNREPRESENTED + self._SEPARATOR
nucCategory += self.INF_DILUTE + self._SEPARATOR

if nucName in depletableNuclides:
nucCategory += self.DEPLETABLE
Expand Down
65 changes: 48 additions & 17 deletions armi/physics/neutronics/tests/test_crossSectionManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@ def test_createRepresentativeBlock(self):
avgB = self.bc.createRepresentativeBlock()
self.assertAlmostEqual(avgB.p.percentBu, 50.0)

def test_getBlockNuclideTemperature(self):
# doesn't have to be in median block tests, but this is a simpler test
nuc = "U235"
testBlock = self.blockList[0]
amt, amtWeightedTemp = 0, 0
for c in testBlock:
dens = c.getNumberDensity(nuc)
if dens > 0:
thisAmt = dens * c.getVolume()
amt += thisAmt
amtWeightedTemp += thisAmt * c.temperatureInC
avgTemp = amtWeightedTemp / amt
self.assertAlmostEqual(
avgTemp, crossSectionGroupManager.getBlockNuclideTemperature(testBlock, nuc)
)


class TestBlockCollectionAverage(unittest.TestCase):
@classmethod
Expand Down Expand Up @@ -403,7 +419,7 @@ def test_ComponentAverageRepBlock(self):

assert "AC" in xsgm.representativeBlocks, (
"Assemblies not in the core should still have XS groups"
"see getUnrepresentedBlocks()"
"see _getMissingBlueprintBlocks()"
)


Expand Down Expand Up @@ -701,29 +717,36 @@ def setUp(self):
self.csm._setBuGroupBounds([3, 10, 30, 100])
self.csm.interactBOL()

def test_enableBuGroupUpdates(self):
self.csm._buGroupUpdatesEnabled = False
self.csm.enableBuGroupUpdates()
self.assertTrue(self.csm.enableBuGroupUpdates)

def test_disableBuGroupUpdates(self):
self.csm._buGroupUpdatesEnabled = False
res = self.csm.disableBuGroupUpdates()
self.assertFalse(res)
def test_enableEnvGroupUpdates(self):
self.csm._envGroupUpdatesEnabled = False
self.csm.enableEnvGroupUpdates()
self.assertTrue(self.csm._envGroupUpdatesEnabled)
# test flipping again keeps true
self.csm.enableEnvGroupUpdates()
self.assertTrue(self.csm._envGroupUpdatesEnabled)

def test_disableEnvGroupUpdates(self):
self.csm._envGroupUpdatesEnabled = True
wasEnabled = self.csm.disableEnvGroupUpdates()
self.assertTrue(wasEnabled)
self.assertFalse(self.csm._envGroupUpdatesEnabled)
wasEnabled = self.csm.disableEnvGroupUpdates()
self.assertFalse(wasEnabled)
self.assertFalse(self.csm._envGroupUpdatesEnabled)

def test_updateBurnupGroups(self):
self.blockList[1].p.percentBu = 3.1
self.blockList[2].p.percentBu = 10.0

self.csm._updateBurnupGroups(self.blockList)
self.csm._updateEnvironmentGroups(self.blockList)

self.assertEqual(self.blockList[0].p.buGroup, "A")
self.assertEqual(self.blockList[1].p.buGroup, "B")
self.assertEqual(self.blockList[2].p.buGroup, "B")
self.assertEqual(self.blockList[-1].p.buGroup, "D")
self.assertEqual(self.blockList[0].p.envGroup, "A")
self.assertEqual(self.blockList[1].p.envGroup, "B")
self.assertEqual(self.blockList[2].p.envGroup, "B")
self.assertEqual(self.blockList[-1].p.envGroup, "D")

def test_setBuGroupBounds(self):
self.assertAlmostEqual(self.csm._upperBuGroupBounds[2], 30.0)
self.assertAlmostEqual(self.csm._buGroupBounds[2], 30.0)

with self.assertRaises(ValueError):
self.csm._setBuGroupBounds([3, 10, 300])
Expand All @@ -734,6 +757,14 @@ def test_setBuGroupBounds(self):
with self.assertRaises(ValueError):
self.csm._setBuGroupBounds([1, 5, 3])

def test_setTempGroupBounds(self):
# negative temps in C are allowed
self.csm._setTempGroupBounds([-5, 3, 10, 300])
self.assertAlmostEqual(self.csm._tempGroupBounds[2], 10.0)

with self.assertRaises(ValueError):
self.csm._setTempGroupBounds([1, 5, 3])

def test_addXsGroupsFromBlocks(self):
blockCollectionsByXsGroup = {}
blockCollectionsByXsGroup = self.csm._addXsGroupsFromBlocks(
Expand All @@ -748,7 +779,7 @@ def test_calcWeightedBurnup(self):
self.blockList[3].p.percentBu = 1.5
for b in self.blockList[4:]:
b.p.percentBu = 0.0
self.csm._updateBurnupGroups(self.blockList)
self.csm._updateEnvironmentGroups(self.blockList)
blockCollectionsByXsGroup = {}
blockCollectionsByXsGroup = self.csm._addXsGroupsFromBlocks(
blockCollectionsByXsGroup, self.blockList
Expand Down
4 changes: 2 additions & 2 deletions armi/physics/neutronics/tests/test_crossSectionSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_homogeneousXsDefaultSettingAssignment(self):
self.assertEqual(xsModel["YA"].geometry, "0D")
self.assertEqual(xsModel["YA"].criticalBuckling, True)

def test_setDefaultSettingsByLowestBuGroupHomogeneous(self):
def test_setDefaultSettingsByLowestEnvGroupHomogeneous(self):
# Initialize some micro suffix in the cross sections
cs = settings.Settings()
xs = XSSettings()
Expand All @@ -145,7 +145,7 @@ def test_setDefaultSettingsByLowestBuGroupHomogeneous(self):
self.assertNotIn("JB", xs)
self.assertNotEqual(xs["JD"], xs["JB"])

def test_setDefaultSettingsByLowestBuGroupOneDimensional(self):
def test_setDefaultSettingsByLowestEnvGroupOneDimensional(self):
# Initialize some micro suffix in the cross sections
cs = settings.Settings()
xsModel = XSSettings()
Expand Down
73 changes: 43 additions & 30 deletions armi/reactor/blockParameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from armi.reactor.parameters import NoDefault, Parameter, ParamLocation
from armi.reactor.parameters.parameterDefinitions import isNumpyArray
from armi.utils import units
from armi.utils.units import ASCII_LETTER_A
from armi.utils.units import ASCII_LETTER_A, ASCII_LETTER_Z, ASCII_LETTER_a


def getBlockParameterDefinitions():
Expand Down Expand Up @@ -196,53 +196,66 @@ def getBlockParameterDefinitions():

with pDefs.createBuilder(default=0.0, location=ParamLocation.AVERAGE) as pb:

def buGroup(self, buGroupChar):
if isinstance(buGroupChar, (int, float)):
intValue = int(buGroupChar)
def envGroup(self, envGroupChar):
if isinstance(envGroupChar, (int, float)):
intValue = int(envGroupChar)
runLog.warning(
f"Attempting to set `b.p.buGroup` to int value ({buGroupChar}). Possibly loading from old database",
f"Attempting to set `b.p.envGroup` to int value ({envGroupChar})."
"Possibly loading from old database",
single=True,
label="bu group as int " + str(intValue),
label="env group as int " + str(intValue),
)
self.buGroupNum = intValue
self.envGroupNum = intValue
return
elif not isinstance(buGroupChar, six.string_types):
elif not isinstance(envGroupChar, six.string_types):
raise Exception(
f"Wrong type for buGroupChar {buGroupChar}: {type(buGroupChar)}"
f"Wrong type for envGroupChar {envGroupChar}: {type(envGroupChar)}"
)

buGroupNum = ord(buGroupChar) - ASCII_LETTER_A
self._p_buGroup = buGroupChar
self._p_buGroupNum = buGroupNum
buGroupNumDef = parameters.ALL_DEFINITIONS["buGroupNum"]
buGroupNumDef.assigned = parameters.SINCE_ANYTHING
if envGroupChar.islower():
# if lower case find the distance from lowercase a and add the span of A to Z
lowerCaseOffset = ASCII_LETTER_Z - ASCII_LETTER_A + 1 # 26
envGroupNum = ord(envGroupChar) - ASCII_LETTER_a + lowerCaseOffset
else:
envGroupNum = ord(envGroupChar) - ASCII_LETTER_A
self._p_envGroup = envGroupChar
self._p_envGroupNum = envGroupNum
envGroupNumDef = parameters.ALL_DEFINITIONS["envGroupNum"]
envGroupNumDef.assigned = parameters.SINCE_ANYTHING

pb.defParam(
"buGroup",
"envGroup",
units=units.UNITLESS,
description="The burnup group letter of this block",
description="The environment group letter of this block",
default="A",
setter=buGroup,
setter=envGroup,
)

def buGroupNum(self, buGroupNum):
if buGroupNum > 26:
def envGroupNum(self, envGroupNum):
# support capital and lowercase alpha chars (52= 26*2)
if envGroupNum > 52:
raise RuntimeError(
"Invalid bu group number ({}): too many groups. 26 is the max.".format(
buGroupNum
"Invalid env group number ({}): too many groups. 52 is the max.".format(
envGroupNum
)
)
self._p_buGroupNum = buGroupNum
self._p_buGroup = chr(buGroupNum + ASCII_LETTER_A)
buGroupDef = parameters.ALL_DEFINITIONS["buGroup"]
buGroupDef.assigned = parameters.SINCE_ANYTHING

pb.defParam(
"buGroupNum",
self._p_envGroupNum = envGroupNum
lowerCaseOffset = ASCII_LETTER_Z - ASCII_LETTER_A
if envGroupNum > lowerCaseOffset:
envGroupNum = envGroupNum - (lowerCaseOffset + 1)
self._p_envGroup = chr(envGroupNum + ASCII_LETTER_a)
else:
self._p_envGroup = chr(envGroupNum + ASCII_LETTER_A)
envGroupDef = parameters.ALL_DEFINITIONS["envGroup"]
envGroupDef.assigned = parameters.SINCE_ANYTHING

pb.defParam(
"envGroupNum",
units=units.UNITLESS,
description="An integer representation of the burnup group, linked to buGroup.",
description="An integer representation of the environment group "
"(burnup/temperature/etc. environment). linked to envGroup.",
onufer marked this conversation as resolved.
Show resolved Hide resolved
default=0,
setter=buGroupNum,
setter=envGroupNum,
)

pb.defParam(
Expand Down
26 changes: 14 additions & 12 deletions armi/reactor/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ def __init__(self, name: str, height: float = 1.0):

def __repr__(self):
# be warned, changing this might break unit tests on input file generations
return "<{type} {name} at {loc} XS: {xs} BU GP: {bu}>".format(
return "<{type} {name} at {loc} XS: {xs} ENV GP: {env}>".format(
type=self.getType(),
name=self.getName(),
xs=self.p.xsType,
bu=self.p.buGroup,
env=self.p.envGroup,
loc=self.getLocation(),
)

Expand Down Expand Up @@ -403,26 +403,28 @@ def getMicroSuffix(self):

Notes
-----
The single-letter use for xsType and buGroup limit users to 26 groups of each.
ARMI will allow 2-letter xsType designations if and only if the `buGroups`
setting has length 1 (i.e. no burnup groups are defined). This is useful for
The single-letter use for xsType and envGroup limit users to 52 groups of each.
ARMI will allow 2-letter xsType designations if and only if the `envGroup`
setting has length 1 (i.e. no burnup/temp groups are defined). This is useful for
high-fidelity XS modeling of V&V models such as the ZPPRs.
"""
bu = self.p.buGroup
if not bu:
env = self.p.envGroup
if not env:
raise RuntimeError(
"Cannot get MicroXS suffix because {0} in {1} does not have a burnup group"
"Cannot get MicroXS suffix because {0} in {1} does not have a environment(env) group"
"".format(self, self.parent)
)

xsType = self.p.xsType
if len(xsType) == 1:
return xsType + bu
elif len(xsType) == 2 and ord(bu) > ord("A"):
return xsType + env
elif len(xsType) == 2 and ord(env) != ord("A"):
# default is "A" so if we got an off default 2 char, there is no way to resolve.
raise ValueError(
"Use of multiple burnup groups is not allowed with multi-character xs groups!"
"Use of non-default env groups is not allowed with multi-character xs groups!"
)
else:
# ignore env group, multi Char XS type to support assigning 2 chars in blueprints
return xsType

def getHeight(self):
Expand Down Expand Up @@ -1885,7 +1887,7 @@ def createHomogenizedCopy(self, pinSpatialLocators=False):
# assign macros and LFP
b.macros = self.macros
b._lumpedFissionProducts = self._lumpedFissionProducts
b.p.buGroup = self.p.buGroup
b.p.envGroup = self.p.envGroup

hexComponent = Hexagon(
"homogenizedHex",
Expand Down
2 changes: 1 addition & 1 deletion armi/reactor/blueprints/tests/test_customIsotopics.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def test_expandedNatural(self):
self.assertNotIn("FE51", c.getNumberDensities()) # un-natural
self.assertNotIn("FE", c.getNumberDensities())

def test_unrepresentedAreOnlyNatural(self):
def test_infDiluteAreOnlyNatural(self):
"""Make sure nuclides specified as In-Problem but not actually in any material are only natural isotopics."""
self.assertIn("AL27", self.bp.allNuclidesInProblem)
self.assertNotIn("AL26", self.bp.allNuclidesInProblem)
Expand Down
Loading
Loading