""" This module defines the Python class passport used in ONIX"""
import math as m
from . import data as d
from .utils import functions as fct
[docs]class Passport(object):
"""Passport stores all the relevant data of indivudual nuclides and offers methods to extract information on them.
The passport class is individually instantiated for each nuclide. It contains two types of information: constant and variable data.
Constant data, such as the atomic mass, decay constant or the element's family (actinide or fission products) do not change over the course of a simulation.
Variable data such as cross sections or fission yields do vary during a simulation and need to be updated regularly during a simulation.
Some of the data are created at the time of the instantiation of the class such as the element's family or the nuclide's
neutron reaction daughters. Other type of data, typically large in size such as cross sections and decay constants, are to be explicitly set or loaded by the code or by the user.
Parameters
----------
nuc_id : str
Name or z-a-m id of the nuclide.
"""
_mass = None
_fission_E = None
_name = None
_zamid = None
#_allreacs_dic_list = [] # A list of dicts of all the creation and destruction terms at eacch sequence point
# Number id of the nuclide for which a passport is instantiated
def __init__(self, nuc_id):
self.nuc_id = nuc_id # probably useless
if self._get_id_input_type(nuc_id) == 'name':
self._name = self.nuc_id
self._zamid = fct.name_to_zamid(self._name)
elif self._get_id_input_type(nuc_id) == 'zamid':
self._zamid = self.nuc_id
self._name = fct.zamid_to_name(self._zamid)
self._set_state()
self._set_xschild()
self._set_decaychild()
self._set_xsparent()
self._set_decayparent()
self._set_all_parent()
self._set_all_child()
# self._all_non0_child = self.get_all_non0_child()
self._all_reacs_dic = None
self._allreacs_dic_list = []
self.__current_sorted_allreacs_tuple_list = None
self._sorted_allreacs_tuple_mat = []
self._current_dens = None
self._dens_seq = None
self._current_dens_subseq = None
self._dens_subseq_mat = None
self._current_xs = None
self._xs_seq = None
self._decay_a = None
self._decay_b = None
self._fy = None
if self.get_FAM() == 'ACT':
self._set_energy_per_fission()
@property
def mass(self):
"""Returns the atomic mass of the nuclide in grams"""
if self._mass is None:
pass # define exception for undefined variable
return self._mass
@mass.setter
def mass(self, new_mass):
"""Sets the atomic mass of the nuclide in grams"""
self._mass = new_mass
# Passport instantiation will read and extract by itself mass for the corresponding nuclide
[docs] def load_mass(self):
"""Loads the atomic mass of the nuclide in gram.
This method directly fetches the atomic mass from the mass library and automatically sets it
to the passport object"""
zamid = self._zamid
zaid = zamid[:-1]
# Some (rare) nuclides (for example Ag133 from ENDFVIII) are not in the mass library
# For these nuclides, the mass number is used for the mass
if zaid not in d.default_atm_mass_lib:
mass = int(zaid[-3:])
else:
mass = d.default_atm_mass_lib[zaid]
self._mass = mass
@property
def decay_a(self):
"""Returns the absolute values of the decay constant of the nuclide"""
if self._decay_a is None:
pass # define exception for undefined variable
return self._decay_a
@property
def decay_b(self):
"""Returns the fraction percent values of the decay constant of the nuclide"""
if self._decay_b is None:
pass # define exception for undefined variable
return self._decay_b
@decay_a.setter
def decay_a(self, new_decay_a):
"""Sets the absolute values of the decay constant of the nuclide"""
self._decay_a = new_decay_a
@decay_b.setter
def decay_b(self, new_decay_b):
"""Sets the fraction percent values of the decay constant of the nuclide"""
self._decay_b = new_decay_b
[docs] def set_decay(self, decay_a, decay_b):
"""Sets the absolute and fracional values of the decay constant of the nuclide"""
self.decay_a = decay_a
self.decay_b = decay_b
[docs] def load_decay(self):
"""Loads the decay constant value of the nuclide.
This method directly fetches the decay constant values (absolute and fractional) from the decay library and automatically sets it to the passport object"""
zamid = self._zamid
decay_a = d.default_decay_lib_a[zamid]
decay_b = d.default_decay_lib_b[zamid]
self.decay_a = decay_a
self.decay_b = decay_b
@property
def current_xs(self):
"""Returns the current cross sections dictionnary of the nuclide"""
if self._current_xs is None:
pass # define exception for undefined variable
return self._current_xs
@current_xs.setter
def current_xs(self, new_xs):
"""Sets the current cross sections dictionnary of the nuclide"""
self._current_xs = new_xs
@property
def xs_seq(self):
"""Returns the sequence of cross section dictionnaries of the nuclide"""
return self._xs_seq
@xs_seq.setter
def xs_seq(self, new_xs_seq):
"""Sets the sequence of cross section dictionnaries of the nuclide"""
self._xs_seq = new_xs_seq
def _append_xs_seq(self, new_xs):
"""Appends a new cross section dictionnary to the sequence of cross section dictionnaries"""
self._xs_seq.append(new_xs)
def _set_xs(self, new_xs):
"""This method first verifies if a macro sequence of cross section dictionnaries has been already created for the nuclide.
If not, it creates a sequence and append the new cross section dictionnary.
If a sequence already exists, it just appends the new dictionnary to the already existing sequence"""
# If this is the fist step
if self.current_xs == None:
self.xs_seq = [new_xs]
else:
self._append_xs_seq(new_xs)
self.current_xs = new_xs
# self._append_time_subseq_mat()
def _overwrite_xs(self, new_xs):
"""This method first verifies if a macro sequence of cross section dictionnaries has been already created for the nuclide.
If not, it creates a sequence and append the new cross section dictionnary.
If a sequence already exists, it overwrites the last dictionnary with the new dictionnary in the already existing sequence"""
# If this is the fist step
if self.current_xs == None:
self.xs_seq = [new_xs]
else:
self.xs_seq[-1] = new_xs
self.current_xs = new_xs
# self._append_time_subseq_mat()
# Passport instantiation will read and extract by itself mass for the corresponding nuclide
[docs] def load_xs(self):
"""Loads the cross sections data of the nuclide.
This method directly fetches the cross sections data from the cross section library and automatically sets
it to the passport object"""
zamid = self._zamid
xs_lib = d.default_xs_lib
xs = xs_lib[zamid]
self._current_xs = xs
@property
def fy(self):
"""Returns the fission yields dictionnary.
Fission yield dictionnary are defined per fission product. Each key of the dictionnary is the zamid of one of the
fission parents and the corresponding entry is the fission yield normalized to 1 (value between 0 and 1)"""
if self._fy is None:
pass # define exception for undefined variable)
return self._fy
@fy.setter
def fy(self, new_fy):
"""Sets the fission yields dictionnary to a nuclide
Parameters
----------
new_fy : dict
Dictionnary of fission yields for a daughter nuclide
"""
if self.get_FAM == 'ACT':
raise Not_a_Fission_Product("{} is not a FP and can't be given fission yields".format(self._zamid))
self._fy = new_fy
# Passport instantiation will read and extract by itself mass for the corresponding nuclide
[docs] def load_fy(self):
"""Load the fission yields dictionnary of the nuclide
This method directly fetches the fission yields data from the fission yield library and automatically sets
it to the passport object
If the nuclide for which the fission yields data are being loaded is not a fission product,
the error onix.Not_a_Fission_Product will be raised"""
if self.get_FAM() == 'ACT':
raise Not_a_Fission_Product("{} is not a FP and can't be given fission yields".format(self._zamid))
zamid = self._zamid
fy_lib = d.default_fy_lib
fy = fy_lib[zamid]
self._fy = fy
@property
def current_dens(self):
"""Returns the current density of the nuclide in atom per :math:`cm^{3}`"""
if self._current_dens is None:
pass # define exception for undefined variable
return self._current_dens
@current_dens.setter
def current_dens(self, new_dens):
"""Sets the current density of the nuclide in atom per :math:`cm^{3}`"""
self._current_dens = new_dens
@property
def dens_seq(self):
"""Returns the macro sequence of densities of the nuclide in atom per :math:`cm^{3}`"""
return self._dens_seq
@dens_seq.setter
def dens_seq(self, new_dens_seq):
"""Sets the macro sequence of densities of the nuclide in atom per :math:`cm^{3}`"""
self._dens_seq = new_dens_seq
def _append_dens_seq(self, new_dens):
"""Appends a new density value to the macro sequence of densities of the nuclide in atom per :math:`cm^{3}`"""
self._dens_seq.append(new_dens)
[docs] def get_current_dens_subseq(self):
"""Returns the current micro sequence of densities (i.e. micro sequence corresponding to the current macro step)"""
return self._dens_subseq_mat[-1]
@property
def dens_subseq_mat(self):
"""Returns the list of micro sequences of densities (one per macro step)"""
return self._dens_subseq_mat
[docs] def get_dens_subseq(self, s):
"""Returns the micro sequence of densities number s (i.e. micro sequence corresponding to the macro step number s)"""
return self._dens_subseq_mat[s]
def _append_dens_subseq_mat(self, new_dens, ss):
"""Sets new density value to the list of micro sequences of densities
If this is the first micro step, creates a new micro sequence"""
if ss == 0:
self._dens_subseq_mat.append([])
self._dens_subseq_mat[-1].append(new_dens)
def _set_initial_dens(self, new_dens):
"""Sets initial density"""
self.current_dens = new_dens
self.dens_seq = [new_dens]
self._dens_subseq_mat = [[new_dens]]
def _set_step_dens(self):
"""Sets a new density in the macro sequence of densities
The current density is set to the new density
The new density is appended to the macro sequence of densities"""
dens = self.current_dens
self._append_dens_seq(dens)
def _set_substep_dens(self, dens, ss):
"""Sets a new density in the micro sequence of densities
The current density is set to the new density
The new density is appended to the micro sequence of densities"""
self.current_dens = dens
self._append_dens_subseq_mat(dens, ss)
@property
def zamid(self):
"""Returns the z-a-m-id of the nuclide"""
return self._zamid
@property
def name(self):
"""Returns the name of the nuclide"""
return self._name
def _get_xs_prod_from_dic(self):
"""Produces a dictionnary that contains the operations required to be done on the zamid number of the nuclide
to obtain the zamid of nuclides produced by the different types of neutron-induced reaction"""
state = self._state
if state == 0:
xs_prod_from = d.xs_prod_fromS_toS.copy()
xs_prod_from.update(d.xs_prod_fromS_toX)
elif state == 1:
xs_prod_from = d.xs_prod_fromX_toS.copy()
xs_prod_from.update(d.xs_prod_fromX_toX)
return xs_prod_from
def _get_decay_prod_from_dic(self):
"""Produces a dictionnary that contains the operations required to be done on the zamid number of the nuclide
to obtain the zamid of nuclides produced by the different types of decay reaction"""
state = self._state
if state == 0:
decay_prod_from = d.decay_prod_fromS_toS.copy()
decay_prod_from.update(d.decay_prod_fromS_toX)
elif state == 1:
decay_prod_from = d.decay_prod_fromX_toS.copy()
decay_prod_from.update(d.decay_prod_fromX_toX)
return decay_prod_from
def _get_xs_prod_to_dic(self):
"""Produces a dictionnary that contains the operations required to be done on the zamid number of other nuclides
that produces the nuclide via different types of neutron-induced reaction"""
state = self._state
if state == 0:
xs_prod_to = d.xs_prod_fromS_toS.copy()
xs_prod_to.update(d.xs_prod_fromX_toS)
elif state == 1:
xs_prod_to = d.xs_prod_fromS_toX.copy()
xs_prod_to.update(d.xs_prod_fromX_toX)
return xs_prod_to
def _get_decay_prod_to_dic(self):
"""Produces a dictionnary that contains the operations required to be done on the zamid number of other nuclides
that produces the nuclide via different types of decay reaction"""
state = self._state
if state == 0:
decay_prod_to = d.decay_prod_fromS_toS.copy()
decay_prod_to.update(d.decay_prod_fromX_toS)
elif state == 1:
decay_prod_to = d.decay_prod_fromS_toX.copy()
decay_prod_to.update(d.decay_prod_fromX_toX)
return decay_prod_to
@property
def xs_child(self):
"""Returns the daughter products of the nuclide via neutron-induced rections"""
return self._xs_child
def _set_xschild(self):
zamid = int(self._zamid)
xs_prod_from_dic = self._get_xs_prod_from_dic()
child_dic = {}
for i in xs_prod_from_dic:
# If nuclide is Li6 or Be10, add children from (n,t)
# (n,t) rxn rate only tallied for Li6 and Be10
if i == '(n,t)':
if zamid in [30060, 50100]:
# In the case of (n,t) (and (n,alpha) as well), the reaction creates two new nuclides
# The reaction is thus branched out in two new reactions:
# 1) (n,x) which creates nuclides according to xs_prod_from_dic
# 2) (n,x)x which creates x, which is the particle emitted (tritium or alpha)
child_dic['(n,t)t'] = '10030'
elif zamid not in [30060, 50100]:
continue
# Note that after that, Li6 and Be10 are still being added the other child from (n,t), as below
child_zamid = zamid + 10000*xs_prod_from_dic[i][0]+ 10*xs_prod_from_dic[i][1] + xs_prod_from_dic[i][2]
child_dic[i] = str(child_zamid)
self._xs_child = child_dic
@property
def decay_child(self):
"""Returns the daughter products of the nuclide via decay reactions"""
return self._decay_child
def _set_decaychild(self):
zamid = int(self._zamid)
decay_prod_from_dic = self._get_decay_prod_from_dic()
child_dic = {}
for i in decay_prod_from_dic:
child_zamid = zamid + 10000*decay_prod_from_dic[i][0]+ 10*decay_prod_from_dic[i][1] + decay_prod_from_dic[i][2]
child_dic[i] = str(child_zamid)
self._decay_child = child_dic
@property
def xs_parent(self):
"""Returns the parents of the nuclide via neutron-induced reactions"""
return self._xs_parent
def _set_xsparent(self):
zamid = int(self._zamid)
parent_dic = {}
xs_prod_to_dic = self._get_xs_prod_to_dic()
for i in xs_prod_to_dic:
if i == '(n,t)':
# If nuclide is not a possible children of (n,t) reaction of Li6 or Be10
# Skip
if zamid not in [10030,20040, 30070]:
continue
# In the case of tritium itself, the nuclide has two parents from the same type
# of reaction (Li6 and Be10 from (n,t))
# Therefore, we branched out this channel into two new channels
# Note that ONIX should not try to find a parent for tritium through xs_prod_to_dic in this case
# It would track back to Helium5 (He5 +n = H3 + H3)), which itself has no (n,t) reaction allocated
if zamid == 10030:
parent_dic['Li6(n,t)'] = '30060'
parent_dic['Be10(n,t)'] = '50100'
continue
# This leaves us with only He4 and Li7 who are the products of Li6 (n,t) and Be10 (n,t) respectively
# For these two nuclides, ONIX finds the parents through xs_prod_to_dict as below
parent_zamid = zamid - 10000*xs_prod_to_dic[i][0]- 10*xs_prod_to_dic[i][1] - xs_prod_to_dic[i][2]
parent_dic[i] = str(parent_zamid)
self._xs_parent = parent_dic
@property
def decay_parent(self):
"""Returns the parents of the nuclide via decay reactions"""
return self._decay_parent
def _set_decayparent(self):
zamid = int(self._zamid)
parent_dic = {}
decay_prod_to_dic = self._get_decay_prod_to_dic()
for i in decay_prod_to_dic:
parent_zamid = zamid - 10000*decay_prod_to_dic[i][0]- 10*decay_prod_to_dic[i][1] - decay_prod_to_dic[i][2]
parent_dic[i] = str(parent_zamid)
self._decay_parent = parent_dic
@property
def all_parent(self):
"""Returns all parents of the nuclide"""
return self._all_parent
def _set_all_parent(self):
xs_parent = self._xs_parent
decay_parent = self._decay_parent
parent_list = []
for i in xs_parent:
parent_list.append(xs_parent[i])
for i in decay_parent:
parent_list.append(decay_parent[i])
self._all_parent = parent_list
@property
def all_child(self):
"""Returns all daughter products of the nuclide."""
return self._all_child
def _set_all_child(self):
xs_child = self._xs_child
decay_child = self._decay_child
child_list = []
for i in xs_child:
child_list.append(xs_child[i])
for i in decay_child:
child_list.append(decay_child[i])
self._all_child = child_list
@property
def fission_child(self):
"""Returns daughter products of the nuclide via fission reactions."""
return self._fission_child
@fission_child.setter
def _fission_child(self, fission_child):
# I am not sure why I set this error
# If the parent has no cross section, it is ok to still list its fission child
# if self._current_xs == None:
# raise XS_not_yet_set("{} has no cross-sections yet".format(self._zamid))
# Same here. Might not be important
# if 'fission' not in self._current_xs:
# raise No_fission_XS("{} has no fission cross-sections".format(self._zamid))
self._fission_child = fission_child
# Non0 parent and child are only the relatives when the reaction that link them to the nuclide is non-zero
# def get_all_non0_parent(self):
# xs_parent = self._xs_parent
# decay_parent = self._decay_parent
# parent_list = []
# for i in xs_parent:
# parent_list.append(xs_parent[i])
# for i in decay_parent:
# parent_list.append(decay_parent[i])
# return parent_list
# @property
# def all__non0_parent(self):
# return self._all_parent
# On the fly calculation
[docs] def get_all_non0_child(self):
"""Gets all daughter products of the nuclide produced via reactions that are non-zero (i.e., the reaction cross section or decay constant is not null) in the library used in the simulation"""
xs_child = self._xs_child
decay_child = self._decay_child
non0_child_list = []
xs = self._current_xs
decay_a = self._decay_a
state = self._state
if xs != None:
for i in xs_child:
# If this is an excited state, you need to remove the X at the beginning of the reaction name
if state ==1:
j = i[1:]
elif state == 0:
j = i
if j in xs:
if xs[j][0] != 0:
non0_child_list.append(xs_child[i])
if decay_a != None:
for i in decay_child:
# If this is an excited state, you need to remove the X at the beginning of the reaction _name
if state ==1:
j = i[1:]
elif state == 0:
j = i
if j in decay_a:
if decay_a[j] != 0:
non0_child_list.append(decay_child[i])
return non0_child_list
# On the fly calculation since an AVT can become a FP if fy is set
[docs] def get_FAM(self):
"""Returns the family of a nuclide (AVT= Activation, FP= Fission Product, ACT= Actinide)"""
nz = self.get_z
FAM = 'AVT'
if self._fy != None:
FAM = 'FP'
elif nz > 89:
FAM= 'ACT'
return FAM
@property
def get_a(self):
"""Returns the mass number of the nuclide"""
zaid = self._zamid
if len(zaid) == 5:
a = int(zaid[1:4])
else:
a = int(zaid[2:5])
return a
@property
def get_z(self):
"""Returns the atomic number of the nuclide"""
zaid = self._zamid
if len(zaid) == 5:
z = int(zaid[0:1])
else:
z = int(zaid[0:2])
return z
@property
def state(self):
"""Return the excitation state of the nuclide (excited or ground state)"""
return self._state
def _set_state(self):
"""Sets the excitation state of the nuclide (excited or ground state)"""
zamid = self._zamid
state = int(zamid[-1])
self._state = state
def _get_id_input_type(self, nuc_id):
if fct.is_int(nuc_id) == True:
id_type = 'zamid'
else:
id_type = 'name'
return id_type
@property
def fission_E(self):
"""Returns the fission energy of the nuclide"""
return self._fission_E
@fission_E.setter
def fission_E(self, fission_E):
"""Sets the fission energy of the nuclide"""
self._fission_E = fission_E
def _set_energy_per_fission(self):
a = self.get_a
z = self.get_z
fission_E = 1.29927e-3*(z**2)*(a**0.5) + 33.12
self._fission_E = fission_E
[docs] def get_natural_abundance(self):
"""Gets the natural abundance of the nuclide normalized to 1 (value between 0 and 1)"""
name_new_format = fct.onix_name_to_openmc_name(self.name)
nat_abun_dict = d.NATURAL_ABUNDANCE
if name_new_format in nat_abun_dict:
nat_abun = nat_abun_dict[name_new_format]
else:
nat_abun = 0.0
return nat_abun
@property
def destruction_dic(self):
"""Returns a dictionnary of all the destruction terms of the nuclide.
The keys are the reactions' names
The entries are the reaction rates of the reactions in :math:`barn^{-1}cm^{-1}s^{-1}`"""
return self._destruction_dic
@destruction_dic.setter
def destruction_dic(self, destruction_dic):
"""Sets a dictionnary of all the destruction terms of the nuclide"""
self._destruction_dic = destruction_dic
@property
def creation_dic(self):
"""Returns a dictionnary of all the production terms of the nuclide.
The keys are the parent nuclides' names with the reaction in parenthesis
The entries are the reaction rates of the reactions in :math:`barn^{-1}cm^{-1}s^{-1}`"""
return self._creation_dic
@creation_dic.setter
def creation_dic(self, creation_dic):
self._creation_dic = creation_dic
@property
def allreacs_dic(self):
"""Returns an aggregated dictionnary with production and destruction terms of the nuclide"""
return self._allreacs_dic
@allreacs_dic.setter
def allreacs_dic(self, allreacs_dic):
self._allreacs_dic = allreacs_dic
@property
def allreacs_dic_list(self):
"""Returns a list of aggregated dictionnaries with production and destruction terms of the nuclide. One dictionnary per microstep"""
return self._allreacs_dic_list
def _allreacs_dic_list_append(self, allreacs_dic):
self._allreacs_dic_list.append(allreacs_dic)
@property
def current_sorted_allreacs_tuple_list(self):
return self._current_sorted_allreacs_tuple_list
def _append_current_sorted_allreacs_tuple_list(self, sorted_allreacs, ss):
# If this is the beginning of a new step
if ss == 0:
self._current_sorted_allreacs_tuple_list = []
self._current_sorted_allreacs_tuple_list.append(sorted_allreacs)
@property
def sorted_allreacs_tuple_mat(self):
"""Returns a list of tuples that contains destruction and production terms
For destruction terms, the first element is the reaction name and the second element
is the reaction rate in :math:`barn^{-1}cm^{-1}s^{-1}`
For production terms, the first element is the parent nuclide witht the reaction name in parenthesis
and the second element is the reactio rate in :math:`barn^{-1}cm^{-1}s^{-1}`
These tuples are ranked from lower reaction rate to higher reaction rate"""
return self._sorted_allreacs_tuple_mat
def _append_sorted_allreacs_tuple_mat(self):
self._sorted_allreacs_tuple_mat.append(self.current_sorted_allreacs_tuple_list)
@property
def pikachu(self):
return 'PIKA PIKA!'
class Incorrect_nuc_id(Exception):
"""Raise when the id input format in passport instantiation is incorrect"""
pass
class Nuc_xs_not_found(Exception):
"""Raise when the user requests a cross-sections of a nuclide that is not in the nuclide set """
pass
class Not_a_Fission_Product(Exception):
"""Raise when the user tries to set fission yields for a non fission product nuclide """
pass
class XS_not_yet_set(Exception):
"""Raise when the user tries to access XS for a nuclide which XS have not been set yet """
pass
class No_fission_XS(Exception):
"""Raise when the user tries to access fission XS for a nuclide which fission XS have not been set yet """
pass