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 all 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
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ DATASET=A.MCC2
22 CM2455 7 202.55000
22 CM2465 7 202.55000
22 CM2475 7 202.55000
01 ARMI generated \\path\to\mc2\2.0\mc2.exe case for caseTitle mrtTWRP600v6rev3OS, block <twrp starter fuel B0064F at A8009F XS: B BU GP: A>
01 ARMI generated \\path\to\mc2\2.0\mc2.exe case for caseTitle mrtTWRP600v6rev3OS, block <twrp starter fuel B0064F at A8009F XS: B ENV GP: A>
DATASET=A.DLAY
01 CREATE VERSION VI DLAYXS FILE FROM FUELI
03 1 0 0 0 0
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
221 changes: 150 additions & 71 deletions armi/physics/neutronics/crossSectionGroupManager.py

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions armi/physics/neutronics/crossSectionSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
CONF_MIN_DRIVER_DENSITY = "minDriverDensity"
CONF_DUCT_HETEROGENEOUS = "ductHeterogeneous"
CONF_TRACE_ISOTOPE_THRESHOLD = "traceIsotopeThreshold"
CONF_XS_TEMP_ISOTOPE = "xsTempIsotope"


class XSGeometryTypes(Enum):
Expand Down Expand Up @@ -121,6 +122,7 @@ def getStr(cls, typeSpec: Enum):
CONF_XS_EXECUTE_EXCLUSIVE,
CONF_XS_PRIORITY,
CONF_XS_MAX_ATOM_NUMBER,
CONF_XS_TEMP_ISOTOPE,
},
XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_SLAB): {
CONF_XSID,
Expand All @@ -134,6 +136,7 @@ def getStr(cls, typeSpec: Enum):
CONF_XS_PRIORITY,
CONF_XS_MAX_ATOM_NUMBER,
CONF_MIN_DRIVER_DENSITY,
CONF_XS_TEMP_ISOTOPE,
},
XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_CYLINDER): {
CONF_XSID,
Expand All @@ -155,6 +158,7 @@ def getStr(cls, typeSpec: Enum):
CONF_MIN_DRIVER_DENSITY,
CONF_DUCT_HETEROGENEOUS,
CONF_TRACE_ISOTOPE_THRESHOLD,
CONF_XS_TEMP_ISOTOPE,
},
XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): {
CONF_XSID,
Expand All @@ -171,6 +175,7 @@ def getStr(cls, typeSpec: Enum):
CONF_XS_PRIORITY,
CONF_XS_MAX_ATOM_NUMBER,
CONF_MIN_DRIVER_DENSITY,
CONF_XS_TEMP_ISOTOPE,
},
}

Expand Down Expand Up @@ -203,6 +208,7 @@ def getStr(cls, typeSpec: Enum):
vol.Optional(CONF_COMPONENT_AVERAGING): bool,
vol.Optional(CONF_DUCT_HETEROGENEOUS): bool,
vol.Optional(CONF_TRACE_ISOTOPE_THRESHOLD): vol.Coerce(float),
vol.Optional(CONF_XS_TEMP_ISOTOPE): str,
}
)

Expand Down Expand Up @@ -259,19 +265,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 @@ -469,6 +477,10 @@ class XSModelingOptions:
model. The setting takes a float value that represents the number density cutoff
for isotopes to be considered "trace". If no value is provided, the default is 0.0.

xsTempIsotope: str
The isotope whose temperature is interrogated when placing a block in a temperature cross section group.
See `tempGroups`. "U238" is default since it tends to be dominant doppler isotope in most reactors.

