Source code for armi.nucDirectory.transmutations

# 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.

"""
This module contains the definition of :py:class:`~Transmutation` and :py:class:`~Decay` classes.

.. inheritance-diagram::
    Transmutation DecayMode

The mappings between active nuclides during transmutation and decay are described in a
``burn-chain.yaml`` file pointed to by the ``burnChainFileName``
setting. This file contains one entry per nuclide that can transmute or decay that
look similar to the example below::

    U238:
    - nuSF: 2.0000
    - transmutation:
        branch: 1.0
        products:
        - NP237
        type: n2n
    - transmutation:
        branch: 1.0
        products:
        - LFP38
        type: fission
    - transmutation:
        branch: 1.0
        products:
        - NP239
        - PU239
        type: nGamma
    - decay:
        branch: 5.45000e-07
        halfLifeInSeconds: 1.4099935680e+17
        products:
        - LFP38
        type: sf

This example defines 3 transmutations (an ``(n,2n)`` reaction, an ``(n,fission)`` reaction, an
``(n,gamma``)`` reaction), and a spontaneous fission decay reaction with a very low branching
ratio. Valid reaction ``type`` values are listed in :py:class:`~armi.nucDirectory.transmutations.Transmutation`
and :py:class:`~armi.nucDirectory.transmutations.DecayMode`.

The ``branch`` entry determines the fraction of the products of a given reaction that will end up
in a particular product. The branches must never sum up to anything other than 1.0.

The ``products`` entry is a list, but only one entry will be the actual product. The list defines
a preference order. For example, if ``NP239`` is being tracked as an active nuclide in the problem
it will be the product of the ``nGamma`` reaction above. Otherwise, ``U238`` will transmute directly
to the alternate product, ``PU239``.

.. warning:: If you track very short-lived decays explicitly then the burn matrix becomes very
             ill-conditioned and numerical solver issues can result. Specialized matrix
             exponential solvers (e.g. CRAM [1]) are required to get adequate solutions in these cases [2].

The example above also defines a ``nuSF`` item, which is how many neutrons are emitted per spontaneous
fission. This is used for intrinsic source term calculations.

[1] Pusa, Maria, and Jaakko Leppanen. "Computing the matrix exponential in burnup calculations."
    Nuclear science and engineering 164.2 (2010): 140-150.

[2] Moler, Cleve, and Charles Van Loan. "Nineteen dubious ways to compute the exponential of a matrix."
    SIAM review 20.4 (1978): 801-836.
"""
import math

from armi import runLog
from armi.utils import iterables

LN2 = math.log(2)

TRANSMUTATION_TYPES = ["n2n", "fission", "nGamma", "nalph", "np", "nd", "nt"]

DECAY_MODES = [
    "bmd",  # beta minus
    "bpd",  # beta plus
    "ad",  # alpha decay
    "ec",  # electron capture
    "sf",
]  # spontaneous-fission

PRODUCT_PARTICLES = {"nalph": "HE4", "np": "H1", "nd": "H2", "nt": "H3", "ad": "HE4"}


[docs]class Transmutable: """ Transmutable base class. Attributes ---------- parent : NuclideBase The parent nuclide in this reaction. type : str The type name of reaction (e.g. ``n2n``, ``fission``, etc.) productNuclides : list The names of potential product nuclides of this reaction, in order of preference. Multiple options exist to allow the library to specify a transmutation to one nuclide if the user is modeling that nuclide, and other ones as fallbacks in case the user is not tracking the preferred product. Only one of these products will be created. productParticle : str The outgoing particle of this reaction. Could be HE4 for n,alpha, etc. Default is None. branch : float The fraction of the time that this transmutation occurs. Should be between 0 and 1. Less than 1 when a decay or reaction can branch between multiple productNuclides. Do not make this >1 to get more than one product because it scales the reaction cross section which will double-deplete the parent. Notes ----- These are used to link two :py:class:`~armi.nucDirectory.nuclideBases.NuclideBase` objects through transmutation or decay. See Also -------- Transmutation DecayMode """ def __init__(self, parent, dataDict): self.parent = parent self.type = dataDict["type"] self.productNuclides = tuple(dataDict["products"]) self.productParticle = dataDict.get( "productParticle", PRODUCT_PARTICLES.get(self.type) ) self.branch = dataDict.get("branch", None) if self.branch is None: self.branch = 1.0 runLog.info( f"The branching ratio for {self} was not defined and is assumed to be 1.0." )
[docs] def getPreferredProduct(self, libraryNucNames): """ Get the index of the most preferred transmutation product/decay daughter. Notes ----- The ARMI burn chain is not a full burn chain. It short circuits shorter half-lives, and uses lumped nuclides as catch-all objects for things that just sit around. Consequently, the "preferred" product/daughter may not be actual physical product/daughter. """ for product in self.productNuclides: if product in libraryNucNames: return product groupedNames = iterables.split( libraryNucNames, max(1, int(len(libraryNucNames) / 10)) ) msg = ( "Could not find suitable product/daughter for {}.\n" "The available options were:\n {}".format( self, ",\n ".join(", ".join(chunk) for chunk in groupedNames) ) ) raise KeyError(msg)
[docs]class Transmutation(Transmutable): r""" A transmutation from one nuclide to another. Notes ----- The supported transmutation types include: * :math:`n,2n` * :math:`n,fission` * :math:`n,\gamma` (``nGamma``) * :math:`n,\alpha` (``nalph``) * :math:`n,p` (proton) (``np``) * :math:`n,d` (deuteron) (``nd``) * :math:`n,t` (triton) (``nt``) """ def __init__(self, parent, dataDict): Transmutable.__init__(self, parent, dataDict) if self.type not in TRANSMUTATION_TYPES: raise KeyError("{} not in {}".format(self.type, TRANSMUTATION_TYPES)) def __repr__(self): return "<Transmutation by {} from {:7s} to {} with branching ratio of {:12.5E}>".format( self.type, self.parent.name, self.productNuclides, self.branch )
[docs]class DecayMode(Transmutable): r"""Defines a decay from one nuclide to another. Notes ----- The supported decay types are also all transmutations, and include: * :math:`\beta^-` (``bmd``) * :math:`\beta^+` (``bpd``) * :math:`\alpha` (``ad``) * Electron capture (``ec``) * Spontaneous fission (``sf``) Of note, the following are not supported: * Internal conversion * Gamma decay """ def __init__(self, parent, dataDict): Transmutable.__init__(self, parent, dataDict) self.halfLifeInSeconds = parent.halflife # Check for user-defined value of half-life within the burn-chain data. If this is # updated then prefer the user change and then note this to the user. Otherwise, # maintain the default loaded from the nuclide bases. userHalfLife = dataDict.get("halfLifeInSeconds", None) if userHalfLife: if userHalfLife != parent.halflife: runLog.info( f"Half-life provided for {self} will be updated from " f"{parent.halflife:<15.11e} to {userHalfLife:<15.11e} seconds based on " "user provided burn-chain data." ) self.halfLifeInSeconds = userHalfLife self.decay = ( LN2 / self.halfLifeInSeconds * self.branch ) # decay constant, reduced by branch to make it accurate if self.type not in DECAY_MODES: raise KeyError("{} is not in {}".format(self.type, DECAY_MODES)) def __repr__(self): return ( "<DecayMode by {} from {:7s} to {} with a half-life of {:12.5E} s>".format( self.type, self.parent.name, self.productNuclides, self.halfLifeInSeconds, ) )