Skip to content

Commit

Permalink
Fix burnup-dependent reaction rates (#911)
Browse files Browse the repository at this point in the history
  • Loading branch information
keckler committed Oct 1, 2022
1 parent 82b3679 commit 6557412
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 145 deletions.
33 changes: 0 additions & 33 deletions armi/reactor/blocks.py
Expand Up @@ -1496,39 +1496,6 @@ def getLumpedFissionProductCollection(self):
"""
return composites.ArmiObject.getLumpedFissionProductCollection(self)

def getReactionRates(self, nucName, nDensity=None):
"""
Parameters
----------
nucName - str
nuclide name -- e.g. 'U235'
nDensity - float
number Density
Returns
-------
rxnRates : dict
dictionary of reaction rates (rxn/s) for nG, nF, n2n, nA and nP
Note
----
If you set nDensity to 1/CM2_PER_BARN this makes 1 group cross section generation easier
"""
if nDensity is None:
nDensity = self.getNumberDensity(nucName)
try:
return components.component.getReactionRateDict(
nucName,
self.core.lib,
self.p.xsType,
self.getIntegratedMgFlux(),
nDensity,
)
except AttributeError:
# AttributeError because there was no library because no parent.r -- this is a armiObject without flux so
# send it some zeros
return {"nG": 0, "nF": 0, "n2n": 0, "nA": 0, "nP": 0}

def rotate(self, deg):
"""Function for rotating a block's spatially varying variables by a specified angle.
Expand Down
77 changes: 0 additions & 77 deletions armi/reactor/components/component.py
Expand Up @@ -1187,41 +1187,6 @@ def getLumpedFissionProductCollection(self):
else:
return composites.ArmiObject.getLumpedFissionProductCollection(self)

def getReactionRates(self, nucName, nDensity=None):
"""
Parameters
----------
nucName - str
nuclide name -- e.g. 'U235'
nDensity - float
number Density
Returns
-------
rxnRates - dict
dictionary of reaction rates (rxn/s) for nG, nF, n2n, nA and nP
Note
----
if you set nDensity to 1/CM2_PER_BARN this makes 1 group cross section generation easier
"""
if nDensity is None:
nDensity = self.getNumberDensity(nucName)
try:
return getReactionRateDict(
nucName,
self.getAncestorWithFlags(Flags.CORE).lib,
self.parent.p.xsType,
self.getIntegratedMgFlux(),
nDensity,
)
except (AttributeError, KeyError):
# AttributeError because there was no library because no parent.r -- this is a armiObject without flux so
# send it some zeros
# KeyError because nucName was not in the library
return {"nG": 0, "nF": 0, "n2n": 0, "nA": 0, "nP": 0}

def getMicroSuffix(self):
return self.parent.getMicroSuffix()

Expand All @@ -1245,45 +1210,3 @@ class ShapedComponent(Component):
"""A component with well-defined dimensions."""

pass


def getReactionRateDict(nucName, lib, xsType, mgFlux, nDens):
"""
Parameters
----------
nucName - str
nuclide name -- e.g. 'U235', 'PU239', etc. Not to be confused with the nuclide
_label_, see the nucDirectory module for a description of the difference.
lib - isotxs
cross section library
xsType - str
cross section type -- e.g. - 'A'
mgFlux - numpy.nArray
integrated mgFlux (n-cm/s)
nDens - float
number density (at/bn-cm)
Returns
-------
rxnRates - dict
dictionary of reaction rates (rxn/s) for nG, nF, n2n, nA and nP
Note
----
assume there is no n3n cross section in ISOTXS
"""
nucLabel = nuclideBases.byName[nucName].label
key = "{}{}A".format(nucLabel, xsType)
libNuc = lib[key]
rxnRates = {"n3n": 0}
for rxName, mgXSs in [
("nG", libNuc.micros.nGamma),
("nF", libNuc.micros.fission),
("n2n", libNuc.micros.n2n),
("nA", libNuc.micros.nalph),
("nP", libNuc.micros.np),
]:
rxnRates[rxName] = nDens * sum(mgXSs * mgFlux)

return rxnRates
105 changes: 96 additions & 9 deletions armi/reactor/composites.py
Expand Up @@ -47,6 +47,7 @@
from armi.physics.neutronics.fissionProductModel import fissionProductModel
from armi.reactor import grids
from armi.reactor import parameters

from armi.reactor.flags import Flags, TypeSpec
from armi.reactor.parameters import resolveCollections
from armi.utils import densityTools
Expand Down Expand Up @@ -901,7 +902,10 @@ def getMassFrac(self, nucName):
return sum(self.getMassFracs().get(nucName, 0.0) for nucName in nuclideNames)

def getMicroSuffix(self):
pass
raise NotImplementedError(
f"Cannot get the suffix on {type(self)} objects. Only certain subclasses"
" of composite such as Blocks or Components have the concept of micro suffixes."
)

def _getNuclidesFromSpecifier(self, nucSpec):
"""
Expand Down Expand Up @@ -1492,10 +1496,6 @@ def getAncestorWithFlags(self, typeSpec: TypeSpec, exactMatch=False):
armi.composites.ArmiObject
the first ancestor up the chain of parents that matches the passed flags
Notes
-----
This will throw an error if no ancestor can be found that matches the typeSpec
See Also
--------
ArmiObject.hasFlags()
Expand Down Expand Up @@ -3097,15 +3097,58 @@ def getIntegratedMgFlux(self, adjoint=False, gamma=False):
)
return integratedMgFlux

def _getReactionRates(self, nucName, nDensity=None):
"""
Parameters
----------
nucName : str
nuclide name -- e.g. 'U235'
nDensity : float
number density
Returns
-------
rxnRates : dict
dictionary of reaction rates (rxn/s) for nG, nF, n2n, nA and nP
Notes
----
If you set nDensity to 1/CM2_PER_BARN this makes 1 group cross section generation easier
"""
from armi.reactor.blocks import Block

if nDensity is None:
nDensity = self.getNumberDensity(nucName)
try:
return getReactionRateDict(
nucName,
self.getAncestorWithFlags(Flags.CORE).lib,
self.getAncestor(lambda x: isinstance(x, Block)).getMicroSuffix(),
self.getIntegratedMgFlux(),
nDensity,
)
except AttributeError:
runLog.warning(
f"Object {self} does not belong to a core and so has no reaction rates.",
single=True,
)
return {"nG": 0, "nF": 0, "n2n": 0, "nA": 0, "nP": 0}
except KeyError:
runLog.warning(
f"Attempting to get a reaction rate on an isotope not in the lib {nucName}.",
single=True,
)
return {"nG": 0, "nF": 0, "n2n": 0, "nA": 0, "nP": 0}

def getReactionRates(self, nucName, nDensity=None):
"""
Get the reaction rates of a certain nuclide on this object.
Parameters
----------
nucName - str
nucName : str
nuclide name -- e.g. 'U235'
nDensity - float
nDensity : float
number Density
Returns
Expand All @@ -3121,8 +3164,10 @@ def getReactionRates(self, nucName, nDensity=None):
"""
rxnRates = {"nG": 0, "nF": 0, "n2n": 0, "nA": 0, "nP": 0, "n3n": 0}

for armiObject in self:
for rxName, val in armiObject.getReactionRates(
# not all composite objects are iterable (i.e. components), so in that
# case just examine only the object itself
for armiObject in self.getChildren() or [self]:
for rxName, val in armiObject._getReactionRates(
nucName, nDensity=nDensity
).items():
rxnRates[rxName] += val
Expand Down Expand Up @@ -3292,3 +3337,45 @@ def getDominantMaterial(
return samples[maxMatName]

return None


def getReactionRateDict(nucName, lib, xsSuffix, mgFlux, nDens):
"""
Parameters
----------
nucName : str
nuclide name -- e.g. 'U235', 'PU239', etc. Not to be confused with the nuclide
_label_, see the nucDirectory module for a description of the difference.
lib : isotxs
cross section library
xsSuffix : str
cross section suffix, consisting of the type followed by the burnup group,
e.g. 'AB' for the second burnup group of type A
mgFlux : numpy.nArray
integrated mgFlux (n-cm/s)
nDens : float
number density (atom/bn-cm)
Returns
-------
rxnRates - dict
dictionary of reaction rates (rxn/s) for nG, nF, n2n, nA and nP
Notes
-----
Assume there is no n3n cross section in ISOTXS
"""
nucLabel = nuclideBases.byName[nucName].label
key = "{}{}".format(nucLabel, xsSuffix)
libNuc = lib[key]
rxnRates = {"n3n": 0}
for rxName, mgXSs in [
("nG", libNuc.micros.nGamma),
("nF", libNuc.micros.fission),
("n2n", libNuc.micros.n2n),
("nA", libNuc.micros.nalph),
("nP", libNuc.micros.np),
]:
rxnRates[rxName] = nDens * sum(mgXSs * mgFlux)

return rxnRates

0 comments on commit 6557412

Please sign in to comment.