Skip to content

Commit

Permalink
Extension of the framework nuclides (#998)
Browse files Browse the repository at this point in the history
Update to the nuclides that are built into the ARMI framework. This expands the set of nuclide bases to include 4614 nuclides and imports the half-life data from the chart of the nuclides from a combination of the RIPL-3 database and from the IAEA chart of the nuclides.

Other Changes:

Each Element now has a phase and a group.
Element.nuclideBases becomes Element.nuclides - This is an API-breaking change.
  • Loading branch information
jakehader committed Dec 20, 2022
1 parent a03a75a commit 59b0cad
Show file tree
Hide file tree
Showing 55 changed files with 7,738 additions and 17,774 deletions.
2 changes: 0 additions & 2 deletions MANIFEST.in
Expand Up @@ -27,9 +27,7 @@ include armi/nuclearDataIO/tests/library-file-generation/mc2v2-dlayxs.inp
include armi/nuclearDataIO/tests/library-file-generation/mc2v3-AA.inp
include armi/nuclearDataIO/tests/library-file-generation/mc2v3-AB.inp
include armi/nuclearDataIO/tests/library-file-generation/mc2v3-dlayxs.inp
include armi/nuclearDataIO/tests/longLivedRipleData.dat
include armi/nuclearDataIO/tests/simple_hexz.inp
include armi/nuclearDataIO/tests/z036.dat
include armi/tests/1DslabXSByCompTest.yaml
include armi/tests/COMPXS.ascii
include armi/tests/ISOAA
Expand Down
2 changes: 0 additions & 2 deletions armi/_bootstrap.py
Expand Up @@ -57,8 +57,6 @@ def _addCustomTabulateTables():
_addCustomTabulateTables()


from armi import runLog

from armi.nucDirectory import nuclideBases

# Nuclide bases get built explicitly here to have better determinism
Expand Down
13 changes: 13 additions & 0 deletions armi/cases/case.py
Expand Up @@ -538,6 +538,19 @@ def _initBurnChain(self):
but not long after (because nucDir is framework-level and expected to be
up-to-date by lots of modules).
"""
if not self.cs["initializeBurnChain"]:
runLog.info(
"Skipping burn-chain initialization due disabling of the `initializeBurnChain` setting."
)
return

if not os.path.exists(self.cs["burnChainFileName"]):
raise ValueError(
f"The burn-chain file {self.cs['burnChainFileName']} does not exist. The "
f"data cannot be loaded. Fix this path or disable burn-chain initialization using "
f"the `initializeBurnChain` setting."
)

with open(self.cs["burnChainFileName"]) as burnChainStream:
nuclideBases.imposeBurnChain(burnChainStream)

Expand Down
9 changes: 7 additions & 2 deletions armi/materials/material.py
Expand Up @@ -247,7 +247,7 @@ def adjustMassFrac(self, nuclideName: str, massFraction: float) -> None:
molesPerCC = massDensities / atomicMasses # item-wise division

enrichedIndex = nucsNames.index(nuclideName)
isoAndEles = nuclideBases.byName[nuclideName].element.nuclideBases
isoAndEles = nuclideBases.byName[nuclideName].element.nuclides
allIndicesUpdated = [
nucsNames.index(nuc.name)
for nuc in isoAndEles
Expand Down Expand Up @@ -310,8 +310,13 @@ def adjustMassFrac(self, nuclideName: str, massFraction: float) -> None:
updatedMassDensities = molesPerCC * atomicMasses
updatedDensity = updatedMassDensities.sum()
massFracs = updatedMassDensities / updatedDensity
self.p.massFrac = {nuc: weight for nuc, weight in zip(nucsNames, massFracs)}

if not numpy.isclose(sum(massFracs), 1.0, atol=1e-10):
raise RuntimeError(
f"The mass fractions {massFracs} in {self} do not sum to 1.0."
)

self.p.massFrac = {nuc: weight for nuc, weight in zip(nucsNames, massFracs)}
if self.p.refDens != 0.0: # don't update density if not assigned
self.p.refDens = updatedDensity

Expand Down
17 changes: 9 additions & 8 deletions armi/materials/tests/test_lithium.py
Expand Up @@ -31,22 +31,23 @@ def setUp(self):
self.mat = Lithium()

self.Lithium_LI_wt_frac = Lithium()
self.Lithium_LI_wt_frac.applyInputParams(LI_wt_frac=0.5)
self.Lithium_LI_wt_frac.applyInputParams(LI6_wt_frac=0.5)

self.Lithium_LI6_wt_frac = Lithium()
self.Lithium_LI6_wt_frac.applyInputParams(LI6_wt_frac=0.6)

self.Lithium_both = Lithium()
self.Lithium_both.applyInputParams(LI_wt_frac=0.7, LI6_wt_frac=0.8)
self.Lithium_both.applyInputParams(LI6_wt_frac=0.8)

def test_Lithium_material_modifications(self):
self.assertEqual(self.mat.getMassFrac("LI6"), self.defaultMassFrac)

self.assertEqual(self.Lithium_LI_wt_frac.getMassFrac("LI6"), 0.5)

self.assertEqual(self.Lithium_LI6_wt_frac.getMassFrac("LI6"), 0.6)

self.assertEqual(self.Lithium_both.getMassFrac("LI6"), 0.8)
self.assertAlmostEqual(
self.Lithium_LI_wt_frac.getMassFrac("LI6"), 0.5, places=10
)
self.assertAlmostEqual(
self.Lithium_LI6_wt_frac.getMassFrac("LI6"), 0.6, places=10
)
self.assertAlmostEqual(self.Lithium_both.getMassFrac("LI6"), 0.8, places=10)

def test_density(self):
ref = self.mat.density(Tc=100)
Expand Down
44 changes: 14 additions & 30 deletions armi/nucDirectory/__init__.py
Expand Up @@ -21,7 +21,6 @@
#. :ref:`Generic nuclide data <doc-nuclide-bases>` - this includes mass, atomic number, natural
abundance and various names and labels that are used in ARMI for the nuclide. It also includes
decay and transmutation modes.
#. Nuclide specific :ref:`cross section information <doc-nuclides>`.
.. _doc-elements:
Expand Down Expand Up @@ -99,8 +98,8 @@
number (A), the natural abundance, and all of the decay and transmutation modes (well, ARMI's
decay and transmutation modes).
Nuclide names, labels and MC\*\*2 IDs
-------------------------------------
Nuclide names, labels, and IDs
------------------------------
Nuclides have names, labels and IDs.
:py:attr:`INuclide.name <armi.nucDirectory.nuclideBases.INuclide.name>`
Expand All @@ -109,20 +108,15 @@
from the corresponding element symbol and mass number (A).
:py:attr:`INuclide.label <armi.nucDirectory.nuclideBases.INuclide.label>`
The nuclide label is a unique 4 character name which identifies the nuclide from all others
within MC\*\*2 and DIF3D. The label must be 4 characters, because MC\*\*2 and DIF3D only
allow 6 character labels, two of which we reserver for the cross section ID. Labels
are not necessarily human readable, but are generally the nuclide symbol followed by
the last two digits of the mass number (A), so the nuclide for U235 has the label
``U235``, but PU239 has the label ``PU39``.
:py:attr:`INuclide.mc2id <armi.nucDirectory.nuclideBases.INuclide.mc2id>`
The mc2id is the MC\*\*2 id used for MC\*\*2 version 2. This should only be used
when loading an ISOTXS file, or writing MC\*\*2 input.
:py:meth:`INuclide.mc3id() <armi.nucDirectory.nuclideBases.INuclide.mc3id>`
The mc3id is the MC\*\*2 id used for MC\*\*2 version 3. This should only be used
when loading an ISOTXS file, or writing MC\*\*2 input.
The nuclide label is a unique 4 character name which identifies the nuclide from all others.
The label is fixed to 4 characters to conform with the CCCC standard files, which traditionally
only allow for a maximum of 6 character labels in legacy nuclear codes. Of the 6 allowable
characters, 4 are reserved for the unique identifier of the nuclide and 2 characters are reserved
for cross section labels (i.e., AA, AB, ZA, etc.). The cross section labels are based on the
cross section group manager implementation within the framework. These labels are not necessarily
human readable/interpretable, but are generally the nuclide symbol followed by the last two digits
of the mass number (A), so the nuclide for U235 has the label ``U235``, but PU239 has the label
``PU39``.
For reference, there is a complete list of the nuclides along with the names, labels and IDs
:py:mod:`here <armi.nucDirectory.nuclideBases>`.
Expand All @@ -134,8 +128,9 @@
what information you have, or "know," about a nuclide. For example, if you know a nuclide name, use
the :py:data:`~armi.nucDirectory.nuclideBases.byName` dictionary. There are also dictionaries
available for retrieving by the label, :py:data:`~armi.nucDirectory.nuclideBases.byLabel`, and by
the MC\*\*2 ID, :py:data:`~armi.nucDirectory.nuclideBases.byMccId` (this works for both version 2
and version 3 IDs).
other software-specific IDs (i.e., MCNP, Serpent, MC2-2, and MC2-3). The software-specific labels
are incorporated into the framework to support plugin developments and may be extended as needed
by end-users as needs arise.
>>> from armi.nucDirectory import nuclideBases
>>> pu239 = nuclideBases.byName['PU239']
Expand All @@ -160,15 +155,4 @@
However, often times they will also need other information, such as the mass. Consequently,
the index which contains the name or label is not fulfilling its intended purpose; in order to
perform the operation, ``weight * numberDensity``, you'll still need the nuclide object!
.. _doc-nuclides:
Nuclides
========
Another closely related object is the :py:class:`armi.nucDirectory.nuclide.Nuclide`.
:py:class:`Nuclides <nuclide.Nuclide>` are created when loading an ISOTXS file from MC\*\*2.
:py:class:`Nuclides <nuclide.Nuclide>` contain the same information as
:py:class:`INuclides <INuclide>`, but also contain multi-group microscopic cross section data.
"""

0 comments on commit 59b0cad

Please sign in to comment.