from __future__ import division
import re
import sys
import copy
try:
from collections.abc import defaultdict
except ImportError:
from collections import defaultdict
from warnings import warn
from pyne.utils import QA_warn, time_conv_dict
import numpy as np
from pyne import nucname, rxname, data
if sys.version_info[0] > 2:
basestring = str
QA_warn(__name__)
_valexp = re.compile('([0-9.]*)([Ee][+-]?\d*)')
_val = re.compile('(\d*)[.](\d*)')
_specialval = re.compile("([0-9. ]*)[+]([A-Z])")
_specialval2 = re.compile("([A-Z]*)[+]([0-9.]*)")
_errpm = re.compile('[+](\d*)[-](\d*)')
_err = re.compile('[ ]*(\d*)')
_base = '([ \d]{3}[ A-Za-z]{2})'
_ident = re.compile(_base + ' (.{30})(.{26})(.{7})(.{6})')
_g = re.compile(_base + ' G (.{10})(.{2})(.{8})(.{2}).{24}(.{7})(.{2})(.{10})'
+ '(.{2})')
_gc = re.compile(_base + '[0-9A-Za-z] [GE] (.{70})')
_beta = re.compile(_base + ' B (.{10})(.{2})(.{8})(.{2}).{10}(.{8})(.{6})')
_betac = re.compile(_base + '[0-9A-Za-z] ([BE]) (.{70})')
_ec = re.compile(_base + ' E (.{10})(.{2})(.{8})(.{2})'
+ '(.{8})(.{2})(.{8})(.{6})(.{10})(.{2})')
_p = re.compile(_base + ' P (.{10})(.{2})(.{18})(.{10})'
+ '(.{6}).{9}(.{10})(.{2})(.{4})')
_norm = re.compile(_base + ' N (.{10})(.{2})(.{8})(.{2})(.{8})(.{2})(.{8})'
+ '(.{6})(.{7})(.{2})')
_normp = re.compile(_base +
' PN (.{10})(.{2})(.{8})(.{2})(.{8})(.{2})(.{7})(.{2})')
_q = re.compile(_base + ' Q (.{10})(.{2})(.{8})(.{2})'
+ '(.{8})(.{2})(.{8})(.{6})')
_alpha = re.compile(_base + ' A (.{10})(.{2})(.{8})(.{2})(.{8})(.{2})')
_dp = re.compile(_base + ' D(.{1})(.{10})(.{2})(.{8})(.{2})(.{8})(.{10})'
+ '(.{6})')
_decays = ['B-', 'B+A', 'EC', 'B-A', 'B+', 'B+P', 'B-N', 'ECP', 'EC2P', 'N',
'2N', 'IT', 'B+2P', 'B-2N', 'B+3P', 'ECA', 'P', '2P', '2B-', 'SF',
'A', '2B+', '2EC', '14C']
_level_regex = re.compile(_base + ' L (.{10})(.{2})(.{18})(.{10})(.{6})'
+ '(.{9})(.{10})(.{2})(.{1})([ M])([ 1-9])')
_level_cont_regex = re.compile('([ \d]{3}[ A-Za-z]{2})[0-9A-Za-z] L (.*)')
def _getvalue(obj, fn=float, rn=None):
x = obj.strip()
x = x.replace('$', '')
x = x.replace('?', '')
try:
return fn(x)
except ValueError:
return rn
def _to_id(nuc):
if 'NN' not in nuc:
nucid = nucname.ensdf_to_id(nuc.strip())
else:
warn('Neutron data not supported!')
return 0
return nucid
# Energy to half-life conversion: T1/2= ln(2) × (h/2 pi) / energy
# See http://www.nndc.bnl.gov/nudat2/help/glossary.jsp#halflife
# NIST CODATA https://physics.nist.gov/cgi-bin/cuu/Value?hbar
# h-bar = 1.054 571 800(13) x 1e-34 J
# 1 J = 6.241 509 126(38) x 1e18 eV
HBAR_LN2 = 4.5623775832376968e-16 # h-bar ln(2) in eV s
energy_conv_dict = {'ev': HBAR_LN2,
'kev': 1e-3 * HBAR_LN2,
'mev': 1e-6 * HBAR_LN2,
}
def _halflife_to_seconds(value, err, units):
"""Converts a halflife with err and units to seconds.
Parameters
----------
value: number
Time or energy, depending on units.
err : number or (number, number)
Uncertainty, or (plus, minus) uncertainty in [units].
units : str
Units flag, eg 'min', 'ms', 'days', or even 'MeV'.
Returns
-------
sec_time : float
Time value in [sec].
sec_err : None or float or (float, float) in [sec].
Time uncertainty in [sec], or (plus, minus) if asymmetric uncertainty.
"""
if err is None:
plus, minus = 0, 0
elif np.isscalar(err):
plus, minus = err, err
else:
plus, minus = err
units = units.lower()
scale = time_conv_dict.get(units, None)
if scale is not None:
sec_time = scale * value
sec_err = (scale * plus, scale * minus)
else:
scale = energy_conv_dict[units]
sec_time = scale / value
sec_err = (scale / max(0.1*value, value - minus) - sec_time,
sec_time - scale / (value + plus))
if err is None:
return sec_time, None
elif sec_err[0] == sec_err[1]:
return sec_time, sec_err[0]
else:
return sec_time, sec_err
def _to_time(tstr, errstr):
t = tstr.strip()
# This accepts questionable levels
t = t.replace('?', '')
tobj = [s.strip(' ()') for s in t.split()]
if len(tobj) == 2:
t, t_unit = tobj
value, err = _get_val_err(t, errstr)
tfinal, tfinalerr = _halflife_to_seconds(value, err, t_unit)
elif 'STABLE' in t:
tfinal = np.inf
tfinalerr = None
else:
tfinal = None
tfinalerr = None
return tfinal, tfinalerr
def _get_val_err(valstr, errstr):
pm = _errpm.match(errstr)
err = _err.match(errstr)
if pm is None and err.group(1) == '':
return _getvalue(valstr), None
val = _valexp.match(valstr)
if val is None:
valexp = ''
val = valstr
else:
valexp = val.group(2)
val = val.group(1)
punc = _val.match(val.strip())
if pm is not None:
if punc is None:
errplus = _getvalue(pm.group(1) + valexp)
errminus = _getvalue(pm.group(2) + valexp)
else:
errplus = _get_err(len(punc.group(2)), pm.group(1), valexp)
errminus = _get_err(len(punc.group(2)), pm.group(2), valexp)
return _getvalue(valstr), (errplus, errminus)
else:
if punc is None:
errplus = _getvalue(errstr + valexp)
else:
errplus = _get_err(len(punc.group(2)), errstr, valexp)
return _getvalue(valstr), errplus
def _get_err(plen, errstr, valexp):
errp = list((errstr.strip()).zfill(plen))
errp.insert(-plen, '.')
return _getvalue(''.join(errp) + valexp)
def _parse_level_record(l_rec):
"""
This Parses and ENSDF level record
Parameters
----------
g : re.MatchObject
regular expression MatchObject
Returns
-------
e : float
Level energy in keV
tfinal : float
Half life in seconds
from_nuc : int
nuc id of nuclide
state : int
metastable state of level
special : str
A-Z character denoting a group of known levels with no reference
to the ground state. P and N are special characters reserved for
proton and neutron resonances given in center of mass system energy.
"""
lm = re.match("[ ]*([A-Z]+)(?![A-Z0-9+])", l_rec.group(2))
spv = _specialval.match(l_rec.group(2).strip())
spv2 = _specialval2.match(l_rec.group(2).strip())
special = ' '
if lm is not None:
special = lm.group(1)
if "S" in special and len(special.strip()) > 1:
special = special.strip()[1]
e = 0.0
de = np.nan
elif spv is not None:
e, de = _get_val_err(spv.group(1), l_rec.group(3))
special = spv.group(2)
elif spv2 is not None:
e, de = _get_val_err(spv2.group(2), l_rec.group(3))
special = spv2.group(1)
if "S" in special and len(special.strip()) > 1:
special = special.strip()[1]
else:
e, de = _get_val_err(l_rec.group(2).strip('() '), l_rec.group(3))
tfinal, tfinalerr = _to_time(l_rec.group(5), l_rec.group(6))
from_nuc = _to_id(l_rec.group(1))
m = l_rec.group(11)
s = l_rec.group(12)
state = 0
if m == 'M':
state = s.strip()
if 0 < len(state):
state = int(state)
else:
state = 1
return e, tfinal, from_nuc, state, special
def _parse_level_continuation_record(lc_rec):
"""
This Parses and ENSDF level record
Parameters
----------
g : re.MatchObject
regular expression MatchObject
Returns
-------
dat : dict
dictionary of branching ratios of different reaction channels
"""
g = lc_rec.groups()
dat = {}
raw_children = g[-1].replace(' AP ', '=')
raw_children = raw_children.replace('$', ' ').split()
for raw_child in raw_children:
if '=' in raw_child:
rx, br = raw_child.split('=')[:2]
br = br.strip()
else:
continue
if '%' in rx and '?' not in br and len(br) > 0:
dat[rx] = br
return dat
def _parse_gamma_record(g):
"""
This parses an ENSDF gamma record
Parameters
----------
g : re.MatchObject
regular expression MatchObject
Returns
-------
dat : np.ndarray
This array contains 6 floats corresponding to:
* gamma ray energy in keV
* uncertainty in energy
* intensity
* uncertainty in intensity
* electron conversion intensity
* uncertainty in electron conversion intensity
"""
en, en_err = _get_val_err(g.group(2), g.group(3))
inten, inten_err = _get_val_err(g.group(4), g.group(5))
conv, conv_err = _get_val_err(g.group(6), g.group(7))
tti, tti_err = _get_val_err(g.group(8), g.group(9))
return [en, en_err, inten, inten_err, conv, conv_err, tti, tti_err]
def _parse_gamma_continuation_record(g, inten, tti):
"""
This parses an ENSDF gamma continuation record
"""
conversions = {}
entries = g.group(2).split('$')
for entry in entries:
entry = entry.replace('AP', '=')
entry = entry.replace('EL1C+EL2C', 'LC')
if '+=' in entry or 'EAV' in entry:
continue
if 'C=' in entry:
tsplit = entry.split('C')
else:
tsplit = entry.split('=')
tsplit[0] = tsplit[0].lstrip('C')
greff = inten
if '/T' in entry:
tsplit = entry.split('/T')
greff = tti
if greff is None:
greff = inten
if greff is None:
greff = 1.0
if len(tsplit) == 2:
conv = None
err = None
contype = tsplit[0].lstrip('E')
eff = tsplit[1].lstrip('= ').split()
if len(eff) == 2:
conv, err = _get_val_err(eff[0], eff[1])
elif len(eff) == 1:
conv = _getvalue(eff[0])
if conv is None and contype not in conversions:
conversions[contype] = (None, None)
elif contype not in conversions:
conversions[contype] = (conv * greff, err)
return conversions
def _parse_beta_record(b_rec):
"""
This parses an ENSDF beta minus record
Parameters
----------
b_rec : re.MatchObject
regular expression MatchObject
Returns
-------
en : float
b- endpoint energy in keV
en_err : float
error in b- endpoint energy
ib : float
branch intensity
dib : float
error in branch intensity
logft : float
logft of the decay
dft : float
error in logft
"""
en, en_err = _get_val_err(b_rec.group(2), b_rec.group(3))
ib, dib = _get_val_err(b_rec.group(4), b_rec.group(5))
logft, dft = _get_val_err(b_rec.group(6), b_rec.group(7))
return en, en_err, ib, dib, logft, dft
def _parse_beta_continuation_record(bc_rec):
"""
This parse the beta continuation record for EAV
"""
entries = bc_rec.group(3).split('$')
eav = None
eav_err = None
for entry in entries:
if 'EAV' in entry and '=' in entry:
dat = entry.split('=')[1]
dat = dat.split()
if len(dat) == 2:
eav, eav_err = _get_val_err(dat[0], dat[1])
elif len(dat) == 1:
eav = _getvalue(dat[0])
return eav, eav_err
def _parse_ec_record(e_rec):
"""
This parses an ENSDF electron capture + b+ record
Parameters
----------
e_rec : re.MatchObject
regular expression MatchObject
Returns
-------
en : float
b+ endpoint energy in keV
en_err : float
error in b+ endpoint energy
ib : float
b+ branch intensity
dib : float
error in b+ branch intensity
ie : float
ec branch intensity
die : float
error in ec branch intensity
logft : float
logft of the decay
dft : float
error in logft
"""
en, en_err = _get_val_err(e_rec.group(2), e_rec.group(3))
ib, dib = _get_val_err(e_rec.group(4), e_rec.group(5))
ie, die = _get_val_err(e_rec.group(6), e_rec.group(7))
logft, dft = _get_val_err(e_rec.group(8), e_rec.group(9))
tti, dtti = _get_val_err(e_rec.group(10), e_rec.group(11))
return en, en_err, ib, dib, ie, die, logft, dft, tti, dtti
def _parse_normalization_record(n_rec):
"""
This parses an ENSDF normalization record
Parameters
----------
n_rec : re.MatchObject
regular expression MatchObject
Returns
-------
nr : float
Multiplier for converting relative photon intensity to photons per 100
decays of the parent through the decay branch or to photons per 100
neutron captures for (n,g).
nr_err : float
Uncertainty in nr
nt : float
Multiplier for converting relative transition intensity to transitions
per 100 decays of the parent through the decay branch or to photons
per 100 neutron captures for (n,g).
nt_err : float
Uncertainty in nt
br : float
Branching ratio multiplier for converting intensity per 100 decays
through this decay branch to intensity per 100 decays of the parent
nuclide.
br_err : float
Uncertainty in br
nb : float
Multiplier for converting relative B- and EC intensities to intensities
per 100 decays through this decay branch.
nb_err : float
Uncertainty in nb
"""
nr, nr_err = _get_val_err(n_rec.group(2), n_rec.group(3))
nt, nt_err = _get_val_err(n_rec.group(4), n_rec.group(5))
br, br_err = _get_val_err(n_rec.group(6), n_rec.group(7))
nb, nb_err = _get_val_err(n_rec.group(8), n_rec.group(9))
if nr is not None and br is not None:
nrbr = nr * br
else:
nrbr = None
if nr_err is not None and br_err is not None:
nrbr_err = nrbr*np.sqrt((br_err/br) ** 2 * (nr_err/nr) ** 2)
else:
nrbr_err = None
return nr, nr_err, nt, nt_err, br, br_err, nb, nb_err, nrbr, nrbr_err
def _parse_production_normalization_record(np_rec):
"""
This parses an ENSDF production normalization record
Parameters
----------
np_rec : re.MatchObject
regular expression MatchObject
Returns
-------
nrbr : float
Multiplier for converting relative photon intensity to photons per 100
decays of the parent nuclide
nrbr_err : float
Uncertainty in nrbr
ntbr : float
Multiplier for converting relative transition intensity to transitions
per 100 decays of the parent nuclide
ntbr_err : float
Uncertainty in ntbr
nbbr: float
Multiplier for converting relative B- and EC intensities to intensity
per 100 decays of the parent nuclide
nbbr_err : float
Uncertainty in nbbr
"""
nrbr, nrbr_err = _get_val_err(np_rec.group(2), np_rec.group(3))
ntbr, ntbr_err = _get_val_err(np_rec.group(4), np_rec.group(5))
nbbr, nbbr_err = _get_val_err(np_rec.group(6), np_rec.group(7))
return nrbr, nrbr_err, ntbr, ntbr_err, nbbr, nbbr_err
def _parse_parent_record(p_rec):
"""
This parses an ENSDF parent record
Parameters
----------
p_rec : re.MatchObject
regular expression MatchObject
Returns
-------
tfinal : float
half-life in seconds
tfinalerr : float
Uncertainty in half-life in seconds
"""
lm = re.match("[ ]*([A-Z]+)(?![A-Z0-9+])", p_rec.group(2))
spv = _specialval.match(p_rec.group(2).strip())
spv2 = _specialval2.match(p_rec.group(2).strip())
special = ' '
if lm is not None:
special = lm.group(1)
if "S" in special and len(special.strip()) > 1:
special = special.strip()[1]
e = 0.0
de = np.nan
elif spv is not None:
e, de = _get_val_err(spv.group(1), p_rec.group(3))
special = spv.group(2)
elif spv2 is not None:
e, de = _get_val_err(spv2.group(2), p_rec.group(3))
special = spv2.group(1)
if "S" in special and len(special.strip()) > 1:
special = special.strip()[1]
else:
e, de = _get_val_err(p_rec.group(2).strip('() '), p_rec.group(3))
j = p_rec.group(4)
tfinal, tfinalerr = _to_time(p_rec.group(5), p_rec.group(6))
return p_rec.group(1), tfinal, tfinalerr, e, de, special
def _parse_qvalue_record(q_rec):
"""
This parses and ENSDF q-value record
Parameters
----------
q_rec : re.MatchObject
regular expression MatchObject
Returns
-------
qminus : float
total energy for B- decay (if qminus > 0 B- decay is possible)
dqminus : float
standard uncertainty in qminus
sn : float
neutron separation energy in keV
dsn : float
standard uncertainty in sn
sp : float
neutron separation energy in keV
dsp : float
standard uncertainty in sp
qa : float
total energy available for alpha decay of the ground state
dqa : float
standard uncertainty in qa
"""
qminus, dqminus = _get_val_err(q_rec.group(2), q_rec.group(3))
sn, dsn = _get_val_err(q_rec.group(4), q_rec.group(5))
sp, dsp = _get_val_err(q_rec.group(5), q_rec.group(7))
qa, dqa = _get_val_err(q_rec.group(8), q_rec.group(9))
return qminus, dqminus, sn, dsn, sp, dsp, qa, dqa
def _parse_alpha_record(a_rec):
"""
This parses and ENSDF alpha record
Parameters
----------
q_rec : re.MatchObject
regular expression MatchObject
Returns
-------
e : float
energy of alpha particle
de : float
standard uncertainty in energy
ia : float
intensity of the decay branch in percent
dia : float
standard uncertainty in intensity
hf : float
hindrance factor
dhf : float
standard uncertainty in hindrance factor
"""
e, de = _get_val_err(a_rec.group(2), a_rec.group(3))
ia, dia = _get_val_err(a_rec.group(4), a_rec.group(5))
hf, dhf = _get_val_err(a_rec.group(5), a_rec.group(7))
return e, de, ia, dia, hf, dhf
def _parse_delayed_particle_record(dp_rec):
"""
This parses and ENSDF delayed particle record
Parameters
----------
dp_rec : re.MatchObject
regular expression MatchObject
Returns
-------
ptype : str
symbol for delayed particle
e : float
particle energy
de : float
standard uncertainty in energy
ip : float
intensity of delayed particle in percent
dip : float
standard uncertainty in intensity
ei : float
energy level of the intermediate
t : float
half-life of the transition (in seconds)
dt : float
standard uncertainty in half-life
"""
ptype = dp_rec.group(2)
e, de = _get_val_err(dp_rec.group(3), dp_rec.group(4))
ip, dip = _get_val_err(dp_rec.group(5), dp_rec.group(6))
ei = _getvalue(dp_rec.group(7))
t, dt = _to_time(dp_rec.group(8), dp_rec.group(9))
return ptype, e, de, ip, dip, ei, t, dt
def _parse_decay_dataset(lines, decay_s):
"""
This parses a gamma ray dataset. It returns a tuple of the parsed data.
Parameters
----------
lines : list of str
list containing lines from one dataset of an ensdf file
decay_s : str
string of the decay type
Returns
-------
Tuple of decay parameters which is described in detail in gamma_rays docs
"""
gammarays = []
betas = []
alphas = []
ecbp = []
ident = _ident.match(lines[0])
daughter = ident.group(1)
daughter_id = abs(_to_id(daughter))
parent = ident.group(2).split()[0]
parent = parent.split('(')[0]
parents = parent.split(',')
if len(parents) > 1:
pfinal = abs(_to_id(parents[0]))
else:
pfinal = abs(_to_id(parents[0][:5]))
tfinal = None
tfinalerr = None
nrbr = None
nbbr = None
nrbr_err = None
nbbr_err = None
nb_err = None
br_err = None
nb = None
br = None
level = None
special = " "
goodgray = False
parent2 = None
for line in lines:
level_l = _level_regex.match(line)
if level_l is not None:
level, half_lifev, from_nuc, \
state, special = _parse_level_record(level_l)
continue
b_rec = _beta.match(line)
if b_rec is not None:
dat = _parse_beta_record(b_rec)
if parent2 is None:
bparent = pfinal
else:
bparent = parent2
level = 0.0 if level is None else level
bdaughter = abs(data.id_from_level(_to_id(daughter), level))
betas.append([bparent, bdaughter, dat[0], 0.0, dat[2]])
bc_rec = _betac.match(line)
if bc_rec is not None:
bcdat = _parse_beta_continuation_record(bc_rec)
if bcdat[0] is not None:
if bc_rec.group(2) == 'B':
betas[-1][3] = bcdat[0]
else:
ecbp[-1][3] = bcdat[0]
bggc = _gc.match(line)
conv = _parse_gamma_continuation_record(bggc, dat[2],
dat[8])
if 'K' in conv:
ecbp[-1][-3] = conv['K'][0]
if 'L' in conv:
ecbp[-1][-2] = conv['L'][0]
if 'M' in conv:
ecbp[-1][-1] = conv['M'][0]
a_rec = _alpha.match(line)
if a_rec is not None:
dat = _parse_alpha_record(a_rec)
if parent2 is None:
aparent = pfinal
else:
aparent = parent2
level = 0.0 if level is None else level
adaughter = abs(data.id_from_level(_to_id(daughter), level))
alphas.append([aparent, adaughter, dat[0], dat[2]])
ec_rec = _ec.match(line)
if ec_rec is not None:
dat = _parse_ec_record(ec_rec)
if parent2 is None:
ecparent = pfinal
else:
ecparent = parent2
level = 0.0 if level is None else level
ecdaughter = abs(data.id_from_level(_to_id(daughter), level))
ecbp.append([ecparent, ecdaughter, dat[0], 0.0, dat[2], dat[4],
0, 0, 0])
continue
g_rec = _g.match(line)
if g_rec is not None:
dat = _parse_gamma_record(g_rec)
if dat[0] is not None:
gparent = 0
gdaughter = 0
if level is not None:
gparent = abs(data.id_from_level(_to_id(daughter), level,
special))
dlevel = level - dat[0]
gdaughter = abs(data.id_from_level(_to_id(daughter), dlevel,
special))
if parent2 is None:
gp2 = pfinal
else:
gp2 = parent2
dat.insert(0, daughter_id)
dat.insert(0, gp2)
dat.insert(0, gdaughter)
dat.insert(0, gparent)
for i in range(3):
dat.append(0)
gammarays.append(dat)
goodgray = True
else:
goodgray = False
continue
gc_rec = _gc.match(line)
if gc_rec is not None and goodgray is True:
conv = _parse_gamma_continuation_record(gc_rec, gammarays[-1][6],
gammarays[-1][10])
if 'K' in conv:
gammarays[-1][-3] = conv['K'][0]
if 'L' in conv:
gammarays[-1][-2] = conv['L'][0]
if 'M' in conv:
gammarays[-1][-1] = conv['M'][0]
continue
n_rec = _norm.match(line)
if n_rec is not None:
nr, nr_err, nt, nt_err, br, br_err, nb, nb_err, nrbr, nrbr_err = \
_parse_normalization_record(n_rec)
if nb is not None and br is not None:
nbbr = nb * br
if nb_err is not None and br_err is not None and nb_err != 0:
nbbr_err = nbbr*((br_err/br) ** 2 * (nb_err/nb) ** 2) ** 0.5
continue
np_rec = _normp.match(line)
if np_rec is not None:
nrbr2, nrbr_err2, ntbr, ntbr_err, nbbr2, nbbr_err2 = \
_parse_production_normalization_record(np_rec)
if nrbr2 is not None and nrbr is None:
nrbr = nrbr2
nrbr_err = nrbr_err2
if nbbr2 is not None and nbbr is None:
nbbr = nbbr2
nbbr_err = nbbr_err2
continue
p_rec = _p.match(line)
if p_rec is not None:
# only 2 parents are supported so this can be here
multi = False
if parent2 is not None:
multi = True
pfinal = [parent2,]
tfinal = [t,]
tfinalerr = [terr,]
parent2, t, terr, e, e_err, special = _parse_parent_record(p_rec)
parent2 = abs(data.id_from_level(_to_id(parent2), e, special))
if terr is not None and not isinstance(terr, float):
terr = (terr[0] + terr[1])/2.0
if multi:
tfinal.append(t)
tfinalerr.append(terr)
pfinal.append(parent2)
else:
tfinal = t
tfinalerr = terr
pfinal = parent2
continue
if len(gammarays) > 0 or len(alphas) > 0 or len(betas) > 0 or len(ecbp) > 0:
if len(parents) > 1 and parent2 is None:
pfinal = []
for item in parents:
pfinal.append(_to_id(item))
return pfinal, daughter_id, rxname.id(decay_s.strip().lower()), \
tfinal, tfinalerr, \
br, br_err, nrbr, nrbr_err, nbbr, nbbr_err, gammarays, alphas, \
betas, ecbp
return None
_BAD_RX = frozenset([
# Be-6 doesn't really alpha decay (leaving He-2), rather it emits 2p
(40060000, 1089),
# Li-8 -> He-4 + beta- + alpha is really a shortcut for
# Li-8 -> Be-8 + beta- -> He-4 + alpha
(30080000, 1355894000),
])
def _adjust_ge100_branches(levellist):
"""This adjust branches that are greater than or equal to 100% to be
100% - sum(other branches). This helps prevent unphysical errors
downstream.
"""
n = len(levellist)
brsum = defaultdict(float)
bridx = defaultdict(lambda: (-1, -1.0))
baddies = []
for i, (nuc, rx, hl, lvl, br, ms, sp) in enumerate(levellist):
if rx == 0:
continue
if br >= bridx[nuc][1]:
bridx[nuc] = (i, br)
brsum[nuc] += br
nucrx = (nuc, rx)
if nucrx in _BAD_RX:
baddies.append(i)
# adjust branch ratios
for nuc, (i, br) in bridx.items():
row = levellist[i]
# this line ensures that all branches sum to 100.0 within floating point
new_br = 100.0 - brsum[nuc] + br
new_row = row[:4] + (new_br,) + row[5:]
levellist[i] = new_row
# remove bad reaction rows
for i in baddies[::-1]:
del levellist[i]
# State Id, Bad Metastable Number, (Replacement State ID, optional) Replacement Metastable Number
_BAD_METASTABLES = {
# Rh-110 misreports its ground state as a first meta-stable and its first
# metastable as its second.
(451100000, 1): 0,
(451100001, 2): 1,
# Pm-154 misreports its ground state as a first metastable
(611540000, 1): 0,
# Ga-72M is not listed as metastable
(310720002, 0): 1,
# Rh-108M is not listed as metastable
(451080004, 0): 1,
# Pm-136 mislabels two states as both metastable or ground.
# Replacing with what KAERI and NNDC report
(611360001, 2): (611360000, 0),
(611360000, 1): (611360001, 1),
}
def _adjust_metastables(levellist):
"""Adjusts misreported metastable states in place."""
for i in range(len(levellist)):
key = (levellist[i][0], levellist[i][5])
if key in _BAD_METASTABLES:
row = list(levellist[i])
new_id = _BAD_METASTABLES[key]
if not isinstance(new_id, int):
row[0], new_id = new_id
row[5] = new_id
levellist[i] = tuple(row)
# State Id, Rx Id : New Half-lives
_BAD_HALF_LIVES = {
# Eu-151 lists a very long half-life (5.364792e+25) even though it
# lists no reaction, and thus no children, and no branch ratio.
# set to infinity for consistency.
(631510000, 0): float('inf'),
}
def _adjust_half_lives(levellist):
"""Resets misbehaving half-lives to new value."""
for i in range(len(levellist)):
key = levellist[i][:2]
if key in _BAD_HALF_LIVES:
row = list(levellist[i])
row[2] = _BAD_HALF_LIVES[key]
levellist[i] = tuple(row)
[docs]def levels(filename, levellist=None):
"""
This takes an ENSDF filename or file object and parses the ADOPTED LEVELS
records to assign level numbers by energy. It also parses the different
reported decay types and branching ratios.
Parameters
----------
filename : str or file
Name of ENSDF formatted file or a file-like object containing ENSDF
formatted data
levellist : list of tuples
This is a list object which all newly processed levels will be added
to. If it's None a new one will be created.
Returns
-------
levellist : list of tuples
This is a list of all the level data. Each level has base entry with a
reaction id of 0 and additional entries for any listed decays. The
format of each row is:
nuc_id : int
The state_id of the level
rx_id : int
The id of the decay "reaction" in PyNE reaction id form.
half_life : float
Half life of the state in s
level : float
energy of the level in keV
branch_ratio : float
if rx_id != 0 this is the percent of decays in that channel
metastable : int
metastable id number of the level (if given)
special : string
single character denoting levels with unknown relation to ground
state
"""
badlist = ["ecsf", "34si", "|b{+-}fission", "{+24}ne",
"{+22}ne", "24ne", "b-f", "{+20}o", "2|e", "b++ec",
"ecp+ec2p", "ecf", "mg", "ne", "{+20}ne", "{+25}ne",
"{+28}mg", "sf(+ec+b+)"]
special = ""
if levellist is None:
levellist = []
if isinstance(filename, str):
with open(filename, 'r') as f:
dat = f.read()
else:
dat = filename.read()
datasets = dat.split(80 * " " + "\n")[0:-1]
for dataset in datasets:
lines = dataset.splitlines()
ident = re.match(_ident, lines[0])
if ident is None:
continue
if 'ADOPTED LEVELS' in ident.group(2):
leveln = 0
brs = {}
level_found = False
for line in lines:
level_l = _level_regex.match(line)
if level_l is not None:
if len(brs) > 0:
for key, val in brs.items():
goodkey = True
keystrip = key.replace("%", "").lower()
for item in badlist:
if keystrip == item:
goodkey = False
if goodkey is True:
rx = rxname.id(keystrip)
branch_percent = float(val.split("(")[0])
levellist.append((nuc_id, rx, half_lifev,
level, branch_percent,
state, special))
if level_found is True:
levellist.append((nuc_id, 0, half_lifev, level, 0.0,
state, special))
brs = {}
level, half_lifev, from_nuc, state, special = \
_parse_level_record(level_l)
if from_nuc is not None:
nuc_id = from_nuc + leveln
leveln += 1
level_found = True
else:
level_found = False
continue
levelc = _level_cont_regex.match(line)
if levelc is not None:
brs.update(_parse_level_continuation_record(levelc))
continue
if len(brs) > 0:
for key, val in brs.items():
goodkey = True
keystrip = key.replace("%", "").lower()
for item in badlist:
if keystrip == item:
goodkey = False
if goodkey is True:
rx = rxname.id(keystrip)
branch_percent = float(val.split("(")[0])
levellist.append((nuc_id, rx, half_lifev, level,
branch_percent, state, special))
if level_found is True:
levellist.append((nuc_id, 0, half_lifev, level, 0.0, state,
special))
_adjust_ge100_branches(levellist)
_adjust_metastables(levellist)
_adjust_half_lives(levellist)
return levellist
[docs]def decays(filename, decaylist=None):
"""
This splits an ENSDF file into datasets. It then passes the dataset to the
appropriate parser. Currently only a subset of decay datasets are
supported. The output is a list of objects containing information
pertaining to a particular decay.
Parameters
----------
filename : str or file
Name of ENSDF formatted file or a file-like object containing ENSDF
formatted data
decaylist : list of tuples
This is a list object which all newly processed decays will be added
to. If it's None a new one will be created.
Returns
-------
decaylist : list of tuples
list of objects containing information pertaining to a particular
decay. This information is in the following format:
int
nuc_id of the parent
int
nuc_id of the daughter
int
PyNE reaction id
float
half-life in seconds
float
half-life error in seconds
float
branching ratio (percent)
float
Conversion factor for gamma intensity to photons per 100 decays of the
parent
float
Error in conversion factor for gamma intensity
float
Conversion factor for electron capture/beta intensity to electron
captures/betas per 100 decays of the parent
float
Error in conversion factor for electron capture/beta intensity
list
a list containing information about each gamma ray:
* starting level of gamma transition in stats_id form
* final level of gamma transition in state_id form
* original parent
* energy in keV
* uncertainty in energy
* intensity (multiply by conversion factor for percentage)
* uncertainty in intensity
* electron conversion intensity
* uncertainty in electron conversion intensity
* total transition intensity
* total transition intensity error
* k electron conversion intensity
* l electron conversion intensity
* m electron conversion intensity
list
a list containing information about each alpha:
* parent nuclide id in state_id form
* child nuclide id in state_id form
* alpha energy
* alpha intensity in percent of total alphas
list
a list containing information about each beta minus from the parent
decay:
* parent nuclide id in state_id form
* child nuclide id in state_id form
* beta endpoint energy
* beta average energy
* beta intensity (multiply by conversion factor for percentage)
list
a list containing information about each beta plus and electron capture
from the parent decay:
* parent nuclide id in state_id form
* child nuclide id in state_id form
* beta plus endpoint energy
* beta plus average energy
* beta intensity (multiply by conversion factor for percentage)
* electron capture intensity (multiply by conversion factor for
percentage)
* k electron conversion intensity
* l electron conversion intensity
* m electron conversion intensity
"""
if decaylist is None:
decaylist = []
if isinstance(filename, str):
with open(filename, 'r') as f:
dat = f.read()
else:
dat = filename.read()
datasets = dat.split(80 * " " + "\n")
for dataset in datasets:
lines = dataset.splitlines()
if len(lines) == 0:
continue
ident = re.match(_ident, lines[0])
if ident is None:
continue
if 'DECAY' in ident.group(2):
decay_s = ident.group(2).split()[1]
decay = _parse_decay_dataset(lines, decay_s)
if decay is not None:
if isinstance(decay[0], list):
if isinstance(decay[3], list):
for i, parent in enumerate(decay[0]):
dc = copy.deepcopy(list(decay))
dc[0] = parent
dc[3] = decay[3][i]
dc[4] = decay[4][i]
for gamma in dc[11]:
gamma[2] = parent
for alpha in dc[12]:
alpha[0] = parent
for beta in dc[13]:
beta[0] = parent
for ecbp in dc[14]:
ecbp[0] = parent
decaylist.append(tuple(dc))
else:
for parent in decay[0]:
dc = copy.deepcopy(list(decay))
dc[0] = parent
for gamma in dc[11]:
gamma[2] = parent
for alpha in dc[12]:
alpha[0] = parent
for beta in dc[13]:
beta[0] = parent
for ecbp in dc[14]:
ecbp[0] = parent
decaylist.append(tuple(dc))
else:
decaylist.append(decay)
return decaylist
def _dlist_gen(f):
"""
This compiles a list of decay types in an ensdf file
Parameters
----------
f : str
Name of ENSDF formatted file
Returns
-------
decaylist : list
list of decay types in the ENSDF file eg. ['B+','B-','A']
"""
if isinstance(f, str):
with open(f, 'r') as f:
dat = f.read()
else:
dat = f.read()
decaylist = []
datasets = dat.split(80 * " " + "\n")[0:-1]
for dataset in datasets:
lines = dataset.splitlines()
ident = re.match(_ident, lines[0])
if ident is not None:
if 'DECAY' in ident.group(2):
fin = ident.group(2).split()[1]
if fin not in decaylist:
decaylist.append(fin)
return decaylist
def _level_dlist_gen(f, keys):
"""
This compiles a list of decay types in an ensdf file
Parameters
----------
f : str
Name of ENSDF formatted file
Returns
-------
decaylist : list
list of decay types in the ENSDF file eg. ['B+','B-','A']
"""
if isinstance(f, str):
with open(f, 'r') as f:
dat = f.read()
else:
dat = f.read()
datasets = dat.split(80 * " " + "\n")[0:-1]
for dataset in datasets:
lines = dataset.splitlines()
ident = re.match(_ident, lines[0])
if ident is not None:
if 'ADOPTED LEVELS' in ident.group(2):
for line in lines:
levelc = _level_cont_regex.match(line)
if levelc is None:
continue
ddict = _parse_level_continuation_record(levelc)
for item in ddict.keys():
if item in keys:
continue
keys.append(item)
return keys