Source code for lvlspy.io.ensdf._ensdf

"""
Module to handle ENSDF input and output
"""

import re
import math

import lvlspy.level as lv
import lvlspy.species as ls
import lvlspy.properties as lp
import lvlspy.transition as lt
import lvlspy.calculate as calc


[docs] def update_from_ensdf(coll, file, sp): """Method to update a species collection from an ENSDF file. Args: ``coll`` (:obj:`obj`) The collection to be read from the ENSDF file ``file`` (:obj:`str`) The file name to update from. ``sp`` (:obj:`str`): The species to be read from file. Returns: On successful return, the species collection has been updated. """ _get_species_from_ensdf(coll, file, sp)
def _set_level_properties(levels): properties = [ "parity", "energy uncertainty", "j^pi", "isomer state", "half life", "half life uncertainty", "angular momentum transfer", "spectroscopic strength", "spectroscopic strength uncertainty", "Comment flag", "questionable character", "useability", ] levs = [] for i, l in enumerate(levels): # setting the level with properties levs.append(lv.Level(l[0], l[1])) additional_properties = [ {key: value} for key, value in zip(properties, l[2:-1]) ] additional_properties.append({properties[-1]: l[-1]}) for j in additional_properties: levs[i].update_properties(j) return levs def _get_species_from_ensdf(coll, file, sp): match = re.search(r"\d+", sp) a = int(match.group()) # mass number identifiers = _get_file_sp_and_identifiers(match, sp, a) levels, transitions = _get_level_and_transition_data(file, identifiers) # setting the levels and transitions in lvlspy format levs = _set_level_properties(levels) s = ls.Species(sp, levels=levs) lvs = s.get_levels() for tran in enumerate(transitions): if tran[1][1] == -1: continue if ( lvs[tran[1][1]].get_properties()["useability"] is False or lvs[tran[1][0]].get_properties()["useability"] is False ): continue t = lt.Transition(lvs[tran[1][0]], lvs[tran[1][1]], 0.0) t = _set_transition_properties(t, tran[1]) ein_a = calc.Weisskopf().estimate_from_ensdf(t, a) t.update_einstein_a(ein_a) s.add_transition(t) coll.add_species(s) def _set_transition_properties(t, tran): properties = [ "E_gamma", "Delta_E", "Relative_Total_Intensity", "Relative_Total_Intensity_Uncertainty", "Transition_Multipolarity", "Mixing_Ratio", "Mixing_Ratio_Uncertainty", "Total_Conversion_Coefficient", "Total_Conversion_Coefficient_Uncertainty", "Relative_Total_Transition_Intensity", "Relative_Total_Transition_Intensity_Uncertainty", "Comment", "Coincidence", "Question", "Reduced_Matrix_Coefficient", ] add_properties = [ {key: value} for key, value in zip(properties, tran[2:-1]) ] add_properties.append({properties[-1]: tran[-1]}) for j in add_properties: t.update_properties(j) if t.get_properties()["Reduced_Matrix_Coefficient"] != "": _extract_rmc(t) return t def _extract_rmc(t): s = t.get_properties()["Reduced_Matrix_Coefficient"] parts = s.split("$") if "=" in parts[0]: rmc = parts[0].split()[2].split("=") if "<" in parts[0]: rmc = parts[0].split()[2].split("<") if ">" in parts[0]: rmc = parts[0].split()[2].split(">") t.update_properties({"tran_1_type": rmc[0]}) t.update_properties({"tran_1_val": float(rmc[1])}) if len(parts) == 2: rmc = parts[1].split()[0].split("=") t.update_properties({"tran_2_type": rmc[0]}) t.update_properties({"tran_2_val": float(rmc[1])}) return t
[docs] def update_reduced_matrix_coefficient(sp, a, t, rmc, mr=0): """Method to update a transition's reduced matrix coefficient and Einstein A coefficient Args: ``sp`` (:obj:`lvlspy.species`) The species where the transition is found ``a`` (:obj:`int`) The species mass number ``t`` (:obj:`lvlspy.transition`) The transition to be updated ``rmc`` (:obj:`list`) A list of tuples containing the new updated reduced matrix coefficients. A sample would be [('BM1W',0.05)] ``mr`` (:obj:`float`,optional) An updated mixing ratio Returns: On successful return, the transition's Einstein A coefficient will be updated based on the new coefficients. """ s = sp.get_name() identifiers = _get_file_sp_and_identifiers(re.search(r"\d+", s), s, a) if mr != 0: t.update_properties({"Mixing_Ratio": mr}) for i, b in enumerate(rmc): t.update_properties({"tran_" + str(i + 1) + "_type": b[0]}) t.update_properties({"tran_" + str(i + 1) + "_val": b[1]}) new_string = identifiers[2] + " G " + rmc[0][0] + "=" + str(rmc[0][1]) if len(rmc) == 2: new_string = new_string + "$" + rmc[1][0] + "=" + str(rmc[1][1]) t.update_properties({"Reduced_Matrix_Coefficient": new_string}) t.update_einstein_a(calc.Weisskopf().estimate_from_ensdf(t, a)) return t
def _get_additional_level_properties(line): delta_e = line[19:21].strip() # energy uncertainty jpi = line[21:39].strip() # strip the spaces iso = line[77:79].strip() # isomer indicator t_half = line[39:49].strip() # level half life delta_t_half = line[49:55].strip() # half life uncertainty l = line[55:64].strip() # Angular momentum transfer s = line[64:74].strip() # spectroscopic strength delta_s = line[74:76].strip() # uncertainty in S comment = line[76] # comment flag q = line[79] # questionable level return [ delta_e, jpi, iso, t_half, delta_t_half, l, s, delta_s, comment, q, ] def _get_additional_gamma_properties(line): delta_energy = line[19:21].strip() # gamma energy uncertainty ri = line[21:29].strip() # relative photon intensity dri = line[29:31].strip() # uncertainty in RI m = line[31:41].strip() # multipolarity of transition mr = line[41:49].strip() # mixing ratio dmr = line[49:55].strip() # uncertainty in MR cc = line[55:62].strip() # Total conversion coefficient dcc = line[62:64].strip() # uncertainty in CC ti = line[64:74].strip() # relative total intensity dti = line[74:76].strip() # uncertainty in TI c = line[76] # comment flag coin = line[77] # coincidence flag q = line[79] # questions transition existance return [ delta_energy, ri, dri, m, mr, dmr, cc, dcc, ti, dti, c, coin, q, ] def _read_levels(line, a, zero_counter): energy = line[9:19].strip() temp = [] if energy[0] in a: str_dummy = energy[0] + "+" energy = energy.replace(str_dummy, "") if energy[-1] in a: str_dummy = "+" + energy[-1] energy = energy.replace(str_dummy, "") energy = float(energy) # strip the spaces and cast to float if energy == 0.0: zero_counter += 1 if zero_counter == 2: return temp, zero_counter properties = _get_additional_level_properties(line) multi, parity, useable = _extract_multi_parity(properties[1]) temp = [ energy, multi, parity, ] # temporary dummy array for re-use for prop in enumerate(properties): temp.append(prop[1]) temp.append(useable) return temp, zero_counter def _read_transition(line, a, lvls): e_g = line[9:19].strip() # gamma ray energy if e_g[0] in a: str_dummy = e_g[0] + "+" e_g = e_g.replace(str_dummy, "") if e_g[-1] in a: str_dummy = "+" + e_g[0] e_g = e_g.replace(str_dummy, "") if e_g.isalpha() or e_g == "": e_g = str(0) e_g = float(e_g) index = -1 for i, lev in enumerate(lvls): if math.isclose( abs(e_g - (lvls[-1][0] - lev[0])), 0.0, abs_tol=1.0, ): index = i break temp = [len(lvls) - 1, index, e_g] properties = _get_additional_gamma_properties(line) for prop in enumerate(properties): temp.append(prop[1]) temp.append("") return temp def _get_level_and_transition_data(file, identifiers): lvls = ( [] ) # lvls format is (energy, multiplicity, parity, rest of properties) trans = [] # trans format is (top level, bottom level, reduced matrix) a = ["X", "Y", "Z", "U", "V", "W", "A", "B"] zero_counter = ( 0 # zero counter required as to only read in the adopted values ) with open(file, "r", encoding="utf-8") as f: for line in f: # reading in level if line.startswith(identifiers[0]): temp, zero_counter = _read_levels(line, a, zero_counter) lvls.append(temp) if zero_counter == 2: lvls.pop(-1) break # reading in gamma info if line.startswith(identifiers[1]): temp = _read_transition(line, a, lvls) trans.append(temp) if line.startswith(identifiers[2]): trans[-1][-1] = line return lvls, trans def _extract_multi_parity(jpi): """ Takes jpi as the input and extracts the j and the parity and calculates the multiplicity Args: ``jpi'' (:obj: `str'): specifies the j and parity of the level Returns: ``multi'' (:obj: `int') : the multiplicity of the level. If multiplicity not clearly defined in ENSDF, will default to 10000 ``parity'' (:obj: `str'): the parity of the level `` useable'' (:obj: `bool'): boolean if the level is useable or not depending on if jpi clearly defined """ # first strip any available parentheses jpi = jpi.replace("(", "") jpi = jpi.replace(")", "") if jpi == "": multi = 10000 parity = "+" useable = False elif "TO" in jpi or "," in jpi or ":" in jpi or "OR" in jpi: useable = False j_range = _get_jpi_range(jpi) multi = j_range[0][0] parity = j_range[0][1] else: if "+" not in jpi and "-" not in jpi: parity = "+" multi = int(2 * lp.Properties().evaluate_expression(jpi) + 1) useable = True else: parity = jpi[-1] multi = int(2 * lp.Properties().evaluate_expression(jpi[0:-1]) + 1) useable = True return multi, parity, useable def _get_file_sp_and_identifiers(match, sp, a): file_sp = ( str(a) + sp.replace(match.group(), "").upper() ) # species string found in ENSDF file # retrieving species identifier to loop over in ENSDF file if len(match.group()) == 1: identifier = " " + file_sp elif len(match.group()) == 2: identifier = " " + file_sp else: identifier = file_sp sym_len = len(sp.replace(match.group(), "")) if sym_len == 1: l_identifier = identifier + " L" # level identifier g_identifier = identifier + " G" # gamma transition identifier else: l_identifier = identifier + " L" # level identifier g_identifier = identifier + " G" # gamma transition identifier b_identifier = ( identifier + "B " ) # reduced transition probability identifier return [l_identifier, g_identifier, b_identifier]
[docs] def write_to_ensdf(coll, file): """ Method that writes a collection of species to ENSDF format Args: ``coll`` (:obj:`lvlspy.spcoll.SpColl`) The collection to be written to file. Each species in the collection must have the species' name, level and gamma properties must be within ENSDF spec Returns: On successful return, the species collection has been written """ with open(file, "w+", encoding="utf-8") as f: for sp in coll.get(): match = re.search(r"\d+", sp) a = int(match.group()) # mass number identifiers = _get_file_sp_and_identifiers(match, sp, a) levels = coll.get()[sp].get_levels() for lev in levels: line = _construct_level_line(lev, identifiers) f.write(line + "\n") linked_levels = coll.get()[sp].get_lower_linked_levels(lev) if linked_levels != []: for l_lev in linked_levels: transition = coll.get()[ sp ].get_level_to_level_transition(lev, l_lev) line = _construct_gamma_line(transition, identifiers) f.write(line + "\n") if ( transition.get_properties()[ "Reduced_Matrix_Coefficient" ] != "" ): f.write( transition.get_properties()[ "Reduced_Matrix_Coefficient" ] )
def _construct_level_line(lev, identifiers): energy = lev.get_energy() properties = lev.get_properties() props = { "energy_uncertainty": [19, 21], "j^pi": [21, 39], "half life": [39, 49], "half life uncertainty": [49, 55], "angular momentum transfer": [55, 64], "spectroscopic strength": [64, 74], "spectroscopic strength uncertainty": [74, 76], "Comment flag": [76], "isomer state": [77, 79], "questionable character": [79], } s = " " * 80 s = identifiers[0] + s[8:] s = s[:9] + str(energy).center(19 - 9) + s[19:] for key, indices in props.items(): if key in properties and len(indices) == 2: s = ( s[: indices[0]] + str(properties[key]).center(indices[1] - indices[0]) + s[indices[1] :] ) if key in properties and len(indices) == 1: s = s[: indices[0]] + str(properties[key]) + s[indices[0] + 1 :] return s def _construct_gamma_line(transition, identifiers): props = { "E_gamma": [9, 19], "Delta_E": [19, 21], "Relative_Total_Intensity": [21, 29], "Relative_Total_Intensity_Uncertainty": [29, 31], "Transition_Multipolarity": [31, 41], "Mixing_Ratio": [41, 49], "Mixing_Ratio_Uncertainty": [49, 55], "Total_Conversion_Coefficient": [55, 62], "Total_Conversion_Coefficient_Uncertainty": [62, 64], "Relative_Total_Transition_Intensity": [64, 74], "Relative_Total_Transition_Intensity_Uncertainty": [74, 76], "Comment": [76], "Coincidence": [77], "Question": [79], } properties = transition.get_properties() s = " " * 80 s = identifiers[1] + s[8:] for key, indices in props.items(): if key in properties and len(indices) == 2: s = ( s[: indices[0]] + str(properties[key]).center(indices[1] - indices[0]) + s[indices[1] :] ) if key in properties and len(indices) == 1: s = s[: indices[0]] + str(properties[key]) + s[indices[0] + 1 :] return s
[docs] def fill_missing_ensdf_transitions(sp, a): """Method to fill in missing transitions from either not listed in ENSDF or level with useable property flagged as false due to unclear J^pi Args: ``sp`` (:obj:`lvlspy.species.Species`) The species read in from ENSDF to fill in missing transitions ``a`` (:obj:`int`) Mass number of species Returns: Upon successful return, the species would be updated with all transitions """ levels = sp.get_levels() for i in range(1, len(levels)): for j in range(i): if sp.get_level_to_level_transition(levels[i], levels[j]) is None: ein_a = 0.0 jpi_i = levels[i].get_properties()["j^pi"] jpi_j = levels[j].get_properties()["j^pi"] e = [levels[i].get_energy(), levels[j].get_energy()] if ( levels[i].get_properties()["useability"] is False and levels[j].get_properties()["useability"] is True ): sp.add_transition( lt.Transition( levels[i], levels[j], _get_ein_a_from_mixed_upper_level_to_lower( [e, ein_a, jpi_i, levels[j], a] ), ) ) continue if ( levels[i].get_properties()["useability"] is True and levels[j].get_properties()["useability"] is True ): jj = [ (levels[i].get_multiplicity() - 1) // 2, (levels[j].get_multiplicity() - 1) // 2, ] p = [ levels[i].get_properties()["parity"], levels[j].get_properties()["parity"], ] p = lp.Properties().set_parity(p) sp.add_transition( lt.Transition( levels[i], levels[j], calc.Weisskopf().estimate(e, jj, p, a), ) ) continue if ( levels[i].get_properties()["useability"] and levels[j].get_properties()["useability"] is False ): sp.add_transition( lt.Transition( levels[i], levels[j], _get_ein_a_to_mixed_lower_level( [e, ein_a, jpi_j, levels[i], a] ), ) ) continue if ( levels[i].get_properties()["useability"] is False and levels[j].get_properties()["useability"] is False ): sp.add_transition( lt.Transition( levels[i], levels[j], _get_ein_a_from_mixed_to_mixed( [e, ein_a, jpi_i, jpi_j, a] ), ) ) continue
def _get_ein_a_from_mixed_to_mixed(in_list): jpi_i_range = _get_jpi_range(in_list[2]) jpi_j_range = _get_jpi_range(in_list[3]) for ki in jpi_i_range: for kj in jpi_j_range: jj = [(ki[0] - 1) // 2, (kj[0] - 1) // 2] p = [ki[1], kj[1]] p = lp.Properties().set_parity(p) in_list[1] += ( calc.Weisskopf().estimate(in_list[0], jj, p, in_list[4]) / len(jpi_i_range) / len(jpi_j_range) ) return in_list[1] def _get_ein_a_to_mixed_lower_level(in_list): jpi_j_range = _get_jpi_range(in_list[2]) for k in jpi_j_range: jj = [(in_list[3].get_multiplicity() - 1) // 2, (k[0] - 1) // 2] p = [in_list[3].get_properties()["parity"], k[1]] p = lp.Properties().set_parity(p) in_list[1] += calc.Weisskopf().estimate( in_list[0], jj, p, in_list[4] ) / len(jpi_j_range) return in_list[1] def _get_ein_a_from_mixed_upper_level_to_lower(in_list): jpi_i_range = _get_jpi_range(in_list[2]) for k in jpi_i_range: jj = [(k[0] - 1) // 2, (in_list[3].get_multiplicity() - 1) // 2] p = [k[1], in_list[3].get_properties()["parity"]] p = lp.Properties().set_parity(p) in_list[1] += calc.Weisskopf().estimate( in_list[0], jj, p, in_list[4] ) / len(jpi_i_range) return in_list[1] def _get_jpi_range(jpi): # first strip any available parentheses jpi = jpi.replace("(", "") jpi = jpi.replace(")", "") j_range = [] if jpi == "": return j_range if "TO" in jpi or ":" in jpi: p = jpi[-1] if "TO" in jpi: jpi = jpi.split("TO") if ":" in jpi: jpi = jpi.split(":") if "+" not in jpi and "-" not in jpi: p = "+" m1 = int(2 * lp.Properties().evaluate_expression(jpi[0].strip(p)) + 1) m2 = int(2 * lp.Properties().evaluate_expression(jpi[1].strip(p)) + 1) for i in range(m1, m2 + 1): j_range.append([i, p]) else: if "OR" in jpi: jpi = jpi.split("OR") if "," in jpi: jpi = jpi.split(",") for j in jpi: if "+" not in j and "-" not in j: m = int(2 * lp.Properties().evaluate_expression(j) + 1) p = "+" j_range.append([m, p]) else: p = j[-1] m = int(2 * lp.Properties().evaluate_expression(j[0:-1]) + 1) j_range.append([m, p]) return j_range
[docs] def remove_undefined_levels(sp, all_levs=False): """Method that removes levels read from ensdf where j^pi is left blank or unclear. This feature Wfacilitates calculations made in the isomer module Args: ``sp`` (:obj:`lvlspy.species.Species`) The species of which the levels are to be trimmed ``all`` (:obj:`bool`) A flag to remove all undefined levels which have j^pi set blank or a range of values. Defaults to False so only the blanks are removed Returns: Upon successful return, the levels with blank j^pi from the ENSDF record will be removed """ levels = sp.get_levels() if all_levs: for l in levels: if l.get_properties()["useability"] is False: sp.remove_level(l) else: for l in levels: if l.get_properties()["j^pi"] == "": sp.remove_level(l)