Notes
-----
Not all default attributes may be useful for your specific application and you may
Expand Down Expand Up @@ -503,6 +515,7 @@ def __init__(
minDriverDensity=0.0,
ductHeterogeneous=False,
traceIsotopeThreshold=0.0,
xsTempIsotope="U238",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only for homogeneous compositions or does it also work with other modeling options as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd guess it's independent of the treatment since this occurs in the XS group manager rather than specific modeling options applied for the input generation. Also, I assume that U238 is selected here because of fuel, but does this work if you select an isotope that isn't in the composition? For example if the isotope is not in the block, does this fail, should it fail, or should the environment/temperature group never be updated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for other based my expected implementation and on Mikes testing.

):
self.xsID = xsID
self.geometry = geometry
Expand Down Expand Up @@ -531,6 +544,7 @@ def __init__(
# these are related to execution
self.xsExecuteExclusive = xsExecuteExclusive
self.xsPriority = xsPriority
self.xsTempIsotope = xsTempIsotope

def __repr__(self):
if self.xsIsPregenerated:
Expand All @@ -551,7 +565,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 @@ -307,7 +307,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
144 changes: 127 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 @@ -763,29 +779,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 @@ -796,6 +819,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 @@ -810,7 +841,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 Expand Up @@ -1077,6 +1108,85 @@ def test_copyPregeneratedFiles(self):
self.assertTrue(os.path.exists("rzmflxYA"))


class TestCrossSectionGroupManagerWithTempGrouping(unittest.TestCase):
def setUp(self):
cs = settings.Settings()
cs["tempGroups"] = [300, 400, 500]
self.blockList = makeBlocks(11)
buAndTemps = (
(1, 340),
(2, 150),
(6, 410),
(10.5, 290),
(2.5, 360),
(4, 460),
(15, 370),
(16, 340),
(15, 700),
(14, 720),
)
for b, env in zip(self.blockList, buAndTemps):
bu, temp = env
comps = b.getComponents(Flags.FUEL)
assert len(comps) == 1
c = next(iter(comps))
c.setTemperature(temp)
b.p.percentBu = bu
core = self.blockList[0].core

def getBlocks(includeAll=True):
return self.blockList

# this sets XSGM to only analyze the blocks in the block list.
core.getBlocks = getBlocks

self.csm = CrossSectionGroupManager(self.blockList[0].core.r, cs)
self.csm._setBuGroupBounds([3, 10, 30, 100])
self.csm.interactBOL()

def test_updateEnvironmentGroups(self):
self.csm.createRepresentativeBlocks()
BL = self.blockList
loners = [BL[1], BL[3]]

self.assertNotEqual(loners[0].getMicroSuffix(), loners[1].getMicroSuffix())
sameGroups = [(BL[0], BL[4]), (BL[2], BL[5]), (BL[6], BL[7]), (BL[8], BL[9])]

# check that likes have like and different are different
for group in sameGroups:
b1, b2 = group
xsSuffix = b1.getMicroSuffix()
self.assertEqual(xsSuffix, b2.getMicroSuffix())
for group in sameGroups:
newb1, newb2 = group
if b1 is newb1:
continue
self.assertNotEqual(xsSuffix, newb1.getMicroSuffix())
self.assertNotEqual(xsSuffix, newb2.getMicroSuffix())
for lone in loners:
self.assertNotEqual(xsSuffix, lone.getMicroSuffix())
self.assertNotEqual(loners[0].getMicroSuffix(), loners[1].getMicroSuffix())

# calculated based on the average of buAndTemps
expectedIDs = ["AF", "AA", "AL", "AC", "AH", "AR"]
expectedTemps = [
(340 + 360) / 2,
150,
(410 + 460) / 2,
290,
(370 + 340) / 2,
(700 + 720) / 2,
]
expectedBurnups = (1.75, 2, 5, 10.5, 15.5, 14.5)
for xsID, expectedTemp, expectedBurnup in zip(
expectedIDs, expectedTemps, expectedBurnups
):
b = self.csm.representativeBlocks[xsID]
thisTemp = self.csm.avgNucTemperatures[xsID]["U238"]
self.assertAlmostEqual(thisTemp, expectedTemp)
self.assertAlmostEqual(b.p.percentBu, expectedBurnup)


class TestXSNumberConverters(unittest.TestCase):
def test_conversion(self):
label = crossSectionGroupManager.getXSTypeLabelFromNumber(65)
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 @@ -121,7 +121,7 @@ def test_homogeneousXsDefaultSettingAssignment(self):
self.assertEqual(xsModel["YA"].ductHeterogeneous, False)
self.assertEqual(xsModel["YA"].traceIsotopeThreshold, 0.0)

def test_setDefaultSettingsByLowestBuGroupHomogeneous(self):
def test_setDefaultSettingsByLowestEnvGroupHomogeneous(self):
# Initialize some micro suffix in the cross sections
cs = settings.Settings()
xs = XSSettings()
Expand All @@ -147,7 +147,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
Loading
Loading