# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Handle awareness of Thermal Scattering Laws.
Scattering characteristics of thermal neutrons are often significantly different
between a free atom and one bound in a particular molecule. Nuclear data libraries
often have special tables to account for the bound states.
These data are commonly represented as S(alpha, beta) tables.
Here we provide objects representing the thermal scattering law (TSL) information.
We expect them to be most useful as class attributes on :py:class:`~armi.materials.material.Material` subclasses
to inform physics solvers that support thermal scattering of the TSLs. See
:py:class:`~armi.materials.graphite.Graphite` for an example.
We do not provide special versions of various NuclideBases like C12 because of
potential errors in choosing one over the other
The information contained in here is based on Parsons, LA-UR-18-25096,
https://mcnp.lanl.gov/pdf_files/la-ur-18-25096.pdf
Scattering law data are currently available for a variety of classifications:
* Element in Compound (H in H2O, Be in BeO)
* Element in structure (C in Graphite, Be in metal)
* Can be separated as crystalline, 30% porous, 10% porous, etc.
* Element in spin isomer (para H, ortho H, para D, ortho D, etc.)
* Compound in phase (solid CH4, liquid CH4, SiO2-alpha, SiO2-beta).
* Just compound (benzene)
* Just isotope (Fe56, Al27)
The labels for these vary across evaluations (e.g. ENDF/B-VII, ENDF/B-VIII, etc.). We provide
ENDF/B-III.0 and ACE labels. Other physics kernels will have to derive their own labels
as appropriate in client code.
Like NuclideBase and Element, we want to have only one ThermalScattering instance
for each TSL, so we use a module-level directory called ``byNbAndCompound``. This improves
efficiency and allows better cross-referencing when thousands of material instances
would otherwise have identical instances of these.
Thus, in practice, users should rarely instantiate these on their own.
"""
from typing import Tuple, Union
from armi.nucDirectory import nuclideBases as nb
from armi.nucDirectory import elements
BE_METAL = "Be-metal"
BEO = "BeO"
SIC = "SiC"
D2O = "D2O"
H2O = "H2O"
UN = "UN"
UO2 = "UO2"
ZRH = "ZrH"
CRYSTALLINE_GRAPHITE = "crystalline-graphite"
GRAPHITE_10P = "reactor-graphite-10P"
GRAPHITE_30P = "reactor-graphite-30P"
byNbAndCompound = {}
[docs]class ThermalScattering:
"""
Thermal Scattering data.
Parameters
----------
nuclideBases : INuclide or tuple of INuclide
One or more nuclide bases whose existence would trigger the inclusion of the TSL.
Generally items here will be a NaturalNuclideBase like ``nb.byName["C"]`` for Carbon
but it is a tuple to capture, e.g. the C and H in *methane*.
compoundName : str, optional
Label indicating what the subjects are in (e.g. ``"Graphite"`` or ``"H2O"``.
Can be left off for, e.g. Fe56.
endf8Label : str, optional
Label for endf8 evaluation
aceLabel: str, optional
ace label
"""
def __init__(
self,
nuclideBases: Union[nb.INuclide, Tuple[nb.INuclide]],
compoundName: str = None,
endf8Label: str = None,
aceLabel: str = None,
):
if isinstance(nuclideBases, nb.INuclide):
# handle common single entry for convenience
nuclideBases = [nuclideBases]
self.nbs = frozenset(set(nuclideBases))
self.compoundName = compoundName
self.endf8Label = endf8Label or self._genENDFB8Label()
self.aceLabel = aceLabel or self._genACELabel()
def __repr__(self):
return f"<ThermalScatteringLaw - Compound: {self.compoundName}, Nuclides: {self.nbs}"
def __hash__(self):
return hash((self.compoundName, self.nbs))
def __eq__(self, other):
return hash(self) == hash(other)
[docs] def getSubjectNuclideBases(self):
"""
Return all nuclide bases that could be subject to this law.
In cases where the law is defined by a NaturalNuclideBase, all potential
isotopes of the element as well as the element it self should trigger it.
This helps handle cases where, for example, C or C12 is present.
"""
subjectNbs = set()
for nbi in self.nbs:
subjectNbs.add(nbi)
if isinstance(nbi, nb.NaturalNuclideBase):
for nuc in nbi.element.nuclides:
subjectNbs.add(nuc)
subjectNbs = sorted(subjectNbs)
return subjectNbs
def _genENDFB8Label(self):
"""
Generate the ENDF/B-VIII.0 label.
Several ad-hoc assumptions are made in converting this object to a ENDF/B-VIII label
which may not apply in all cases.
It is believed that these rules cover most ENDF TSLs listed in
Parsons, LA-UR-18-25096, https://mcnp.lanl.gov/pdf_files/la-ur-18-25096.pdf
Unfortunately, the ace labels are not as easily derived.
* If nuclideBases is length one and contains a ``NaturalNuclideBase``, then the
name will be assumed to be ``Element_in_compoundName``
* If nuclideBases is length one and is a NuclideBase, it is assumed to be an isotope
like Fe-56 and the label will be (for example) 026_Fe_056
* If nuclideBases has length greater than one, the compoundName will form the
entire of the label. So, if Si and O are in the bases, the compoundName must
be ``SiO2-alpha`` in order to get ``tsl-SiO2-alpha.endf`` as a endf8 label.
"""
first = next(iter(self.nbs))
if len(self.nbs) > 1:
# just compound (like SiO2)
label = f"tsl-{self.compoundName}.endf"
elif isinstance(first, nb.NaturalNuclideBase):
# element in compound
label = f"tsl-{first.element.symbol}in{self.compoundName}.endf"
elif isinstance(first, nb.NuclideBase):
# just isotope
element = elements.byZ[first.z]
label = (
f"tsl-{first.z:03d}_{element.symbol.capitalize()}_{first.a:03d}.endf"
)
else:
raise ValueError(f"{self} label cannot be generated")
return label
def _genACELabel(self):
"""
Attempt to derive the ACE label of a TSL.
There are certain exceptions that cannot be derived and must be provided
by the user upon instantiation, for example:
* ``grph10``
* ``grph30``
* ``grph``
"""
first = next(iter(self.nbs))
if len(self.nbs) > 1:
# just compound (like SiO2)
label = f"{self.compoundName[:4].lower()}"
elif isinstance(first, nb.NaturalNuclideBase):
# element in compound
label = f"{first.element.symbol.lower()}-{self.compoundName.lower()}"
elif isinstance(first, nb.NuclideBase):
# just isotope
element = elements.byZ[first.z]
label = f"{element.symbol.lower()}-{first.a:d}"
else:
raise ValueError(f"{self} label cannot be generated")
return label
[docs]def factory():
"""
Generate the :class:`ThermalScattering` instances.
The logic for these is a bit complex so we skip reading a text file and code
it up here.
This is called by the nuclideBases factory since it must ALWAYS be re-run when
the nuclideBases are rebuilt.
See Also
--------
armi.nucDirectory.nuclideBases.factory
Calls this during ARMI initialization.
.. warning::
This gets called automatically during init, so don't call it
unless you know what you're doing.
"""
global byNbAndCompound
byNbAndCompound.clear()
al27 = nb.byName["AL27"]
fe56 = nb.byName["FE56"]
be = nb.byName["BE"]
c = nb.byName["C"]
d = nb.byName["H2"]
n = nb.byName["N"]
o = nb.byName["O"]
h = nb.byName["H"]
u = nb.byName["U"]
zr = nb.byName["ZR"]
si = nb.byName["SI"]
for isotope in [al27, fe56]:
byNbAndCompound[isotope, None] = ThermalScattering(isotope)
byNbAndCompound[be, BE_METAL] = ThermalScattering(
be, BE_METAL, endf8Label=f"tsl-{BE_METAL}.endf", aceLabel="be-met"
)
byNbAndCompound[be, BEO] = ThermalScattering(
be, BEO, endf8Label=BEO, aceLabel="be-beo"
)
byNbAndCompound[c, SIC] = ThermalScattering(c, SIC)
byNbAndCompound[d, D2O] = ThermalScattering(d, D2O, f"tsl-Din{D2O}.endf", "d-d2o")
byNbAndCompound[h, H2O] = ThermalScattering(h, H2O)
byNbAndCompound[h, ZRH] = ThermalScattering(h, ZRH)
byNbAndCompound[n, UN] = ThermalScattering(n, UN)
byNbAndCompound[o, BEO] = ThermalScattering(o, BEO)
byNbAndCompound[o, D2O] = ThermalScattering(o, D2O, f"tsl-Oin{D2O}.endf", "o-d2o")
byNbAndCompound[o, UO2] = ThermalScattering(o, UO2)
byNbAndCompound[u, UO2] = ThermalScattering(u, UO2)
byNbAndCompound[u, UN] = ThermalScattering(u, UN)
byNbAndCompound[zr, ZRH] = ThermalScattering(zr, ZRH)
byNbAndCompound[si, SIC] = ThermalScattering(si, SIC)
byNbAndCompound[c, CRYSTALLINE_GRAPHITE] = ThermalScattering(
c, CRYSTALLINE_GRAPHITE, f"tsl-{CRYSTALLINE_GRAPHITE}.endf", "grph"
)
byNbAndCompound[c, GRAPHITE_10P] = ThermalScattering(
c, GRAPHITE_10P, f"tsl-{GRAPHITE_10P}.endf", "grph10"
)
byNbAndCompound[c, GRAPHITE_30P] = ThermalScattering(
c, GRAPHITE_30P, f"tsl-{GRAPHITE_30P}.endf", "grph30"
)