Module dchain_tools

This module serves to function as a library of functions related to DCHAIN which can be easily imported into and used by scripts for processing DCHIAN output. They are summarized below.

General Purpose Functions

Relating to DCHAIN data libraries

DCHAIN output file parsing

DCHAIN output data plotting

Other

Expand source code
'''

This module serves to function as a library of functions related to DCHAIN which can be easily
imported into and used by scripts for processing DCHIAN output.  They are summarized below.


### General Purpose Functions

- `Dname_to_ZAM`                               : converts a DCHAIN-formatted nuclide name to a ZZZAAAM number
- `ZAM_to_Dname`                               : converts a ZZZAAAM number to a DCHAIN-formatted nuclide name
- `Dname_to_Latex`                             : converts a DCHAIN-formatted nuclide name to pretty LaTeX formatting
- `nuclide_plain_str_to_Dname`                 : converts a plaintext string for a nuclide to a DCHAIN-formatted nuclide name string

### Relating to DCHAIN data libraries  

- `rxn_to_dchain_str`                          : converts a reaction to the format used by the neutron reaction cross section libraries
- `ZZZAAAM_to_dchain_xs_lib_str`               : converts a ZZZAAAM number to the 7-character nuclide string used by the n rxn xs libs
- `ECCO1968_Ebins`                             : returns the n highest energy bins of the ECCO 1968-group structure
- `retrieve_rxn_xs_from_lib`                   : returns cross section for a reaction from a provided n rxn xs library file
- `calc_one_group_nrxn_xs_dchain`              : provided a neutron flux, reaction, and library file, determine 1-grp nrxn xs

###  DCHAIN output file parsing 
- `parse_DCHAIN_act_file`                      : parser for the *.act file from DCHAIN
- `generate_nuclide_time_profiles`             : processes nuclide output from parse_DCHAIN_act_file into a more usable form
- `parse_DCHAIN_act_file_legacy`               : legacy version of parse_DCHAIN_act_file from before error implementation
- `generate_nuclide_time_profiles_legacy`      : legacy version of generate_nuclide_time_profiles from before error implementation
- `parse_DCS_file_from_DCHAIN`                 : parser for the *.dcs file from DCHAIN
- `parse_dtrk_file`                            : parser for the *.dtrk file from PHITS meant for DCHAIN
- `parse_dyld_files`                           : parser for the *.dyld files from PHITS meant for DCHAIN
- `process_dchain_simulation_output`           : **This is main master function for parsing DCHAIN output and is generally the function one should use for this purpose.**

###  DCHAIN output data plotting 
- `plot_top10_nuclides`                        : generates a nice visualization on the ranking of nuclides


###  Other 
- `find`                                       : return index of the first instance of a value in a list
- `Element_Z_to_Sym`                           : returns elemental symbol provided the atomic number Z
- `Element_Sym_to_Z`                           : returns an atomic number Z provided the elemental symbol
- `Element_ZorSym_to_name`                     : returns a string of the name of an element provided its atomic number Z or symbol
- `Element_ZorSym_to_mass`                     : returns the average atomic mass of an element provided its atomic number Z or symbol
- `nuclide_to_Latex_form`                      : form a LaTeX-formatted string of a nuclide provided its information
- `nuclide_plain_str_to_latex_str`             : convert a plaintext string for a nuclide to a LaTeX formatted raw string
- `nuclide_plain_str_ZZZAAAM`                  : convert a plaintext string for a nuclide to an integer ZZZAAAM value
- `time_str_to_sec_multiplier`                 : determine multiplier to convert a time unit to seconds
- `seconds_to_dhms`                            : convert a time in seconds to a string of human-relatable time units
- `seconds_to_ydhms`                           : convert a time in seconds to a string of human-relatable time units (also with years)

'''


import numpy as np
import os
import sys
import matplotlib.pyplot as plt
import time
import re
import bisect
import unicodedata as ud
#from  Hunters_tools import *
from munch import *


def Dname_to_ZAM(Dname):
    '''
    Description:
        Converts a DCHAIN-formatted nuclide name to a ZZZAAAM number
    
    Inputs:
        - `Dname` = nuclide identification string in DCHAIN format 
       
    Outputs:
        - `ZZZAAAM` = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m
    
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    AAA = Dname[2:5]
    A = int(AAA)
    symbol = Dname[0:2]
    if 'XX' in symbol: symbol='n '
    Z = find(symbol,elms)
    if Dname[-1] == ' ':
        m = 0
    elif Dname[-1] == 'm':
        m = 1
    elif Dname[-1] == 'n':
        m = 2
    ZAM = int(10000*Z + 10*A + m)
    return ZAM

def ZAM_to_Dname(ZAM):
    '''
    Description:
        Converts a ZZZAAAM number to a DCHAIN-formatted nuclide name
       
    Inputs:
        - `ZZZAAAM` = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m
        
    Outputs:
        - `Dname` = nuclide identification string in DCHAIN format 
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    m = int(str(ZAM)[-1])
    A = int(str(ZAM)[-4:-1])
    Z = int(str(ZAM)[:-4])
    sym = elms[Z]
    A_str = '{:>3}'.format(A)
    m_str_list = [' ','m','n']
    m_str = m_str_list[m]
    Dname = sym + A_str + m_str
    return Dname

def Dname_to_Latex(Dname):
    '''
    Description:
        Converts a DCHAIN-formatted nuclide name to a LaTeX-formatted string
    
    Inputs:
        - `Dname` = nuclide identification string in DCHAIN format 
       
    Outputs:
        - nuclide name as a LaTeX-formatted raw string
    '''
    AAA = Dname[2:5].strip()
    symbol = Dname[0:2].strip()
    m = Dname[-1].strip()
    latex_str = r"$^{{{}{}}}$".format(AAA,m) + "{}".format(symbol)
    return latex_str


def nuclide_plain_str_to_Dname(nuc_str):
    '''
    Description:
        Converts a plaintext string of a nuclide to a DCHAIN-formatted nuclide string
    
    Dependencies:
        - `nuclide_plain_str_ZZZAAAM` 
        - `ZAM_to_Dname`
    
    Inputs:
    
       - `nuc_str` = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
            + Isomeric/metastable state characters must always immediately follow the atomic mass characters.
                Isomeric state labels MUST either:
                  - (1) be a single lower-case character
                  - (2) begin with any non-numeric character and end with a number
            + Atomic mass numbers must be nonnegative integers OR the string `"nat"` (in which case no metastable states can be written)
            + Elemental symbols MUST begin with an upper-case character    
    
    Outputs:
        - DCHAIN-formatted string of nuclide name
    '''
    return ZAM_to_Dname(nuclide_plain_str_ZZZAAAM(nuc_str))

def rxn_to_dchain_str(target,reaction=None,product=None):
    '''
    Desription:
        Provided a target nuclide and reaction, and optionally a target, generate a reaction string in the format used in DCHAIN's nrxn libs
    
    Dependencies:
        `nuclide_plain_str_ZZZAAAM`
        
        `ZZZAAAM_to_dchain_xs_lib_str`
    
    Inputs:
        - `target` = string in general format of target nuclide
        - Note: at least one of the below options must be provided.
             - `reaction` = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
             - `product` = (optional) string in general format of product nuclide (if omitted, product in ground state is assumed)
    
    Outputs:
        - `rxn_dchain_str` = string formatted identically as that found in DCHAIN's neutron reaction cross section libraries
    '''
    
    if not reaction and not product:
        print('Warning: no reaction or product provided with target {}.'.format(target))
        ZZZAAAM = nuclide_plain_str_ZZZAAAM(target)
        target_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM) 
        dstr = target_dstr + '(N,'
        return dstr
    
    
    ZZZAAAM = nuclide_plain_str_ZZZAAAM(target)
    target_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM)
    if product: # if not None (product is provided)
        ZZZAAAM_prod = nuclide_plain_str_ZZZAAAM(product)
        product_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM_prod)        
    
    react = ['   ', '   ', '   ', '   ', 'N  ', '   ', '   ', '   ', '   ', '   ', '   ', '2ND', '   ', '   ', '   ', '   ', '2N ', '3N ', 'FIS', '   ', 
             '   ', '   ', 'NA ', 'N3A', '2NA', '3NA', '   ', '   ', 'NP ', 'N2A', '2N2', '   ', 'ND ', 'NT ', 'NE ', 'ND2', 'NT2', '4N ', '   ', '   ', 
             '   ', '2NP', '3NP', '   ', 'N2P', 'NPA', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', 
             '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', 
             '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', 
             '   ', '   ', 'G  ', 'P  ', 'D  ', 'T  ', 'HE3', 'A  ', '2A ', '3A ', '   ', '2P ', 'PA ', 'T2A', 'D2A', 'PD ', 'PT ', 'DA ', '   ', '   ']
    dZ = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -6, -2, -2, 0, 0, -1, -4, -4, 0, -1, -1, -2, -5, -5, 0, 0, 0, 0, -1, -1,
          0, -2, -3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -2, -2, -4, -6, 0, -2, -3, -5, -5, -2, -2, -3, 0, 0]
    dA = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -1, -2, 0, 0, 0, 0, -4, -12, -5, -6, 0, 0, -1, -8, -9, 0, -2, -3, -3, -10, -11, -3, 0, 0, 0, -2,
          -3, 0, -2, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, -1, -2, -2, -3, -7, -11, 0, -1, -4, -10, -9, -2, -3, -5, 0, 0]
    
    if reaction: # reaction is provided
        # Get reaction from MT / check if valid reaction
        if isinstance(reaction, float): reaction = int(reaction)
        if isinstance(reaction, int):
            MT = reaction
            rxn = react[MT]
        else:
            rxn = "{:3}".format(reaction.upper())
            if rxn not in react:
                print('Reaction "{}" not in reaction list for DCHAIN, check formatting or enter ENDF MT number (0-119)'.format(rxn))
                return None 
            else:
                MT = react.index(rxn)
    
    if not product: # need to figure out product from parent and rxn, assume ground state (but omit final isomeric characters from string in case xslib has other states too)
        ZAM_str = str(ZZZAAAM)
        M = 0
        A = int(ZAM_str[-4:-1]) + dA[MT]
        Z = int(ZAM_str[:-4]) + dZ[MT]
        ZZZAAAM_prod = 10000*Z + 10*A + M 
        product_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM_prod)[:-2]
        if MT == 18: product_dstr = '' # fission
    
    if not reaction: # need to find missing reaction from target and product, isomeric states are ignored
        ZAM_str = str(ZZZAAAM)
        A_tar = int(ZAM_str[-4:-1])
        Z_tar = int(ZAM_str[:-4]) 
        ZAM_str_prod = str(ZZZAAAM_prod)
        A_prod = int(ZAM_str_prod[-4:-1])
        Z_prod = int(ZAM_str_prod[:-4]) 
        rxn_dA = A_prod - A_tar 
        rxn_dZ = Z_prod - Z_tar 
        matching_dA = [index for index,value in enumerate(dA) if value == rxn_dA]
        matching_dZ = [index for index,value in enumerate(dZ) if value == rxn_dZ]
        MT = set(matching_dA).intersection(matching_dZ).pop() # gets the value which is common between the two lists, in this case the index of the reaction
        rxn = react[MT]
    
    if reaction and product: # check to make sure they are compatible
        # if mismatch occurs, assume product is more likely correct than rxn
        ZAM_str = str(ZZZAAAM)
        A_tar = int(ZAM_str[-4:-1])
        Z_tar = int(ZAM_str[:-4]) 
        ZAM_str_prod = str(ZZZAAAM_prod)
        A_prod = int(ZAM_str_prod[-4:-1])
        Z_prod = int(ZAM_str_prod[:-4]) 
        rxn_dA = A_prod - A_tar 
        rxn_dZ = Z_prod - Z_tar 
        matching_dA = [index for index,value in enumerate(dA) if value == rxn_dA]
        matching_dZ = [index for index,value in enumerate(dZ) if value == rxn_dZ]
        if A_tar==A_prod and Z_tar==Z_prod:
            MT_calc = 4
        else:
            MT_calc = set(matching_dA).intersection(matching_dZ).pop() # gets the value which is common between the two lists, in this case the index of the reaction
        rxn_calc = react[MT_calc]
        if rxn_calc != rxn:
            print('Warning, mismatch between reaction (N,{}) and product ({}) with target {}; assuming product ({}) is correct and reaction should be (N,{}) instead.'.format(rxn.strip(),product,target,product_dstr,rxn_calc.strip()))
            rxn = rxn_calc
        
    dstr = target_dstr + '(N,{})'.format(rxn) + product_dstr
    
    return dstr
        
def ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM):
    '''
    Description:
       Converts a ZZZAAAM number to the 7-character nuclide string used by the DCHAIN neutron reaction cross section libraries.
       
    Inputs:
        - `ZZZAAAM` = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m
        
    Outputs:
        - `D_xs_name` = nuclide identification string in DCHAIN's cross section library format 
    '''
    ZAM_str = str(ZZZAAAM)
    M = int(ZAM_str[-1])
    A = str(int(ZAM_str[-4:-1]))
    Z = ZAM_str[:-4]
    sym = Element_Z_to_Sym(int(Z)).upper()
    if M==0:
        M_str = '  '
    else:
        M_str = 'M' + str(M)
    dstr = "{:2}{:>3}{:2}".format(sym,A,M_str)
    return dstr 

def ECCO1968_Ebins(n):
    '''
    Description:
        Returns the n highest energy bin values of the ECCO 1968-group energy binning structure.
        
    Inputs:
        - `n` = number of energy bins (from 20 MeV down) to be returned 
    
    Outputs:
        - list of `n` energy bins of the ECCO 1968-group structure 
    '''
    ECCO_bins = [
        1.964033E+01,1.947734E+01,1.931570E+01,1.915541E+01,1.899644E+01,1.883880E+01,1.868246E+01,1.852742E+01,1.837367E+01,1.822119E+01,1.806998E+01,1.792002E+01,1.777131E+01,1.762383E+01,1.747757E+01,1.733253E+01,1.718869E+01,1.704605E+01,1.690459E+01,1.676430E+01,1.662518E+01,
        1.648721E+01,1.635039E+01,1.621470E+01,1.608014E+01,1.594670E+01,1.581436E+01,1.568312E+01,1.555297E+01,1.542390E+01,1.529590E+01,1.516897E+01,1.504309E+01,1.491825E+01,1.479444E+01,1.467167E+01,1.454991E+01,1.442917E+01,1.430943E+01,1.419068E+01,1.407291E+01,
        1.395612E+01,1.384031E+01,1.372545E+01,1.361155E+01,1.349859E+01,1.338657E+01,1.327548E+01,1.316531E+01,1.305605E+01,1.294770E+01,1.284025E+01,1.273370E+01,1.262802E+01,1.252323E+01,1.241930E+01,1.231624E+01,1.221403E+01,1.211267E+01,1.201215E+01,1.191246E+01,
        1.181360E+01,1.171557E+01,1.161834E+01,1.152193E+01,1.142631E+01,1.133148E+01,1.123745E+01,1.114419E+01,1.105171E+01,1.095999E+01,1.086904E+01,1.077884E+01,1.068939E+01,1.060068E+01,1.051271E+01,1.042547E+01,1.033895E+01,1.025315E+01,1.016806E+01,1.008368E+01,
        1.000000E+01,9.917013E+00,9.834715E+00,9.753099E+00,9.672161E+00,9.591895E+00,9.512294E+00,9.433354E+00,9.355070E+00,9.277435E+00,9.200444E+00,9.124092E+00,9.048374E+00,8.973284E+00,8.898818E+00,8.824969E+00,8.751733E+00,8.679105E+00,8.607080E+00,8.535652E+00,
        8.464817E+00,8.394570E+00,8.324906E+00,8.255820E+00,8.187308E+00,8.119363E+00,8.051983E+00,7.985162E+00,7.918896E+00,7.853179E+00,7.788008E+00,7.723377E+00,7.659283E+00,7.595721E+00,7.532687E+00,7.470175E+00,7.408182E+00,7.346704E+00,7.285736E+00,7.225274E+00,
        7.165313E+00,7.105850E+00,7.046881E+00,6.988401E+00,6.930406E+00,6.872893E+00,6.815857E+00,6.759294E+00,6.703200E+00,6.647573E+00,6.592406E+00,6.537698E+00,6.483443E+00,6.429639E+00,6.376282E+00,6.323367E+00,6.270891E+00,6.218851E+00,6.167242E+00,6.116062E+00,
        6.065307E+00,6.014972E+00,5.965056E+00,5.915554E+00,5.866462E+00,5.817778E+00,5.769498E+00,5.721619E+00,5.674137E+00,5.627049E+00,5.580351E+00,5.534042E+00,5.488116E+00,5.442572E+00,5.397406E+00,5.352614E+00,5.308195E+00,5.264143E+00,5.220458E+00,5.177135E+00,
        5.134171E+00,5.091564E+00,5.049311E+00,5.007408E+00,4.965853E+00,4.924643E+00,4.883775E+00,4.843246E+00,4.803053E+00,4.763194E+00,4.723666E+00,4.684465E+00,4.645590E+00,4.607038E+00,4.568805E+00,4.530890E+00,4.493290E+00,4.456001E+00,4.419022E+00,4.382350E+00,
        4.345982E+00,4.309916E+00,4.274149E+00,4.238679E+00,4.203504E+00,4.168620E+00,4.134026E+00,4.099719E+00,4.065697E+00,4.031957E+00,3.998497E+00,3.965314E+00,3.932407E+00,3.899773E+00,3.867410E+00,3.835316E+00,3.803488E+00,3.771924E+00,3.740621E+00,3.709579E+00,
        3.678794E+00,3.648265E+00,3.617989E+00,3.587965E+00,3.558189E+00,3.528661E+00,3.499377E+00,3.470337E+00,3.441538E+00,3.412978E+00,3.384654E+00,3.356566E+00,3.328711E+00,3.301087E+00,3.273692E+00,3.246525E+00,3.219583E+00,3.192864E+00,3.166368E+00,3.140091E+00,
        3.114032E+00,3.088190E+00,3.062562E+00,3.037147E+00,3.011942E+00,2.986947E+00,2.962159E+00,2.937577E+00,2.913199E+00,2.889023E+00,2.865048E+00,2.841272E+00,2.817693E+00,2.794310E+00,2.771121E+00,2.748124E+00,2.725318E+00,2.702701E+00,2.680272E+00,2.658030E+00,
        2.635971E+00,2.614096E+00,2.592403E+00,2.570889E+00,2.549554E+00,2.528396E+00,2.507414E+00,2.486605E+00,2.465970E+00,2.445505E+00,2.425211E+00,2.405085E+00,2.385126E+00,2.365332E+00,2.345703E+00,2.326237E+00,2.306932E+00,2.287787E+00,2.268802E+00,2.249973E+00,
        2.231302E+00,2.212785E+00,2.194421E+00,2.176211E+00,2.158151E+00,2.140241E+00,2.122480E+00,2.104866E+00,2.087398E+00,2.070076E+00,2.052897E+00,2.035860E+00,2.018965E+00,2.002210E+00,1.985595E+00,1.969117E+00,1.952776E+00,1.936570E+00,1.920499E+00,1.904561E+00,
        1.888756E+00,1.873082E+00,1.857538E+00,1.842122E+00,1.826835E+00,1.811675E+00,1.796640E+00,1.781731E+00,1.766944E+00,1.752281E+00,1.737739E+00,1.723318E+00,1.709017E+00,1.694834E+00,1.680770E+00,1.666821E+00,1.652989E+00,1.639271E+00,1.625667E+00,1.612176E+00,
        1.598797E+00,1.585530E+00,1.572372E+00,1.559323E+00,1.546383E+00,1.533550E+00,1.520823E+00,1.508202E+00,1.495686E+00,1.483274E+00,1.470965E+00,1.458758E+00,1.446652E+00,1.434646E+00,1.422741E+00,1.410934E+00,1.399225E+00,1.387613E+00,1.376098E+00,1.364678E+00,
        1.353353E+00,1.342122E+00,1.330984E+00,1.319938E+00,1.308985E+00,1.298122E+00,1.287349E+00,1.276666E+00,1.266071E+00,1.255564E+00,1.245145E+00,1.234812E+00,1.224564E+00,1.214402E+00,1.204324E+00,1.194330E+00,1.184418E+00,1.174589E+00,1.164842E+00,1.155175E+00,
        1.145588E+00,1.136082E+00,1.126654E+00,1.117304E+00,1.108032E+00,1.098836E+00,1.089717E+00,1.080674E+00,1.071706E+00,1.062812E+00,1.053992E+00,1.045245E+00,1.036571E+00,1.027969E+00,1.019438E+00,1.010978E+00,1.002588E+00,9.942682E-01,9.860171E-01,9.778344E-01,
        9.697197E-01,9.616723E-01,9.536916E-01,9.457772E-01,9.379285E-01,9.301449E-01,9.224259E-01,9.147709E-01,9.071795E-01,8.996511E-01,8.921852E-01,8.847812E-01,8.774387E-01,8.701570E-01,8.629359E-01,8.557746E-01,8.486728E-01,8.416299E-01,8.346455E-01,8.277190E-01,
        8.208500E-01,8.140380E-01,8.072825E-01,8.005831E-01,7.939393E-01,7.873507E-01,7.808167E-01,7.743369E-01,7.679109E-01,7.615382E-01,7.552184E-01,7.489511E-01,7.427358E-01,7.365720E-01,7.304594E-01,7.243976E-01,7.183860E-01,7.124243E-01,7.065121E-01,7.006490E-01,
        6.948345E-01,6.890683E-01,6.833499E-01,6.776790E-01,6.720551E-01,6.664779E-01,6.609470E-01,6.554620E-01,6.500225E-01,6.446282E-01,6.392786E-01,6.339734E-01,6.287123E-01,6.234948E-01,6.183206E-01,6.131893E-01,6.081006E-01,6.030542E-01,5.980496E-01,5.930866E-01,
        5.881647E-01,5.832837E-01,5.784432E-01,5.736429E-01,5.688824E-01,5.641614E-01,5.594796E-01,5.548366E-01,5.502322E-01,5.456660E-01,5.411377E-01,5.366469E-01,5.321934E-01,5.277769E-01,5.233971E-01,5.190535E-01,5.147461E-01,5.104743E-01,5.062381E-01,5.020369E-01,
        4.978707E-01,4.937390E-01,4.896416E-01,4.855782E-01,4.815485E-01,4.775523E-01,4.735892E-01,4.696591E-01,4.657615E-01,4.618963E-01,4.580631E-01,4.542618E-01,4.504920E-01,4.467535E-01,4.430460E-01,4.393693E-01,4.357231E-01,4.321072E-01,4.285213E-01,4.249651E-01,
        4.214384E-01,4.179410E-01,4.144727E-01,4.110331E-01,4.076220E-01,4.042393E-01,4.008846E-01,3.975578E-01,3.942586E-01,3.909868E-01,3.877421E-01,3.845243E-01,3.813333E-01,3.781687E-01,3.750304E-01,3.719181E-01,3.688317E-01,3.657708E-01,3.627354E-01,3.597252E-01,
        3.567399E-01,3.537795E-01,3.508435E-01,3.479320E-01,3.450446E-01,3.421812E-01,3.393415E-01,3.365254E-01,3.337327E-01,3.309631E-01,3.282166E-01,3.254928E-01,3.227916E-01,3.201129E-01,3.174564E-01,3.148219E-01,3.122093E-01,3.096183E-01,3.070489E-01,3.045008E-01,
        3.019738E-01,2.994678E-01,2.985000E-01,2.972000E-01,2.969826E-01,2.945181E-01,2.920740E-01,2.896501E-01,2.872464E-01,2.848626E-01,2.824986E-01,2.801543E-01,2.778293E-01,2.755237E-01,2.732372E-01,2.709697E-01,2.687210E-01,2.664910E-01,2.642794E-01,2.620863E-01,
        2.599113E-01,2.577544E-01,2.556153E-01,2.534941E-01,2.513904E-01,2.493042E-01,2.472353E-01,2.451835E-01,2.431488E-01,2.411310E-01,2.391299E-01,2.371455E-01,2.351775E-01,2.332258E-01,2.312903E-01,2.293709E-01,2.274674E-01,2.255797E-01,2.237077E-01,2.218512E-01,
        2.200102E-01,2.181844E-01,2.163737E-01,2.145781E-01,2.127974E-01,2.110314E-01,2.092801E-01,2.075434E-01,2.058210E-01,2.041130E-01,2.024191E-01,2.007393E-01,1.990734E-01,1.974214E-01,1.957830E-01,1.941583E-01,1.925470E-01,1.909491E-01,1.893645E-01,1.877930E-01,
        1.862346E-01,1.846891E-01,1.831564E-01,1.816364E-01,1.801291E-01,1.786342E-01,1.771518E-01,1.756817E-01,1.742237E-01,1.727779E-01,1.713441E-01,1.699221E-01,1.685120E-01,1.671136E-01,1.657268E-01,1.643514E-01,1.629875E-01,1.616349E-01,1.602936E-01,1.589634E-01,
        1.576442E-01,1.563359E-01,1.550385E-01,1.537519E-01,1.524760E-01,1.512106E-01,1.499558E-01,1.487113E-01,1.474772E-01,1.462533E-01,1.450396E-01,1.438360E-01,1.426423E-01,1.414586E-01,1.402847E-01,1.391205E-01,1.379660E-01,1.368210E-01,1.356856E-01,1.345596E-01,
        1.334429E-01,1.323355E-01,1.312373E-01,1.301482E-01,1.290681E-01,1.279970E-01,1.269348E-01,1.258814E-01,1.248368E-01,1.238008E-01,1.227734E-01,1.217545E-01,1.207441E-01,1.197421E-01,1.187484E-01,1.177629E-01,1.167857E-01,1.158165E-01,1.148554E-01,1.139022E-01,
        1.129570E-01,1.120196E-01,1.110900E-01,1.101681E-01,1.092538E-01,1.083471E-01,1.074480E-01,1.065563E-01,1.056720E-01,1.047951E-01,1.039254E-01,1.030630E-01,1.022077E-01,1.013595E-01,1.005184E-01,9.968419E-02,9.885694E-02,9.803655E-02,9.722297E-02,9.641615E-02,
        9.561602E-02,9.482253E-02,9.403563E-02,9.325525E-02,9.248135E-02,9.171388E-02,9.095277E-02,9.019798E-02,8.944945E-02,8.870714E-02,8.797098E-02,8.724094E-02,8.651695E-02,8.579897E-02,8.508695E-02,8.438084E-02,8.368059E-02,8.298615E-02,8.250000E-02,8.229747E-02,
        8.161451E-02,8.093721E-02,8.026554E-02,7.959944E-02,7.950000E-02,7.893887E-02,7.828378E-02,7.763412E-02,7.698986E-02,7.635094E-02,7.571733E-02,7.508897E-02,7.446583E-02,7.384786E-02,7.323502E-02,7.262726E-02,7.202455E-02,7.142684E-02,7.083409E-02,7.024626E-02,
        6.966330E-02,6.908519E-02,6.851187E-02,6.794331E-02,6.737947E-02,6.682031E-02,6.626579E-02,6.571586E-02,6.517051E-02,6.462968E-02,6.409333E-02,6.356144E-02,6.303396E-02,6.251086E-02,6.199211E-02,6.147765E-02,6.096747E-02,6.046151E-02,5.995976E-02,5.946217E-02,
        5.896871E-02,5.847935E-02,5.799405E-02,5.751277E-02,5.703549E-02,5.656217E-02,5.609278E-02,5.562728E-02,5.516564E-02,5.470784E-02,5.425384E-02,5.380360E-02,5.335710E-02,5.291430E-02,5.247518E-02,5.203971E-02,5.160785E-02,5.117957E-02,5.075484E-02,5.033364E-02,
        4.991594E-02,4.950170E-02,4.909090E-02,4.868351E-02,4.827950E-02,4.787884E-02,4.748151E-02,4.708747E-02,4.669671E-02,4.630919E-02,4.592488E-02,4.554376E-02,4.516581E-02,4.479099E-02,4.441928E-02,4.405066E-02,4.368510E-02,4.332257E-02,4.296305E-02,4.260651E-02,
        4.225293E-02,4.190229E-02,4.155455E-02,4.120970E-02,4.086771E-02,4.052857E-02,4.019223E-02,3.985869E-02,3.952791E-02,3.919988E-02,3.887457E-02,3.855196E-02,3.823203E-02,3.791476E-02,3.760011E-02,3.728808E-02,3.697864E-02,3.667176E-02,3.636743E-02,3.606563E-02,
        3.576633E-02,3.546952E-02,3.517517E-02,3.488326E-02,3.459377E-02,3.430669E-02,3.402199E-02,3.373965E-02,3.345965E-02,3.318198E-02,3.290662E-02,3.263353E-02,3.236272E-02,3.209415E-02,3.182781E-02,3.156368E-02,3.130174E-02,3.104198E-02,3.078437E-02,3.052890E-02,
        3.027555E-02,3.002430E-02,2.977514E-02,2.952804E-02,2.928300E-02,2.903999E-02,2.879899E-02,2.856000E-02,2.850000E-02,2.832299E-02,2.808794E-02,2.785485E-02,2.762369E-02,2.739445E-02,2.716711E-02,2.700000E-02,2.694166E-02,2.671808E-02,2.649635E-02,2.627647E-02,
        2.605841E-02,2.584215E-02,2.562770E-02,2.541502E-02,2.520411E-02,2.499495E-02,2.478752E-02,2.458182E-02,2.437782E-02,2.417552E-02,2.397489E-02,2.377593E-02,2.357862E-02,2.338295E-02,2.318890E-02,2.299646E-02,2.280562E-02,2.261636E-02,2.242868E-02,2.224255E-02,
        2.205796E-02,2.187491E-02,2.169338E-02,2.151335E-02,2.133482E-02,2.115777E-02,2.098218E-02,2.080806E-02,2.063538E-02,2.046413E-02,2.029431E-02,2.012589E-02,1.995887E-02,1.979324E-02,1.962898E-02,1.946608E-02,1.930454E-02,1.914434E-02,1.898547E-02,1.882791E-02,
        1.867166E-02,1.851671E-02,1.836305E-02,1.821066E-02,1.805953E-02,1.790966E-02,1.776104E-02,1.761364E-02,1.746747E-02,1.732251E-02,1.717876E-02,1.703620E-02,1.689482E-02,1.675461E-02,1.661557E-02,1.647768E-02,1.634094E-02,1.620533E-02,1.607085E-02,1.593748E-02,
        1.580522E-02,1.567406E-02,1.554398E-02,1.541499E-02,1.528706E-02,1.516020E-02,1.503439E-02,1.490963E-02,1.478590E-02,1.466319E-02,1.454151E-02,1.442083E-02,1.430116E-02,1.418247E-02,1.406478E-02,1.394806E-02,1.383231E-02,1.371752E-02,1.360368E-02,1.349079E-02,
        1.337883E-02,1.326780E-02,1.315770E-02,1.304851E-02,1.294022E-02,1.283283E-02,1.272634E-02,1.262073E-02,1.251599E-02,1.241212E-02,1.230912E-02,1.220697E-02,1.210567E-02,1.200521E-02,1.190558E-02,1.180678E-02,1.170880E-02,1.161163E-02,1.151527E-02,1.141970E-02,
        1.132494E-02,1.123095E-02,1.113775E-02,1.104532E-02,1.095366E-02,1.086276E-02,1.077261E-02,1.068321E-02,1.059456E-02,1.050664E-02,1.041944E-02,1.033298E-02,1.024723E-02,1.016219E-02,1.007785E-02,9.994221E-03,9.911282E-03,9.829031E-03,9.747463E-03,9.666572E-03,
        9.586352E-03,9.506797E-03,9.427903E-03,9.349664E-03,9.272074E-03,9.195127E-03,9.118820E-03,9.043145E-03,8.968099E-03,8.893675E-03,8.819869E-03,8.746676E-03,8.674090E-03,8.602106E-03,8.530719E-03,8.459926E-03,8.389719E-03,8.320095E-03,8.251049E-03,8.182576E-03,
        8.114671E-03,8.047330E-03,7.980548E-03,7.914319E-03,7.848641E-03,7.783507E-03,7.718914E-03,7.654857E-03,7.591332E-03,7.528334E-03,7.465858E-03,7.403901E-03,7.342458E-03,7.281525E-03,7.221098E-03,7.161172E-03,7.101744E-03,7.042809E-03,6.984362E-03,6.926401E-03,
        6.868921E-03,6.811918E-03,6.755388E-03,6.699327E-03,6.643731E-03,6.588597E-03,6.533920E-03,6.479697E-03,6.425924E-03,6.372597E-03,6.319712E-03,6.267267E-03,6.215257E-03,6.163678E-03,6.112528E-03,6.061802E-03,6.011496E-03,5.961609E-03,5.912135E-03,5.863072E-03,
        5.814416E-03,5.766164E-03,5.718312E-03,5.670858E-03,5.623797E-03,5.577127E-03,5.530844E-03,5.484945E-03,5.439427E-03,5.394287E-03,5.349521E-03,5.305127E-03,5.261101E-03,5.217441E-03,5.174143E-03,5.131204E-03,5.088622E-03,5.046393E-03,5.004514E-03,4.962983E-03,
        4.921797E-03,4.880952E-03,4.840447E-03,4.800277E-03,4.760441E-03,4.720936E-03,4.681758E-03,4.642906E-03,4.604375E-03,4.566165E-03,4.528272E-03,4.490693E-03,4.453426E-03,4.416468E-03,4.379817E-03,4.343471E-03,4.307425E-03,4.271679E-03,4.236230E-03,4.201075E-03,
        4.166211E-03,4.131637E-03,4.097350E-03,4.063347E-03,4.029627E-03,3.996186E-03,3.963023E-03,3.930135E-03,3.897520E-03,3.865175E-03,3.833099E-03,3.801290E-03,3.769744E-03,3.738460E-03,3.707435E-03,3.676668E-03,3.646157E-03,3.615898E-03,3.585891E-03,3.556133E-03,
        3.526622E-03,3.497355E-03,3.468332E-03,3.439549E-03,3.411005E-03,3.382698E-03,3.354626E-03,3.326787E-03,3.299179E-03,3.271800E-03,3.244649E-03,3.217722E-03,3.191019E-03,3.164538E-03,3.138276E-03,3.112233E-03,3.086405E-03,3.060792E-03,3.035391E-03,3.010202E-03,
        2.985221E-03,2.960447E-03,2.935879E-03,2.911515E-03,2.887354E-03,2.863392E-03,2.839630E-03,2.816065E-03,2.792695E-03,2.769519E-03,2.746536E-03,2.723743E-03,2.701139E-03,2.678723E-03,2.656494E-03,2.634448E-03,2.612586E-03,2.590904E-03,2.569403E-03,2.548081E-03,
        2.526935E-03,2.505965E-03,2.485168E-03,2.464545E-03,2.444092E-03,2.423809E-03,2.403695E-03,2.383747E-03,2.363965E-03,2.344347E-03,2.324892E-03,2.305599E-03,2.286465E-03,2.267490E-03,2.248673E-03,2.230012E-03,2.211506E-03,2.193153E-03,2.174953E-03,2.156904E-03,
        2.139004E-03,2.121253E-03,2.103650E-03,2.086192E-03,2.068879E-03,2.051710E-03,2.034684E-03,2.017798E-03,2.001053E-03,1.984447E-03,1.967979E-03,1.951647E-03,1.935451E-03,1.919389E-03,1.903461E-03,1.887665E-03,1.871999E-03,1.856464E-03,1.841058E-03,1.825780E-03,
        1.810628E-03,1.795602E-03,1.780701E-03,1.765923E-03,1.751268E-03,1.736735E-03,1.722323E-03,1.708030E-03,1.693855E-03,1.679798E-03,1.665858E-03,1.652034E-03,1.638324E-03,1.624728E-03,1.611245E-03,1.597874E-03,1.584613E-03,1.571463E-03,1.558422E-03,1.545489E-03,
        1.532663E-03,1.519944E-03,1.507331E-03,1.494822E-03,1.482417E-03,1.470115E-03,1.457915E-03,1.445816E-03,1.433817E-03,1.421919E-03,1.410118E-03,1.398416E-03,1.386811E-03,1.375303E-03,1.363889E-03,1.352571E-03,1.341346E-03,1.330215E-03,1.319176E-03,1.308228E-03,
        1.297372E-03,1.286605E-03,1.275928E-03,1.265339E-03,1.254839E-03,1.244425E-03,1.234098E-03,1.223857E-03,1.213700E-03,1.203628E-03,1.193639E-03,1.183734E-03,1.173910E-03,1.164168E-03,1.154507E-03,1.144926E-03,1.135425E-03,1.126002E-03,1.116658E-03,1.107391E-03,
        1.098201E-03,1.089088E-03,1.080050E-03,1.071087E-03,1.062198E-03,1.053383E-03,1.044641E-03,1.035972E-03,1.027375E-03,1.018849E-03,1.010394E-03,1.002009E-03,9.936937E-04,9.854473E-04,9.772694E-04,9.691593E-04,9.611165E-04,9.531405E-04,9.452307E-04,9.373865E-04,
        9.296074E-04,9.218928E-04,9.142423E-04,9.066553E-04,8.991312E-04,8.916696E-04,8.842699E-04,8.769316E-04,8.696542E-04,8.624372E-04,8.552801E-04,8.481824E-04,8.411435E-04,8.341631E-04,8.272407E-04,8.203756E-04,8.135676E-04,8.068160E-04,8.001205E-04,7.934805E-04,
        7.868957E-04,7.803654E-04,7.738894E-04,7.674671E-04,7.610981E-04,7.547820E-04,7.485183E-04,7.423066E-04,7.361464E-04,7.300373E-04,7.239790E-04,7.179709E-04,7.120126E-04,7.061038E-04,7.002441E-04,6.944330E-04,6.886701E-04,6.829550E-04,6.772874E-04,6.716668E-04,
        6.660928E-04,6.605651E-04,6.550832E-04,6.496469E-04,6.442557E-04,6.389092E-04,6.336071E-04,6.283489E-04,6.231345E-04,6.179633E-04,6.128350E-04,6.077492E-04,6.027057E-04,5.977040E-04,5.927438E-04,5.878248E-04,5.829466E-04,5.781089E-04,5.733114E-04,5.685536E-04,
        5.638354E-04,5.591563E-04,5.545160E-04,5.499142E-04,5.453506E-04,5.408249E-04,5.363368E-04,5.318859E-04,5.274719E-04,5.230946E-04,5.187536E-04,5.144486E-04,5.101793E-04,5.059455E-04,5.017468E-04,4.975830E-04,4.934537E-04,4.893587E-04,4.852976E-04,4.812703E-04,
        4.772763E-04,4.733156E-04,4.693877E-04,4.654923E-04,4.616294E-04,4.577984E-04,4.539993E-04,4.502317E-04,4.464953E-04,4.427900E-04,4.391154E-04,4.354713E-04,4.318575E-04,4.282736E-04,4.247195E-04,4.211949E-04,4.176995E-04,4.142332E-04,4.107955E-04,4.073865E-04,
        4.040057E-04,4.006530E-04,3.973281E-04,3.940308E-04,3.907608E-04,3.875180E-04,3.843021E-04,3.811129E-04,3.779502E-04,3.748137E-04,3.717032E-04,3.686185E-04,3.655595E-04,3.625258E-04,3.595173E-04,3.565338E-04,3.535750E-04,3.506408E-04,3.477309E-04,3.448452E-04,
        3.419834E-04,3.391454E-04,3.363309E-04,3.335398E-04,3.307719E-04,3.280269E-04,3.253047E-04,3.226051E-04,3.199279E-04,3.172729E-04,3.146399E-04,3.120288E-04,3.094394E-04,3.068715E-04,3.043248E-04,3.017993E-04,2.992948E-04,2.968110E-04,2.943479E-04,2.919052E-04,
        2.894827E-04,2.870804E-04,2.846980E-04,2.823354E-04,2.799924E-04,2.776688E-04,2.753645E-04,2.730793E-04,2.708131E-04,2.685657E-04,2.663370E-04,2.641267E-04,2.619348E-04,2.597611E-04,2.576054E-04,2.554676E-04,2.533476E-04,2.512451E-04,2.491601E-04,2.470924E-04,
        2.450418E-04,2.430083E-04,2.409917E-04,2.389917E-04,2.370084E-04,2.350416E-04,2.330910E-04,2.311567E-04,2.292384E-04,2.273360E-04,2.254494E-04,2.235784E-04,2.217230E-04,2.198830E-04,2.180583E-04,2.162487E-04,2.144541E-04,2.126744E-04,2.109095E-04,2.091592E-04,
        2.074234E-04,2.057021E-04,2.039950E-04,2.023021E-04,2.006233E-04,1.989584E-04,1.973073E-04,1.956699E-04,1.940461E-04,1.924358E-04,1.908388E-04,1.892551E-04,1.876845E-04,1.861269E-04,1.845823E-04,1.830505E-04,1.815315E-04,1.800250E-04,1.785310E-04,1.770494E-04,
        1.755802E-04,1.741231E-04,1.726781E-04,1.712451E-04,1.698239E-04,1.684146E-04,1.670170E-04,1.656310E-04,1.642565E-04,1.628933E-04,1.615415E-04,1.602010E-04,1.588715E-04,1.575531E-04,1.562456E-04,1.549489E-04,1.536631E-04,1.523879E-04,1.511232E-04,1.498691E-04,
        1.486254E-04,1.473920E-04,1.461688E-04,1.449558E-04,1.437529E-04,1.425599E-04,1.413768E-04,1.402036E-04,1.390401E-04,1.378862E-04,1.367420E-04,1.356072E-04,1.344818E-04,1.333658E-04,1.322590E-04,1.311615E-04,1.300730E-04,1.289935E-04,1.279231E-04,1.268615E-04,
        1.258087E-04,1.247646E-04,1.237292E-04,1.227024E-04,1.216842E-04,1.206744E-04,1.196729E-04,1.186798E-04,1.176949E-04,1.167182E-04,1.157496E-04,1.147890E-04,1.138364E-04,1.128917E-04,1.119548E-04,1.110258E-04,1.101044E-04,1.091907E-04,1.082845E-04,1.073859E-04,
        1.064947E-04,1.056110E-04,1.047345E-04,1.038654E-04,1.030034E-04,1.021486E-04,1.013009E-04,1.004603E-04,9.962658E-05,9.879981E-05,9.797990E-05,9.716679E-05,9.636043E-05,9.556076E-05,9.476773E-05,9.398128E-05,9.320136E-05,9.242791E-05,9.166088E-05,9.090021E-05,
        9.014586E-05,8.939776E-05,8.865588E-05,8.792015E-05,8.719052E-05,8.646695E-05,8.574939E-05,8.503778E-05,8.433208E-05,8.363223E-05,8.293819E-05,8.224991E-05,8.156734E-05,8.089044E-05,8.021915E-05,7.955344E-05,7.889325E-05,7.823854E-05,7.758926E-05,7.694537E-05,
        7.630682E-05,7.567357E-05,7.504558E-05,7.442280E-05,7.380518E-05,7.319270E-05,7.258529E-05,7.198293E-05,7.138556E-05,7.079316E-05,7.020566E-05,6.962305E-05,6.904527E-05,6.847228E-05,6.790405E-05,6.734053E-05,6.678169E-05,6.622749E-05,6.567789E-05,6.513285E-05,
        6.459233E-05,6.405630E-05,6.352471E-05,6.299754E-05,6.247474E-05,6.195628E-05,6.144212E-05,6.093223E-05,6.042657E-05,5.992511E-05,5.942781E-05,5.893464E-05,5.844556E-05,5.796053E-05,5.747954E-05,5.700253E-05,5.652948E-05,5.606036E-05,5.559513E-05,5.513376E-05,
        5.467623E-05,5.422248E-05,5.377251E-05,5.332626E-05,5.288373E-05,5.244486E-05,5.200963E-05,5.157802E-05,5.114999E-05,5.072551E-05,5.030456E-05,4.988709E-05,4.947309E-05,4.906253E-05,4.865538E-05,4.825160E-05,4.785117E-05,4.745407E-05,4.706026E-05,4.666972E-05,
        4.628243E-05,4.589834E-05,4.551744E-05,4.513971E-05,4.476511E-05,4.439361E-05,4.402521E-05,4.365985E-05,4.329753E-05,4.293822E-05,4.258189E-05,4.222851E-05,4.187807E-05,4.153054E-05,4.118589E-05,4.084410E-05,4.050514E-05,4.016900E-05,3.983565E-05,3.950507E-05,
        3.917723E-05,3.885211E-05,3.852969E-05,3.820994E-05,3.789285E-05,3.757838E-05,3.726653E-05,3.695727E-05,3.665057E-05,3.634642E-05,3.604479E-05,3.574566E-05,3.544902E-05,3.515484E-05,3.486310E-05,3.457378E-05,3.428686E-05,3.400233E-05,3.372015E-05,3.344032E-05,
        3.316281E-05,3.288760E-05,3.261467E-05,3.234401E-05,3.207560E-05,3.180942E-05,3.154544E-05,3.128365E-05,3.102404E-05,3.076658E-05,3.051126E-05,3.025805E-05,3.000695E-05,2.975793E-05,2.951098E-05,2.926607E-05,2.902320E-05,2.878235E-05,2.854349E-05,2.830662E-05,
        2.807171E-05,2.783875E-05,2.760773E-05,2.737862E-05,2.715141E-05,2.692609E-05,2.670264E-05,2.648104E-05,2.626128E-05,2.604335E-05,2.582722E-05,2.561289E-05,2.540033E-05,2.518954E-05,2.498050E-05,2.477320E-05,2.456761E-05,2.436373E-05,2.416154E-05,2.396104E-05,
        2.376219E-05,2.356499E-05,2.336944E-05,2.317550E-05,2.298317E-05,2.279244E-05,2.260329E-05,2.241572E-05,2.222969E-05,2.204522E-05,2.186227E-05,2.168084E-05,2.150092E-05,2.132249E-05,2.114554E-05,2.097006E-05,2.079603E-05,2.062345E-05,2.045231E-05,2.028258E-05,
        2.011426E-05,1.994734E-05,1.978180E-05,1.961764E-05,1.945484E-05,1.929339E-05,1.913328E-05,1.897449E-05,1.881703E-05,1.866087E-05,1.850601E-05,1.835244E-05,1.820013E-05,1.804910E-05,1.789931E-05,1.775077E-05,1.760346E-05,1.745738E-05,1.731250E-05,1.716883E-05,
        1.702635E-05,1.688506E-05,1.674493E-05,1.660597E-05,1.646816E-05,1.633150E-05,1.619597E-05,1.606156E-05,1.592827E-05,1.579609E-05,1.566500E-05,1.553500E-05,1.540608E-05,1.527823E-05,1.515144E-05,1.502570E-05,1.490101E-05,1.477735E-05,1.465472E-05,1.453310E-05,
        1.441250E-05,1.429289E-05,1.417428E-05,1.405665E-05,1.394000E-05,1.382431E-05,1.370959E-05,1.359582E-05,1.348299E-05,1.337110E-05,1.326014E-05,1.315010E-05,1.304097E-05,1.293274E-05,1.282542E-05,1.271898E-05,1.261343E-05,1.250876E-05,1.240495E-05,1.230201E-05,
        1.219991E-05,1.209867E-05,1.199827E-05,1.189870E-05,1.179995E-05,1.170203E-05,1.160492E-05,1.150861E-05,1.141311E-05,1.131839E-05,1.122446E-05,1.113132E-05,1.103894E-05,1.094733E-05,1.085648E-05,1.076639E-05,1.067704E-05,1.058843E-05,1.050056E-05,1.041342E-05,
        1.032701E-05,1.024130E-05,1.015631E-05,1.007203E-05,9.988446E-06,9.905554E-06,9.823351E-06,9.741830E-06,9.660985E-06,9.580812E-06,9.501303E-06,9.422455E-06,9.344261E-06,9.266715E-06,9.189814E-06,9.113550E-06,9.037919E-06,8.962916E-06,8.888536E-06,8.814772E-06,
        8.741621E-06,8.669077E-06,8.597135E-06,8.525790E-06,8.455037E-06,8.384871E-06,8.315287E-06,8.246281E-06,8.177848E-06,8.109982E-06,8.042680E-06,7.975936E-06,7.909746E-06,7.844105E-06,7.779009E-06,7.714454E-06,7.650434E-06,7.586945E-06,7.523983E-06,7.461544E-06,
        7.399622E-06,7.338215E-06,7.277317E-06,7.216925E-06,7.157034E-06,7.097640E-06,7.038739E-06,6.980326E-06,6.922399E-06,6.864952E-06,6.807981E-06,6.751484E-06,6.695455E-06,6.639892E-06,6.584789E-06,6.530144E-06,6.475952E-06,6.422210E-06,6.368914E-06,6.316060E-06,
        6.263645E-06,6.211665E-06,6.160116E-06,6.108995E-06,6.058298E-06,6.008022E-06,5.958164E-06,5.908719E-06,5.859684E-06,5.811056E-06,5.762832E-06,5.715008E-06,5.667581E-06,5.620547E-06,5.573904E-06,5.527647E-06,5.481775E-06,5.436284E-06,5.391169E-06,5.346430E-06,
        5.302061E-06,5.258061E-06,5.214426E-06,5.171153E-06,5.128239E-06,5.085681E-06,5.043477E-06,4.918953E-06,4.797503E-06,4.679053E-06,4.563526E-06,4.450853E-06,4.340961E-06,4.233782E-06,4.129250E-06,4.000000E-06,3.927860E-06,3.830880E-06,3.736300E-06,3.644050E-06,
        3.554080E-06,3.466330E-06,3.380750E-06,3.300000E-06,3.217630E-06,3.137330E-06,3.059020E-06,2.983490E-06,2.909830E-06,2.837990E-06,2.767920E-06,2.720000E-06,2.659320E-06,2.600000E-06,2.550000E-06,2.485030E-06,2.421710E-06,2.382370E-06,2.360000E-06,2.300270E-06,
        2.242050E-06,2.185310E-06,2.130000E-06,2.100000E-06,2.059610E-06,2.020000E-06,1.974490E-06,1.930000E-06,1.884460E-06,1.855390E-06,1.840000E-06,1.797000E-06,1.755000E-06,1.711970E-06,1.670000E-06,1.629510E-06,1.590000E-06,1.544340E-06,1.500000E-06,1.475000E-06,
        1.440000E-06,1.404560E-06,1.370000E-06,1.337500E-06,1.300000E-06,1.267080E-06,1.235000E-06,1.202060E-06,1.170000E-06,1.150000E-06,1.123000E-06,1.110000E-06,1.097000E-06,1.080000E-06,1.071000E-06,1.045000E-06,1.035000E-06,1.020000E-06,9.960000E-07,9.860000E-07,
        9.720000E-07,9.500000E-07,9.300000E-07,9.100000E-07,8.764250E-07,8.600000E-07,8.500000E-07,8.194500E-07,7.900000E-07,7.800000E-07,7.415500E-07,7.050000E-07,6.825600E-07,6.531500E-07,6.250000E-07,5.952800E-07,5.669600E-07,5.400000E-07,5.315800E-07,5.196200E-07,
        5.000000E-07,4.850000E-07,4.670100E-07,4.496800E-07,4.330000E-07,4.139900E-07,4.000000E-07,3.910000E-07,3.699300E-07,3.500000E-07,3.346600E-07,3.200000E-07,3.145000E-07,3.000000E-07,2.800000E-07,2.635100E-07,2.480000E-07,2.335800E-07,2.200000E-07,2.091400E-07,
        1.988100E-07,1.890000E-07,1.800000E-07,1.697100E-07,1.600000E-07,1.530300E-07,1.463700E-07,1.400000E-07,1.340000E-07,1.150000E-07,1.000000E-07,9.500000E-08,8.000000E-08,7.700000E-08,6.700000E-08,5.800000E-08,5.000000E-08,4.200000E-08,3.500000E-08,3.000000E-08,
        2.500000E-08,2.000000E-08,1.500000E-08,1.000000E-08,6.900000E-09,5.000000E-09,3.000000E-09,1.000010E-11
        ]
    
    #pstr = ''
    #for i in range(len(ECCO_bins)):
    #    pstr += ECCO_bins[i] + ','
    #    if i!=0 and i%20==0:
    #        pstr += '\n'       
    #print(pstr)
    
    return ECCO_bins[:n]

def retrieve_rxn_xs_from_lib(libfile,target,reaction=None,product=None):
    '''
    Description:
        Provided a DCHAIN neutron rxn cross section library file and sufficient information
        about a reaction, return that reaction's cross section.
    
    Dependencies:
        `rxn_to_dchain_str` 
        
        `ECCO1968_Ebins`
    
    Inputs:
        - `libfile` = string of file path to data library file to be searched
        - `target` = string in general format of target nuclide
        - `reaction` = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
                   if `reaction = 'tot'` or `'total'`, the summed total transmutation xs (reactions which change the target's nuclide species) is provided and input for product is ignored; 
                   this behavior is also assumed when missing both reaction and product information
        - `product` = (optional) string in general format of product nuclide (if omitted, sum of all isomeric states is assumed; if provided product but not isomeric state, ground state is assumed)
        
    Outputs:
        - `xs` = a 2x1968 numpy array containing energy [eV] (i=0) and cross sections [b] (i=1) for all 1968 ECCO bins
        - `rxn_tex_str` = a LaTeX-formatted string of the reaction
    '''
    
    datafolder, lib = os.path.split(libfile.replace('_n_act_xs_lib',''))
    
    n_occurances = 0
    rxn_locations= []
    rxn_listings = []
    xs_data_raw  = []
    
    # determine if rxn or product is provided
    if (not reaction and not product) or (reaction=='tot' or reaction=='total'): # only target provided
        #print('Total transmutation cross section of {}'.format(target))
        calc_total_xs = True 
    else:
        calc_total_xs = False
        
    if calc_total_xs: # only concerned with target, not target or reaction
        # First, assemble reaction string
        rt = rxn_to_dchain_str(target,None,target)[:10] # only concerned with target + '(N,'
        rt_alt = None # alternate string to cover ground state when 'g' is present in product too
        catalog_rxn_text = rt[0] + rt[1:].lower()
        catalog_rxn_text_alt = None
        # make pretty Latex str of reaction too
        target_str = rt[0]  + rt[1:7].lower()
        not_symbol = r'$\neg$' # '!'
        rxn_str    = '(n,*)'
        tex_target = nuclide_plain_str_to_latex_str(target_str)
        if target_str[-2:] == '  ':
            tex_product = nuclide_plain_str_to_latex_str(target_str[:-2]+'g ')
        else:
            tex_product = tex_target
        rxn_tex_str = tex_target + rxn_str + not_symbol + tex_product
    else:
        # First, assemble reaction string
        rt = rxn_to_dchain_str(target,reaction,product)
        rt_alt = None # alternate string to cover ground state when 'g' is present in product too
        if product:
            if rt[-2] == ' ': rt_alt = rt[:-2] + 'G '
        # Format string found in the catalog file
        catalog_rxn_text = rt[0] + rt[1:14].lower() + rt[14] + rt[15:].lower()
        catalog_rxn_text_alt = None
        if rt_alt: catalog_rxn_text_alt = rt_alt[0] + rt_alt[1:14].lower() + rt_alt[14] + rt_alt[15:].lower()
        # make pretty Latex str of reaction too
        target_str = rt[0]  + rt[1:7].lower()
        product_str= rt[14] + rt[15:22].lower()
        rxn_str    = rt[7:14].lower().replace(' ','')
        if 'a' in rxn_str: rxn_str = rxn_str.replace('a',r'$\alpha$')
        if 'g' in rxn_str: rxn_str = rxn_str.replace('g',r'$\gamma$')
        if 'he3' in rxn_str: rxn_str = rxn_str.replace('he3',r'$^3$He')
        tex_target = nuclide_plain_str_to_latex_str(target_str)
        tex_product= nuclide_plain_str_to_latex_str(product_str)
        rxn_tex_str = tex_target + rxn_str + tex_product
    
    if lib[0]=='h': # in hybrid_lib_dchain_names: 
        hlib = True 
    else:
        hlib = False
    catalog_file = libfile.replace('_n_act_xs_lib','_n_reaction_list.txt')
    library_file = libfile
    if os.path.isfile(catalog_file): # catalog file exists
        search_file = catalog_file
        search_text = catalog_rxn_text
        search_text_alt = catalog_rxn_text_alt
        using_catalog_file = True
    elif os.path.isfile(library_file): # library file exists
        search_file = library_file
        search_text = rt
        search_text_alt = rt_alt
        using_catalog_file = False
    else:
        print('\t{} library files not found.'.format(lib))
        return None
    with open(search_file) as f:
        asterisk_counter = -1
        for num, line in enumerate(f, 1):
            if '*' in line: asterisk_counter += 1
            if search_text in line or (rt_alt and search_text_alt in line):
                n_occurances += 1
                if using_catalog_file:
                    rxn_locations.append(num-2) # index of reaction in reaction lib
                    rxn_listings.append(line.replace('\n',''))
                else: # searching through actual library file
                    rxn_locations.append(asterisk_counter) # index of reaction in reaction lib
                    rxn_listings.append(line[20:41])
    
    if n_occurances == 0:
        print('Reaction "{}" not found in library "{}".'.format(catalog_rxn_text,lib))
        return None, 'null'
    if n_occurances > 1:
        if calc_total_xs:
            print('   Multiple reactions found.  The sum of all will be used.')
        else:
            print('   Multiple product isomers for reaction found.  The sum of all isomeric states will be used.')
        for j in range(n_occurances):
            print('\t{}   (lib entry index {} of {})'.format(rxn_listings[j],rxn_locations[j],lib))
    
    if hlib:
        print('Hybrid library {} uses data from {} for reaction {}.'.format(lib,rxn_listings[0][26:],catalog_rxn_text))
    
    # State library availability
    #if hlib and n_occurances>0: # this is a hybrid library
    #    src_lib  = rxn_listings[libi][0][26:]
    #    print('\t{}   {} (from {})'.format(lib,n_occurances,src_lib))
    #    lib_colors[libi] = lib_colors[lib_names.index(src_lib)]
    #else: # normal library
    #    print('\t{}   {}'.format(lib,n_occurances[libi]))
        
    #if n_occurances[libi]>0 and libi in libis_to_compare: n_plotable_lines += 1
    
    if not os.path.isfile(library_file):
        print('\t{} library file not found.'.format(lib))
        return None, 'null'
    if n_occurances == 0:
        print('\t\tCross section not present in this library, skipping')
        return None, 'null'
    entry_index = -1
    with open(library_file) as f:
        lines = f.readlines()
        for line in lines:
            if '*' in line: entry_index += 1
            if entry_index in rxn_locations: # reached bookmarked entry index of interest for this lib
                found_entry = True
                enti = rxn_locations.index(entry_index)
                
                if '*' in line:
                    if rxn_listings[enti][:22].upper() not in line: # double check that entry is correct
                        print('Library index mismatch!')
                        found_entry = False
                    xs_vals = []
                    entry_li = -1
                if found_entry:
                    entry_li += 1
                    if entry_li == 0: # first line
                        nEbins = int(line[12:18])
                    elif entry_li == 1 or entry_li == 2: # skip descriptive lines
                        continue 
                    else:
                        xs_vals += [np.float(xsi) for xsi in line.replace('\n','').split()]
                        
                    if len(xs_vals) == nEbins:
                        if len(xs_data_raw)==0: # need to initialize entry
                            xs_data_raw = np.zeros(1968)
                        xs_data_raw[:nEbins] += np.array(xs_vals)
    
    # Now pretty up the results to be provided to the user
    xs = np.zeros((2,1968))
    xs[0,:] = (1e6)*np.array(ECCO1968_Ebins(1968)[::-1]) # all energy bins in increasing order in eV
    # now flip order of lists to have them in energy increasing order
    for i in range(len(xs_data_raw)): 
        xs[1,1967-i] = xs_data_raw[i]
        
    return xs, rxn_tex_str



def calc_one_group_nrxn_xs_dchain(neutron_flux,neutron_flux_errors,libfile,target,reaction=None,product=None):
    '''
    Description:
        Combines a neutron flux and cross section into a flux-weighted single-group cross section.
        
    Inputs:
        - `neutron_flux` = 1D array of flux values
        - `neutron_flux_errors` = 1D array of flux absolute uncertainties
        - `libfile` = string of file path to data library file to be searched
        - `target` = string in general format of target nuclide
        - `reaction` = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
                   
              if `reaction = 'tot'` or `'total'`, the summed total transmutation xs (reactions which change the target's nuclide species) is provided and input for product is ignored; 
              this behavior is also assumed when missing both reaction and product information
        - `product` = (optional) string in general format of product nuclide (if omitted, sum of all isomeric states is assumed; if provided product but not isomeric state, ground state is assumed)
        
    Outputs:
        - `xs` = a length-2 list of the single group cross section and its absolute uncertainty
    '''
    
    xs_out, rxn_str_out = retrieve_rxn_xs_from_lib(libfile,target,reaction,product)
    xs_vals = xs_out[1,:]
    
    flux_val = neutron_flux
    flux_err = neutron_flux_errors
    
    if len(xs_vals) != len(flux_val):
        print('Warning: mismatch in flux and cross section energy binning!')
    
    xs = np.sum(flux_val*xs_vals)/np.sum(flux_val)
    xs_fer_num = np.sqrt(np.sum((flux_err*xs_vals)**2))/np.sum(flux_val*xs_vals)
    xs_fer_denom = np.sqrt(np.sum(flux_err**2))/np.sum(flux_val)
    xs_fer = np.sqrt(xs_fer_num**2 + xs_fer_denom**2)
    xs_aer = xs*xs_fer
    
    return [xs, xs_aer] 




def parse_DCHAIN_act_file_legacy(act_file_path):
    '''
    Description:
       This code parses the .act file generated by DCHAIN (without uncertainty values)
    
    Inputs:
        - path to a DCHAIN-generated .act file
        
    Outputs:
        - length R list of region numbers 
        - length T list of measurement times (in sec) from start of irradiation
        - time of end of irradiation (in sec)
        - NumPy array of dimension RxTxNx11 of nuclide table data (N=max recorded table lenth)
        - NumPy array of dimension RxTxNx5 of gamma spectrum table data
        - NumPy array of dimension RxTxNx12 of top-10 list table data
        - List containing 3 lists of column headers for the preceding three NumPy arrays
        - list of the below lists/arrays of summary information
            - NumPy array of dimension Rx7 of region-specific summary info
            - list of length 7 containing descriptions of the above items
            - NumPy array of dimension RxTx12 of region and time-specific summary info
            - list of length 12 containing descriptions of the above items
    '''
    # Extract file info
    f = open(act_file_path)
    lines = f.readlines()
    f.close()
    
    # Parse file to determine number of regions
    nreg = 0
    reg_nos = []
    for line in lines:
        if 'region number' in line:
            nreg += 1
            reg_nos.append(int(line[21:31]))
    
    # Parse file again to determine number of time steps
    current_reg_no = -1
    #irradiation_time = -1.0 # seconds
    end_of_irradiation_time = -1.0 # seconds
    ntimes = 0
    time_strs = []
    time_list_sec = [] # time steps in seconds
    for line in lines:
        #if 'irradiation time' in line: irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33])
        if 'region number' in line: current_reg_no = int(line[21:31])
        if current_reg_no != reg_nos[0]: continue # only read times from first region
        if '--- output time ---' in line:
            ntimes += 1
            time_strs.append(line)
            time_list_sec.append(float(line[40:53]))
        if ('--- output time ---' in line) and ('after the last shutdown' in line) and end_of_irradiation_time == -1:
            end_of_irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33]) - float(line[88:97])*time_str_to_sec_multiplier(line[99])
            
    # Extract "summary info" from file
    ri = -1 # region index
    ti = -1 # time index
    r_summary_info = np.empty((nreg,7), dtype='object') # (regionwise) initialize Rx7 array
    r_summary_info_description = ['region number','irradiation time [s]','region volume [cc]','neutron flux [n/cm^2/s]','beam power [MW]','beam energy [GeV]','beam current [mA]'] # (regionwise) initialize Rx7 array
    '''
    index  meaning
       0   region number
       1   irradiation time [sec]
       2   region volume [cc]
       3   neutron flux [n/cm^2/s]
       4   beam power [MW]
       5   beam energy [GeV]
       6   beam current [mA] 
    '''
    rt_summary_info = np.empty((nreg,ntimes,12), dtype='object') # (region-and-timewise) initialize RxTx12 array
    rt_summary_info_description = ['total gamma flux [#/s/cc]','total gamma energy flux [MeV/s/cc]','annihilation gamma flux [#/s/cc]','gamma current underflow [#/s]','gamma current overflow [#/s]','total activity [Bq/cc]','total decay heat [W/cc]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','activated atoms [#/cc]','total gamma dose rate [uSV/h*m^2]'] # (region-and-timewise) initialize RxTx12 array
    '''
    index  meaning
       0   total gamma flux [#/s/cc]
       1   total gamma energy flux [MeV/s/cc]
       2   annihilation gamma flux [#/s/cc]
       3   gamma current underflow [#/s] (gammas below lowest energy bin)
       4   gamma current overflow [#/s] (gammas above highest energy bin)
       5   total activity [Bq/cc]
       6   total decay heat [W/cc]
       7     beta decay heat [W/cc]
       8     gamma decay heat [W/cc]
       9     alpha decay heat [W/cc]
       10  activated atoms [#/cc]
       11  total gamma dose rate [uSV/h*m^2]
    '''
    for li in range(len(lines)):
        line = lines[li]
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
            
            # region-specific summary info
            r_summary_info[ri,0] = int(line[21:31])
            r_summary_info[ri,1] = float(lines[li-1][21:31])*time_str_to_sec_multiplier(lines[li-1][33])
            r_summary_info[ri,2] = float(lines[li-2][21:31])
            r_summary_info[ri,3] = float(lines[li-3][21:31])
            r_summary_info[ri,4] = float(lines[li-4][21:31])
            r_summary_info[ri,5] = float(lines[li-5][21:31])
            r_summary_info[ri,6] = float(lines[li-6][21:31])
            
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            
        # gamma info specific to region and time
        if 'total gamma-ray flux' in line:
            rt_summary_info[ri,ti,0] = float(line[39:49])
            rt_summary_info[ri,ti,1] = float(lines[li+1][39:49])
            rt_summary_info[ri,ti,2] = float(lines[li+2][39:49])
            if 'group limitation' in lines[li+3]:
                rt_summary_info[ri,ti,3] = float(lines[li+3][91:101])
                rt_summary_info[ri,ti,4] = float(lines[li+3][64:74])
            else:
                rt_summary_info[ri,ti,3] = 0.0
                rt_summary_info[ri,ti,4] = 0.0
        
        # activation info specific to region and time
        if 'total activity' in line:
            rt_summary_info[ri,ti,5] = float(line[25:36])
            rt_summary_info[ri,ti,6] = float(lines[li+1][25:36])
            rt_summary_info[ri,ti,7] = float(lines[li+2][25:36])
            rt_summary_info[ri,ti,8] = float(lines[li+3][25:36])
            rt_summary_info[ri,ti,9] = float(lines[li+4][25:36])
            rt_summary_info[ri,ti,10]= float(lines[li+5][25:36])
            rt_summary_info[ri,ti,11]= float(lines[li+6][25:36])
    
    summary_info = [r_summary_info, r_summary_info_description, rt_summary_info, rt_summary_info_description]
    
    # Extract major "blocks" (nuclides, gamma spec, top10 list) for each time step in each region
    act_block_text = np.empty((nreg,ntimes,3), dtype='object') # initialize RxTx3 array to hold character strings where final index 0=nuclides, 1=gamma-spec, and 2=top10-list
    ri = -1 # region index
    ti = -1 # time index
    qi = -1 # quantity index - 0=nuclides, 1=gamma-spec, and 2=top10-list
    max_array_len = [0,0,0] # maximum number of entries for a given quantity
    current_array_len = [0,0,0] # current number of entries for a given quantity
    for line in lines:
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            qi = 0 # reset quantity index
        if 'gamma-ray spectrum weighted by energy' in line:
            qi = 1
        if 'dominant nuclides (top 10)' in line:
            qi = 2
        if 'total' in line[:20] or line=='\n': # no longer reading info block
            if current_array_len[qi] > max_array_len[qi]: 
                max_array_len[qi] = current_array_len[qi]
            current_array_len[qi] = 0
            qi = -1 
            
        if qi < 0: continue # not in region of interest
        
        try:
            act_block_text[ri,ti,qi] += line 
        except:
            act_block_text[ri,ti,qi] = line 
            
        current_array_len[qi] += 1
        
    header_len = [3,4,3] # number of lines present in table header
    
    nuclides_produced = np.empty((nreg,ntimes,max_array_len[0]-header_len[0],11), dtype='object') # initialize RxTxNx11 array to hold nuclide information
    gamma_spectra     = np.empty((nreg,ntimes,max_array_len[1]-header_len[1], 5), dtype='object') # initialize RxTxNx5 array to hold gamma spec information
    top10_lists       = np.empty((nreg,ntimes,max_array_len[2]-header_len[2],12), dtype='object') # initialize RxTxNx12 array to hold top 10 list information
    
    column_headers = [ [],[],[] ]
    
    # Now, populate each array
    
    # Nuclides produced
    column_headers[0] = ['nuclide','atoms [#/cc]','activity [Bq/cc]','activity [Bq]','rate [%]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','total decay heat [W/cc]','half life [s]','dose-rate [uSv/h*m^2]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,0].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[0]: continue # in header lines
                ii = ei - header_len[0] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                nuclides_produced[ri,ti,ii,0] = line[3:9]            # nuclide
                nuclides_produced[ri,ti,ii,1] = float(line[13:23])   # atoms [#/cc]
                nuclides_produced[ri,ti,ii,2] = float(line[26:36])   # activity [Bq/cc]
                nuclides_produced[ri,ti,ii,3] = float(line[38:48])   # activity [Bq]
                nuclides_produced[ri,ti,ii,4] = float(line[49:56].replace('     ','0.0'))   # rate [%]
                nuclides_produced[ri,ti,ii,5] = float(line[58:67])   # beta decay heat [W/cc]
                nuclides_produced[ri,ti,ii,6] = float(line[69:78])   # gamma decay heat [W/cc]
                nuclides_produced[ri,ti,ii,7] = float(line[80:89])   # alpha decay heat [W/cc]
                nuclides_produced[ri,ti,ii,8] = float(line[91:100])  # total decay heat [W/cc]
                nuclides_produced[ri,ti,ii,9] = float(line[104:113]) # half life [s]
                nuclides_produced[ri,ti,ii,10]= float(line[116:125]) # dose-rate [uSv/h*m^2]
                
    # Gamma-ray spectra
    column_headers[1] = ['group number','bin energy lower-bound [MeV]','bin energy upper-bound [MeV]','flux [#/s/cc]','energy flux [MeV/s/cc]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,1].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[1]: continue # in header lines
                ii = ei - header_len[1] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                gamma_spectra[ri,ti,ii,0] = int(line[1:4])       # group number
                gamma_spectra[ri,ti,ii,1] = float(line[17:25])   # bin energy lower-bound [MeV]
                gamma_spectra[ri,ti,ii,2] = float(line[7:15])    # bin energy upper-bound [MeV]
                gamma_spectra[ri,ti,ii,3] = float(line[28:38])   # flux [#/s/cc]
                gamma_spectra[ri,ti,ii,4] = float(line[41:51])   # energy flux [MeV/s/cc]
    
    # Top 10 lists
    column_headers[2] = ['rank','nuclide -  Activity ranking','activity [Bq/cc]','activity [Bq]','rate [%]','nuclide - Decay heat ranking','decay heat [W/cc]','decay heat [W]','rate [%]','nuclide - Dose rate ranking','dose-rate [uSv/h*m^2]','rate [%]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,2].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[2]: continue # in header lines
                ii = ei - header_len[2] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                top10_lists[ri,ti,ii,0] = int(line[1:5])       # number/rank
                top10_lists[ri,ti,ii,1] = line[8:14]           # nuclide -  Activity ranking
                top10_lists[ri,ti,ii,2] = float(line[16:26])   # activity [Bq/cc]
                top10_lists[ri,ti,ii,3] = float(line[27:37])   # activity [Bq]
                top10_lists[ri,ti,ii,4] = float(line[38:43])   # rate [%]
                top10_lists[ri,ti,ii,5] = line[48:54]          # nuclide - Decay heat ranking
                top10_lists[ri,ti,ii,6] = float(line[56:66])   # decay heat [W/cc]
                top10_lists[ri,ti,ii,7] = float(line[67:77])   # decay heat [W]
                top10_lists[ri,ti,ii,8] = float(line[78:83])   # rate [%]
                top10_lists[ri,ti,ii,9] = line[88:94]          # nuclide - Dose rate ranking
                top10_lists[ri,ti,ii,10]= float(line[96:106])  # dose-rate [uSv/h*m^2]
                top10_lists[ri,ti,ii,11]= float(line[107:113]) # rate [%]
    
    return reg_nos, time_list_sec, end_of_irradiation_time, nuclides_produced, gamma_spectra, top10_lists, column_headers, summary_info

def generate_nuclide_time_profiles_legacy(nuclides_info_array):
    '''
    Description:
        Reformats DCHAIN's tabular nuclide data into time profiles of each nuclide in each region (without uncertainty values)
    
    Inputs:
       - the `nuclides_produced' array from function "parse_DCHAIN_act_file"
       
    Outputs:
       - List of length R of lists containing names of nuclides produced in each region 
       - List of length R of lists containing LaTeX-formatted names of nuclides produced in each region 
       - List of length R of lists containing ZZZAAAM values (10000Z+10A+M) of nuclides produced in each region
       - List of length R of lists containing half lives of nuclides produced in each region (in seconds)
       - List of length R of NumPy arrays of dimension NxTx7 of nuclide info
       - List of length 7 containing text descriptions of the 7 columns of the info arrays
    '''
    nuclide_names = []
    nuclide_ZAM_vals = []
    nuclide_Latex_names = []
    nuclide_half_lives = []
    nuclide_info = []
    nuclide_info_headers = ['Atoms [#/cc]','Activity [Bq/cc]','Beta decay heat [W/cc]','Gamma decay heat [W/cc]','Alpha decay heat [W/cc]','Total decay heat [W/cc]','Dose-rate [uSv/h*m^2]']
    
    
    nreg = np.shape(nuclides_info_array)[0]
    ntime= np.shape(nuclides_info_array)[1]
    nnuc = np.shape(nuclides_info_array)[2]
    
    # Get nuclide name info first, ordering them by increasing Z and A
    for ri in range(nreg):
        reg_nuclides = []
        reg_t_halves = []
        reg_ZAM_vals = []
        reg_tex_nuclides = []
        for ti in range(ntime):
            for ni in range(nnuc):
                Dname = nuclides_info_array[ri,ti,ni,0]
                if Dname == None: continue
                ZAM = Dname_to_ZAM(Dname)
                if ZAM not in reg_ZAM_vals:
                    bisect.insort_left(reg_ZAM_vals, ZAM)
                    zami = reg_ZAM_vals.index(ZAM)
                    #zami = find(ZAM,reg_ZAM_vals)
                    reg_nuclides.insert(zami,Dname)
                    reg_t_halves.insert(zami,nuclides_info_array[ri,ti,ni,9])
                    reg_tex_nuclides.insert(zami,Dname_to_Latex(Dname))
        
        nuclide_names.append(reg_nuclides)
        nuclide_ZAM_vals.append(reg_ZAM_vals)
        nuclide_half_lives.append(reg_t_halves)
        nuclide_Latex_names.append(reg_tex_nuclides)
    
    # Now get arrays of nuclide info
    for ri in range(nreg):
        reg_nnuc = len(nuclide_names[ri])
        reg_nuclide_info = np.zeros((ntime,reg_nnuc,7))
        for ti in range(ntime): 
            for ni in range(reg_nnuc):
                if nuclide_names[ri][ni] not in nuclides_info_array[ri,ti,:,0]: continue
                sni = find(nuclide_names[ri][ni],nuclides_info_array[ri,ti,:,0])
                reg_nuclide_info[ti,ni,0] = nuclides_info_array[ri,ti,sni,1]   # atoms [#/cc]
                reg_nuclide_info[ti,ni,1] = nuclides_info_array[ri,ti,sni,2]   # activity [Bq/cc]
                reg_nuclide_info[ti,ni,2] = nuclides_info_array[ri,ti,sni,5]   # beta decay heat [W/cc]
                reg_nuclide_info[ti,ni,3] = nuclides_info_array[ri,ti,sni,6]   # gamma decay heat [W/cc]
                reg_nuclide_info[ti,ni,4] = nuclides_info_array[ri,ti,sni,7]   # alpha decay heat [W/cc]
                reg_nuclide_info[ti,ni,5] = nuclides_info_array[ri,ti,sni,8]   # total decay heat [W/cc]
                reg_nuclide_info[ti,ni,6] = nuclides_info_array[ri,ti,sni,10]  # dose-rate [uSv/h*m^2]
                
        nuclide_info.append(reg_nuclide_info)
            
    return nuclide_names, nuclide_Latex_names, nuclide_ZAM_vals, nuclide_half_lives, nuclide_info, nuclide_info_headers


def parse_DCHAIN_act_file(act_file_path):
    '''
    Description: 
         This code parses the .act file generated by DCHAIN
    
    Inputs:
        - path to a DCHAIN-generated .act file
    
    Outputs:
        - length R list of region numbers 
        - length T list of measurement times (in sec) from start of irradiation
        - time of end of irradiation (in sec)
        - NumPy array of dimension RxTxNx11x2 of nuclide table data (N=max recorded table length)
        - NumPy array of dimension RxTxEx5x2 of gamma spectrum table data (E=number of energy groups of gamma spectra)
        - NumPy array of dimension RxTxNx12x2 of top-10 list table data
        - List containing 3 lists of column headers for the preceding three NumPy arrays
        - list of the below lists/arrays of summary information
            + NumPy array of dimension Rx7 of region-specific summary info:
                Beam current, beam energy, beam power, total neutron flux, region volume, irradiation time, region number
            + list of length 7 containing descriptions of the above items
            + NumPy array of dimension RxTx12x2 of region and time-specific summary info
                Rank, [nuclide, A/cc, A, %], [nuclide, P/cc, P, %], [nuclide, H, %] with values and absolute uncertainties
            + list of length 12 containing descriptions of the above items
    '''
    # Extract file info
    f = open(act_file_path)
    lines = f.readlines()
    f.close()
    
    # Parse file to determine number of regions
    nreg = 0
    reg_nos = []
    for line in lines:
        if 'region number' in line:
            nreg += 1
            reg_nos.append(int(line[21:31]))
    
    # Parse file again to determine number of time steps
    current_reg_no = -1
    #irradiation_time = -1.0 # seconds
    end_of_irradiation_time = -1.0 # seconds
    ntimes = 0
    time_strs = []
    time_list_sec = [] # time steps in seconds
    time_list_sec_after_EOB = [] # time steps in seconds after EOB
    for line in lines:
        #if 'irradiation time' in line: irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33])
        if 'region number' in line: current_reg_no = int(line[21:31])
        if current_reg_no != reg_nos[0]: continue # only read times from first region
        if '--- output time ---' in line:
            ntimes += 1
            time_strs.append(line)
            time_list_sec.append(float(line[40:53]))
            time_list_sec_after_EOB.append(0.0)
        if ('--- output time ---' in line) and ('after the last shutdown' in line):
            time_list_sec_after_EOB[ntimes-1] = float(line[87:97])*time_str_to_sec_multiplier(line[99])
            if end_of_irradiation_time == -1:
                end_of_irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33]) - float(line[87:97])*time_str_to_sec_multiplier(line[99])
            
    for ti in range(len(time_list_sec_after_EOB)):
        if time_list_sec_after_EOB[ti]==0.0:
            time_list_sec_after_EOB[ti] = time_list_sec[ti] - end_of_irradiation_time
            
    # Extract "summary info" from file
    ri = -1 # region index
    ti = -1 # time index
    r_summary_info = np.empty((nreg,7), dtype='object') # (regionwise) initialize Rx7 array
    r_summary_info_description = ['region number','irradiation time [s]','region volume [cc]','neutron flux [n/cm^2/s]','beam power [MW]','beam energy [GeV]','beam current [mA]'] # (regionwise) initialize Rx7 array
    '''
    r_summary_info[i,j,k]
       i = region number
       j = category (see table below)
    index  meaning
       0   region number
       1   irradiation time [sec]
       2   region volume [cc]
       3   neutron flux [n/cm^2/s]
       4   beam power [MW]
       5   beam energy [GeV]
       6   beam current [mA] 
    '''
    rt_summary_info = np.empty((nreg,ntimes,12,2), dtype='object') # (region-and-timewise) initialize RxTx12x2 array
    rt_summary_info_description = ['total gamma flux [#/s/cc]','total gamma energy flux [MeV/s/cc]','annihilation gamma flux [#/s/cc]','gamma current underflow [#/s]','gamma current overflow [#/s]','total activity [Bq/cc]','total decay heat [W/cc]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','activated atoms [#/cc]','total gamma dose rate [uSV/h*m^2]'] # (region-and-timewise) initialize RxTx12 array
    '''
    rt_summary_info[i,j,k,m]
       i = region number
       j = output time step
       k = category (see table below)
       m = value (k=0) or absolute uncertainty (k=1)
    index  meaning
       0   total gamma flux [#/s/cc]
       1   total gamma energy flux [MeV/s/cc]
       2   annihilation gamma flux [#/s/cc]
       3   gamma current underflow [#/s] (gammas below lowest energy bin)
       4   gamma current overflow [#/s] (gammas above highest energy bin)
       5   total activity [Bq/cc]
       6   total decay heat [W/cc]
       7     beta decay heat [W/cc]
       8     gamma decay heat [W/cc]
       9     alpha decay heat [W/cc]
       10  activated atoms [#/cc]
       11  total gamma dose rate [uSV/h*m^2]
    '''
    for li in range(len(lines)):
        line = lines[li]
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
            
            # region-specific summary info
            r_summary_info[ri,0] = int(line[21:31])
            r_summary_info[ri,1] = float(lines[li-1][21:31])*time_str_to_sec_multiplier(lines[li-1][33])
            r_summary_info[ri,2] = float(lines[li-2][21:31])
            r_summary_info[ri,3] = float(lines[li-3][21:31])
            r_summary_info[ri,4] = float(lines[li-4][21:31])
            r_summary_info[ri,5] = float(lines[li-5][21:31])
            r_summary_info[ri,6] = float(lines[li-6][21:31])
            
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            
        # gamma info specific to region and time
        if 'total gamma-ray flux' in line:
            rt_summary_info[ri,ti,0,0] = float(line[38:49])
            rt_summary_info[ri,ti,1,0] = float(lines[li+1][38:49])
            rt_summary_info[ri,ti,2,0] = float(lines[li+2][38:49])
            rt_summary_info[ri,ti,0,1] = float(line[53:64])
            rt_summary_info[ri,ti,1,1] = float(lines[li+1][53:64])
            rt_summary_info[ri,ti,2,1] = float(lines[li+2][53:64])
            if 'group limitation' in lines[li+3]:
                rt_summary_info[ri,ti,3,0] = float(lines[li+3][91:101])
                rt_summary_info[ri,ti,4,0] = float(lines[li+3][64:74])
            else:
                rt_summary_info[ri,ti,3,0] = 0.0
                rt_summary_info[ri,ti,4,0] = 0.0
        
        if 'no gamma-ray' in line:
            rt_summary_info[ri,ti,0,0] = 0.0
            rt_summary_info[ri,ti,1,0] = 0.0
            rt_summary_info[ri,ti,2,0] = 0.0
            rt_summary_info[ri,ti,0,1] = 0.0
            rt_summary_info[ri,ti,1,1] = 0.0
            rt_summary_info[ri,ti,2,1] = 0.0
            rt_summary_info[ri,ti,3,0] = 0.0
            rt_summary_info[ri,ti,4,0] = 0.0
        
        # activation info specific to region and time
        if 'total activity' in line:
            rt_summary_info[ri,ti,5,0] = float(line[24:36])
            rt_summary_info[ri,ti,6,0] = float(lines[li+1][24:36])
            rt_summary_info[ri,ti,7,0] = float(lines[li+2][24:36])
            rt_summary_info[ri,ti,8,0] = float(lines[li+3][24:36])
            rt_summary_info[ri,ti,9,0] = float(lines[li+4][24:36])
            rt_summary_info[ri,ti,10,0]= float(lines[li+5][24:36])
            rt_summary_info[ri,ti,11,0]= float(lines[li+6][24:36])
            rt_summary_info[ri,ti,5,1] = float(line[40:52])
            rt_summary_info[ri,ti,6,1] = float(lines[li+1][40:52])
            rt_summary_info[ri,ti,7,1] = float(lines[li+2][40:52])
            rt_summary_info[ri,ti,8,1] = float(lines[li+3][40:52])
            rt_summary_info[ri,ti,9,1] = float(lines[li+4][40:52])
            rt_summary_info[ri,ti,10,1]= float(lines[li+5][40:52])
            rt_summary_info[ri,ti,11,1]= float(lines[li+6][40:52])
    
    summary_info = [r_summary_info, r_summary_info_description, rt_summary_info, rt_summary_info_description]
    
    # Extract major "blocks" (nuclides, gamma spec, top10 list) for each time step in each region
    act_block_text = np.empty((nreg,ntimes,3), dtype='object') # initialize RxTx3 array to hold character strings where final index 0=nuclides, 1=gamma-spec, and 2=top10-list
    ri = -1 # region index
    ti = -1 # time index
    qi = -1 # quantity index - 0=nuclides, 1=gamma-spec, and 2=top10-list
    max_array_len = [0,0,0] # maximum number of entries for a given quantity
    current_array_len = [0,0,0] # current number of entries for a given quantity
    for line in lines:
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            qi = 0 # reset quantity index
        if 'gamma-ray spectrum weighted by energy' in line:
            qi = 1
        if 'dominant nuclides (top 10)' in line:
            qi = 2
        if 'total' in line[:20] or line=='\n': # no longer reading info block
            if current_array_len[qi] > max_array_len[qi]: 
                max_array_len[qi] = current_array_len[qi]
            current_array_len[qi] = 0
            qi = -1 
            
        if qi < 0: continue # not in region of interest
        
        try:
            act_block_text[ri,ti,qi] += line 
        except:
            act_block_text[ri,ti,qi] = line 
            
        current_array_len[qi] += 1
        
    header_len = [3,4,3] # number of lines present in table header
    
    nuclides_produced = np.empty((nreg,ntimes,max_array_len[0]-header_len[0],11,2), dtype='object') # initialize RxTxNx11x2 array to hold nuclide information
    gamma_spectra     = np.empty((nreg,ntimes,max_array_len[1]-header_len[1], 5,2), dtype='object') # initialize RxTxNx5x2 array to hold gamma spec information
    top10_lists       = np.empty((nreg,ntimes,max_array_len[2]-header_len[2],12,2), dtype='object') # initialize RxTxNx12x2 array to hold top 10 list information
    
    column_headers = [ [],[],[] ]
    
    # Now, populate each array
    
    # Nuclides produced
    column_headers[0] = ['nuclide','atoms [#/cc]','activity [Bq/cc]','activity [Bq]','rate [%]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','total decay heat [W/cc]','half life [s]','dose-rate [uSv/h*m^2]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,0].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[0]: continue # in header lines
                ii = ei - header_len[0] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                rel_err = float(line[51:61])
                nuclides_produced[ri,ti,ii,0,0] = line[3:9]            # nuclide
                nuclides_produced[ri,ti,ii,1,0] = float(line[12:23].replace('      ','0.0'))    # atoms [#/cc]
                nuclides_produced[ri,ti,ii,2,0] = float(line[25:36].replace('      ','0.0'))    # activity [Bq/cc]
                nuclides_produced[ri,ti,ii,3,0] = float(line[38:49].replace('      ','0.0'))    # activity [Bq]
                nuclides_produced[ri,ti,ii,4,0] = float(line[61:68].replace('     ','0.0'))    # rate [%]
                nuclides_produced[ri,ti,ii,5,0] = float(line[70:80].replace('      ','0.0'))    # beta decay heat [W/cc]
                nuclides_produced[ri,ti,ii,6,0] = float(line[81:91].replace('      ','0.0'))    # gamma decay heat [W/cc]
                nuclides_produced[ri,ti,ii,7,0] = float(line[92:102].replace('      ','0.0'))   # alpha decay heat [W/cc]
                nuclides_produced[ri,ti,ii,8,0] = float(line[103:113].replace('      ','0.0'))  # total decay heat [W/cc]
                nuclides_produced[ri,ti,ii,9,0] = float(line[116:126].replace('stable','0.0')) # half life [s]
                if nuclides_produced[ri,ti,ii,9,0] == 0:
                    nuclides_produced[ri,ti,ii,10,0]= 0.0  # dose-rate [uSv/h*m^2]
                else:
                    nuclides_produced[ri,ti,ii,10,0]= float(line[128:138].replace('      ','0.0'))  # dose-rate [uSv/h*m^2]
                
                # absolute errors for corresponding values
                nuclides_produced[ri,ti,ii,1,1] = nuclides_produced[ri,ti,ii,1,0]*rel_err
                nuclides_produced[ri,ti,ii,2,1] = nuclides_produced[ri,ti,ii,2,0]*rel_err
                nuclides_produced[ri,ti,ii,3,1] = nuclides_produced[ri,ti,ii,3,0]*rel_err
                nuclides_produced[ri,ti,ii,4,1] = nuclides_produced[ri,ti,ii,4,0]*rel_err
                nuclides_produced[ri,ti,ii,5,1] = nuclides_produced[ri,ti,ii,5,0]*rel_err
                nuclides_produced[ri,ti,ii,6,1] = nuclides_produced[ri,ti,ii,6,0]*rel_err
                nuclides_produced[ri,ti,ii,7,1] = nuclides_produced[ri,ti,ii,7,0]*rel_err
                nuclides_produced[ri,ti,ii,8,1] = nuclides_produced[ri,ti,ii,8,0]*rel_err
                nuclides_produced[ri,ti,ii,9,1] = nuclides_produced[ri,ti,ii,9,0]*rel_err
                nuclides_produced[ri,ti,ii,10,1]= nuclides_produced[ri,ti,ii,10,0]*rel_err
                
    # Gamma-ray spectra
    column_headers[1] = ['group number','bin energy lower-bound [MeV]','bin energy upper-bound [MeV]','flux [#/s/cc]','energy flux [MeV/s/cc]']
    for ri in range(nreg):
        for ti in range(ntimes):
            if not act_block_text[ri,ti,1]: 
                gamma_spectra[ri,ti,:,0,0] = None  # group number
                gamma_spectra[ri,ti,:,1,0] = None  # bin energy lower-bound [MeV]
                gamma_spectra[ri,ti,:,2,0] = None  # bin energy upper-bound [MeV]
                gamma_spectra[ri,ti,:,3,0] = 0.0   # flux [#/s/cc]
                gamma_spectra[ri,ti,:,4,0] = 0.0   # energy flux [MeV/s/cc]
                gamma_spectra[ri,ti,:,3,1] = 0.0   # flux absolute error [#/s/cc]
                gamma_spectra[ri,ti,:,4,1] = 0.0   # energy flux absolute error [MeV/s/cc]
                continue
            table_text = act_block_text[ri,ti,1].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[1]: continue # in header lines
                ii = ei - header_len[1] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                gamma_spectra[ri,ti,ii,0,0] = int(line[1:4])       # group number
                gamma_spectra[ri,ti,ii,1,0] = float(line[17:25])   # bin energy lower-bound [MeV]
                gamma_spectra[ri,ti,ii,2,0] = float(line[7:15])    # bin energy upper-bound [MeV]
                gamma_spectra[ri,ti,ii,3,0] = float(line[27:38])   # flux [#/s/cc]
                gamma_spectra[ri,ti,ii,4,0] = float(line[40:51])   # energy flux [MeV/s/cc]
                gamma_spectra[ri,ti,ii,3,1] = gamma_spectra[ri,ti,ii,3,0]*float(line[53:64])   # flux absolute error [#/s/cc]
                gamma_spectra[ri,ti,ii,4,1] = gamma_spectra[ri,ti,ii,4,0]*float(line[53:64])   # energy flux absolute error [MeV/s/cc]
    
    # Top 10 lists
    column_headers[2] = ['rank','nuclide -  Activity ranking','activity [Bq/cc]','activity [Bq]','rate [%]','nuclide - Decay heat ranking','decay heat [W/cc]','decay heat [W]','rate [%]','nuclide - Dose rate ranking','dose-rate [uSv/h*m^2]','rate [%]']
    for ri in range(nreg):
        for ti in range(ntimes):
            if not act_block_text[ri,ti,2]: continue
            table_text = act_block_text[ri,ti,2].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[2]: continue # in header lines
                ii = ei - header_len[2] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                top10_lists[ri,ti,ii,0,0] = int(line[1:5])       # number/rank
                top10_lists[ri,ti,ii,1,0] = line[8:14]           # nuclide -  Activity ranking
                top10_lists[ri,ti,ii,2,0] = float(line[15:26])   # activity [Bq/cc]
                top10_lists[ri,ti,ii,3,0] = float(line[26:37])   # activity [Bq]
                top10_lists[ri,ti,ii,4,0] = float(line[48:55])   # rate [%]
                rel_err = float(line[37:48])
                top10_lists[ri,ti,ii,2,1] = top10_lists[ri,ti,ii,2,0]*rel_err   # activity absolute error [Bq/cc]
                top10_lists[ri,ti,ii,3,1] = top10_lists[ri,ti,ii,3,0]*rel_err   # activity absolute error [Bq]
                top10_lists[ri,ti,ii,4,1] = top10_lists[ri,ti,ii,4,0]*rel_err   # rate absolute error [%]
                
                top10_lists[ri,ti,ii,5,0] = line[60:66]          # nuclide - Decay heat ranking
                top10_lists[ri,ti,ii,6,0] = float(line[67:78])   # decay heat [W/cc]
                top10_lists[ri,ti,ii,7,0] = float(line[78:89])   # decay heat [W]
                top10_lists[ri,ti,ii,8,0] = float(line[100:107]) # rate [%]
                rel_err = float(line[89:100])
                top10_lists[ri,ti,ii,6,1] = top10_lists[ri,ti,ii,6,0]*rel_err   # decay heat absolute error [W/cc]
                top10_lists[ri,ti,ii,7,1] = top10_lists[ri,ti,ii,7,0]*rel_err   # decay heat absolute error [W]
                top10_lists[ri,ti,ii,8,1] = top10_lists[ri,ti,ii,8,0]*rel_err   # rate absolute error [%]
                
                top10_lists[ri,ti,ii,9,0] = line[112:118]        # nuclide - Dose rate ranking
                top10_lists[ri,ti,ii,10,0]= float(line[119:130]) # dose-rate [uSv/h*m^2]
                top10_lists[ri,ti,ii,11,0]= float(line[142:149]) # rate [%]
                rel_err = float(line[131:142])
                top10_lists[ri,ti,ii,10,1]= top10_lists[ri,ti,ii,10,0]*rel_err # dose-rate absolute error [uSv/h*m^2]
                top10_lists[ri,ti,ii,11,1]= top10_lists[ri,ti,ii,11,0]*rel_err # rate absolute error [%]
    
    return reg_nos, time_list_sec, time_list_sec_after_EOB, end_of_irradiation_time, nuclides_produced, gamma_spectra, top10_lists, column_headers, summary_info


def generate_nuclide_time_profiles(nuclides_info_array):
    '''
    Description:
        Reformats DCHAIN's tabular nuclide data into time profiles of each nuclide in each region
    
    Inputs:
       - the `nuclides_produced` array from function "parse_DCHAIN_act_file"
    
    Outputs:
       - List of length R of lists containing names of nuclides produced in each region 
       - List of length R of lists containing LaTeX-formatted names of nuclides produced in each region 
       - List of length R of lists containing ZZZAAAM values (10000Z+10A+M) of nuclides produced in each region
       - List of length R of lists containing half lives of nuclides produced in each region (in seconds)
       - List of length R of NumPy arrays of dimension NxTx7x2 of nuclide info
       - List of length 7 containing text descriptions of the 7 columns of the info arrays
    '''
    nuclide_names = []
    nuclide_ZAM_vals = []
    nuclide_Latex_names = []
    nuclide_half_lives = []
    nuclide_info = []
    nuclide_info_headers = ['Atoms [#/cc]','Activity [Bq/cc]','Beta decay heat [W/cc]','Gamma decay heat [W/cc]','Alpha decay heat [W/cc]','Total decay heat [W/cc]','Dose-rate [uSv/h*m^2]']
    
    
    nreg = np.shape(nuclides_info_array)[0]
    ntime= np.shape(nuclides_info_array)[1]
    nnuc = np.shape(nuclides_info_array)[2]
    
    # Get nuclide name info first, ordering them by increasing Z and A
    for ri in range(nreg):
        reg_nuclides = []
        reg_t_halves = []
        reg_ZAM_vals = []
        reg_tex_nuclides = []
        for ti in range(ntime):
            for ni in range(nnuc):
                Dname = nuclides_info_array[ri,ti,ni,0,0]
                if Dname == None: continue
                ZAM = Dname_to_ZAM(Dname)
                if ZAM not in reg_ZAM_vals:
                    bisect.insort_left(reg_ZAM_vals, ZAM)
                    zami = reg_ZAM_vals.index(ZAM)
                    #zami = find(ZAM,reg_ZAM_vals)
                    reg_nuclides.insert(zami,Dname)
                    reg_t_halves.insert(zami,nuclides_info_array[ri,ti,ni,9,0])
                    reg_tex_nuclides.insert(zami,Dname_to_Latex(Dname))
        
        nuclide_names.append(reg_nuclides)
        nuclide_ZAM_vals.append(reg_ZAM_vals)
        nuclide_half_lives.append(reg_t_halves)
        nuclide_Latex_names.append(reg_tex_nuclides)
    
    # Now get arrays of nuclide info
    for ri in range(nreg):
        reg_nnuc = len(nuclide_names[ri])
        reg_nuclide_info = np.zeros((ntime,reg_nnuc,7,2))
        for ti in range(ntime): 
            for ni in range(reg_nnuc):
                if nuclide_names[ri][ni] not in nuclides_info_array[ri,ti,:,0,0]: continue
                sni = find(nuclide_names[ri][ni],nuclides_info_array[ri,ti,:,0,0])
                reg_nuclide_info[ti,ni,0,0] = nuclides_info_array[ri,ti,sni,1,0]   # atoms [#/cc]
                reg_nuclide_info[ti,ni,1,0] = nuclides_info_array[ri,ti,sni,2,0]   # activity [Bq/cc]
                reg_nuclide_info[ti,ni,2,0] = nuclides_info_array[ri,ti,sni,5,0]   # beta decay heat [W/cc]
                reg_nuclide_info[ti,ni,3,0] = nuclides_info_array[ri,ti,sni,6,0]   # gamma decay heat [W/cc]
                reg_nuclide_info[ti,ni,4,0] = nuclides_info_array[ri,ti,sni,7,0]   # alpha decay heat [W/cc]
                reg_nuclide_info[ti,ni,5,0] = nuclides_info_array[ri,ti,sni,8,0]   # total decay heat [W/cc]
                reg_nuclide_info[ti,ni,6,0] = nuclides_info_array[ri,ti,sni,10,0]  # dose-rate [uSv/h*m^2]
                
                reg_nuclide_info[ti,ni,0,1] = nuclides_info_array[ri,ti,sni,1,1]   # atoms absolute error [#/cc]
                reg_nuclide_info[ti,ni,1,1] = nuclides_info_array[ri,ti,sni,2,1]   # activity absolute error [Bq/cc]
                reg_nuclide_info[ti,ni,2,1] = nuclides_info_array[ri,ti,sni,5,1]   # beta decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,3,1] = nuclides_info_array[ri,ti,sni,6,1]   # gamma decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,4,1] = nuclides_info_array[ri,ti,sni,7,1]   # alpha decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,5,1] = nuclides_info_array[ri,ti,sni,8,1]   # total decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,6,1] = nuclides_info_array[ri,ti,sni,10,1]  # dose-rate absolute error [uSv/h*m^2]
                
        nuclide_info.append(reg_nuclide_info)
            
    return nuclide_names, nuclide_Latex_names, nuclide_ZAM_vals, nuclide_half_lives, nuclide_info, nuclide_info_headers


def parse_DCS_file_from_DCHAIN(filepath,relevancy_threshold=0.01,print_progress=False,nch_max=100):
    '''
    Description:
        Parse a decay chain information file produced by DCHAIN-SP
    
    Dependencies:
        `import numpy as np`
        
        `import time`
    
    Inputs:
       (required)
       
       - `filepath` = string, path to DCS file
        
    Inputs:
       (optional, keyword)
       
       - `print_progress` = logical variable denoting whether time and significant nuclide info will be printed while scanning file (D=`False`)
       - `nch_max` = maximum number of chains per isotope (D=`100`)
       - `relevancy_threshold` = what fraction of total activity must a nuclide contribute to be deemed relevant
        
    Outputs:
       
       | dimension      |  meaning for output array dimensions |
       | :------------- | :-------------------------------- |
       |   R (n_reg)    | regions                           |
       |   T (ntsteps)  | time steps                        |
       |   N (nnuc_max) | max number of nuclides            |
       |   C (chni_max) | maximum index of relevant chains  |
       |   L (chln_max) | maximum number of links per chain |
       
        
        - 0) `inventory`    = universal columns of DCS file `[R,T,N,C,vi]`, vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
        - 1) `l_chains`     = `[R,T,N,C]`, length of listed chain
        - 2) `prod_nuc`     = `[R,T,N]`, strings of the nuclide being produced
        - 3) `chn_indx`     = `[R,T,N]`, lists of the chain indices printed
        - 4) `link_nuc`     = `[R,T,N,C,L]`, strings of the nuclides in each chain
        - 5) `decay_mode`   = `[R,T,N,C,L]`, strings of the decay modes each link undergoes to produce the next link
        - 6) `link_dN_info` = `[R,T,N,C,L,di]`, extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total (only generated if these values are found in file, 'None' otherwise)
        - 7) `end_of_irradiation_time` = time of end of final irradiation step [seconds]
        - 8) `notable_nuclides_names_by_region` = list of lists (one per region) containing the relevant nuclides per region
        - 9) `notable_nuclides_AvT_by_region`   = list of arrays (one per region, `[T,N_rlv-nuc,3]`) containing the time[s]/inventory[atm/cc]/activity[Bq/cc] data of relevant nuclides
    '''
    global start
    
    try:
        start_time = start
    except:
        start_time = time.time()
    start = start_time
    
    
    print('Processing the *.DCS decay chain file...     ({:0.2f} seconds elapsed)'.format(time.time()-start))
    
    # Extract text from file
    f = open(filepath)
    lines = f.readlines()
    f.close()
    
    # Determine if extra data is written per isotope by querying whether an isotope's name or blank spaces are present in the line immediately below the first nuclide
    if len(lines[9][3:9].strip())==0: 
        extra_chain_data_present = True
    else:
        extra_chain_data_present = False 
    
    # First, scan for regions
    n_reg = 0
    reg_nos = []
    reg_labels = []
    for line in lines:
        if 'c<>-<>   no.' in line:
            n_reg += 1
            reg_nos.append(int(line[12:19]))
        if 'c<>-<>   region label :' in line:
            reg_labels.append(line[23:52].strip())
    print('{} regions found...     ({:0.2f} seconds elapsed)'.format(n_reg,time.time()-start))
    
    
    # Then, scan for time steps.  Need all individual times from beginning and time of end of irradiation.
    ntsteps = 0
    wtimes = [] # written times since start of calculations in seconds (so, the times at the end of each time step) 
    beam_state = [] # in each time step, 1 if beam on, 0 if beam off
    end_of_irradiation_time = 0.0 # time (in seconds since start of irradiation) at which beam was switched off for the final time
    end_irr_time_located = False
    for line in lines:
        if ' --- during' in line:
            if 'irradiation' in line:
                beam_state.append(1)
            elif 'cooling' in line:
                beam_state.append(0)
            else:
                beam_state.append(None)
                print('found weird beam condition')
        elif ' --- output time' in line:
            ntsteps += 1
            t = np.float(line[39:53])
            wtimes.append(t)
            if 'after the last shutdown:' in line and not end_irr_time_located:
                taeoi_val = np.float(line[88:97])
                taeoi_unit = line[99]
                taeoi = taeoi_val*(time_str_to_sec_multiplier(taeoi_unit))
                end_of_irradiation_time = t - taeoi
                end_irr_time_located = True
        elif 'end of irradiation and decay calculation for this region' in line:
            break
    wtimes = np.array(wtimes)
    
    pstr = '{} time steps found\nend of irradiation at t = {:g} sec ({})\nend of calculation at t = {:g} sec ({})...                ({:0.2f} seconds elapsed)'.format(
            ntsteps,end_of_irradiation_time,seconds_to_ydhms(end_of_irradiation_time),wtimes[-1],seconds_to_ydhms(wtimes[-1]),time.time()-start)
    print(pstr)
    
    
    
    # Next, scan for other maximum limiting dimensions
    nnuc_max = 0 # maximum number of nuclides listed in a time step
    chni_max = 0 # highest index of a relevant chain found
    chln_max = 0 # maximum number of links (nuclides) found in any chain
    
    current_tstep_nnuc = 0 # number of nuclides in current time step
    chni = 0 # chain number index
    chln = 0 # chain length
    
    for line in lines:
        if len(line) < 5: continue
        
        if ' --- output time' in line or 'end of irradiation' in line:
            if current_tstep_nnuc > nnuc_max: nnuc_max = current_tstep_nnuc
            current_tstep_nnuc = 0 # reset count of nuclides in time step
            
        if len(line[3:9].strip())!=0 and line[11]=='(': # first chain entry of a nuclide
            current_tstep_nnuc += 1
            
        if line[11]=='(': # all chains have this in common
            chni = int(line[12:16])
            if chni > chni_max: chni_max = chni 
            chln = 1 + line.count(')->')
            if chln > chln_max: chln_max = chln 
    
    pstr =  '{} = maximum number of nuclides listed in a single time step\n'.format(nnuc_max)
    pstr += '{} = highest index found of all relevant chains\n'.format(chni_max)
    pstr += '{} = length of longest chain listed...                          ({:0.2f} seconds elapsed)'.format(chln_max,time.time()-start)
    print(pstr)
    
    
    # Construct arrays to hold decay chain information
    #   R (n_reg)    regions
    #   T (ntsteps)  time steps
    #   N (nnuc_max) max number of nuclides
    #   C (chni_max) maximum index of relevant chains
    #   L (chln_max) maximum number of links per chain
    
    inventory = np.zeros((n_reg,ntsteps,nnuc_max,chni_max,5)) # universal columns of DCS file [R,T,N,C,vi], vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
    l_chains  = np.zeros((n_reg,ntsteps,nnuc_max,chni_max)) # [R,T,N,C], length of listed chain
    prod_nuc  = np.empty((n_reg,ntsteps,nnuc_max), dtype='object') # [R,T,N], strings of the nuclide being produced
    chn_indx  = np.empty((n_reg,ntsteps,nnuc_max), dtype='object') # [R,T,N], lists of the chain indices printed
    link_nuc  = np.empty((n_reg,ntsteps,nnuc_max,chni_max,chln_max), dtype='object') # [R,T,N,C,L], strings of the nuclides in each chain
    decay_mode= np.empty((n_reg,ntsteps,nnuc_max,chni_max,chln_max), dtype='object') # [R,T,N,C,L], strings of the decay modes each link undergoes to produce the next link
    nuc_relvnt= np.empty((n_reg,ntsteps,nnuc_max), dtype='object') # [R,T,N], True/False denoting whether a nuclide meets the relevancy threshold in each time step
    if extra_chain_data_present:
        link_dN_info = np.zeros((n_reg,ntsteps,nnuc_max,chni_max,chln_max,3)) # [R,T,N,C,L,di], extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total
        col_strs = ['dN_Beam','dN_Decay/nx','dN_Total']
        nexcol = len(col_strs)
    else:
        link_dN_info = None 
    
    # Populate these arrays
    ri = None # region index
    ti = None # time step index
    ni = None # nuclide index
    ci = None # chain index (1 lower than Fortran value)
    
    # character column and spacing numbers for decay chains
    ch_sci = 95 # chain start column index
    dc_sci = 105 # column index of first decay mode listing
    vl_sci = 94 # column index of first extra decay chain value
    link_gap_sts = 17 # number of characters between start of one link and the next
    
    for line in lines:
        if len(line) < 5: continue
        
        if 'c<>-<>   no.' in line: # entering new region
            ri = find(int(line[12:19]),reg_nos)
            continue 
        elif ' --- output time' in line: # entering new time step
            t = np.float(line[39:53])
            ti = find(t,wtimes) 
            ni = -1 # reset nuclide index
            continue 
        elif len(line[3:9].strip())!=0 and line[11]=='(': # entering new output nuclide
            ni += 1
            prod_nuc[ri,ti,ni] = line[3:9]
            
        if line[11]=='(': # if line contains a chain
            ci = int(line[12:16]) - 1 # chain index
            if not chn_indx[ri,ti,ni]:
                chn_indx[ri,ti,ni] = [ci]
            else:
                chn_indx[ri,ti,ni].append(ci)
            col_vals = line[25:92].strip().split()
            for vi in range(len(col_vals)):
                inventory[ri,ti,ni,ci,vi] = np.float(col_vals[vi])
            chln = 1 + line.count(')->')
            l_chains[ri,ti,ni,ci] = chln 
            for li in range(chln):
                nci1 = ch_sci + li*link_gap_sts
                nci2 = nci1 + 6
                link_nuc[ri,ti,ni,ci,li] = line[nci1:nci2]
                if li != chln:
                    dci1 = dc_sci + li*link_gap_sts
                    dci2 = dci1 + 2
                    decay_mode[ri,ti,ni,ci,li] = line[dci1:dci2]
            continue
        
        if len(line[3:9].strip())==0 and extra_chain_data_present:
            vi = None 
            for i in range(nexcol):
                if col_strs[i] in line: 
                    vi = i 
            for li in range(chln):
                vci1 = vl_sci + li*link_gap_sts
                vci2 = vci1 + 14
                if len(line[vci1:vci2].strip())==0:
                    val = 0
                else:
                    val = np.float(line[vci1:vci2])
                link_dN_info[ri,ti,ni,ci,li,vi] = val
    
    
    
    # Now extract results from the data arrays
    print('\nNow processing decay chain results...        ({:0.2f} seconds elapsed)'.format(time.time()-start)) 
    
    notable_nuclides_AvT_by_region = [] # list of arrays (one per region) containing the time/inventory/activity data of relevant nuclides
    notable_nuclides_names_by_region = [] # list of lists (one per region) containing the relevant nuclides per region
    
    for ri in range(n_reg):
        print('Region no. {} ({})'.format(reg_nos[ri],reg_labels[ri]))
        relevant_nuclides = []
        for ti in range(ntsteps):
            t = wtimes[ti]
            if print_progress:
                if t > end_of_irradiation_time:
                    tai = t - end_of_irradiation_time
                    print('\tAt time {:g} sec ({}), which is {:g} sec ({}) after end of final irradiation'.format(t,seconds_to_ydhms(t),tai,seconds_to_ydhms(tai)))
                else:
                    print('\tAt time {:g} sec ({})'.format(t,seconds_to_ydhms(t)))
             
            # First, get total information for the time step
            N0 = [] # inventory at start of time step
            N1 = [] # inventory at end of time step
            A0 = [] # activity at start of time step
            A1 = [] # activity at end of time step
            
            for ni in range(nnuc_max):
                if not prod_nuc[ri,ti,ni]: continue # skip empty nuclide indices
                chain_indices = chn_indx[ri,ti,ni]
                ci0 = chain_indices[0] # First chain
                ci1 = chain_indices[-1] # Last (nonzero) chain
                N0.append(inventory[ri,ti,ni,ci0,0])
                N1.append(inventory[ri,ti,ni,ci1,2])
                lam = inventory[ri,ti,ni,ci0,3]/inventory[ri,ti,ni,ci0,2]
                A0.append(lam*inventory[ri,ti,ni,ci0,0])
                A1.append(inventory[ri,ti,ni,ci1,3])
              
            N0_tot_tstep = np.sum(N0) # total inventory at start of time step
            N1_tot_tstep = np.sum(N1) # total inventory at end of time step
            A0_tot_tstep = np.sum(A0) # total activity at start of time step
            A1_tot_tstep = np.sum(A1) # total activity at end of time step
            
            # Determine which nuclides meet the relevancy threshold
            pstr = ('\t\tRelevant radionuclides include:\n')
            
            nii = -1
            for ni in range(nnuc_max):
                if not prod_nuc[ri,ti,ni]: continue # skip empty nuclide indices
                nii += 1
                A1_fract_threshold = relevancy_threshold # Nuclide must be responsible for at least 0.1% total activity at end of time step
                A1_nuc_frac = A1[nii]/A1_tot_tstep
                if A1_nuc_frac > A1_fract_threshold: 
                    nuc_relvnt[ri,ti,ni] = True
                    if prod_nuc[ri,ti,ni] not in relevant_nuclides: relevant_nuclides.append(prod_nuc[ri,ti,ni])
                    pstr += ('\t\t  - {} with {:0.2f}% total activity\n'.format(prod_nuc[ri,ti,ni],100*A1_nuc_frac))
                else:
                    nuc_relvnt[ri,ti,ni] = False
            
            if print_progress:
                print(pstr)
                
        n_relevant_nuclides = len(relevant_nuclides)
        
        # now collect activity of each nuclide at each time step
        relv_nuc_inv  = np.zeros((ntsteps,n_relevant_nuclides,3)) # [T,rlvN,3], time[s]/inventory[atm/cc]/activity[Bq/cc] for relevant nuclides
        
        for ti in range(ntsteps):
            t = wtimes[ti]
            relv_nuc_inv[ti,:,0] = t
            for ni in range(nnuc_max):
                if not prod_nuc[ri,ti,ni]: continue # skip empty nuclide indices
                if prod_nuc[ri,ti,ni] not in relevant_nuclides: continue # only want nuclides which are at some point relevant
                chain_indices = chn_indx[ri,ti,ni]
                ci1 = chain_indices[-1]
                
                #if nuclide is relevant, find its index among the relevant ones
                rni = find(prod_nuc[ri,ti,ni],relevant_nuclides)
                relv_nuc_inv[ti,rni,1] = inventory[ri,ti,ni,ci1,2]
                relv_nuc_inv[ti,rni,2] = inventory[ri,ti,ni,ci1,3]
        
        notable_nuclides_names_by_region.append(relevant_nuclides)
        notable_nuclides_AvT_by_region.append(relv_nuc_inv)
        
    
    return inventory, l_chains, prod_nuc, chn_indx, link_nuc, decay_mode, link_dN_info, end_of_irradiation_time, notable_nuclides_names_by_region, notable_nuclides_AvT_by_region


def parse_dtrk_file(path_to_dtrk_file,return_metadata=False):
    '''
    Description:
        Parses the output file of a T-Track tally generated by PHITS.  Note that this specific function assumes that the T-Track 
        tally was one automatically generated by and corresponding to a T-Dchain tally but in principle works with any T-Track tally.
        This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.
        
    Inputs:
        - `path_to_dtrk_file` = path to the T-Track tally output file to be parsed
        - `return_metadata` = Boolean indicating whether additional information is outputted with the flux (D=`False`)
        
    Outputs:
        - `flux` = a RxEx4 array containing regionwise fluxes [Elower/Eupper/flux/abs_error]
        - `dtrk_metadata` (only returned if `return_metadata=True`) = list of length two
               - `dtrk_metadata[0]` = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
               - `dtrk_metadata[1]` = string denoting mesh type as either 'reg', 'xyz', or 'tet'
    '''
    
    # Extract text from file
    f = open(path_to_dtrk_file)
    file_text = f.read()
    lines = file_text.split('\n')
    f.close()
    
    # Determine geometry type (mesh = reg, xyz, or tet)
    for line in lines:
        if 'mesh =' in line:
            meshtype = line.replace('mesh =','').strip().split()[0]
            break 
    
    # Determine if original or reduced format (axis = eng or axis = dchain)
    for line in lines:
        if 'axis =' in line:
            axistype = line.replace('axis =','').strip().split()[0]
            break 
    # Double check 
    for li, line in enumerate(lines):
        if li>500: break
        if '#  e-lower      e-upper      neutron     r.err ' in line: 
            axistype='eng'
            break
    
    dtrk_metadata = [axistype,meshtype]
    
    # Determine number of regions
    if axistype=='eng':
        nreg = file_text.count('#   no. =')
    elif axistype=='dchain':
        for li, line in reversed(list(enumerate(lines))):
            #print(line)
            if '0    0   0.0000E+00  0.0000' in line:
                nreg = int(lines[li-1].split()[0])
                break 
    
    if axistype=='dchain':
        nEbins = 1968
    else:
        for line in lines:
            if 'ne =' in line:
                nEbins = int(line.replace('ne =','').strip().split()[0])
                break
    
    flux = np.zeros((nreg,nEbins,4))
    
    if axistype=='eng':
        in_flux_lines = False
        ei = 0
        ri = -1
        
        for line in lines:
            if '#   no. =' in line:
                ri += 1
            if '#  e-lower      e-upper      neutron     r.err ' in line:
                in_flux_lines = True 
                continue 
            if in_flux_lines:
                flux[ri,ei,:] = [np.float(x) for x in line.split()]
                flux[ri,ei,3] = flux[ri,ei,3]*flux[ri,ei,2] # convert relative error to absolute error
                ei += 1
                if ei == nEbins:
                    in_flux_lines = False 
                    ei = 0
    elif axistype=='dchain':
        ebins = [20.0] + ECCO1968_Ebins(1968)
        ebins = ebins[::-1]
        in_flux_lines = False
        for line in lines:
            if '# num ie flux r.err' in line:
                in_flux_lines = True 
                continue 
            if '0    0   0.0000E+00  0.0000' in line:
                in_flux_lines = False
                break 
            if in_flux_lines:
                vals = line.split() 
                ri = int(vals[0])-1
                ei = int(vals[1])-1
                fval = np.float(vals[2])
                ferr = np.float(vals[3])
                
                flux[ri,ei,0] = ebins[ei]
                flux[ri,ei,1] = ebins[ei+1]
                flux[ri,ei,2] = fval
                flux[ri,ei,3] = fval*ferr 
                
    if return_metadata:
        return flux, dtrk_metadata
    else:
        return flux


def parse_dyld_files(path_to_dyld_file,iredufmt=None):
    '''
    Description:
        Parses the output files of a T-Yield tally generated by PHITS with axis=dchain.  This function assumes
        that the T-Yield tally was one automatically generated by and corresponding to a T-Dchain tally (axis=dchain).
        This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.
        
    Inputs:
        - `path_to_dyld_file` = path to the T-Yield tally output file to be parsed 
                           (the *_err.dyld file of the same name is automatically searched for and read, if present)
        - `iredufmt` = (DEPRICATED; this is now determined automatically)
                   integer 1 or 0 specifying how the xyz meshes are ordered relative to the internal region numbers.
                   In the new format (1), region indices are incremented as x->y->z (x=innermost loop); this is reversed in the old format (0).
                   Ultimately, this corresponds to the same iredufmt parameter in PHITS/DCHAIN, 1='new' and 0='old'.
                   This variable is only used for xyz meshes where this ordering matters.
        
    Outputs:
        - `yields` = a RxNx2 array containing regionwise yields (and their absolute uncertainties) for all nuclides produced in T-Yield
        - `nuclide_names_yld` = a length N list of all nuclide names in order
    '''
    
    # Extract text from file
    f = open(path_to_dyld_file)
    file_text = f.read()
    lines = file_text.split('\n')
    f.close()
    
    # determine if in reduced format
    iredufmt=0
    for li, line in enumerate(lines):
        if "# num nucleusID yield r.err" in line:
            iredufmt=1
            break
        if "isotope production #" in line:
            iredufmt=0
            break
    
    
    # Get error data if available
    if iredufmt==0:
        try:
            f_err = open(path_to_dyld_file.replace('.dyld','_err.dyld'))
            file_text_err = f_err.read()
            lines_err = file_text_err.split('\n')
            f_err.close()
            err_dyld_found = True
        except:
            file_text_err = None
            lines_err = None
            err_dyld_found = False
    
    # Determine geometry type (mesh = reg, xyz, or tet)
    for line in lines:
        if 'mesh =' in line:
            meshtype = line.replace('mesh =','').strip().split()[0]
            break 
    
    # If xyz mesh, need to find mesh dimensions:
    if meshtype=='xyz':
        for line in lines:
            if 'nx =' in line:
                nx = int(line.replace('nx =','').strip().split()[0])
            elif 'ny =' in line:
                ny = int(line.replace('ny =','').strip().split()[0])
            elif 'nz =' in line:
                nz = int(line.replace('nz =','').strip().split()[0])
                break 
    
    # Find starting line
    for li, line in enumerate(lines):
        if 'nuclear yield (or production)' in line:
            li_start = li 
            break 
    
    if iredufmt==1:
        # Count number of nuclides present in whole file
        nreg = 0 
        nuc_id_list = []
        for li, line in enumerate(lines):
            if li <= li_start+3: continue # in header
            vals = line.strip().split()
            if int(vals[0])==0: break # reached end
            if int(vals[0])>nreg: nreg = int(vals[0])
            zzzaaam = int(vals[1])
            if zzzaaam not in nuc_id_list: nuc_id_list.append(zzzaaam)
        
        nnuc = len(nuc_id_list)
        yields = np.zeros((nreg,nnuc,2))
        nuclide_names_yld = []
        
        # Get names
        nuc_id_list = sorted(nuc_id_list)
        for id in nuc_id_list:
            nuclide_names_yld.append(ZAM_to_Dname(id)) 
        
        # Get values
        for li, line in enumerate(lines):
            if li <= li_start+3: continue # in header
            vals = line.strip().split()
            if int(vals[0])==0: break # reached end
            ri = int(vals[0]) - 1
            zzzaaam = int(vals[1])
            ni = nuc_id_list.index(zzzaaam)
            yields[ri,ni,0] = np.float(vals[2])
            yields[ri,ni,1] = np.float(vals[3])*np.float(vals[2])
              
    else: # old ''traditional'' format
        # Count number of nuclides present in whole file
        nnuc = 0
        for li, line in enumerate(lines):
            if li <= li_start+2: continue # skip header lines
            if 'isotope production' in line: 
                N_bounds = line.strip().split('=')[-1].split()
                N_bounds = [int(i) for i in N_bounds]
                nnuc += N_bounds[1] - N_bounds[0] + 1
        
        # Determine number of regions
        nreg = 0
        for li, line in enumerate(lines):
            if li <= li_start+4: continue # skip header lines
            if len(line) < 2: break # reached end of first element block 
            nreg += 1
        
        yields = np.zeros((nreg,nnuc,2))
        nuclide_names_yld = []
        
        # Extract yield data
        ni = 0 # nuclide index
        ni_newstart = 0
        ri = 0 # region index
        for li, line in enumerate(lines):
            if li <= li_start+2: continue # skip header lines
            if len(line) < 2: continue # skip line breaks
            
            if 'isotope production' in line: 
                # extract Z and A info
                Z = int(line.strip().split('-')[0])
                N_bounds = line.strip().split('=')[-1].split()
                N_bounds = [int(i) for i in N_bounds]
                N_list = []
                for i in range(N_bounds[1]-N_bounds[0]+1):
                    N_list.append(N_bounds[0]+i)
                nisotopes = len(N_list)
                A_list = [N+Z for N in N_list]
                ni_newstart = len(nuclide_names_yld)
                for A in A_list:
                    ZAM = 10*A + 10000*Z
                    nuclide_names_yld.append(ZAM_to_Dname(ZAM)) 
                on_buffer_line = True
                continue 
            
            if on_buffer_line:
                ri = 0
                on_buffer_line = False 
                continue 
                  
            if '# Information for Restart Calculation' in line: break # reached end of useful info
            
            # Only lines making it to this point will be ones with region and yield data
            vals = line.strip().split()
            if meshtype=='xyz':
                yvals = vals[3:]
                if err_dyld_found: yvals_rerr = lines_err[li].strip().split()[3:]
                jx,jy,jz = int(vals[0]),int(vals[1]),int(vals[2])
                rii = jz + (jy-1)*nz + (jx-1)*(nz*ny)
                #if iredufmt==1:
                #    rii = jx + (jy-1)*nx + (jz-1)*(nx*ny) 
                #else:
                #    rii = jz + (jy-1)*nz + (jx-1)*(nz*ny)
            else:
                yvals = vals[1:]
                if err_dyld_found: yvals_rerr = lines_err[li].split()[1:]
                rii = ri 
            
            for i in range(nisotopes):
                yields[rii,ni_newstart+i,0] = np.float(yvals[i])
                if err_dyld_found: yields[rii,ni_newstart+i,1] = np.float(yvals_rerr[i])*yields[rii,ni_newstart+i,0]
                
            ri += 1
        
    return yields, nuclide_names_yld
            

def process_dchain_simulation_output(simulation_folder_path,simulation_basename,dtrk_filepath=None,dyld_filepath=None,process_DCS_file=False):
    '''
    Description:
        This is intended to be a single master function for processing DCHAIN output.
        
    Dependencies:
        from munch import *
    
    Inputs:
        - `simulation_folder_path` = text string of path to folder containing simulation output (should end with / or \)
        - `simulation_basename` = common string of the DCHAIN simulations; output files are named THIS_NAME.*
        - `dtrk_filepath` = file path to \*.dtrk file, only necessary if it has a different basename and there are multiple \*.dtrk files in the folder
        - `dyld_filepath` = file path to \*.dyld files, only necessary if it has a different basename and there are multiple \*.dyld files in the folder
        - `process_DCS_file` (optional) = Boolean variable specifying whether the DCS file should be processed too. (D=False)
        
    Outputs:
        - `dchain_output` = a dictionary object containing all information from DCHAIN's output files.  See the keys breakdown below.
                       
            Technically, it tries to return a "munchify" object which can be used both exactly like a dictionary but also 
            provides attribute-style access like a namedtuple or Class object such as `dchain_output.time.of_EOB_sec` rather
            than the dictionary style `dchain_output['time']['of_EOB_sec']`
        
   
    dchain_output dictionary structure:
    --------
        # Notation for output array dimensions
        #   R  regions
        #   T  time steps
        #   N  max number of nuclides found in a single region
        #   E  number of gamma energy bins
        # le10 for top 10 lists, a number <= 10
    
        {
        dchain_output = {
        'time':{                            #  ~  Time information
            'from_start_sec'                # [T] list of times from start time [sec]
            'from_EOB_sec'                  # [T] times from end of final bombardment [sec]
            'of_EOB_sec'                    #     scalar time of end of final bombardment [sec]
            }                              
        
        'region':{                          #  ~  Information which only varies with region
            'numbers'                       # [R] region numbers
            'number'                        # [R] region numbers
            'irradiation_time_sec'          # [R] irradiation time per region
            'volume'                        # [R] volume in [cc] per region
            'neutron_flux'                  # [R] neutron flux in [n/cm^2/s] per region
            'beam_power_MW'                 # [R] beam power in [MW] per region
            'beam_energy_GeV'               # [R] beam energy in [GeV] per region
            'beam_current_mA'               # [R] beam current in [mA] per region
            }                              
                                            
        'nuclides':{                        #  ~  Main nuclide results from *.act file
            'names'                         # [R][N] names of nuclides produced in each region 
            'TeX_names'                     # [R][N] LaTeX-formatted names of nuclides produced
            'ZZZAAAM'                       # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                                            #        (ground state m=0, metastable m=1,2,etc.)
            'half_life'                     # [R][N] half lives of nuclides produced [sec]
            'inventory':{'value'            # [R][T,N] atoms [#/cc]
                         'error'}           # [R][T,N] atoms [#/cc]
            'activity':{'value'             # [R][T,N] activity [Bq/cc]
                        'error'}            # [R][T,N] activity [Bq/cc]
            'dose_rate':{'value'            # [R][T,N] dose-rate [uSv/h*m^2]
                         'error'}           # [R][T,N] dose-rate [uSv/h*m^2]
            'decay_heat':{                  
                'total':{'value'            # [R][T,N] total decay heat [W/cc]
                         'error'}           # [R][T,N] total decay heat [W/cc]
                'beta':{'value'             # [R][T,N] beta decay heat [W/cc]
                        'error'}            # [R][T,N] beta decay heat [W/cc]
                'gamma':{'value'            # [R][T,N] gamma decay heat [W/cc]
                         'error'}           # [R][T,N] gamma decay heat [W/cc]
                'alpha':{'value'            # [R][T,N] alpha decay heat [W/cc]
                         'error'}           # [R][T,N] alpha decay heat [W/cc]
                }                          
            'column_headers'                # Length 7 list of the *.act columns' descriptions
            'total':{                       #  ~  Total values summed over all nuclides
                'activity':{'value'         # [R][T] total activity [Bq/cc]
                            'error'}        # [R][T] total activity [Bq/cc]
                'decay_heat':{'value'       # [R][T] total decay heat [W/cc]
                              'error'}      # [R][T] total decay heat [W/cc]
                'beta_heat':{'value'        # [R][T] total beta decay heat [W/cc]
                             'error'}       # [R][T] total beta decay heat [W/cc]
                'gamma_heat':{'value'       # [R][T] total gamma decay heat [W/cc]
                              'error'}      # [R][T] total gamma decay heat [W/cc]
                'alpha_heat':{'value'       # [R][T] total alpha decay heat [W/cc]
                              'error'}      # [R][T] total alpha decay heat [W/cc]
                'activated_atoms':{'value'  # [R][T] total activated atoms [#/cc]
                                   'error'} # [R][T] total activated atoms [#/cc]
                'gamma_dose_rate':{'value'  # [R][T] total gamma dose rate [uSV/h*m^2]
                                   'error'} # [R][T] total gamma dose rate [uSV/h*m^2]
                }
            }
        
        'gamma':{                           #  ~  Gamma spectra and totals
            'spectra':{
                'group_number'              # [R][T,E] group number
                'E_lower'                   # [R][T,E] bin energy lower-bound [MeV]
                'E_upper'                   # [R][T,E] bin energy upper-bound [MeV]
                'flux':{'value'             # [R][T,E] flux [#/s/cc]
                        'error'}            # [R][T,E] flux [#/s/cc]
                'energy_flux':{'value'      # [R][T,E] energy flux [MeV/s/cc]
                               'error'}     # [R][T,E] energy flux [MeV/s/cc]
                }                          
            'total_flux':{'value'           # [R][T] total gamma flux [#/s/cc]
                          'error'}          # [R][T] total gamma flux [#/s/cc]
            'total_energy_flux':{'value'    # [R][T] total gamma energy flux [MeV/s/cc]
                                 'error'}   # [R][T] total gamma energy flux [MeV/s/cc]
            'annihilation_flux':{'value'    # [R][T] annihilation gamma flux [#/s/cc]
                                 'error'}   # [R][T] annihilation gamma flux [#/s/cc]
            'current_underflow':{'value'    # [R][T] gamma current underflow [#/s]
                                 'error'}   # no error reported
            'current_overflow':{'value'     # [R][T] gamma current overflow [#/s]
                                'error'}    # no error reported
            }
        
        'top10':{                           #  ~  Top 10 lists from *.act file
            'activity':{
                'rank'                      # [R][T,le10] rank
                'nuclide'                   # [R][T,le10] nuclide name
                'value'                     # [R][T,le10] activity [Bq/cc]
                'error'                     # [R][T,le10] activity [Bq/cc]
                'percent'                   # [R][T,le10] percent of total activity
                }                         
            'decay_heat':{                 
                'rank'                      # [R][T,le10] rank
                'nuclide'                   # [R][T,le10] nuclide name
                'value'                     # [R][T,le10] decay heat [W/cc]
                'error'                     # [R][T,le10] decay heat [W/cc]
                'percent'                   # [R][T,le10] percent of total decay heat
                }                         
            'gamma_dose':{                 
                'rank':                     # [R][T,le10] rank
                'nuclide'                   # [R][T,le10] nuclide name
                'value'                     # [R][T,le10] dose-rate [uSv/h*m^2]
                'error'                     # [R][T,le10] dose-rate [uSv/h*m^2]
                'percent'                   # [R][T,le10] percent of total gamma dose rate
                }
            }
        
        'number_of':{                       #  ~  Maximum values of R, T, N, and E
            'regions'                       #  R  = total number of regions
            'time_steps'                    #  T  = total number of time steps
            'max_nuclides_in_any_region'    #  N  = maximum unique nuclides found in any region
            'gamma_energy_bins'             #  E  = number of gamma energy bins (default=42)
            }
        
        }
        
        if process_dtrk_file:
        dchain_output.update({
        'neutron':{                         #  ~  Neutron spectra and totals
            'spectra':{                     #  -  Actual values used in DCHAIN
                'E_lower'                   # [R][E] bin energy lower-bound [MeV]
                'E_upper'                   # [R][E] bin energy upper-bound [MeV]
                'flux':{'value'             # [R][E] neutron flux [#/s/cm^2]
                        'error'}            # [R][E] neutron flux [#/s/cm^2]
                }                          
            'total_flux':{'value'           # [R] total neutron flux [#/s/cm^2]
                          'error'}          # [R] total neutron flux [#/s/cm^2]
            'unit_spectra':{                #  -  Flux per unit source particle
                'E_lower'                   # [R][E] bin energy lower-bound [MeV]
                'E_upper'                   # [R][E] bin energy upper-bound [MeV]
                'flux':{'value'             # [R][E] neutron flux [#/s/cm^2/s.p.]
                        'error'}            # [R][E] neutron flux [#/s/cm^2/s.p.]
                }                          
            }
        })
        
        if process_dyld_files:
        dchain_output.update({
        'yields':{                          #  ~  Yield spectra
            'all_names'                     # [N] names of all nuclides produced 
            'names'                         # [R][N] names of nuclides produced in each region 
            'TeX_names'                     # [R][N] LaTeX-formatted names of nuclides produced
            'ZZZAAAM'                       # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                                            #        (ground state m=0, metastable m=1,2,etc.)
            'rate':{                        #  -  Actual values used in DCHAIN (100% beam power)
                   'value'                  # [R][E] nuclide yield rate [#/s/cm^3]
                   'error'                  # [R][E] nuclide yield rate [#/s/cm^3]
                }                          
            'unit_spectra':{                #  -  Yields per unit source particle
                   'value'                  # [R][E] nuclide yield rate [#/s.p.]
                   'error'                  # [R][E] nuclide yield rate [#/s.p.]
                }                          
            }
        })
        
        if process_DCS_file: # add extra information
            # Notation for output array dimensions
            #   R  (n_reg)    regions
            #   Td (ntsteps)  time steps in DCS file (usually differs from that of *.act file!)
            #   Nd (nnuc_max) max number of nuclides (this index differs from the *.act N index)
            #   C  (chni_max) maximum index of relevant chains
            #   L  (chln_max) maximum number of links per chain
            
        dchain_output.update({
    
        'DCS':{
            'time':{
                'from_start_sec'            # [Td] list of times from start time [sec]
                'from_EOB_sec'              # [Td] times from end of final bombardment [sec]
                'of_EOB_sec'                #      scalar time of end of final bombardment [sec]
                }
            
            'number_of':{                   #  ~  Maximum values of R, Td, Nd, C, and L
                'regions'                   #  R  = total number of regions
                'time_steps'                #  Td = total number of time steps
                'max_nuclides'              #  Nd = max number of end nuclides in any time step
                'max_number_of_chains'      #  C  = highest index of a relevant chain found
                'max_chain_length'          #  L  = max number of links (nuclides) in any chain
                }
            
            'end_nuclide':{                 #  ~  Informtaion on nuclides ending each chain 
                'names'                     # [R][Td,Nd] nuclide names
                'inventory':{             
                    'N_previous'            # [R][Td,Nd,C] N in previous time step [atoms/cc]
                    'N_now'                 # [R][Td,Nd,C] N in current time step [atoms/cc]
                    'dN'                    # [R][Td,Nd,C] change in N of end nuclide from
                                            #              prev. to current time step [atoms/cc]
                    }                      
                'activity':{                
                    'A_previous'            # [R][Td,Nd,C] A in previous time step [Bq/cc]
                    'A_now'                 # [R][Td,Nd,C] A in the current time step [Bq/cc] 
                    'dA'                    # [R][Td,Nd,C] change in A of end nuclide from 
                                            #              prev. to current time step [Bq/cc]
                    }
                }
                   
            'chains':{                      #  ~  Chains, links, and their contributions
                'indices_of_printed_chains' # [R][Td,Nd]     list of chain indices printed to
                                            #                *.dcs, valid values of C index
                'length'                    # [R][Td,Nd,C]   length of listed chain, L max index
                'link_nuclides'             # [R][Td,Nd,C,L] strings of nuclides in each chain
                'link_decay_modes'          # [R][Td,Nd,C,L] strings of decay modes each link
                                            #                undergoes to produce the next link
                'link_dN':{                 # (only filled if values in file, 'None' otherwise)
                    'beam'                  # [R][Td,Nd,C,L] beam contribution to dN per link
                    'decay_nrxn'            # [R][Td,Nd,C,L] decay/neutron rxn dN contribution
                    'total'                 # [R][Td,Nd,C,L] total contribution to dN per link 
                    }
                }
            
            'relevant_nuclides':{           #  ~  A vs t of nuclides over relevancy threshold
                'names'                     # [R]        list of relevant nuclides per region
                'times'                     # [R][Td,Nd] time [s]
                'inventory'                 # [R][Td,Nd] inventory [atm/cc]
                'activity'                  # [R][Td,Nd] activity [Bq/cc]
                } 
            }
        })
    
    '''
    global start
    
    try:
        start_time = start
    except:
        start_time = time.time()
    start = start_time
    
    if simulation_basename[-3:] == '.in': simulation_basename = simulation_basename[:-3]
    simulation_file_basic_path = simulation_folder_path + simulation_basename # only need to add output file extension
    
    act_file = simulation_file_basic_path + '.act'
    dcs_file = simulation_file_basic_path + '.dcs'
    
    process_dtrk_file = True
    if dtrk_filepath: # DTRK file manually provided
        dtrk_file = dtrk_filepath
        if not os.path.exists(dtrk_file):
            print('    Provided .dtrk file could not be found: {}'.format(dtrk_filepath))
            process_dtrk_file = False
    else: # automatically find DTRK file
        dtrk_file = simulation_file_basic_path + '.dtrk'
        if not os.path.exists(dtrk_file):
            # look for any files with the .dtrk extension in the simulation folder path
            valid_files = []
            valid_filepaths = []
            for file in os.listdir(simulation_folder_path):
                if file.endswith(".dtrk"):
                    valid_files.append(file)
                    valid_filepaths.append(os.path.join(simulation_folder_path, file))
            if len(valid_files)>0:
                print('    Could not find default .dtrk file {}, using {} in same directory instead.'.format(simulation_basename + '.dtrk',valid_files[0]))
                dtrk_file = valid_filepaths[0]
            else:
                print('    No .dtrk files could not be found in provided simulation folder: {}'.format(simulation_folder_path))
                process_dtrk_file = False
                
    if process_dtrk_file:
        neutron_flux, dtrk_metadata = parse_dtrk_file(dtrk_file,return_metadata=True)
    
    process_dyld_file = True
    if dyld_filepath: # dyld file manually provided
        dyld_file = dyld_filepath
        if not os.path.exists(dyld_file):
            print('    Provided .dyld file could not be found: {}'.format(dyld_filepath))
            process_dyld_file = False
    else: # automatically find dyld file
        dyld_file = simulation_file_basic_path + '.dyld'
        if not os.path.exists(dyld_file):
            # look for any files with the .dyld extension in the simulation folder path
            valid_files = []
            valid_filepaths = []
            for file in os.listdir(simulation_folder_path):
                if file.endswith(".dyld"):
                    valid_files.append(file)
                    valid_filepaths.append(os.path.join(simulation_folder_path, file))
            if len(valid_files)>0:
                print('    Could not find default .dyld file {}, using {} in same directory instead.'.format(simulation_basename + '.dyld',valid_files[0]))
                dyld_file = valid_filepaths[0]
            else:
                print('    No .dyld files could not be found in provided simulation folder: {}'.format(simulation_folder_path))
                process_dyld_file = False
    
    print('{:<50}     ({:0.2f} seconds elapsed)'.format('    Parsing DCHAIN activation file...',time.time()-start))
    
    parse_DCHAIN_act_file_OUTPUT = parse_DCHAIN_act_file(act_file)
    reg_nos           = parse_DCHAIN_act_file_OUTPUT[0]  # length R list of region numbers 
    time_list_sec     = parse_DCHAIN_act_file_OUTPUT[1]  # length T list of measurement times (in sec) from start of irradiation
    time_list_sec_after_EOB = parse_DCHAIN_act_file_OUTPUT[2] # length T list of measurement times (in sec) from end of last irradiation
    irradiation_end_t = parse_DCHAIN_act_file_OUTPUT[3]  # time of end of irradiation (in sec)
    nuclides_produced = parse_DCHAIN_act_file_OUTPUT[4]  # NumPy array of dimension RxTxNx11x2 of nuclide table data (N=max recorded table lenth) (last index 0=value, 1=abs error) [0='nuclide',1='atoms [#/cc]',2='activity [Bq/cc]',3='activity [Bq]',4='rate [%]',5='beta decay heat [W/cc]',6='gamma decay heat [W/cc]',7='alpha decay heat [W/cc]',8='total decay heat [W/cc]',9='half life [s]',10='dose-rate [uSv/h*m^2]']
    gamma_spectra     = parse_DCHAIN_act_file_OUTPUT[5]  # NumPy array of dimension RxTxEx5x2 of gamma spectrum table data (last index 0=value, 1=abs error) [0='group number',1='bin energy lower-bound [MeV]',2='bin energy upper-bound [MeV]',3='flux [#/s/cc]',4='energy flux [MeV/s/cc]']
    top10_lists       = parse_DCHAIN_act_file_OUTPUT[6]  # NumPy array of dimension RxTxNx12x2 of top-10 list table data (last index 0=value, 1=abs error) [0='rank',1='nuclide -  Activity ranking',2='activity [Bq/cc]',3='activity [Bq]',4='rate [%]',5='nuclide - Decay heat ranking',6='decay heat [W/cc]',7='decay heat [W]',8='rate [%]',9='nuclide - Dose rate ranking',10='dose-rate [uSv/h*m^2]',11='rate [%]']
    column_headers    = parse_DCHAIN_act_file_OUTPUT[7]  # List containing 3 lists of column headers for the preceding three NumPy arrays
    summary_info      = parse_DCHAIN_act_file_OUTPUT[8]  # list of the below lists/arrays of summary information
    r_summary_info              = summary_info[0]  # NumPy array of dimension Rx7 of region-specific summary info [0='region number',1='irradiation time [s]',2='region volume [cc]',3='neutron flux [n/cm^2/s]',4='beam power [MW]',5='beam energy [GeV]',6='beam current [mA]']
    r_summary_info_description  = summary_info[1]  # list of length 7 containing descriptions of the above items [0='region number',1='irradiation time [s]',2='region volume [cc]',3='neutron flux [n/cm^2/s]',4='beam power [MW]',5='beam energy [GeV]',6='beam current [mA]']
    rt_summary_info             = summary_info[2]  # NumPy array of dimension RxTx12x2 of region and time-specific summary info (last index 0=value, 1=abs error) [0='total gamma flux [#/s/cc]',1='total gamma energy flux [MeV/s/cc]',2='annihilation gamma flux [#/s/cc]',3='gamma current underflow [#/s]',4='gamma current overflow [#/s]',5='total activity [Bq/cc]',6='total decay heat [W/cc]',7='beta decay heat [W/cc]',8='gamma decay heat [W/cc]',9='alpha decay heat [W/cc]',10='activated atoms [#/cc]',11='total gamma dose rate [uSV/h*m^2]']
    rt_summary_info_description = summary_info[3]  # list of length 12 containing descriptions of the above items [0='total gamma flux [#/s/cc]',1='total gamma energy flux [MeV/s/cc]',2='annihilation gamma flux [#/s/cc]',3='gamma current underflow [#/s]',4='gamma current overflow [#/s]',5='total activity [Bq/cc]',6='total decay heat [W/cc]',7='beta decay heat [W/cc]',8='gamma decay heat [W/cc]',9='alpha decay heat [W/cc]',10='activated atoms [#/cc]',11='total gamma dose rate [uSV/h*m^2]']
    
    
    print('{:<50}     ({:0.2f} seconds elapsed)'.format('    Restructuring nuclide data table array...',time.time()-start))
    
    generate_nuclide_time_profiles_OUTPUT = generate_nuclide_time_profiles(nuclides_produced)
    nuclide_names        = generate_nuclide_time_profiles_OUTPUT[0]  # List of length R of lists containing names of nuclides produced in each region 
    LaTeX_nuclide_names  = generate_nuclide_time_profiles_OUTPUT[1]  # List of length R of lists containing LaTeX-formatted names of nuclides produced in each region 
    nuclide_ZAM_vals     = generate_nuclide_time_profiles_OUTPUT[2]  # List of length R of lists containing ZZZAAAM values (=10000*Z+10*A+M) of nuclides produced in each region (ground state m=0, metastable m=1,2,etc.)
    nuclide_half_lives   = generate_nuclide_time_profiles_OUTPUT[3]  # List of length R of lists containing half lives of nuclides produced in each region (in seconds)
    nuclide_info         = generate_nuclide_time_profiles_OUTPUT[4]  # List of length R of NumPy arrays of dimension TxNx7x2 of nuclide info (last index 0=value, 1=abs error) [0='atoms [#/cc]',1='activity [Bq/cc]','2=beta decay heat [W/cc]','3=gamma decay heat [W/cc]','4=alpha decay heat [W/cc]','5=total decay heat [W/cc]','6=dose-rate [uSv/h*m^2]']
    nuclide_info_headers = generate_nuclide_time_profiles_OUTPUT[5]  # List of length 7 containing text descriptions of the 7 columns of the info arrays [0='atoms [#/cc]',1='activity [Bq/cc]','2=beta decay heat [W/cc]','3=gamma decay heat [W/cc]','4=alpha decay heat [W/cc]','5=total decay heat [W/cc]','6=dose-rate [uSv/h*m^2]']
    
    
    if process_dyld_file:
        if process_dtrk_file:
            if dtrk_metadata[0]=='dchain':
                iredufmt=1
            else: # dtrk_metadata[0]=='eng'
                iredufmt=0
        else:
            iredufmt=0
        yields, nuclide_names_yld = parse_dyld_files(dyld_file,iredufmt=iredufmt)
        
        # Now reformat for more user-friendly output values
        # regionwise values
        beam_fluxs = (6.2415064e15)*r_summary_info[:,6] # particles/sec (converted from mA)
        reg_vols = r_summary_info[:,2]
        nregs,nnuc = np.shape(yields)[0],np.shape(yields)[1]
        reg_yld_names = []
        reg_yld_texnames = []
        reg_yld_zam = []
        yield_values_by_reg = [[],[],[],[]] # yield, yield abs err, unit yield, unit yield abs err
        for ri in range(nregs):
            for ni in range(nnuc):
                if ni==0:
                    reg_yld_names.append([])
                    reg_yld_texnames.append([])
                    reg_yld_zam.append([])
                    for j in range(4):
                        yield_values_by_reg[j].append([])
                if yields[ri,ni,0] != 0.0:
                    reg_yld_names[-1].append(nuclide_names_yld[ni])
                    reg_yld_texnames[-1].append(Dname_to_Latex(nuclide_names_yld[ni]))
                    reg_yld_zam[-1].append(Dname_to_ZAM(nuclide_names_yld[ni]))
                    yield_values_by_reg[0][-1].append(beam_fluxs[ri]*yields[ri,ni,0]/reg_vols[ri])
                    yield_values_by_reg[1][-1].append(beam_fluxs[ri]*yields[ri,ni,1]/reg_vols[ri])
                    yield_values_by_reg[2][-1].append(yields[ri,ni,0])
                    yield_values_by_reg[3][-1].append(yields[ri,ni,1])
    
    
    
    if process_DCS_file:
        # Control parameters
        relevancy_threshold=0.01 # fraction of total activity an isotope must be at any time step in DCS file to be placed in the "relevant" array
        
        fcn_out = parse_DCS_file_from_DCHAIN(dcs_file,relevancy_threshold=relevancy_threshold)
        
        # Notation for output array dimensions
        #   R (n_reg)    regions
        #   T (ntsteps)  time steps
        #   N (nnuc_max) max number of nuclides
        #   C (chni_max) maximum index of relevant chains
        #   L (chln_max) maximum number of links per chain
        
        inventory  = fcn_out[0] # universal columns of DCS file [R,T,N,C,vi], vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
        l_chains   = fcn_out[1] # [R,T,N,C], length of listed chain
        prod_nuc   = fcn_out[2] # [R,T,N], strings of the nuclide being produced
        chn_indx   = fcn_out[3] # [R,T,N], lists of the chain indices printed
        link_nuc   = fcn_out[4] # [R,T,N,C,L], strings of the nuclides in each chain
        decay_mode = fcn_out[5] # [R,T,N,C,L], strings of the decay modes each link undergoes to produce the next link
        link_dN_info=fcn_out[6] # [R,T,N,C,L,di], extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total (only generated if these values are found in file, 'None' otherwise)
        end_of_irradiation_time = fcn_out[7] # time of end of final irradiation step [seconds]
        notable_nuclides_names_by_region = fcn_out[8] # list of lists (one per region) containing the relevant nuclides per region
        notable_nuclides_AvT_by_region   = fcn_out[9] # list of arrays (one per region, [T,N_rlv-nuc,3]) containing the time[s]/inventory[atm/cc]/activity[Bq/cc] data of relevant nuclides
        
        n_reg,ntsteps,nnuc_max,chni_max,chln_max = np.shape(decay_mode)
        
        relevant_nuclides = notable_nuclides_names_by_region[0]
        n_relevant_nuclides = len(relevant_nuclides)
        relv_nuc_inv = notable_nuclides_AvT_by_region[0]
        
        t_from_start = relv_nuc_inv[:,0,0]
        t_after_irrad_sec = ((relv_nuc_inv[:,0,0]-end_of_irradiation_time))
    
    nreg = len(reg_nos)
    rilist = range(nreg)
    #regionwise_gamma_spectra = [gamma_spectra[ri,:,:,:,:] for ri in range(nreg)]
    #rw_totgflux_val = [rt_summary_info[ri,:,0,0] for ri in range(nreg)]
    #rw_totgflux_ae = [rt_summary_info[ri,:,0,1] for ri in range(nreg)]
    #rw_totegflux = [rt_summary_info[ri,:,1,:] for ri in range(nreg)]
    
    
    # Notation for output array dimensions
    #   R  regions
    #   T  time steps
    #   N  max number of nuclides found in a single region
    #   E  number of gamma energy bins
    # le10 for top 10 lists, a number <= 10
    
    dchain_output = {
        'time':{                                              #  ~  Time information
            'from_start_sec':time_list_sec,                   # [T] list of times from start time [sec]
            'from_EOB_sec':time_list_sec_after_EOB,           # [T] list of times from end of final bombardment [sec]
            'of_EOB_sec':irradiation_end_t,                   #     scalar time marking end of final bombardment [sec]
            },
        
        'region':{                                            #  ~  Information which only varies with region
            'numbers':reg_nos,                                # [R] list of all region numbers
            'number': r_summary_info[:,0],                    # [R] list of all region numbers
            'irradiation_time_sec': r_summary_info[:,1],      # [R] list of irradiation time per region
            'volume': r_summary_info[:,2],                    # [R] list of volume in cc per region
            'neutron_flux': r_summary_info[:,3],              # [R] list of neutron flux in n/cm^2/s per region
            'beam_power_MW': r_summary_info[:,4],             # [R] list of beam power in MW per region
            'beam_energy_GeV': r_summary_info[:,5],           # [R] list of beam energy in GeV per region
            'beam_current_mA': r_summary_info[:,6]            # [R] list of beam current in mA per region
            },
        
        'nuclides':{                                          #  ~  Main nuclide results from *.act file
            'names':nuclide_names,                            # [R][N] list of lists containing names of nuclides produced in each region 
            'TeX_names':LaTeX_nuclide_names,                  # [R][N] list of lists containing LaTeX-formatted names of nuclides produced in each region 
            'ZZZAAAM':nuclide_ZAM_vals,                       # [R][N] list of lists containing ZZZAAAM values (=10000*Z+10*A+M) of nuclides produced in each region (ground state m=0, metastable m=1,2,etc.)
            'half_life':nuclide_half_lives,                   # [R][N] list of lists containing half lives of nuclides produced in each region (in seconds)
            'inventory':{'value':[nuclide_info[ri][:,:,0,0] for ri in rilist],       # [R][T,N] atoms [#/cc]
                         'error':[nuclide_info[ri][:,:,0,1] for ri in rilist]},      # [R][T,N] atoms [#/cc]
            'activity':{'value':[nuclide_info[ri][:,:,1,0] for ri in rilist],        # [R][T,N] activity [Bq/cc]
                        'error':[nuclide_info[ri][:,:,1,1] for ri in rilist]},       # [R][T,N] activity [Bq/cc]
            'dose_rate':{'value':[nuclide_info[ri][:,:,6,0] for ri in rilist],       # [R][T,N] dose-rate [uSv/h*m^2]
                         'error':[nuclide_info[ri][:,:,6,1] for ri in rilist]},      # [R][T,N] dose-rate [uSv/h*m^2]
            'decay_heat':{
                'total':{'value':[nuclide_info[ri][:,:,5,0] for ri in rilist],       # [R][T,N] total decay heat [W/cc]
                         'error':[nuclide_info[ri][:,:,5,1] for ri in rilist]},      # [R][T,N] total decay heat [W/cc]
                'beta':{'value':[nuclide_info[ri][:,:,2,0] for ri in rilist],        # [R][T,N] beta decay heat [W/cc]
                        'error':[nuclide_info[ri][:,:,2,1] for ri in rilist]},       # [R][T,N] beta decay heat [W/cc]
                'gamma':{'value':[nuclide_info[ri][:,:,3,0] for ri in rilist],       # [R][T,N] gamma decay heat [W/cc]
                         'error':[nuclide_info[ri][:,:,3,1] for ri in rilist]},      # [R][T,N] gamma decay heat [W/cc]
                'alpha':{'value':[nuclide_info[ri][:,:,4,0] for ri in rilist],       # [R][T,N] alpha decay heat [W/cc]
                         'error':[nuclide_info[ri][:,:,4,1] for ri in rilist]},      # [R][T,N] alpha decay heat [W/cc]
                },
            'column_headers':nuclide_info_headers, # List of length 7 containing text descriptions of the 7 columns of the info arrays [0='atoms [#/cc]',1='activity [Bq/cc]','2=beta decay heat [W/cc]','3=gamma decay heat [W/cc]','4=alpha decay heat [W/cc]','5=total decay heat [W/cc]','6=dose-rate [uSv/h*m^2]']
            'total':{                                                                       #  ~  Total values summed over all nuclides 
                'activity':{'value':[rt_summary_info[ri,:,5,0] for ri in rilist] ,          # [R][T] total activity [Bq/cc]
                            'error':[rt_summary_info[ri,:,5,1] for ri in rilist]},          # [R][T] total activity [Bq/cc]
                'decay_heat':{'value':[rt_summary_info[ri,:,6,0] for ri in rilist] ,        # [R][T] total decay heat [W/cc]
                              'error':[rt_summary_info[ri,:,6,1] for ri in rilist]},        # [R][T] total decay heat [W/cc]
                'beta_heat':{'value':[rt_summary_info[ri,:,7,0] for ri in rilist] ,         # [R][T] total beta decay heat [W/cc]
                             'error':[rt_summary_info[ri,:,7,1] for ri in rilist]},         # [R][T] total beta decay heat [W/cc]
                'gamma_heat':{'value':[rt_summary_info[ri,:,8,0] for ri in rilist] ,        # [R][T] total gamma decay heat [W/cc]
                              'error':[rt_summary_info[ri,:,8,1] for ri in rilist]},        # [R][T] total gamma decay heat [W/cc]
                'alpha_heat':{'value':[rt_summary_info[ri,:,9,0] for ri in rilist] ,        # [R][T] total alpha decay heat [W/cc]
                              'error':[rt_summary_info[ri,:,9,1] for ri in rilist]},        # [R][T] total alpha decay heat [W/cc]
                'activated_atoms':{'value':[rt_summary_info[ri,:,10,0] for ri in rilist],   # [R][T] total activated atoms [#/cc]
                                   'error':[rt_summary_info[ri,:,10,1] for ri in rilist]},  # [R][T] total activated atoms [#/cc]
                'gamma_dose_rate':{'value':[rt_summary_info[ri,:,11,0] for ri in rilist],   # [R][T] total gamma dose rate [uSV/h*m^2]
                                   'error':[rt_summary_info[ri,:,11,1] for ri in rilist]}   # [R][T] total gamma dose rate [uSV/h*m^2]
                }
            },
        
        
        'gamma':{                                                                                              #  ~  Gamma spectra and totals
            'spectra':{
                'group_number':[gamma_spectra[ri,:,:,0,0] for ri in rilist],                                   # [R][T,E] group number
                'E_lower':[gamma_spectra[ri,:,:,1,0] for ri in rilist],                                        # [R][T,E] bin energy lower-bound [MeV]
                'E_upper':[gamma_spectra[ri,:,:,2,0] for ri in rilist],                                        # [R][T,E] bin energy upper-bound [MeV]
                'flux':{'value':[gamma_spectra[ri,:,:,3,0] for ri in rilist],                                  # [R][T,E] flux [#/s/cc]
                        'error':[gamma_spectra[ri,:,:,3,0]*gamma_spectra[ri,:,:,3,1] for ri in rilist]},       # [R][T,E] flux [#/s/cc]
                'energy_flux':{'value':[gamma_spectra[ri,:,:,4,0] for ri in rilist],                           # [R][T,E] energy flux [MeV/s/cc]
                               'error':[gamma_spectra[ri,:,:,4,0]*gamma_spectra[ri,:,:,4,1] for ri in rilist]} # [R][T,E] energy flux [MeV/s/cc]
                },
            'total_flux':{'value':[rt_summary_info[ri,:,0,0] for ri in rilist],                                # [R][T] total gamma flux [#/s/cc]
                          'error':[rt_summary_info[ri,:,0,1] for ri in rilist]},                               # [R][T] total gamma flux [#/s/cc]
            'total_energy_flux':{'value':[rt_summary_info[ri,:,1,0] for ri in rilist] ,                        # [R][T] total gamma energy flux [MeV/s/cc]
                                 'error':[rt_summary_info[ri,:,1,1] for ri in rilist]},                        # [R][T] total gamma energy flux [MeV/s/cc]
            'annihilation_flux':{'value':[rt_summary_info[ri,:,2,0] for ri in rilist] ,                        # [R][T] annihilation gamma flux [#/s/cc]
                                 'error':[rt_summary_info[ri,:,2,1] for ri in rilist]},                        # [R][T] annihilation gamma flux [#/s/cc]
            'current_underflow':{'value':[rt_summary_info[ri,:,3,0] for ri in rilist] ,                        # [R][T] gamma current underflow [#/s]
                                 'error':[rt_summary_info[ri,:,3,1] for ri in rilist]}, # no error reported
            'current_overflow':{'value':[rt_summary_info[ri,:,4,0] for ri in rilist],                          # [R][T] gamma current overflow [#/s]
                                'error':[rt_summary_info[ri,:,4,1] for ri in rilist]}   # no error reported
            },
        
        'top10':{                                                          #  ~  Top 10 lists from *.act file
            'activity':{ 
                'rank':[top10_lists[ri,:,:,0,0] for ri in rilist],         # [R][T,le10] rank
                'nuclide':[top10_lists[ri,:,:,1,0] for ri in rilist],      # [R][T,le10] nuclide name
                'value':[top10_lists[ri,:,:,2,0] for ri in rilist],        # [R][T,le10] activity [Bq/cc]
                'error':[top10_lists[ri,:,:,2,1] for ri in rilist],        # [R][T,le10] activity [Bq/cc]
                'percent':[top10_lists[ri,:,:,4,0] for ri in rilist],      # [R][T,le10] percent of total activity
                },
            'decay_heat':{
                'rank':[top10_lists[ri,:,:,0,0] for ri in rilist],         # [R][T,le10] rank
                'nuclide':[top10_lists[ri,:,:,5,0] for ri in rilist],      # [R][T,le10] nuclide name
                'value':[top10_lists[ri,:,:,6,0] for ri in rilist],        # [R][T,le10] decay heat [W/cc]
                'error':[top10_lists[ri,:,:,6,1] for ri in rilist],        # [R][T,le10] decay heat [W/cc]
                'percent':[top10_lists[ri,:,:,8,0] for ri in rilist],      # [R][T,le10] percent of total decay heat
                },
            'gamma_dose':{
                'rank':[top10_lists[ri,:,:,0,0] for ri in rilist],         # [R][T,le10] rank
                'nuclide':[top10_lists[ri,:,:,9,0] for ri in rilist],      # [R][T,le10] nuclide name
                'value':[top10_lists[ri,:,:,10,0] for ri in rilist],       # [R][T,le10] dose-rate [uSv/h*m^2]
                'error':[top10_lists[ri,:,:,10,1] for ri in rilist],       # [R][T,le10] dose-rate [uSv/h*m^2]
                'percent':[top10_lists[ri,:,:,11,0] for ri in rilist],     # [R][T,le10] percent of total gamma dose rate
                }
            },
        
        'number_of':{                                                      #  ~  Maximum values of R, T, N, and E
            'regions':nreg,                                                #  R  = total number of regions
            'time_steps':len(time_list_sec),                               #  T  = total number of time steps
            'max_nuclides_in_any_region':np.shape(nuclides_produced)[2],   #  N  = maximum unique nuclides found in any region
            'gamma_energy_bins':np.shape(gamma_spectra)[2]                 #  E  = number of gamma energy bins (default=42)
            }
        
        }
    
    if process_dtrk_file:
        dflux_norm = []
        for ri in rilist:
            if np.sum(neutron_flux[ri,:,2]) != 0:
                dflux_norm.append(r_summary_info[ri,3]/np.sum(neutron_flux[ri,:,2]))
            else:
                dflux_norm.append(0.0)
        #dflux_norm = [r_summary_info[ri,3]/np.sum(neutron_flux[ri,:,2]) for ri in rilist] # normalize unit flux to total flux
        dchain_output.update({
            'neutron':{
                'spectra':{                                                                                         # Actual values used by DCHAIN for rate calcs
                    'E_lower':[neutron_flux[ri,:,0] for ri in rilist],                                              # [R][E] bin energy lower-bound [MeV]
                    'E_upper':[neutron_flux[ri,:,1] for ri in rilist],                                              # [R][E] bin energy upper-bound [MeV]
                    'flux':{'value':[neutron_flux[ri,:,2]*dflux_norm[ri] for ri in rilist],                         # [R][E] neutron flux [#/s/cm^2]
                            'error':[neutron_flux[ri,:,3]*dflux_norm[ri] for ri in rilist]},                        # [R][E] neutron flux [#/s/cm^2]
                    },
                'total_flux':{'value':[np.sum(neutron_flux[ri,:,2])*dflux_norm[ri] for ri in rilist],               # [R] total neutron flux [#/s/cm^2]
                              'error':[np.sqrt(np.sum(neutron_flux[ri,:,3]**2))*dflux_norm[ri] for ri in rilist]},  # [R] total neutron flux [#/s/cm^2]
                'unit_spectra':{                                                         # Raw T-Track output from PHITS  
                    'E_lower':[neutron_flux[ri,:,0] for ri in rilist],                   # [R][E] bin energy lower-bound [MeV]
                    'E_upper':[neutron_flux[ri,:,1] for ri in rilist],                   # [R][E] bin energy upper-bound [MeV]
                    'flux':{'value':[neutron_flux[ri,:,2] for ri in rilist],             # [R][E] neutron flux [#/s/cm^2]
                            'error':[neutron_flux[ri,:,3] for ri in rilist]},            # [R][E] neutron flux [#/s/cm^2]
                    },
                }
            })
    
    if process_dyld_file:
        dchain_output.update({
            'yields':{                                              #  ~  Yield spectra
                'all_names':nuclide_names_yld,                      # [N] names of nuclides produced in all regions
                'names':reg_yld_names,                              # [R][N] names of nuclides produced in each region 
                'TeX_names':reg_yld_texnames,                       # [R][N] LaTeX-formatted names of nuclides produced
                'ZZZAAAM':reg_yld_zam,                              # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                'rate':{'value':yield_values_by_reg[0],             # [R][E] nuclide yield rate [#/s/cm^3]
                        'error':yield_values_by_reg[1]},            # [R][E] nuclide yield rate [#/s/cm^3]
                'unit_rate':{'value':yield_values_by_reg[2],        # [R][E] unit nuclide yield rate [#/s.p.]
                             'error':yield_values_by_reg[3]}        # [R][E] unit nuclide yield rate [#/s.p.]
                }
            })
    
    if process_DCS_file: # add extra information
        # Notation for output array dimensions
        #   R  (n_reg)    regions
        #   Td (ntsteps)  time steps in DCS file (usually different from that of ACT file!)
        #   Nd (nnuc_max) max number of nuclides (this index differs from the ACT N index)
        #   C  (chni_max) maximum index of relevant chains
        #   L  (chln_max) maximum number of links per chain
        
        dchain_output.update({'DCS':{
            'time':{
                'from_start_sec':t_from_start,         # [Td] list
                'from_EOB_sec':t_after_irrad_sec,      # [Td] list
                'of_EOB_sec':irradiation_end_t         #      scalar
                },
            
            'number_of':{                              #  ~  Maximum values of R, Td, Nd, C, and L
                'regions':n_reg,                       #  R  = total number of regions
                'time_steps':ntsteps,                  #  Td = total number of time steps
                'max_nuclides':nnuc_max,               #  Nd = maximum number of nuclides listed in a time step
                'max_number_of_chains':chni_max,       #  C  = highest index of a relevant chain found
                'max_chain_length':chln_max            #  L  = maximum number of links (nuclides) found in any chain
                },
            
            'end_nuclide':{
                'names':[prod_nuc[ri,:,:] for ri in rilist],                  # [R][Td,Nd] nuclide names
                
                'inventory':{
                    'N_previous':[inventory[ri,:,:,:,0] for ri in rilist],    # [R][Td,Nd,C] inventory of end nuclide in previous time step [atoms/cc]
                    'N_now':[inventory[ri,:,:,:,2] for ri in rilist],         # [R][Td,Nd,C] inventory of end nuclide in the current time step [atoms/cc]
                    'dN':[inventory[ri,:,:,:,1] for ri in rilist]             # [R][Td,Nd,C] change in inventory of end nuclide from the previous to the current time step [atoms/cc]
                    },
                
                'activity':{
                    'A_previous':[inventory[ri,:,:,:,0]*(inventory[ri,:,:,:,3]/inventory[ri,:,:,:,2]) for ri in rilist], # [R][Td,Nd,C] activity of end nuclide in previous time step [Bq/cc]
                    'A_now':[inventory[ri,:,:,:,3] for ri in rilist],                                                    # [R][Td,Nd,C] activity of end nuclide in the current time step [Bq/cc] 
                    'dA':[inventory[ri,:,:,:,1]*(inventory[ri,:,:,:,3]/inventory[ri,:,:,:,2]) for ri in rilist]          # [R][Td,Nd,C] change in activity of end nuclide from the previous to the current time step [Bq/cc]
                    }
                },
                
                
            'chains':{
                'indices_of_printed_chains':[chn_indx[ri,:,:] for ri in rilist],           # [R][Td,Nd]     lists of the chain indices printed
                'length':[l_chains[ri,:,:,:] for ri in rilist],                            # [R][Td,Nd,C]   length of listed chain
                'link_nuclides':[link_nuc[ri,:,:,:,:] for ri in rilist],                   # [R][Td,Nd,C,L] strings of the nuclides in each chain
                'link_decay_modes':[decay_mode[ri,:,:,:,:] for ri in rilist],              # [R][Td,Nd,C,L] strings of the decay modes each link undergoes to produce the next link
                'link_dN':{
                    'beam':[None if link_dN_info==None else link_dN_info[ri,:,:,:,:,0] for ri in rilist],                  # [R][Td,Nd,C,L] beam contribution to dN from each link (only generated if these values are found in file, 'None' otherwise)
                    'decay_nrxn':[None if link_dN_info==None else link_dN_info[ri,:,:,:,:,1] for ri in rilist],            # [R][Td,Nd,C,L] decay + neutron rxn contribution to dN from each link (only generated if these values are found in file, 'None' otherwise)
                    'total':[None if link_dN_info==None else link_dN_info[ri,:,:,:,:,2] for ri in rilist]                  # [R][Td,Nd,C,L] total contribution to dN from each link (only generated if these values are found in file, 'None' otherwise)
                    }
                },
            
            'relevant_nuclides':{
                'names':notable_nuclides_names_by_region,                                  # [R]        list of relevant nuclides per region
                'times':[notable_nuclides_AvT_by_region[ri][:,:,0] for ri in rilist],      # [R][Td,Nd] time [s]
                'inventory':[notable_nuclides_AvT_by_region[ri][:,:,1] for ri in rilist],  # [R][Td,Nd] inventory [atm/cc]
                'activity':[notable_nuclides_AvT_by_region[ri][:,:,2] for ri in rilist]    # [R][Td,Nd] activity [Bq/cc]
                }
                
            }})
        
    
    # https://github.com/Infinidat/munch
    try:
        dchain_output = munchify(dchain_output)
    except:
        print("munchify failed.  Returned object is a conventional dictionary rather than a munchify object.")
    
    return dchain_output


def plot_top10_nuclides(dchain_output,rank_val='activity',xaxis_val='time',xaxis_type='indices',regions=None,region_indices=None,times=None,time_indices=None,rank_cutoff=10,xscale='linear'):
    '''
    Description:
        Generate a nice plot illustrating dominant nuclides as a function of time or region
    
    Dependencies:
        - `import numpy as np`
        - `import matplotlib.pyplot as plt`
        - `process_dchain_simulation_output`
    
    Inputs:
        (required)
        
        - `dchain_output` = dictionary output from the process_dchain_simulation_output for a simulation
        
    Inputs:
        (optional, keyword)
       
        - `rank_val` = which top 10 list is selected. (D=`'activity'`, options include `'activity'`, `'decay_heat'`, and `'gamma_dose'`)
        - `xaxis_val` = value to be plotted on x-axis; can be either `"time"` (default) or `"region"`
        - `xaxis_type` = space xaxis entries either equally by `"indices"` (default) or realistically by `"values"`
        - `regions` = list of region numbers (or individual value) to generate plots for (D=`None`, plotting all regions)
        - `region_indices` = same as above but uses indices rather than region numbers; this has higher priority if specified (D=`None`, plot all regions)
        - `times` = list of times (from start in seconds) (or individual value) to generate plots for (D=`None`, plotting all times)
        - `time_indices` = same as above but uses indices rather than time values; this has higher priority if specified (D=`None`, plot all times)
        - `rank_cutoff` = highest rank (or number of ranks) to be displayed (D=`10`, cannot be any greater than 10)
        - `xscale` = string specifying scale of x-axis, either `'linear'` (default) or `'log'`
        
    Outputs:
        - `fig_list` = list of figures which can be plotted.
    '''
    
    max_nregs = len(dchain_output['region']['numbers'])
    all_regs = dchain_output['region']['numbers']
    max_ntimes = len(dchain_output['time']['from_start_sec'])
    all_times = dchain_output['time']['from_start_sec']
    
    if not regions and not region_indices:
        regions = dchain_output['region']['numbers']
        region_indices  = range(len(regions))
    elif region_indices:
        if isinstance(region_indices, list):
            regions = []
            for i in region_indices:
                if i >= max_nregs:
                    print('region index {} greater than number of regions {}, skipping...'.format(i,max_nregs))
                    region_indices.remove(i)
                    continue
                regions.append(dchain_output['region']['numbers'][i])
        else:
            if region_indices < max_nregs:
                regions = [dchain_output['region']['numbers'][region_indices]]
                region_indices = [region_indices]
            else:
                print('Single provided region index {} is out of bounds of total number of regions {}, aborting...'.format(region_indices,max_nregs))
                return None
    elif regions:
        if isinstance(regions, list):
            region_indices = []
            for i in regions:
                if i not in all_regs:
                    print('region {} is not contained in list of region numbers, skipping...'.format(i))
                    regions.remove(i)
                    continue
                region_indices.append(dchain_output['region']['numbers'].index(i))
        else:
            if regions in all_regs:
                region_indices = [dchain_output['region']['numbers'].index(regions)]
                regions = [regions]
            else:
                print('Single provided region {} is not in the simulated region numbers, aborting...'.format(region_indices))
                return None
    
    if not times and not time_indices:
        times = dchain_output['time']['from_start_sec']
        time_indices  = range(len(times))
    elif time_indices:
        if isinstance(time_indices, list):
            times = []
            for i in time_indices:
                if i >= max_ntimes:
                    print('time index {} greater than number of times {}, skipping...'.format(i,max_ntimes))
                    time_indices.remove(i)
                    continue
                times.append(dchain_output['time']['from_start_sec'][i])
        else:
            if time_indices < max_ntimes:
                times = [dchain_output['time']['from_start_sec'][time_indices]]
                time_indices = [time_indices]
            else:
                print('Single provided time index {} is out of bounds of total number of times {}, aborting...'.format(time_indices,max_ntimes))
                return None
    elif times:
        if isinstance(times, list):
            time_indices = []
            for i in times:
                if i not in all_times:
                    print('time {} is not contained in list of times, skipping...'.format(i))
                    times.remove(i)
                    continue
                time_indices.append(dchain_output['time']['from_start_sec'].index(i))
        else:
            if times in all_times:
                time_indices = [dchain_output['time']['from_start_sec'].index(times)]
                times = [times]
            else:
                print('Single provided time {} is not in the outputted times, aborting...'.format(time_indices))
                return None
            
    if rank_val=='photon_dose':
        rank_val = 'gamma_dose'
    if rank_val not in ['activity','decay_heat','gamma_dose']:
        print("rank_val must be either 'activity', 'decay_heat', or 'gamma_dose', aborting...")
        return None
    
    if xaxis_val not in ['time','region']:
        print("xaxis_val must be either 'time' or 'region', aborting...")
        return None
    
    if xaxis_type=='index': xaxis_type = 'indices'
    if xaxis_type=='value': xaxis_type = 'values'
    if xaxis_type not in ['indices','values']:
        print("xaxis_val must be either 'indices' or 'values', aborting...")
        return None
    
    fig_list = []
    ax_list = []
    figi = 0
    
    if xaxis_val=='time':
        major_indices = region_indices
        minor_indices = time_indices
        major_values = regions 
        minor_values = times
        xstr = 'Time'
    else:
        major_indices = time_indices
        minor_indices = region_indices
        major_values = times
        minor_values = regions
        xstr = 'Region'
        
    if xaxis_type=='indices':
        xdata = minor_indices
        xstr += ' (index)'
    else:
        xdata = minor_values
        
    for majori in major_indices:
        figi += 1
        # Assemble list of all ranked nuclides
        nuclides = []
        for minori in minor_indices:
            
            if xaxis_val=='time':
                ri = majori 
                ti = minori
            else:
                ri = minori 
                ti = majori
            
            for tti in range(len(dchain_output['top10'][rank_val]['nuclide'][ri][ti,:])):
                if (dchain_output['top10'][rank_val]['nuclide'][ri][ti,tti]!=None) and (dchain_output['top10'][rank_val]['nuclide'][ri][ti,tti] not in nuclides) and (dchain_output['top10'][rank_val]['rank'][ri][ti,tti]<=rank_cutoff):
                    nuclides.append(dchain_output['top10'][rank_val]['nuclide'][ri][ti,tti])
        # Now assemble plot for each nuclide
        plot_dicts = []
        for nuclide in nuclides:
            ni = nuclides.index(nuclide)
            ydata = []
            for minori in minor_indices:
                if xaxis_val=='time':
                    ri = majori 
                    ti = minori
                else:
                    ri = minori 
                    ti = majori
                if nuclide in dchain_output['top10'][rank_val]['nuclide'][ri][ti,:]:
                    tti = dchain_output['top10'][rank_val]['nuclide'][ri][ti,:].tolist().index(nuclide)
                    #tti = np.where(dchain_output['top10'][rank_val]['nuclide'][ri][ti,:]==nuclide)
                    if dchain_output['top10'][rank_val]['rank'][ri][ti,tti]<=rank_cutoff:
                        ydata.append( 1 + rank_cutoff - dchain_output['top10'][rank_val]['rank'][ri][ti,tti] )
                    else:
                        ydata.append(np.NaN)
                else:
                    ydata.append(np.NaN)
            tex_name = nuclide_plain_str_to_latex_str(nuclide)
            # dict = {'xdata':xdata,'ydata':ydata,'marker':tex_name,'markersize':30,'color':colors_list_12(ni%12)}
            dict = {'xdata':xdata,'ydata':ydata,'marker':tex_name,'markersize':30}
            plot_dicts.append(dict)
        # Now generate plot
        
        if xaxis_val=='time':
            title_str = 'Top {} nuclides by {} in region {}'.format(rank_cutoff,rank_val.replace('_',' '),major_values[major_indices.index(majori)])
        else:
            title_str = 'Top {} nuclides by {} at t = {} seconds'.format(rank_cutoff,rank_val.replace('_',' '),major_values[major_indices.index(majori)])
        ystr = 'Rank'
        
        
        
        # For public version, just make this a basic plot rather than using my complicated personal plotting function
        fig1 = plt.figure()
        ax1 = plt.subplot(111)
        
        for entry in plot_dicts:
            ax1.plot(entry['xdata'],entry['ydata'],marker=entry['marker'],markersize=entry['markersize'],ls='')
        
        plt.xlabel(xstr,fontsize=14)
        plt.ylabel(ystr,fontsize=14)
        plt.xscale(xscale)
        fig1.tight_layout()
        fig1.set_size_inches(0.2+6.3*(len(plot_dicts[0]['xdata'])/12),6.5*(rank_cutoff/10)+0.1*(10-rank_cutoff))
        
        '''
        fig1, ax1 = fancy_plot(
                               xdata_lists=None, 
                               ydata_lists=None, 
                               dictionaries=plot_dicts,
                               figi=figi,
                               title_str=title_str,
                               x_label_str=xstr,  
                               y_label_str=ystr,
                               x_scale=xscale,
                               y_scale='linear',
                               fig_height_inch=6.5*(rank_cutoff/10)+0.1*(10-rank_cutoff)
                               )
        '''
        
        ax1.set_yticks(range(1,rank_cutoff+1))
        ax1.set_yticklabels([str(i) for i in range(rank_cutoff,0,-1)])
        
        if xaxis_type=='indices':
            ax1.set_xticks(minor_indices)
            ax1.set_xticklabels([str(i) for i in minor_indices])
        
        plt.grid(b=True, which='major', linestyle='-', alpha=0)#0.25)
        plt.grid(b=True, which='minor', linestyle='-', alpha=0)#0.10)
        
        fig_list.append(fig1)
        ax_list.append(ax1)
        
    
    return fig_list #, ax_list




















'''
**************************************************************************************************
------------------------------------ Other misc. functions ---------------------------------------
**************************************************************************************************
'''

def find(target, myList):
    '''
    Description:
        Search for and return the index of the first occurance of a value in a list.
        
    Inputs:
        - `target` = value to be searched for
        - `myList` = list of values 
        
    Outputs:
        - index of first instance of `target` in `myList`
    '''
    for i in range(len(myList)):
        if myList[i] == target:
            return i


def Element_Z_to_Sym(Z):
    '''
    Description:
        Returns elemental symbol for a provided atomic number Z        
    
    Inputs:
        - `Z` = atomic number 
    
    Outputs:
        - `sym` = string of elemental symbol for element of atomic number Z
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    i = int(Z)
    if i < 0 or i > len(elms): 
        print('Z={} is not valid, please select a number from 0 to 118 (inclusive).'.format(str(Z)))
        return None 
    return elms[i].strip()

def Element_Sym_to_Z(sym):
    '''
    Description:
        Returns atomic number Z for a provided elemental symbol        
    
    Dependencies:
        `find` 
    
    Inputs:
        - `sym` = string of elemental symbol for element of atomic number Z
    
    Outputs:
        - `Z` = atomic number 
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    
    if len(sym.strip())>2:
        print('Please provide a valid elemental symbol (1 or 2 characters), {} is too long'.format(sym))
        return -1
    
    # handle exception for neutron first
    if sym.strip()=='XX':
        return 0
    
    # make sure string is formatted to match entries in elms list
    sym2 = sym.strip()
    if len(sym2)==1: sym2 += ' '
    sym2 = sym2[0].upper() + sym2[1].lower()
    
    Z = find(sym2,elms)
    
    if Z==None:
        print('Z could not be found for element "{}"; please make sure entry is correct.'.format(sym))
        return -1
    
    return Z

def Element_ZorSym_to_name(Z):
    '''
    Description:
        Returns an element's name provided its atomic number Z or elemental symbol        
    
    Inputs:
        - `Z` = string of elemental symbol or atomic number Z
    
    Outputs:
        - `name` = element name
    '''
    element_names = ['neutron','Hydrogen','Helium','Lithium','Beryllium','Boron','Carbon','Nitrogen','Oxygen','Fluorine',
                     'Neon','Sodium','Magnesium','Aluminium','Silicon','Phosphorus','Sulfur','Chlorine','Argon',
                     'Potassium','Calcium','Scandium','Titanium','Vanadium','Chromium','Manganese','Iron','Cobalt',
                     'Nickel','Copper','Zinc','Gallium','Germanium','Arsenic','Selenium','Bromine','Krypton',
                     'Rubidium','Strontium','Yttrium','Zirconium','Niobium','Molybdenum','Technetium','Ruthenium',
                     'Rhodium','Palladium','Silver','Cadmium','Indium','Tin','Antimony','Tellurium','Iodine','Xenon',
                     'Caesium','Barium','Lanthanum','Cerium','Praseodymium','Neodymium','Promethium','Samarium',
                     'Europium','Gadolinium','Terbium','Dysprosium','Holmium','Erbium','Thulium','Ytterbium',
                     'Lutetium','Hafnium','Tantalum','Tungsten','Rhenium','Osmium','Iridium','Platinum','Gold',
                     'Mercury','Thallium','Lead','Bismuth','Polonium','Astatine','Radon','Francium','Radium',
                     'Actinium','Thorium','Protactinium','Uranium','Neptunium','Plutonium','Americium','Curium',
                     'Berkelium','Californium','Einsteinium','Fermium','Mendelevium','Nobelium','Lawrencium',
                     'Rutherfordium','Dubnium','Seaborgium','Bohrium','Hassium','Meitnerium','Darmstadtium',
                     'Roentgenium','Copernicium','Nihonium','Flerovium','Moscovium','Livermorium','Tennessine','Oganesson']
    
    try:
        zi = int(Z)
    except:
        zi = Element_Sym_to_Z(Z)
    
    return element_names[zi]

def Element_ZorSym_to_mass(Z):
    '''
    Description:
        Returns an element's average atomic mass provided its atomic number Z or elemental symbol        
    
    Inputs:
        - `Z` = string of elemental symbol or atomic number Z
    
    Outputs:
        - `A_avg` = average atomic mass
    '''
    
    average_atomic_masses = [1.008664,1.007,4.002602,6.941,9.012182,10.811,12.0107,14.0067,15.9994,18.9984032,
                             20.1797,22.98976928,24.305,26.9815386,28.0855,30.973762,32.065,35.453,39.948,39.0983,
                             40.078,44.955912,47.867,50.9415,51.9961,54.938045,55.845,58.933195,58.6934,63.546,65.38,
                             69.723,72.63,74.9216,78.96,79.904,83.798,85.4678,87.62,88.90585,91.224,92.90638,95.96,98,
                             101.07,102.9055,106.42,107.8682,112.411,114.818,118.71,121.76,127.6,126.90447,131.293,
                             132.9054519,137.327,138.90547,140.116,140.90765,144.242,145,150.36,151.964,157.25,
                             158.92535,162.5,164.93032,167.259,168.93421,173.054,174.9668,178.49,180.94788,183.84,
                             186.207,190.23,192.217,195.084,196.966569,200.59,204.3833,207.2,208.9804,209,210,222,
                             223,226,227,232.03806,231.03588,238.02891,237,244,243,247,247,251,252,257,258,259,
                             266,267,268,269,270,277,278,281,282,285,286,289,290,293,294,294]
    
    try:
        zi = int(Z)
    except:
        zi = Element_Sym_to_Z(Z)
        
    return average_atomic_masses[zi]

def nuclide_to_Latex_form(Z,A,m=''):
    '''
    Description:
        Form a LaTeX-formatted string of a nuclide provided its information
    
    Dependencies:
        `Element_Z_to_Sym` 
        (only required if inputed Z is not already an elemental symbol)
    
    Inputs:
        - `Z` = atomic number of nuclide (int, float, or string) or elemental symbol (string)
        - `A` = atomic mass of nuclide (int, float, or string) or string to go in place of A (ex. 'nat')
        - `m` = metastable state (D='', ground state); this will be appended to the end of A
              if not a string already, it will be converted into one and appended to 'm' (ex. 1 -> 'm1')
    
    Outputs:
        - LaTeX-formatted raw string of a nuclide, excellent for plot titles, labels, and auto-generated LaTeX documents
    '''
    if isinstance(A,(int,float)): A = str(int(A))
    if not isinstance(Z,str): symbol = Element_Z_to_Sym(int(Z))
    if isinstance(m,float): m = int(m)
    if isinstance(m,int): m = 'm' + str(m)
    latex_str = r"$^{{{}{}}}$".format(A,m) + "{}".format(symbol)
    return latex_str

def nuclide_plain_str_to_latex_str(nuc_str,include_Z=False):
    '''
    Description:
        Converts a plaintext string of a nuclide to a LaTeX-formatted raw string
        Note: if you already have the Z, A, and isomeric state information determined, the "nuclide_to_Latex_form" function can be used instead
    
    Dependencies:
        - `Element_Z_to_Sym` (only required if `include_Z = True`)
    
    Inputs:
        (required)
     
       - `nuc_str` = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
           + Isomeric/metastable state characters must always immediately follow the atomic mass characters.
               Isomeric state labels MUST either:
               - (1) be a single lower-case character OR 
               - (2) begin with any non-numeric character and end with a number
           + Atomic mass numbers must be nonnegative integers OR the string `"nat"` (in which case no metastable states can be written)
           + Elemental symbols MUST begin with an upper-case character
    
    Inputs:
       (optional)
        
       - `include_Z` = `True`/`False` determining whether the nuclide's atomic number Z will be printed as a subscript beneath the atomic mass
    
    Outputs:
        - LaTeX-formatted raw string of nuclide
    '''
    tex_str = r''
    
    # remove unwanted characters from provided string
    delete_characters_list = [' ', '-', '_']
    for dc in delete_characters_list:
        nuc_str = nuc_str.replace(dc,'')
    
    # determine which characters are letters versus numbers
    isalpha_list = []
    isdigit_list = []
    for c in nuc_str:
        isalpha_list.append(c.isalpha())
        isdigit_list.append(c.isdigit())
    
    symbol = ''
    mass = ''
    isost = ''
    
    # string MUST begin with either mass number or elemental symbol
    if isdigit_list[0] or nuc_str[0:3]=='nat': # mass first
        mass_first = True 
    else:
        mass_first = False 
    
    if mass_first:
        if nuc_str[0:3]=='nat':
            mass = 'nat'
            ci = 3
        else:
            ci = 0
            while isdigit_list[ci]:
                mass += nuc_str[ci]
                ci += 1
            mass = str(int(mass)) # eliminate any extra leading zeros
            # encountered a non-numeric character, end of mass
            # now, determine if metastable state is listed or if element is listed next
            # first, check to see if any other numerals are in string
            lni = 0 # last numeral index
            for i in range(ci,len(nuc_str)):
                if isdigit_list[i]: 
                    lni = i
            if lni != 0:
                # grab all characters between ci and last numeral as metastable state
                isost = nuc_str[ci:lni+1]
                ci = lni + 1
            else: # no more numerals in string, now check for single lower-case letter
                if isalpha_list[ci] and nuc_str[ci].islower():
                    isost = nuc_str[ci]
                    ci += 1
            
            # Now extract elemental symbol
            for i in range(ci,len(nuc_str)):
                if isalpha_list[i]:
                    symbol += nuc_str[i]
    
    else: # if elemental symbol is listed first
        if 'nat' in nuc_str:
            mass = 'nat'
            nuc_str = nuc_str.replace('nat','')
            
        ci = 0
        # Extract all characters before first number as the elemental symbol
        while nuc_str[ci].isalpha():
            symbol += nuc_str[ci]
            ci += 1
            
        # now, extract mass 
        if mass != 'nat':
            while nuc_str[ci].isdigit():
                mass += nuc_str[ci]
                ci += 1
                if ci == len(nuc_str):
                    break
            
            # lastly, extract isomeric state, if present
            if ci != len(nuc_str):
                isost = nuc_str[ci:]
        
    # treating the cases of lowercase-specified particles (n, d, t, etc.)
    if symbol == '' and isost != '':
        symbol = isost
        isost = ''
        
    # Now assemble LaTeX string for nuclides
    if include_Z:
        if symbol == 'n':
            Z = 0
        elif symbol == 'p' or symbol == 'd' or symbol == 't':
            Z = 1
        else:
            Z = Element_Sym_to_Z(symbol)
        Z = str(int(Z))
        tex_str = r"$^{{{}{}}}_{{{}}}$".format(mass,isost,Z) + "{}".format(symbol)
    else:
        tex_str = r"$^{{{}{}}}$".format(mass,isost) + "{}".format(symbol)
    
    return tex_str

def nuclide_plain_str_ZZZAAAM(nuc_str):
    '''
    Description:
        Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M
    
    Dependencies:
        `Element_Z_to_Sym` 
    
    Inputs:
       - `nuc_str` = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
           + Isomeric/metastable state characters must always immediately follow the atomic mass characters.
               Isomeric state labels MUST either:
               - (1) be a single lower-case character OR 
               - (2) begin with any non-numeric character and end with a number
           + Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
           + Elemental symbols MUST begin with an upper-case character
    
    
    Outputs:
        - ZZZAAAM integer
    '''
    
    # remove unwanted characters from provided string
    delete_characters_list = [' ', '-', '_']
    for dc in delete_characters_list:
        nuc_str = nuc_str.replace(dc,'')
    
    # determine which characters are letters versus numbers
    isalpha_list = []
    isdigit_list = []
    for c in nuc_str:
        isalpha_list.append(c.isalpha())
        isdigit_list.append(c.isdigit())
    
    symbol = ''
    mass = ''
    isost = ''
    
    if 'nat' in nuc_str:
        print('Must specify a specific nuclide, not natural abundances')
        return None 
    
    # string MUST begin with either mass number or elemental symbol
    if isdigit_list[0]: # mass first
        mass_first = True 
    else:
        mass_first = False 
    
    if mass_first:
        ci = 0
        while isdigit_list[ci]:
            mass += nuc_str[ci]
            ci += 1
        mass = str(int(mass)) # eliminate any extra leading zeros
        # encountered a non-numeric character, end of mass
        # now, determine if metastable state is listed or if element is listed next
        # first, check to see if any other numerals are in string
        lni = 0 # last numeral index
        for i in range(ci,len(nuc_str)):
            if isdigit_list[i]: 
                lni = i
        if lni != 0:
            # grab all characters between ci and last numeral as metastable state
            isost = nuc_str[ci:lni+1]
            ci = lni + 1
        else: # no more numerals in string, now check for single lower-case letter
            if isalpha_list[ci] and nuc_str[ci].islower():
                isost = nuc_str[ci]
                ci += 1
        
        # Now extract elemental symbol
        for i in range(ci,len(nuc_str)):
            if isalpha_list[i]:
                symbol += nuc_str[i]
    
    else: # if elemental symbol is listed first
        ci = 0
        # Extract all characters before first number as the elemental symbol
        while nuc_str[ci].isalpha():
            symbol += nuc_str[ci]
            ci += 1
            
        # now, extract mass 
        while nuc_str[ci].isdigit():
            mass += nuc_str[ci]
            ci += 1
            if ci == len(nuc_str):
                break
        
        # lastly, extract isomeric state, if present
        if ci != len(nuc_str):
            isost = nuc_str[ci:]
        
    # treating the cases of lowercase-specified particles (n, d, t, etc.)
    if symbol == '' and isost != '':
        symbol = isost
        isost = ''
        
    
    if symbol == 'n':
        Z = 0
    elif symbol == 'p' or symbol == 'd' or symbol == 't':
        Z = 1
    else:
        Z = Element_Sym_to_Z(symbol)  
    
    A = int(mass)
    
    if isost.strip()=='' or isost=='g':
        M = 0
    elif isost=='m' or isost=='m1':
        M = 1
    elif isost=='n' or isost=='m2':
        M = 2
    elif isost=='o' or isost=='m3':
        M = 3
    elif isost=='p' or isost=='m4':
        M = 4
    elif isost=='q' or isost=='m5':
        M = 5
    else:
        print("Unknown isomeric state {}, assumed ground state".format(isost))
        M = 0
    
    ZZZAAAM = 10000*Z + 10*A + M 
    
    return ZZZAAAM


def time_str_to_sec_multiplier(time_str):
    '''
    Description:
        Provide a time unit and this function provides what those time units need to be multiplied by to obtain seconds.
    
    Inputs:
        - `time_str` = string containing time units character(s) [s,m,h,d,y,ms,us,ns,ps,fs]
    
    Outputs:
        - `m` = multiplier to convert a time of the supplied units to seconds
    
    '''
    try:
        if time_str == 's':
            m = 1
        elif time_str == 'm':
            m = 60
        elif time_str == 'h':
            m = 60*60
        elif time_str == 'd':
            m = 60*60*24
        elif time_str == 'y':
            m = 60*60*24*365.25
        elif time_str == 'ms':
            m = 1e-3
        elif time_str == 'us':
            m = 1e-6
        elif time_str == 'ns':
            m = 1e-9
        elif time_str == 'ps':
            m = 1e-12
        elif time_str == 'fs':
            m = 1e-15
        return m
    except:
        print('"{}" is not a valid time unit; please use one of the following: [s,m,h,d,y,ms,us,ns,ps,fs]'.format(time_str))
        return None

def seconds_to_dhms(t_sec):
    '''
    Description:
        Provide a time in seconds and obtain a string with the time in days, hours, minutes, and seconds
    
    Inputs:
        - `t_sec` = a time in seconds (float or int)
    
    Outputs:
        - `time_str` = string containing the time prettily formatted in d/h/m/s format
    
    '''
    m, s = divmod(t_sec, 60)
    h, m = divmod(m, 60)
    d, h = divmod(h, 24)
    
    if d != 0:
        time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
    elif h != 0:
        time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
    elif m != 0:
        time_str = "{:0.0f}m {:0.2f}s".format(m,s)
    elif s != 0:
        time_str = "{:0.2f}s".format(s)
    else:
        time_str = ""
        
    return time_str

def seconds_to_ydhms(t_sec):
    '''
    Description:
        Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds
    
    Inputs:
        - `t_sec` = a time in seconds (float or int)
    
    Outputs:
        - `time_str` = string containing the time prettily formatted in y/d/h/m/s format
    
    '''
    m, s = divmod(t_sec, 60)
    h, m = divmod(m, 60)
    d, h = divmod(h, 24)
    y, d = divmod(d, 365)
    
    if y>=4 : # if leap year occurred
        n_leap_years = int(y/4)
        d = d-n_leap_years
    
    if y != 0:
        time_str = "{:0.0f}y {:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(y,d,h,m,s)
    elif d != 0:
        time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
    elif h != 0:
        time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
    elif m != 0:
        time_str = "{:0.0f}m {:0.2f}s".format(m,s)
    elif s != 0:
        time_str = "{:0.2f}s".format(s)
    else:
        time_str = ""
        
    return time_str































show_output_from_example = False
if show_output_from_example:   
    test_folder = r'example\\'
    test_basename = 'example_Na22'
    
    output = process_dchain_simulation_output(test_folder,test_basename)
    
    print('\n')
     
    print('Output times relative to start (in seconds):',output['time']['from_start_sec'])
    print('Output times relative to EOB (in seconds):  ',output.time.from_EOB_sec)
    
    print('\n')
    
    print('Total neutron flux in first region (in n/cm^2/sec):',output['region']['neutron_flux'][0])
    print('Fractional uncertainty in total neutron flux in first region:',output['neutron']['total_flux']['error'][0]/output['neutron']['total_flux']['value'][0])
    
    print('\n')
    
    print('All produced nuclides (in DCHAIN-formatted strings):',output['nuclides']['names'])
    #print(np.shape(np.array(output['gamma']['spectra']['flux']['value'])))
    #print(output['gamma']['spectra']['flux']['value'][0][0,:])
    
    #pprint.pprint(output['top10']['activity']['rank'][0][0,:])
    
    print('\n')
    
    print('[T-Yield] high energy reaction-produced nuclides:',output['yields']['names'][0])
    print('[T-Yield] nuclide yields per source particle:',output['yields']['unit_rate']['value'][0])
    
    print('\n')
    
    library_file = r'C:\phits\dchain-sp\data\JEFF-3-3--_n_act_xs_lib'
    #xs_out, rxn_str_out = retrieve_rxn_xs_from_lib(library_file,'F-19','p')
    xs_1g = calc_one_group_nrxn_xs_dchain(output['neutron']['unit_spectra']['flux']['value'][0],output['neutron']['unit_spectra']['flux']['error'][0],library_file,'F-19','p')
    print('Single group cross section (and its absolute uncertainty) for 19F(n,p)19O reaction in provided neutron flux (in barns): ',xs_1g)
    
    
    figs = plot_top10_nuclides(output,region_indices=1,rank_cutoff=5,xaxis_val='time',xaxis_type='indices',xscale='linear')
    plt.show()

Functions

def Dname_to_Latex(Dname)

Description

Converts a DCHAIN-formatted nuclide name to a LaTeX-formatted string

Inputs

  • Dname = nuclide identification string in DCHAIN format

Outputs

  • nuclide name as a LaTeX-formatted raw string
Expand source code
def Dname_to_Latex(Dname):
    '''
    Description:
        Converts a DCHAIN-formatted nuclide name to a LaTeX-formatted string
    
    Inputs:
        - `Dname` = nuclide identification string in DCHAIN format 
       
    Outputs:
        - nuclide name as a LaTeX-formatted raw string
    '''
    AAA = Dname[2:5].strip()
    symbol = Dname[0:2].strip()
    m = Dname[-1].strip()
    latex_str = r"$^{{{}{}}}$".format(AAA,m) + "{}".format(symbol)
    return latex_str
def Dname_to_ZAM(Dname)

Description

Converts a DCHAIN-formatted nuclide name to a ZZZAAAM number

Inputs

  • Dname = nuclide identification string in DCHAIN format

Outputs

  • ZZZAAAM = nuclide identification ineger, calculated as 10000*Z + 10*A + m
Expand source code
def Dname_to_ZAM(Dname):
    '''
    Description:
        Converts a DCHAIN-formatted nuclide name to a ZZZAAAM number
    
    Inputs:
        - `Dname` = nuclide identification string in DCHAIN format 
       
    Outputs:
        - `ZZZAAAM` = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m
    
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    AAA = Dname[2:5]
    A = int(AAA)
    symbol = Dname[0:2]
    if 'XX' in symbol: symbol='n '
    Z = find(symbol,elms)
    if Dname[-1] == ' ':
        m = 0
    elif Dname[-1] == 'm':
        m = 1
    elif Dname[-1] == 'n':
        m = 2
    ZAM = int(10000*Z + 10*A + m)
    return ZAM
def ECCO1968_Ebins(n)

Description

Returns the n highest energy bin values of the ECCO 1968-group energy binning structure.

Inputs

  • n = number of energy bins (from 20 MeV down) to be returned

Outputs

  • list of n energy bins of the ECCO 1968-group structure
Expand source code
def ECCO1968_Ebins(n):
    '''
    Description:
        Returns the n highest energy bin values of the ECCO 1968-group energy binning structure.
        
    Inputs:
        - `n` = number of energy bins (from 20 MeV down) to be returned 
    
    Outputs:
        - list of `n` energy bins of the ECCO 1968-group structure 
    '''
    ECCO_bins = [
        1.964033E+01,1.947734E+01,1.931570E+01,1.915541E+01,1.899644E+01,1.883880E+01,1.868246E+01,1.852742E+01,1.837367E+01,1.822119E+01,1.806998E+01,1.792002E+01,1.777131E+01,1.762383E+01,1.747757E+01,1.733253E+01,1.718869E+01,1.704605E+01,1.690459E+01,1.676430E+01,1.662518E+01,
        1.648721E+01,1.635039E+01,1.621470E+01,1.608014E+01,1.594670E+01,1.581436E+01,1.568312E+01,1.555297E+01,1.542390E+01,1.529590E+01,1.516897E+01,1.504309E+01,1.491825E+01,1.479444E+01,1.467167E+01,1.454991E+01,1.442917E+01,1.430943E+01,1.419068E+01,1.407291E+01,
        1.395612E+01,1.384031E+01,1.372545E+01,1.361155E+01,1.349859E+01,1.338657E+01,1.327548E+01,1.316531E+01,1.305605E+01,1.294770E+01,1.284025E+01,1.273370E+01,1.262802E+01,1.252323E+01,1.241930E+01,1.231624E+01,1.221403E+01,1.211267E+01,1.201215E+01,1.191246E+01,
        1.181360E+01,1.171557E+01,1.161834E+01,1.152193E+01,1.142631E+01,1.133148E+01,1.123745E+01,1.114419E+01,1.105171E+01,1.095999E+01,1.086904E+01,1.077884E+01,1.068939E+01,1.060068E+01,1.051271E+01,1.042547E+01,1.033895E+01,1.025315E+01,1.016806E+01,1.008368E+01,
        1.000000E+01,9.917013E+00,9.834715E+00,9.753099E+00,9.672161E+00,9.591895E+00,9.512294E+00,9.433354E+00,9.355070E+00,9.277435E+00,9.200444E+00,9.124092E+00,9.048374E+00,8.973284E+00,8.898818E+00,8.824969E+00,8.751733E+00,8.679105E+00,8.607080E+00,8.535652E+00,
        8.464817E+00,8.394570E+00,8.324906E+00,8.255820E+00,8.187308E+00,8.119363E+00,8.051983E+00,7.985162E+00,7.918896E+00,7.853179E+00,7.788008E+00,7.723377E+00,7.659283E+00,7.595721E+00,7.532687E+00,7.470175E+00,7.408182E+00,7.346704E+00,7.285736E+00,7.225274E+00,
        7.165313E+00,7.105850E+00,7.046881E+00,6.988401E+00,6.930406E+00,6.872893E+00,6.815857E+00,6.759294E+00,6.703200E+00,6.647573E+00,6.592406E+00,6.537698E+00,6.483443E+00,6.429639E+00,6.376282E+00,6.323367E+00,6.270891E+00,6.218851E+00,6.167242E+00,6.116062E+00,
        6.065307E+00,6.014972E+00,5.965056E+00,5.915554E+00,5.866462E+00,5.817778E+00,5.769498E+00,5.721619E+00,5.674137E+00,5.627049E+00,5.580351E+00,5.534042E+00,5.488116E+00,5.442572E+00,5.397406E+00,5.352614E+00,5.308195E+00,5.264143E+00,5.220458E+00,5.177135E+00,
        5.134171E+00,5.091564E+00,5.049311E+00,5.007408E+00,4.965853E+00,4.924643E+00,4.883775E+00,4.843246E+00,4.803053E+00,4.763194E+00,4.723666E+00,4.684465E+00,4.645590E+00,4.607038E+00,4.568805E+00,4.530890E+00,4.493290E+00,4.456001E+00,4.419022E+00,4.382350E+00,
        4.345982E+00,4.309916E+00,4.274149E+00,4.238679E+00,4.203504E+00,4.168620E+00,4.134026E+00,4.099719E+00,4.065697E+00,4.031957E+00,3.998497E+00,3.965314E+00,3.932407E+00,3.899773E+00,3.867410E+00,3.835316E+00,3.803488E+00,3.771924E+00,3.740621E+00,3.709579E+00,
        3.678794E+00,3.648265E+00,3.617989E+00,3.587965E+00,3.558189E+00,3.528661E+00,3.499377E+00,3.470337E+00,3.441538E+00,3.412978E+00,3.384654E+00,3.356566E+00,3.328711E+00,3.301087E+00,3.273692E+00,3.246525E+00,3.219583E+00,3.192864E+00,3.166368E+00,3.140091E+00,
        3.114032E+00,3.088190E+00,3.062562E+00,3.037147E+00,3.011942E+00,2.986947E+00,2.962159E+00,2.937577E+00,2.913199E+00,2.889023E+00,2.865048E+00,2.841272E+00,2.817693E+00,2.794310E+00,2.771121E+00,2.748124E+00,2.725318E+00,2.702701E+00,2.680272E+00,2.658030E+00,
        2.635971E+00,2.614096E+00,2.592403E+00,2.570889E+00,2.549554E+00,2.528396E+00,2.507414E+00,2.486605E+00,2.465970E+00,2.445505E+00,2.425211E+00,2.405085E+00,2.385126E+00,2.365332E+00,2.345703E+00,2.326237E+00,2.306932E+00,2.287787E+00,2.268802E+00,2.249973E+00,
        2.231302E+00,2.212785E+00,2.194421E+00,2.176211E+00,2.158151E+00,2.140241E+00,2.122480E+00,2.104866E+00,2.087398E+00,2.070076E+00,2.052897E+00,2.035860E+00,2.018965E+00,2.002210E+00,1.985595E+00,1.969117E+00,1.952776E+00,1.936570E+00,1.920499E+00,1.904561E+00,
        1.888756E+00,1.873082E+00,1.857538E+00,1.842122E+00,1.826835E+00,1.811675E+00,1.796640E+00,1.781731E+00,1.766944E+00,1.752281E+00,1.737739E+00,1.723318E+00,1.709017E+00,1.694834E+00,1.680770E+00,1.666821E+00,1.652989E+00,1.639271E+00,1.625667E+00,1.612176E+00,
        1.598797E+00,1.585530E+00,1.572372E+00,1.559323E+00,1.546383E+00,1.533550E+00,1.520823E+00,1.508202E+00,1.495686E+00,1.483274E+00,1.470965E+00,1.458758E+00,1.446652E+00,1.434646E+00,1.422741E+00,1.410934E+00,1.399225E+00,1.387613E+00,1.376098E+00,1.364678E+00,
        1.353353E+00,1.342122E+00,1.330984E+00,1.319938E+00,1.308985E+00,1.298122E+00,1.287349E+00,1.276666E+00,1.266071E+00,1.255564E+00,1.245145E+00,1.234812E+00,1.224564E+00,1.214402E+00,1.204324E+00,1.194330E+00,1.184418E+00,1.174589E+00,1.164842E+00,1.155175E+00,
        1.145588E+00,1.136082E+00,1.126654E+00,1.117304E+00,1.108032E+00,1.098836E+00,1.089717E+00,1.080674E+00,1.071706E+00,1.062812E+00,1.053992E+00,1.045245E+00,1.036571E+00,1.027969E+00,1.019438E+00,1.010978E+00,1.002588E+00,9.942682E-01,9.860171E-01,9.778344E-01,
        9.697197E-01,9.616723E-01,9.536916E-01,9.457772E-01,9.379285E-01,9.301449E-01,9.224259E-01,9.147709E-01,9.071795E-01,8.996511E-01,8.921852E-01,8.847812E-01,8.774387E-01,8.701570E-01,8.629359E-01,8.557746E-01,8.486728E-01,8.416299E-01,8.346455E-01,8.277190E-01,
        8.208500E-01,8.140380E-01,8.072825E-01,8.005831E-01,7.939393E-01,7.873507E-01,7.808167E-01,7.743369E-01,7.679109E-01,7.615382E-01,7.552184E-01,7.489511E-01,7.427358E-01,7.365720E-01,7.304594E-01,7.243976E-01,7.183860E-01,7.124243E-01,7.065121E-01,7.006490E-01,
        6.948345E-01,6.890683E-01,6.833499E-01,6.776790E-01,6.720551E-01,6.664779E-01,6.609470E-01,6.554620E-01,6.500225E-01,6.446282E-01,6.392786E-01,6.339734E-01,6.287123E-01,6.234948E-01,6.183206E-01,6.131893E-01,6.081006E-01,6.030542E-01,5.980496E-01,5.930866E-01,
        5.881647E-01,5.832837E-01,5.784432E-01,5.736429E-01,5.688824E-01,5.641614E-01,5.594796E-01,5.548366E-01,5.502322E-01,5.456660E-01,5.411377E-01,5.366469E-01,5.321934E-01,5.277769E-01,5.233971E-01,5.190535E-01,5.147461E-01,5.104743E-01,5.062381E-01,5.020369E-01,
        4.978707E-01,4.937390E-01,4.896416E-01,4.855782E-01,4.815485E-01,4.775523E-01,4.735892E-01,4.696591E-01,4.657615E-01,4.618963E-01,4.580631E-01,4.542618E-01,4.504920E-01,4.467535E-01,4.430460E-01,4.393693E-01,4.357231E-01,4.321072E-01,4.285213E-01,4.249651E-01,
        4.214384E-01,4.179410E-01,4.144727E-01,4.110331E-01,4.076220E-01,4.042393E-01,4.008846E-01,3.975578E-01,3.942586E-01,3.909868E-01,3.877421E-01,3.845243E-01,3.813333E-01,3.781687E-01,3.750304E-01,3.719181E-01,3.688317E-01,3.657708E-01,3.627354E-01,3.597252E-01,
        3.567399E-01,3.537795E-01,3.508435E-01,3.479320E-01,3.450446E-01,3.421812E-01,3.393415E-01,3.365254E-01,3.337327E-01,3.309631E-01,3.282166E-01,3.254928E-01,3.227916E-01,3.201129E-01,3.174564E-01,3.148219E-01,3.122093E-01,3.096183E-01,3.070489E-01,3.045008E-01,
        3.019738E-01,2.994678E-01,2.985000E-01,2.972000E-01,2.969826E-01,2.945181E-01,2.920740E-01,2.896501E-01,2.872464E-01,2.848626E-01,2.824986E-01,2.801543E-01,2.778293E-01,2.755237E-01,2.732372E-01,2.709697E-01,2.687210E-01,2.664910E-01,2.642794E-01,2.620863E-01,
        2.599113E-01,2.577544E-01,2.556153E-01,2.534941E-01,2.513904E-01,2.493042E-01,2.472353E-01,2.451835E-01,2.431488E-01,2.411310E-01,2.391299E-01,2.371455E-01,2.351775E-01,2.332258E-01,2.312903E-01,2.293709E-01,2.274674E-01,2.255797E-01,2.237077E-01,2.218512E-01,
        2.200102E-01,2.181844E-01,2.163737E-01,2.145781E-01,2.127974E-01,2.110314E-01,2.092801E-01,2.075434E-01,2.058210E-01,2.041130E-01,2.024191E-01,2.007393E-01,1.990734E-01,1.974214E-01,1.957830E-01,1.941583E-01,1.925470E-01,1.909491E-01,1.893645E-01,1.877930E-01,
        1.862346E-01,1.846891E-01,1.831564E-01,1.816364E-01,1.801291E-01,1.786342E-01,1.771518E-01,1.756817E-01,1.742237E-01,1.727779E-01,1.713441E-01,1.699221E-01,1.685120E-01,1.671136E-01,1.657268E-01,1.643514E-01,1.629875E-01,1.616349E-01,1.602936E-01,1.589634E-01,
        1.576442E-01,1.563359E-01,1.550385E-01,1.537519E-01,1.524760E-01,1.512106E-01,1.499558E-01,1.487113E-01,1.474772E-01,1.462533E-01,1.450396E-01,1.438360E-01,1.426423E-01,1.414586E-01,1.402847E-01,1.391205E-01,1.379660E-01,1.368210E-01,1.356856E-01,1.345596E-01,
        1.334429E-01,1.323355E-01,1.312373E-01,1.301482E-01,1.290681E-01,1.279970E-01,1.269348E-01,1.258814E-01,1.248368E-01,1.238008E-01,1.227734E-01,1.217545E-01,1.207441E-01,1.197421E-01,1.187484E-01,1.177629E-01,1.167857E-01,1.158165E-01,1.148554E-01,1.139022E-01,
        1.129570E-01,1.120196E-01,1.110900E-01,1.101681E-01,1.092538E-01,1.083471E-01,1.074480E-01,1.065563E-01,1.056720E-01,1.047951E-01,1.039254E-01,1.030630E-01,1.022077E-01,1.013595E-01,1.005184E-01,9.968419E-02,9.885694E-02,9.803655E-02,9.722297E-02,9.641615E-02,
        9.561602E-02,9.482253E-02,9.403563E-02,9.325525E-02,9.248135E-02,9.171388E-02,9.095277E-02,9.019798E-02,8.944945E-02,8.870714E-02,8.797098E-02,8.724094E-02,8.651695E-02,8.579897E-02,8.508695E-02,8.438084E-02,8.368059E-02,8.298615E-02,8.250000E-02,8.229747E-02,
        8.161451E-02,8.093721E-02,8.026554E-02,7.959944E-02,7.950000E-02,7.893887E-02,7.828378E-02,7.763412E-02,7.698986E-02,7.635094E-02,7.571733E-02,7.508897E-02,7.446583E-02,7.384786E-02,7.323502E-02,7.262726E-02,7.202455E-02,7.142684E-02,7.083409E-02,7.024626E-02,
        6.966330E-02,6.908519E-02,6.851187E-02,6.794331E-02,6.737947E-02,6.682031E-02,6.626579E-02,6.571586E-02,6.517051E-02,6.462968E-02,6.409333E-02,6.356144E-02,6.303396E-02,6.251086E-02,6.199211E-02,6.147765E-02,6.096747E-02,6.046151E-02,5.995976E-02,5.946217E-02,
        5.896871E-02,5.847935E-02,5.799405E-02,5.751277E-02,5.703549E-02,5.656217E-02,5.609278E-02,5.562728E-02,5.516564E-02,5.470784E-02,5.425384E-02,5.380360E-02,5.335710E-02,5.291430E-02,5.247518E-02,5.203971E-02,5.160785E-02,5.117957E-02,5.075484E-02,5.033364E-02,
        4.991594E-02,4.950170E-02,4.909090E-02,4.868351E-02,4.827950E-02,4.787884E-02,4.748151E-02,4.708747E-02,4.669671E-02,4.630919E-02,4.592488E-02,4.554376E-02,4.516581E-02,4.479099E-02,4.441928E-02,4.405066E-02,4.368510E-02,4.332257E-02,4.296305E-02,4.260651E-02,
        4.225293E-02,4.190229E-02,4.155455E-02,4.120970E-02,4.086771E-02,4.052857E-02,4.019223E-02,3.985869E-02,3.952791E-02,3.919988E-02,3.887457E-02,3.855196E-02,3.823203E-02,3.791476E-02,3.760011E-02,3.728808E-02,3.697864E-02,3.667176E-02,3.636743E-02,3.606563E-02,
        3.576633E-02,3.546952E-02,3.517517E-02,3.488326E-02,3.459377E-02,3.430669E-02,3.402199E-02,3.373965E-02,3.345965E-02,3.318198E-02,3.290662E-02,3.263353E-02,3.236272E-02,3.209415E-02,3.182781E-02,3.156368E-02,3.130174E-02,3.104198E-02,3.078437E-02,3.052890E-02,
        3.027555E-02,3.002430E-02,2.977514E-02,2.952804E-02,2.928300E-02,2.903999E-02,2.879899E-02,2.856000E-02,2.850000E-02,2.832299E-02,2.808794E-02,2.785485E-02,2.762369E-02,2.739445E-02,2.716711E-02,2.700000E-02,2.694166E-02,2.671808E-02,2.649635E-02,2.627647E-02,
        2.605841E-02,2.584215E-02,2.562770E-02,2.541502E-02,2.520411E-02,2.499495E-02,2.478752E-02,2.458182E-02,2.437782E-02,2.417552E-02,2.397489E-02,2.377593E-02,2.357862E-02,2.338295E-02,2.318890E-02,2.299646E-02,2.280562E-02,2.261636E-02,2.242868E-02,2.224255E-02,
        2.205796E-02,2.187491E-02,2.169338E-02,2.151335E-02,2.133482E-02,2.115777E-02,2.098218E-02,2.080806E-02,2.063538E-02,2.046413E-02,2.029431E-02,2.012589E-02,1.995887E-02,1.979324E-02,1.962898E-02,1.946608E-02,1.930454E-02,1.914434E-02,1.898547E-02,1.882791E-02,
        1.867166E-02,1.851671E-02,1.836305E-02,1.821066E-02,1.805953E-02,1.790966E-02,1.776104E-02,1.761364E-02,1.746747E-02,1.732251E-02,1.717876E-02,1.703620E-02,1.689482E-02,1.675461E-02,1.661557E-02,1.647768E-02,1.634094E-02,1.620533E-02,1.607085E-02,1.593748E-02,
        1.580522E-02,1.567406E-02,1.554398E-02,1.541499E-02,1.528706E-02,1.516020E-02,1.503439E-02,1.490963E-02,1.478590E-02,1.466319E-02,1.454151E-02,1.442083E-02,1.430116E-02,1.418247E-02,1.406478E-02,1.394806E-02,1.383231E-02,1.371752E-02,1.360368E-02,1.349079E-02,
        1.337883E-02,1.326780E-02,1.315770E-02,1.304851E-02,1.294022E-02,1.283283E-02,1.272634E-02,1.262073E-02,1.251599E-02,1.241212E-02,1.230912E-02,1.220697E-02,1.210567E-02,1.200521E-02,1.190558E-02,1.180678E-02,1.170880E-02,1.161163E-02,1.151527E-02,1.141970E-02,
        1.132494E-02,1.123095E-02,1.113775E-02,1.104532E-02,1.095366E-02,1.086276E-02,1.077261E-02,1.068321E-02,1.059456E-02,1.050664E-02,1.041944E-02,1.033298E-02,1.024723E-02,1.016219E-02,1.007785E-02,9.994221E-03,9.911282E-03,9.829031E-03,9.747463E-03,9.666572E-03,
        9.586352E-03,9.506797E-03,9.427903E-03,9.349664E-03,9.272074E-03,9.195127E-03,9.118820E-03,9.043145E-03,8.968099E-03,8.893675E-03,8.819869E-03,8.746676E-03,8.674090E-03,8.602106E-03,8.530719E-03,8.459926E-03,8.389719E-03,8.320095E-03,8.251049E-03,8.182576E-03,
        8.114671E-03,8.047330E-03,7.980548E-03,7.914319E-03,7.848641E-03,7.783507E-03,7.718914E-03,7.654857E-03,7.591332E-03,7.528334E-03,7.465858E-03,7.403901E-03,7.342458E-03,7.281525E-03,7.221098E-03,7.161172E-03,7.101744E-03,7.042809E-03,6.984362E-03,6.926401E-03,
        6.868921E-03,6.811918E-03,6.755388E-03,6.699327E-03,6.643731E-03,6.588597E-03,6.533920E-03,6.479697E-03,6.425924E-03,6.372597E-03,6.319712E-03,6.267267E-03,6.215257E-03,6.163678E-03,6.112528E-03,6.061802E-03,6.011496E-03,5.961609E-03,5.912135E-03,5.863072E-03,
        5.814416E-03,5.766164E-03,5.718312E-03,5.670858E-03,5.623797E-03,5.577127E-03,5.530844E-03,5.484945E-03,5.439427E-03,5.394287E-03,5.349521E-03,5.305127E-03,5.261101E-03,5.217441E-03,5.174143E-03,5.131204E-03,5.088622E-03,5.046393E-03,5.004514E-03,4.962983E-03,
        4.921797E-03,4.880952E-03,4.840447E-03,4.800277E-03,4.760441E-03,4.720936E-03,4.681758E-03,4.642906E-03,4.604375E-03,4.566165E-03,4.528272E-03,4.490693E-03,4.453426E-03,4.416468E-03,4.379817E-03,4.343471E-03,4.307425E-03,4.271679E-03,4.236230E-03,4.201075E-03,
        4.166211E-03,4.131637E-03,4.097350E-03,4.063347E-03,4.029627E-03,3.996186E-03,3.963023E-03,3.930135E-03,3.897520E-03,3.865175E-03,3.833099E-03,3.801290E-03,3.769744E-03,3.738460E-03,3.707435E-03,3.676668E-03,3.646157E-03,3.615898E-03,3.585891E-03,3.556133E-03,
        3.526622E-03,3.497355E-03,3.468332E-03,3.439549E-03,3.411005E-03,3.382698E-03,3.354626E-03,3.326787E-03,3.299179E-03,3.271800E-03,3.244649E-03,3.217722E-03,3.191019E-03,3.164538E-03,3.138276E-03,3.112233E-03,3.086405E-03,3.060792E-03,3.035391E-03,3.010202E-03,
        2.985221E-03,2.960447E-03,2.935879E-03,2.911515E-03,2.887354E-03,2.863392E-03,2.839630E-03,2.816065E-03,2.792695E-03,2.769519E-03,2.746536E-03,2.723743E-03,2.701139E-03,2.678723E-03,2.656494E-03,2.634448E-03,2.612586E-03,2.590904E-03,2.569403E-03,2.548081E-03,
        2.526935E-03,2.505965E-03,2.485168E-03,2.464545E-03,2.444092E-03,2.423809E-03,2.403695E-03,2.383747E-03,2.363965E-03,2.344347E-03,2.324892E-03,2.305599E-03,2.286465E-03,2.267490E-03,2.248673E-03,2.230012E-03,2.211506E-03,2.193153E-03,2.174953E-03,2.156904E-03,
        2.139004E-03,2.121253E-03,2.103650E-03,2.086192E-03,2.068879E-03,2.051710E-03,2.034684E-03,2.017798E-03,2.001053E-03,1.984447E-03,1.967979E-03,1.951647E-03,1.935451E-03,1.919389E-03,1.903461E-03,1.887665E-03,1.871999E-03,1.856464E-03,1.841058E-03,1.825780E-03,
        1.810628E-03,1.795602E-03,1.780701E-03,1.765923E-03,1.751268E-03,1.736735E-03,1.722323E-03,1.708030E-03,1.693855E-03,1.679798E-03,1.665858E-03,1.652034E-03,1.638324E-03,1.624728E-03,1.611245E-03,1.597874E-03,1.584613E-03,1.571463E-03,1.558422E-03,1.545489E-03,
        1.532663E-03,1.519944E-03,1.507331E-03,1.494822E-03,1.482417E-03,1.470115E-03,1.457915E-03,1.445816E-03,1.433817E-03,1.421919E-03,1.410118E-03,1.398416E-03,1.386811E-03,1.375303E-03,1.363889E-03,1.352571E-03,1.341346E-03,1.330215E-03,1.319176E-03,1.308228E-03,
        1.297372E-03,1.286605E-03,1.275928E-03,1.265339E-03,1.254839E-03,1.244425E-03,1.234098E-03,1.223857E-03,1.213700E-03,1.203628E-03,1.193639E-03,1.183734E-03,1.173910E-03,1.164168E-03,1.154507E-03,1.144926E-03,1.135425E-03,1.126002E-03,1.116658E-03,1.107391E-03,
        1.098201E-03,1.089088E-03,1.080050E-03,1.071087E-03,1.062198E-03,1.053383E-03,1.044641E-03,1.035972E-03,1.027375E-03,1.018849E-03,1.010394E-03,1.002009E-03,9.936937E-04,9.854473E-04,9.772694E-04,9.691593E-04,9.611165E-04,9.531405E-04,9.452307E-04,9.373865E-04,
        9.296074E-04,9.218928E-04,9.142423E-04,9.066553E-04,8.991312E-04,8.916696E-04,8.842699E-04,8.769316E-04,8.696542E-04,8.624372E-04,8.552801E-04,8.481824E-04,8.411435E-04,8.341631E-04,8.272407E-04,8.203756E-04,8.135676E-04,8.068160E-04,8.001205E-04,7.934805E-04,
        7.868957E-04,7.803654E-04,7.738894E-04,7.674671E-04,7.610981E-04,7.547820E-04,7.485183E-04,7.423066E-04,7.361464E-04,7.300373E-04,7.239790E-04,7.179709E-04,7.120126E-04,7.061038E-04,7.002441E-04,6.944330E-04,6.886701E-04,6.829550E-04,6.772874E-04,6.716668E-04,
        6.660928E-04,6.605651E-04,6.550832E-04,6.496469E-04,6.442557E-04,6.389092E-04,6.336071E-04,6.283489E-04,6.231345E-04,6.179633E-04,6.128350E-04,6.077492E-04,6.027057E-04,5.977040E-04,5.927438E-04,5.878248E-04,5.829466E-04,5.781089E-04,5.733114E-04,5.685536E-04,
        5.638354E-04,5.591563E-04,5.545160E-04,5.499142E-04,5.453506E-04,5.408249E-04,5.363368E-04,5.318859E-04,5.274719E-04,5.230946E-04,5.187536E-04,5.144486E-04,5.101793E-04,5.059455E-04,5.017468E-04,4.975830E-04,4.934537E-04,4.893587E-04,4.852976E-04,4.812703E-04,
        4.772763E-04,4.733156E-04,4.693877E-04,4.654923E-04,4.616294E-04,4.577984E-04,4.539993E-04,4.502317E-04,4.464953E-04,4.427900E-04,4.391154E-04,4.354713E-04,4.318575E-04,4.282736E-04,4.247195E-04,4.211949E-04,4.176995E-04,4.142332E-04,4.107955E-04,4.073865E-04,
        4.040057E-04,4.006530E-04,3.973281E-04,3.940308E-04,3.907608E-04,3.875180E-04,3.843021E-04,3.811129E-04,3.779502E-04,3.748137E-04,3.717032E-04,3.686185E-04,3.655595E-04,3.625258E-04,3.595173E-04,3.565338E-04,3.535750E-04,3.506408E-04,3.477309E-04,3.448452E-04,
        3.419834E-04,3.391454E-04,3.363309E-04,3.335398E-04,3.307719E-04,3.280269E-04,3.253047E-04,3.226051E-04,3.199279E-04,3.172729E-04,3.146399E-04,3.120288E-04,3.094394E-04,3.068715E-04,3.043248E-04,3.017993E-04,2.992948E-04,2.968110E-04,2.943479E-04,2.919052E-04,
        2.894827E-04,2.870804E-04,2.846980E-04,2.823354E-04,2.799924E-04,2.776688E-04,2.753645E-04,2.730793E-04,2.708131E-04,2.685657E-04,2.663370E-04,2.641267E-04,2.619348E-04,2.597611E-04,2.576054E-04,2.554676E-04,2.533476E-04,2.512451E-04,2.491601E-04,2.470924E-04,
        2.450418E-04,2.430083E-04,2.409917E-04,2.389917E-04,2.370084E-04,2.350416E-04,2.330910E-04,2.311567E-04,2.292384E-04,2.273360E-04,2.254494E-04,2.235784E-04,2.217230E-04,2.198830E-04,2.180583E-04,2.162487E-04,2.144541E-04,2.126744E-04,2.109095E-04,2.091592E-04,
        2.074234E-04,2.057021E-04,2.039950E-04,2.023021E-04,2.006233E-04,1.989584E-04,1.973073E-04,1.956699E-04,1.940461E-04,1.924358E-04,1.908388E-04,1.892551E-04,1.876845E-04,1.861269E-04,1.845823E-04,1.830505E-04,1.815315E-04,1.800250E-04,1.785310E-04,1.770494E-04,
        1.755802E-04,1.741231E-04,1.726781E-04,1.712451E-04,1.698239E-04,1.684146E-04,1.670170E-04,1.656310E-04,1.642565E-04,1.628933E-04,1.615415E-04,1.602010E-04,1.588715E-04,1.575531E-04,1.562456E-04,1.549489E-04,1.536631E-04,1.523879E-04,1.511232E-04,1.498691E-04,
        1.486254E-04,1.473920E-04,1.461688E-04,1.449558E-04,1.437529E-04,1.425599E-04,1.413768E-04,1.402036E-04,1.390401E-04,1.378862E-04,1.367420E-04,1.356072E-04,1.344818E-04,1.333658E-04,1.322590E-04,1.311615E-04,1.300730E-04,1.289935E-04,1.279231E-04,1.268615E-04,
        1.258087E-04,1.247646E-04,1.237292E-04,1.227024E-04,1.216842E-04,1.206744E-04,1.196729E-04,1.186798E-04,1.176949E-04,1.167182E-04,1.157496E-04,1.147890E-04,1.138364E-04,1.128917E-04,1.119548E-04,1.110258E-04,1.101044E-04,1.091907E-04,1.082845E-04,1.073859E-04,
        1.064947E-04,1.056110E-04,1.047345E-04,1.038654E-04,1.030034E-04,1.021486E-04,1.013009E-04,1.004603E-04,9.962658E-05,9.879981E-05,9.797990E-05,9.716679E-05,9.636043E-05,9.556076E-05,9.476773E-05,9.398128E-05,9.320136E-05,9.242791E-05,9.166088E-05,9.090021E-05,
        9.014586E-05,8.939776E-05,8.865588E-05,8.792015E-05,8.719052E-05,8.646695E-05,8.574939E-05,8.503778E-05,8.433208E-05,8.363223E-05,8.293819E-05,8.224991E-05,8.156734E-05,8.089044E-05,8.021915E-05,7.955344E-05,7.889325E-05,7.823854E-05,7.758926E-05,7.694537E-05,
        7.630682E-05,7.567357E-05,7.504558E-05,7.442280E-05,7.380518E-05,7.319270E-05,7.258529E-05,7.198293E-05,7.138556E-05,7.079316E-05,7.020566E-05,6.962305E-05,6.904527E-05,6.847228E-05,6.790405E-05,6.734053E-05,6.678169E-05,6.622749E-05,6.567789E-05,6.513285E-05,
        6.459233E-05,6.405630E-05,6.352471E-05,6.299754E-05,6.247474E-05,6.195628E-05,6.144212E-05,6.093223E-05,6.042657E-05,5.992511E-05,5.942781E-05,5.893464E-05,5.844556E-05,5.796053E-05,5.747954E-05,5.700253E-05,5.652948E-05,5.606036E-05,5.559513E-05,5.513376E-05,
        5.467623E-05,5.422248E-05,5.377251E-05,5.332626E-05,5.288373E-05,5.244486E-05,5.200963E-05,5.157802E-05,5.114999E-05,5.072551E-05,5.030456E-05,4.988709E-05,4.947309E-05,4.906253E-05,4.865538E-05,4.825160E-05,4.785117E-05,4.745407E-05,4.706026E-05,4.666972E-05,
        4.628243E-05,4.589834E-05,4.551744E-05,4.513971E-05,4.476511E-05,4.439361E-05,4.402521E-05,4.365985E-05,4.329753E-05,4.293822E-05,4.258189E-05,4.222851E-05,4.187807E-05,4.153054E-05,4.118589E-05,4.084410E-05,4.050514E-05,4.016900E-05,3.983565E-05,3.950507E-05,
        3.917723E-05,3.885211E-05,3.852969E-05,3.820994E-05,3.789285E-05,3.757838E-05,3.726653E-05,3.695727E-05,3.665057E-05,3.634642E-05,3.604479E-05,3.574566E-05,3.544902E-05,3.515484E-05,3.486310E-05,3.457378E-05,3.428686E-05,3.400233E-05,3.372015E-05,3.344032E-05,
        3.316281E-05,3.288760E-05,3.261467E-05,3.234401E-05,3.207560E-05,3.180942E-05,3.154544E-05,3.128365E-05,3.102404E-05,3.076658E-05,3.051126E-05,3.025805E-05,3.000695E-05,2.975793E-05,2.951098E-05,2.926607E-05,2.902320E-05,2.878235E-05,2.854349E-05,2.830662E-05,
        2.807171E-05,2.783875E-05,2.760773E-05,2.737862E-05,2.715141E-05,2.692609E-05,2.670264E-05,2.648104E-05,2.626128E-05,2.604335E-05,2.582722E-05,2.561289E-05,2.540033E-05,2.518954E-05,2.498050E-05,2.477320E-05,2.456761E-05,2.436373E-05,2.416154E-05,2.396104E-05,
        2.376219E-05,2.356499E-05,2.336944E-05,2.317550E-05,2.298317E-05,2.279244E-05,2.260329E-05,2.241572E-05,2.222969E-05,2.204522E-05,2.186227E-05,2.168084E-05,2.150092E-05,2.132249E-05,2.114554E-05,2.097006E-05,2.079603E-05,2.062345E-05,2.045231E-05,2.028258E-05,
        2.011426E-05,1.994734E-05,1.978180E-05,1.961764E-05,1.945484E-05,1.929339E-05,1.913328E-05,1.897449E-05,1.881703E-05,1.866087E-05,1.850601E-05,1.835244E-05,1.820013E-05,1.804910E-05,1.789931E-05,1.775077E-05,1.760346E-05,1.745738E-05,1.731250E-05,1.716883E-05,
        1.702635E-05,1.688506E-05,1.674493E-05,1.660597E-05,1.646816E-05,1.633150E-05,1.619597E-05,1.606156E-05,1.592827E-05,1.579609E-05,1.566500E-05,1.553500E-05,1.540608E-05,1.527823E-05,1.515144E-05,1.502570E-05,1.490101E-05,1.477735E-05,1.465472E-05,1.453310E-05,
        1.441250E-05,1.429289E-05,1.417428E-05,1.405665E-05,1.394000E-05,1.382431E-05,1.370959E-05,1.359582E-05,1.348299E-05,1.337110E-05,1.326014E-05,1.315010E-05,1.304097E-05,1.293274E-05,1.282542E-05,1.271898E-05,1.261343E-05,1.250876E-05,1.240495E-05,1.230201E-05,
        1.219991E-05,1.209867E-05,1.199827E-05,1.189870E-05,1.179995E-05,1.170203E-05,1.160492E-05,1.150861E-05,1.141311E-05,1.131839E-05,1.122446E-05,1.113132E-05,1.103894E-05,1.094733E-05,1.085648E-05,1.076639E-05,1.067704E-05,1.058843E-05,1.050056E-05,1.041342E-05,
        1.032701E-05,1.024130E-05,1.015631E-05,1.007203E-05,9.988446E-06,9.905554E-06,9.823351E-06,9.741830E-06,9.660985E-06,9.580812E-06,9.501303E-06,9.422455E-06,9.344261E-06,9.266715E-06,9.189814E-06,9.113550E-06,9.037919E-06,8.962916E-06,8.888536E-06,8.814772E-06,
        8.741621E-06,8.669077E-06,8.597135E-06,8.525790E-06,8.455037E-06,8.384871E-06,8.315287E-06,8.246281E-06,8.177848E-06,8.109982E-06,8.042680E-06,7.975936E-06,7.909746E-06,7.844105E-06,7.779009E-06,7.714454E-06,7.650434E-06,7.586945E-06,7.523983E-06,7.461544E-06,
        7.399622E-06,7.338215E-06,7.277317E-06,7.216925E-06,7.157034E-06,7.097640E-06,7.038739E-06,6.980326E-06,6.922399E-06,6.864952E-06,6.807981E-06,6.751484E-06,6.695455E-06,6.639892E-06,6.584789E-06,6.530144E-06,6.475952E-06,6.422210E-06,6.368914E-06,6.316060E-06,
        6.263645E-06,6.211665E-06,6.160116E-06,6.108995E-06,6.058298E-06,6.008022E-06,5.958164E-06,5.908719E-06,5.859684E-06,5.811056E-06,5.762832E-06,5.715008E-06,5.667581E-06,5.620547E-06,5.573904E-06,5.527647E-06,5.481775E-06,5.436284E-06,5.391169E-06,5.346430E-06,
        5.302061E-06,5.258061E-06,5.214426E-06,5.171153E-06,5.128239E-06,5.085681E-06,5.043477E-06,4.918953E-06,4.797503E-06,4.679053E-06,4.563526E-06,4.450853E-06,4.340961E-06,4.233782E-06,4.129250E-06,4.000000E-06,3.927860E-06,3.830880E-06,3.736300E-06,3.644050E-06,
        3.554080E-06,3.466330E-06,3.380750E-06,3.300000E-06,3.217630E-06,3.137330E-06,3.059020E-06,2.983490E-06,2.909830E-06,2.837990E-06,2.767920E-06,2.720000E-06,2.659320E-06,2.600000E-06,2.550000E-06,2.485030E-06,2.421710E-06,2.382370E-06,2.360000E-06,2.300270E-06,
        2.242050E-06,2.185310E-06,2.130000E-06,2.100000E-06,2.059610E-06,2.020000E-06,1.974490E-06,1.930000E-06,1.884460E-06,1.855390E-06,1.840000E-06,1.797000E-06,1.755000E-06,1.711970E-06,1.670000E-06,1.629510E-06,1.590000E-06,1.544340E-06,1.500000E-06,1.475000E-06,
        1.440000E-06,1.404560E-06,1.370000E-06,1.337500E-06,1.300000E-06,1.267080E-06,1.235000E-06,1.202060E-06,1.170000E-06,1.150000E-06,1.123000E-06,1.110000E-06,1.097000E-06,1.080000E-06,1.071000E-06,1.045000E-06,1.035000E-06,1.020000E-06,9.960000E-07,9.860000E-07,
        9.720000E-07,9.500000E-07,9.300000E-07,9.100000E-07,8.764250E-07,8.600000E-07,8.500000E-07,8.194500E-07,7.900000E-07,7.800000E-07,7.415500E-07,7.050000E-07,6.825600E-07,6.531500E-07,6.250000E-07,5.952800E-07,5.669600E-07,5.400000E-07,5.315800E-07,5.196200E-07,
        5.000000E-07,4.850000E-07,4.670100E-07,4.496800E-07,4.330000E-07,4.139900E-07,4.000000E-07,3.910000E-07,3.699300E-07,3.500000E-07,3.346600E-07,3.200000E-07,3.145000E-07,3.000000E-07,2.800000E-07,2.635100E-07,2.480000E-07,2.335800E-07,2.200000E-07,2.091400E-07,
        1.988100E-07,1.890000E-07,1.800000E-07,1.697100E-07,1.600000E-07,1.530300E-07,1.463700E-07,1.400000E-07,1.340000E-07,1.150000E-07,1.000000E-07,9.500000E-08,8.000000E-08,7.700000E-08,6.700000E-08,5.800000E-08,5.000000E-08,4.200000E-08,3.500000E-08,3.000000E-08,
        2.500000E-08,2.000000E-08,1.500000E-08,1.000000E-08,6.900000E-09,5.000000E-09,3.000000E-09,1.000010E-11
        ]
    
    #pstr = ''
    #for i in range(len(ECCO_bins)):
    #    pstr += ECCO_bins[i] + ','
    #    if i!=0 and i%20==0:
    #        pstr += '\n'       
    #print(pstr)
    
    return ECCO_bins[:n]
def Element_Sym_to_Z(sym)

Description

Returns atomic number Z for a provided elemental symbol

Dependencies

find()

Inputs

  • sym = string of elemental symbol for element of atomic number Z

Outputs

  • Z = atomic number
Expand source code
def Element_Sym_to_Z(sym):
    '''
    Description:
        Returns atomic number Z for a provided elemental symbol        
    
    Dependencies:
        `find` 
    
    Inputs:
        - `sym` = string of elemental symbol for element of atomic number Z
    
    Outputs:
        - `Z` = atomic number 
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    
    if len(sym.strip())>2:
        print('Please provide a valid elemental symbol (1 or 2 characters), {} is too long'.format(sym))
        return -1
    
    # handle exception for neutron first
    if sym.strip()=='XX':
        return 0
    
    # make sure string is formatted to match entries in elms list
    sym2 = sym.strip()
    if len(sym2)==1: sym2 += ' '
    sym2 = sym2[0].upper() + sym2[1].lower()
    
    Z = find(sym2,elms)
    
    if Z==None:
        print('Z could not be found for element "{}"; please make sure entry is correct.'.format(sym))
        return -1
    
    return Z
def Element_Z_to_Sym(Z)

Description

Returns elemental symbol for a provided atomic number Z

Inputs

  • Z = atomic number

Outputs

  • sym = string of elemental symbol for element of atomic number Z
Expand source code
def Element_Z_to_Sym(Z):
    '''
    Description:
        Returns elemental symbol for a provided atomic number Z        
    
    Inputs:
        - `Z` = atomic number 
    
    Outputs:
        - `sym` = string of elemental symbol for element of atomic number Z
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    i = int(Z)
    if i < 0 or i > len(elms): 
        print('Z={} is not valid, please select a number from 0 to 118 (inclusive).'.format(str(Z)))
        return None 
    return elms[i].strip()
def Element_ZorSym_to_mass(Z)

Description

Returns an element's average atomic mass provided its atomic number Z or elemental symbol

Inputs

  • Z = string of elemental symbol or atomic number Z

Outputs

  • A_avg = average atomic mass
Expand source code
def Element_ZorSym_to_mass(Z):
    '''
    Description:
        Returns an element's average atomic mass provided its atomic number Z or elemental symbol        
    
    Inputs:
        - `Z` = string of elemental symbol or atomic number Z
    
    Outputs:
        - `A_avg` = average atomic mass
    '''
    
    average_atomic_masses = [1.008664,1.007,4.002602,6.941,9.012182,10.811,12.0107,14.0067,15.9994,18.9984032,
                             20.1797,22.98976928,24.305,26.9815386,28.0855,30.973762,32.065,35.453,39.948,39.0983,
                             40.078,44.955912,47.867,50.9415,51.9961,54.938045,55.845,58.933195,58.6934,63.546,65.38,
                             69.723,72.63,74.9216,78.96,79.904,83.798,85.4678,87.62,88.90585,91.224,92.90638,95.96,98,
                             101.07,102.9055,106.42,107.8682,112.411,114.818,118.71,121.76,127.6,126.90447,131.293,
                             132.9054519,137.327,138.90547,140.116,140.90765,144.242,145,150.36,151.964,157.25,
                             158.92535,162.5,164.93032,167.259,168.93421,173.054,174.9668,178.49,180.94788,183.84,
                             186.207,190.23,192.217,195.084,196.966569,200.59,204.3833,207.2,208.9804,209,210,222,
                             223,226,227,232.03806,231.03588,238.02891,237,244,243,247,247,251,252,257,258,259,
                             266,267,268,269,270,277,278,281,282,285,286,289,290,293,294,294]
    
    try:
        zi = int(Z)
    except:
        zi = Element_Sym_to_Z(Z)
        
    return average_atomic_masses[zi]
def Element_ZorSym_to_name(Z)

Description

Returns an element's name provided its atomic number Z or elemental symbol

Inputs

  • Z = string of elemental symbol or atomic number Z

Outputs

  • name = element name
Expand source code
def Element_ZorSym_to_name(Z):
    '''
    Description:
        Returns an element's name provided its atomic number Z or elemental symbol        
    
    Inputs:
        - `Z` = string of elemental symbol or atomic number Z
    
    Outputs:
        - `name` = element name
    '''
    element_names = ['neutron','Hydrogen','Helium','Lithium','Beryllium','Boron','Carbon','Nitrogen','Oxygen','Fluorine',
                     'Neon','Sodium','Magnesium','Aluminium','Silicon','Phosphorus','Sulfur','Chlorine','Argon',
                     'Potassium','Calcium','Scandium','Titanium','Vanadium','Chromium','Manganese','Iron','Cobalt',
                     'Nickel','Copper','Zinc','Gallium','Germanium','Arsenic','Selenium','Bromine','Krypton',
                     'Rubidium','Strontium','Yttrium','Zirconium','Niobium','Molybdenum','Technetium','Ruthenium',
                     'Rhodium','Palladium','Silver','Cadmium','Indium','Tin','Antimony','Tellurium','Iodine','Xenon',
                     'Caesium','Barium','Lanthanum','Cerium','Praseodymium','Neodymium','Promethium','Samarium',
                     'Europium','Gadolinium','Terbium','Dysprosium','Holmium','Erbium','Thulium','Ytterbium',
                     'Lutetium','Hafnium','Tantalum','Tungsten','Rhenium','Osmium','Iridium','Platinum','Gold',
                     'Mercury','Thallium','Lead','Bismuth','Polonium','Astatine','Radon','Francium','Radium',
                     'Actinium','Thorium','Protactinium','Uranium','Neptunium','Plutonium','Americium','Curium',
                     'Berkelium','Californium','Einsteinium','Fermium','Mendelevium','Nobelium','Lawrencium',
                     'Rutherfordium','Dubnium','Seaborgium','Bohrium','Hassium','Meitnerium','Darmstadtium',
                     'Roentgenium','Copernicium','Nihonium','Flerovium','Moscovium','Livermorium','Tennessine','Oganesson']
    
    try:
        zi = int(Z)
    except:
        zi = Element_Sym_to_Z(Z)
    
    return element_names[zi]
def ZAM_to_Dname(ZAM)

Description

Converts a ZZZAAAM number to a DCHAIN-formatted nuclide name

Inputs

  • ZZZAAAM = nuclide identification ineger, calculated as 10000*Z + 10*A + m

Outputs

  • Dname = nuclide identification string in DCHAIN format
Expand source code
def ZAM_to_Dname(ZAM):
    '''
    Description:
        Converts a ZZZAAAM number to a DCHAIN-formatted nuclide name
       
    Inputs:
        - `ZZZAAAM` = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m
        
    Outputs:
        - `Dname` = nuclide identification string in DCHAIN format 
    '''
    elms = ["n ",\
            "H ","He","Li","Be","B ","C ","N ","O ","F ","Ne",\
            "Na","Mg","Al","Si","P ","S ","Cl","Ar","K ","Ca",\
            "Sc","Ti","V ","Cr","Mn","Fe","Co","Ni","Cu","Zn",\
            "Ga","Ge","As","Se","Br","Kr","Rb","Sr","Y ","Zr",\
            "Nb","Mo","Tc","Ru","Rh","Pd","Ag","Cd","In","Sn",\
            "Sb","Te","I ","Xe","Cs","Ba","La","Ce","Pr","Nd",\
            "Pm","Sm","Eu","Gd","Tb","Dy","Ho","Er","Tm","Yb",\
            "Lu","Hf","Ta","W ","Re","Os","Ir","Pt","Au","Hg",\
            "Tl","Pb","Bi","Po","At","Rn","Fr","Ra","Ac","Th",\
            "Pa","U ","Np","Pu","Am","Cm","Bk","Cf","Es","Fm",\
            "Md","No","Lr","Rf","Db","Sg","Bh","Hs","Mt","Ds",\
            "Rg","Cn","Nh","Fl","Mc","Lv","Ts","Og"]
    m = int(str(ZAM)[-1])
    A = int(str(ZAM)[-4:-1])
    Z = int(str(ZAM)[:-4])
    sym = elms[Z]
    A_str = '{:>3}'.format(A)
    m_str_list = [' ','m','n']
    m_str = m_str_list[m]
    Dname = sym + A_str + m_str
    return Dname
def ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM)

Description

Converts a ZZZAAAM number to the 7-character nuclide string used by the DCHAIN neutron reaction cross section libraries.

Inputs

  • ZZZAAAM = nuclide identification ineger, calculated as 10000*Z + 10*A + m

Outputs

  • D_xs_name = nuclide identification string in DCHAIN's cross section library format
Expand source code
def ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM):
    '''
    Description:
       Converts a ZZZAAAM number to the 7-character nuclide string used by the DCHAIN neutron reaction cross section libraries.
       
    Inputs:
        - `ZZZAAAM` = nuclide identification ineger, calculated as 10000\*Z + 10\*A + m
        
    Outputs:
        - `D_xs_name` = nuclide identification string in DCHAIN's cross section library format 
    '''
    ZAM_str = str(ZZZAAAM)
    M = int(ZAM_str[-1])
    A = str(int(ZAM_str[-4:-1]))
    Z = ZAM_str[:-4]
    sym = Element_Z_to_Sym(int(Z)).upper()
    if M==0:
        M_str = '  '
    else:
        M_str = 'M' + str(M)
    dstr = "{:2}{:>3}{:2}".format(sym,A,M_str)
    return dstr 
def calc_one_group_nrxn_xs_dchain(neutron_flux, neutron_flux_errors, libfile, target, reaction=None, product=None)

Description

Combines a neutron flux and cross section into a flux-weighted single-group cross section.

Inputs

  • neutron_flux = 1D array of flux values
  • neutron_flux_errors = 1D array of flux absolute uncertainties
  • libfile = string of file path to data library file to be searched
  • target = string in general format of target nuclide
  • reaction = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)

    if reaction = 'tot' or 'total', the summed total transmutation xs (reactions which change the target's nuclide species) is provided and input for product is ignored; this behavior is also assumed when missing both reaction and product information - product = (optional) string in general format of product nuclide (if omitted, sum of all isomeric states is assumed; if provided product but not isomeric state, ground state is assumed)

Outputs

  • xs = a length-2 list of the single group cross section and its absolute uncertainty
Expand source code
def calc_one_group_nrxn_xs_dchain(neutron_flux,neutron_flux_errors,libfile,target,reaction=None,product=None):
    '''
    Description:
        Combines a neutron flux and cross section into a flux-weighted single-group cross section.
        
    Inputs:
        - `neutron_flux` = 1D array of flux values
        - `neutron_flux_errors` = 1D array of flux absolute uncertainties
        - `libfile` = string of file path to data library file to be searched
        - `target` = string in general format of target nuclide
        - `reaction` = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
                   
              if `reaction = 'tot'` or `'total'`, the summed total transmutation xs (reactions which change the target's nuclide species) is provided and input for product is ignored; 
              this behavior is also assumed when missing both reaction and product information
        - `product` = (optional) string in general format of product nuclide (if omitted, sum of all isomeric states is assumed; if provided product but not isomeric state, ground state is assumed)
        
    Outputs:
        - `xs` = a length-2 list of the single group cross section and its absolute uncertainty
    '''
    
    xs_out, rxn_str_out = retrieve_rxn_xs_from_lib(libfile,target,reaction,product)
    xs_vals = xs_out[1,:]
    
    flux_val = neutron_flux
    flux_err = neutron_flux_errors
    
    if len(xs_vals) != len(flux_val):
        print('Warning: mismatch in flux and cross section energy binning!')
    
    xs = np.sum(flux_val*xs_vals)/np.sum(flux_val)
    xs_fer_num = np.sqrt(np.sum((flux_err*xs_vals)**2))/np.sum(flux_val*xs_vals)
    xs_fer_denom = np.sqrt(np.sum(flux_err**2))/np.sum(flux_val)
    xs_fer = np.sqrt(xs_fer_num**2 + xs_fer_denom**2)
    xs_aer = xs*xs_fer
    
    return [xs, xs_aer] 
def find(target, myList)

Description

Search for and return the index of the first occurance of a value in a list.

Inputs

  • target = value to be searched for
  • myList = list of values

Outputs

  • index of first instance of target in myList
Expand source code
def find(target, myList):
    '''
    Description:
        Search for and return the index of the first occurance of a value in a list.
        
    Inputs:
        - `target` = value to be searched for
        - `myList` = list of values 
        
    Outputs:
        - index of first instance of `target` in `myList`
    '''
    for i in range(len(myList)):
        if myList[i] == target:
            return i
def generate_nuclide_time_profiles(nuclides_info_array)

Description

Reformats DCHAIN's tabular nuclide data into time profiles of each nuclide in each region

Inputs

  • the nuclides_produced array from function "parse_DCHAIN_act_file"

Outputs

  • List of length R of lists containing names of nuclides produced in each region
  • List of length R of lists containing LaTeX-formatted names of nuclides produced in each region
  • List of length R of lists containing ZZZAAAM values (10000Z+10A+M) of nuclides produced in each region
  • List of length R of lists containing half lives of nuclides produced in each region (in seconds)
  • List of length R of NumPy arrays of dimension NxTx7x2 of nuclide info
  • List of length 7 containing text descriptions of the 7 columns of the info arrays
Expand source code
def generate_nuclide_time_profiles(nuclides_info_array):
    '''
    Description:
        Reformats DCHAIN's tabular nuclide data into time profiles of each nuclide in each region
    
    Inputs:
       - the `nuclides_produced` array from function "parse_DCHAIN_act_file"
    
    Outputs:
       - List of length R of lists containing names of nuclides produced in each region 
       - List of length R of lists containing LaTeX-formatted names of nuclides produced in each region 
       - List of length R of lists containing ZZZAAAM values (10000Z+10A+M) of nuclides produced in each region
       - List of length R of lists containing half lives of nuclides produced in each region (in seconds)
       - List of length R of NumPy arrays of dimension NxTx7x2 of nuclide info
       - List of length 7 containing text descriptions of the 7 columns of the info arrays
    '''
    nuclide_names = []
    nuclide_ZAM_vals = []
    nuclide_Latex_names = []
    nuclide_half_lives = []
    nuclide_info = []
    nuclide_info_headers = ['Atoms [#/cc]','Activity [Bq/cc]','Beta decay heat [W/cc]','Gamma decay heat [W/cc]','Alpha decay heat [W/cc]','Total decay heat [W/cc]','Dose-rate [uSv/h*m^2]']
    
    
    nreg = np.shape(nuclides_info_array)[0]
    ntime= np.shape(nuclides_info_array)[1]
    nnuc = np.shape(nuclides_info_array)[2]
    
    # Get nuclide name info first, ordering them by increasing Z and A
    for ri in range(nreg):
        reg_nuclides = []
        reg_t_halves = []
        reg_ZAM_vals = []
        reg_tex_nuclides = []
        for ti in range(ntime):
            for ni in range(nnuc):
                Dname = nuclides_info_array[ri,ti,ni,0,0]
                if Dname == None: continue
                ZAM = Dname_to_ZAM(Dname)
                if ZAM not in reg_ZAM_vals:
                    bisect.insort_left(reg_ZAM_vals, ZAM)
                    zami = reg_ZAM_vals.index(ZAM)
                    #zami = find(ZAM,reg_ZAM_vals)
                    reg_nuclides.insert(zami,Dname)
                    reg_t_halves.insert(zami,nuclides_info_array[ri,ti,ni,9,0])
                    reg_tex_nuclides.insert(zami,Dname_to_Latex(Dname))
        
        nuclide_names.append(reg_nuclides)
        nuclide_ZAM_vals.append(reg_ZAM_vals)
        nuclide_half_lives.append(reg_t_halves)
        nuclide_Latex_names.append(reg_tex_nuclides)
    
    # Now get arrays of nuclide info
    for ri in range(nreg):
        reg_nnuc = len(nuclide_names[ri])
        reg_nuclide_info = np.zeros((ntime,reg_nnuc,7,2))
        for ti in range(ntime): 
            for ni in range(reg_nnuc):
                if nuclide_names[ri][ni] not in nuclides_info_array[ri,ti,:,0,0]: continue
                sni = find(nuclide_names[ri][ni],nuclides_info_array[ri,ti,:,0,0])
                reg_nuclide_info[ti,ni,0,0] = nuclides_info_array[ri,ti,sni,1,0]   # atoms [#/cc]
                reg_nuclide_info[ti,ni,1,0] = nuclides_info_array[ri,ti,sni,2,0]   # activity [Bq/cc]
                reg_nuclide_info[ti,ni,2,0] = nuclides_info_array[ri,ti,sni,5,0]   # beta decay heat [W/cc]
                reg_nuclide_info[ti,ni,3,0] = nuclides_info_array[ri,ti,sni,6,0]   # gamma decay heat [W/cc]
                reg_nuclide_info[ti,ni,4,0] = nuclides_info_array[ri,ti,sni,7,0]   # alpha decay heat [W/cc]
                reg_nuclide_info[ti,ni,5,0] = nuclides_info_array[ri,ti,sni,8,0]   # total decay heat [W/cc]
                reg_nuclide_info[ti,ni,6,0] = nuclides_info_array[ri,ti,sni,10,0]  # dose-rate [uSv/h*m^2]
                
                reg_nuclide_info[ti,ni,0,1] = nuclides_info_array[ri,ti,sni,1,1]   # atoms absolute error [#/cc]
                reg_nuclide_info[ti,ni,1,1] = nuclides_info_array[ri,ti,sni,2,1]   # activity absolute error [Bq/cc]
                reg_nuclide_info[ti,ni,2,1] = nuclides_info_array[ri,ti,sni,5,1]   # beta decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,3,1] = nuclides_info_array[ri,ti,sni,6,1]   # gamma decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,4,1] = nuclides_info_array[ri,ti,sni,7,1]   # alpha decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,5,1] = nuclides_info_array[ri,ti,sni,8,1]   # total decay heat absolute error [W/cc]
                reg_nuclide_info[ti,ni,6,1] = nuclides_info_array[ri,ti,sni,10,1]  # dose-rate absolute error [uSv/h*m^2]
                
        nuclide_info.append(reg_nuclide_info)
            
    return nuclide_names, nuclide_Latex_names, nuclide_ZAM_vals, nuclide_half_lives, nuclide_info, nuclide_info_headers
def generate_nuclide_time_profiles_legacy(nuclides_info_array)

Description

Reformats DCHAIN's tabular nuclide data into time profiles of each nuclide in each region (without uncertainty values)

Inputs

  • the `nuclides_produced' array from function "parse_DCHAIN_act_file"

Outputs

  • List of length R of lists containing names of nuclides produced in each region
  • List of length R of lists containing LaTeX-formatted names of nuclides produced in each region
  • List of length R of lists containing ZZZAAAM values (10000Z+10A+M) of nuclides produced in each region
  • List of length R of lists containing half lives of nuclides produced in each region (in seconds)
  • List of length R of NumPy arrays of dimension NxTx7 of nuclide info
  • List of length 7 containing text descriptions of the 7 columns of the info arrays
Expand source code
def generate_nuclide_time_profiles_legacy(nuclides_info_array):
    '''
    Description:
        Reformats DCHAIN's tabular nuclide data into time profiles of each nuclide in each region (without uncertainty values)
    
    Inputs:
       - the `nuclides_produced' array from function "parse_DCHAIN_act_file"
       
    Outputs:
       - List of length R of lists containing names of nuclides produced in each region 
       - List of length R of lists containing LaTeX-formatted names of nuclides produced in each region 
       - List of length R of lists containing ZZZAAAM values (10000Z+10A+M) of nuclides produced in each region
       - List of length R of lists containing half lives of nuclides produced in each region (in seconds)
       - List of length R of NumPy arrays of dimension NxTx7 of nuclide info
       - List of length 7 containing text descriptions of the 7 columns of the info arrays
    '''
    nuclide_names = []
    nuclide_ZAM_vals = []
    nuclide_Latex_names = []
    nuclide_half_lives = []
    nuclide_info = []
    nuclide_info_headers = ['Atoms [#/cc]','Activity [Bq/cc]','Beta decay heat [W/cc]','Gamma decay heat [W/cc]','Alpha decay heat [W/cc]','Total decay heat [W/cc]','Dose-rate [uSv/h*m^2]']
    
    
    nreg = np.shape(nuclides_info_array)[0]
    ntime= np.shape(nuclides_info_array)[1]
    nnuc = np.shape(nuclides_info_array)[2]
    
    # Get nuclide name info first, ordering them by increasing Z and A
    for ri in range(nreg):
        reg_nuclides = []
        reg_t_halves = []
        reg_ZAM_vals = []
        reg_tex_nuclides = []
        for ti in range(ntime):
            for ni in range(nnuc):
                Dname = nuclides_info_array[ri,ti,ni,0]
                if Dname == None: continue
                ZAM = Dname_to_ZAM(Dname)
                if ZAM not in reg_ZAM_vals:
                    bisect.insort_left(reg_ZAM_vals, ZAM)
                    zami = reg_ZAM_vals.index(ZAM)
                    #zami = find(ZAM,reg_ZAM_vals)
                    reg_nuclides.insert(zami,Dname)
                    reg_t_halves.insert(zami,nuclides_info_array[ri,ti,ni,9])
                    reg_tex_nuclides.insert(zami,Dname_to_Latex(Dname))
        
        nuclide_names.append(reg_nuclides)
        nuclide_ZAM_vals.append(reg_ZAM_vals)
        nuclide_half_lives.append(reg_t_halves)
        nuclide_Latex_names.append(reg_tex_nuclides)
    
    # Now get arrays of nuclide info
    for ri in range(nreg):
        reg_nnuc = len(nuclide_names[ri])
        reg_nuclide_info = np.zeros((ntime,reg_nnuc,7))
        for ti in range(ntime): 
            for ni in range(reg_nnuc):
                if nuclide_names[ri][ni] not in nuclides_info_array[ri,ti,:,0]: continue
                sni = find(nuclide_names[ri][ni],nuclides_info_array[ri,ti,:,0])
                reg_nuclide_info[ti,ni,0] = nuclides_info_array[ri,ti,sni,1]   # atoms [#/cc]
                reg_nuclide_info[ti,ni,1] = nuclides_info_array[ri,ti,sni,2]   # activity [Bq/cc]
                reg_nuclide_info[ti,ni,2] = nuclides_info_array[ri,ti,sni,5]   # beta decay heat [W/cc]
                reg_nuclide_info[ti,ni,3] = nuclides_info_array[ri,ti,sni,6]   # gamma decay heat [W/cc]
                reg_nuclide_info[ti,ni,4] = nuclides_info_array[ri,ti,sni,7]   # alpha decay heat [W/cc]
                reg_nuclide_info[ti,ni,5] = nuclides_info_array[ri,ti,sni,8]   # total decay heat [W/cc]
                reg_nuclide_info[ti,ni,6] = nuclides_info_array[ri,ti,sni,10]  # dose-rate [uSv/h*m^2]
                
        nuclide_info.append(reg_nuclide_info)
            
    return nuclide_names, nuclide_Latex_names, nuclide_ZAM_vals, nuclide_half_lives, nuclide_info, nuclide_info_headers
def nuclide_plain_str_ZZZAAAM(nuc_str)

Description

Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000*Z + 10*A + M

Dependencies

Element_Z_to_Sym()

Inputs

  • nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
    • Isomeric/metastable state characters must always immediately follow the atomic mass characters. Isomeric state labels MUST either:
      • (1) be a single lower-case character OR
      • (2) begin with any non-numeric character and end with a number
    • Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
    • Elemental symbols MUST begin with an upper-case character

Outputs

  • ZZZAAAM integer
Expand source code
def nuclide_plain_str_ZZZAAAM(nuc_str):
    '''
    Description:
        Converts a plaintext string of a nuclide to an integer ZZZAAAM = 10000\*Z + 10\*A + M
    
    Dependencies:
        `Element_Z_to_Sym` 
    
    Inputs:
       - `nuc_str` = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
           + Isomeric/metastable state characters must always immediately follow the atomic mass characters.
               Isomeric state labels MUST either:
               - (1) be a single lower-case character OR 
               - (2) begin with any non-numeric character and end with a number
           + Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
           + Elemental symbols MUST begin with an upper-case character
    
    
    Outputs:
        - ZZZAAAM integer
    '''
    
    # remove unwanted characters from provided string
    delete_characters_list = [' ', '-', '_']
    for dc in delete_characters_list:
        nuc_str = nuc_str.replace(dc,'')
    
    # determine which characters are letters versus numbers
    isalpha_list = []
    isdigit_list = []
    for c in nuc_str:
        isalpha_list.append(c.isalpha())
        isdigit_list.append(c.isdigit())
    
    symbol = ''
    mass = ''
    isost = ''
    
    if 'nat' in nuc_str:
        print('Must specify a specific nuclide, not natural abundances')
        return None 
    
    # string MUST begin with either mass number or elemental symbol
    if isdigit_list[0]: # mass first
        mass_first = True 
    else:
        mass_first = False 
    
    if mass_first:
        ci = 0
        while isdigit_list[ci]:
            mass += nuc_str[ci]
            ci += 1
        mass = str(int(mass)) # eliminate any extra leading zeros
        # encountered a non-numeric character, end of mass
        # now, determine if metastable state is listed or if element is listed next
        # first, check to see if any other numerals are in string
        lni = 0 # last numeral index
        for i in range(ci,len(nuc_str)):
            if isdigit_list[i]: 
                lni = i
        if lni != 0:
            # grab all characters between ci and last numeral as metastable state
            isost = nuc_str[ci:lni+1]
            ci = lni + 1
        else: # no more numerals in string, now check for single lower-case letter
            if isalpha_list[ci] and nuc_str[ci].islower():
                isost = nuc_str[ci]
                ci += 1
        
        # Now extract elemental symbol
        for i in range(ci,len(nuc_str)):
            if isalpha_list[i]:
                symbol += nuc_str[i]
    
    else: # if elemental symbol is listed first
        ci = 0
        # Extract all characters before first number as the elemental symbol
        while nuc_str[ci].isalpha():
            symbol += nuc_str[ci]
            ci += 1
            
        # now, extract mass 
        while nuc_str[ci].isdigit():
            mass += nuc_str[ci]
            ci += 1
            if ci == len(nuc_str):
                break
        
        # lastly, extract isomeric state, if present
        if ci != len(nuc_str):
            isost = nuc_str[ci:]
        
    # treating the cases of lowercase-specified particles (n, d, t, etc.)
    if symbol == '' and isost != '':
        symbol = isost
        isost = ''
        
    
    if symbol == 'n':
        Z = 0
    elif symbol == 'p' or symbol == 'd' or symbol == 't':
        Z = 1
    else:
        Z = Element_Sym_to_Z(symbol)  
    
    A = int(mass)
    
    if isost.strip()=='' or isost=='g':
        M = 0
    elif isost=='m' or isost=='m1':
        M = 1
    elif isost=='n' or isost=='m2':
        M = 2
    elif isost=='o' or isost=='m3':
        M = 3
    elif isost=='p' or isost=='m4':
        M = 4
    elif isost=='q' or isost=='m5':
        M = 5
    else:
        print("Unknown isomeric state {}, assumed ground state".format(isost))
        M = 0
    
    ZZZAAAM = 10000*Z + 10*A + M 
    
    return ZZZAAAM
def nuclide_plain_str_to_Dname(nuc_str)

Description

Converts a plaintext string of a nuclide to a DCHAIN-formatted nuclide string

Dependencies

Inputs

  • nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
    • Isomeric/metastable state characters must always immediately follow the atomic mass characters. Isomeric state labels MUST either:
      • (1) be a single lower-case character
      • (2) begin with any non-numeric character and end with a number
    • Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
    • Elemental symbols MUST begin with an upper-case character

Outputs

  • DCHAIN-formatted string of nuclide name
Expand source code
def nuclide_plain_str_to_Dname(nuc_str):
    '''
    Description:
        Converts a plaintext string of a nuclide to a DCHAIN-formatted nuclide string
    
    Dependencies:
        - `nuclide_plain_str_ZZZAAAM` 
        - `ZAM_to_Dname`
    
    Inputs:
    
       - `nuc_str` = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
            + Isomeric/metastable state characters must always immediately follow the atomic mass characters.
                Isomeric state labels MUST either:
                  - (1) be a single lower-case character
                  - (2) begin with any non-numeric character and end with a number
            + Atomic mass numbers must be nonnegative integers OR the string `"nat"` (in which case no metastable states can be written)
            + Elemental symbols MUST begin with an upper-case character    
    
    Outputs:
        - DCHAIN-formatted string of nuclide name
    '''
    return ZAM_to_Dname(nuclide_plain_str_ZZZAAAM(nuc_str))
def nuclide_plain_str_to_latex_str(nuc_str, include_Z=False)

Description

Converts a plaintext string of a nuclide to a LaTeX-formatted raw string Note: if you already have the Z, A, and isomeric state information determined, the "nuclide_to_Latex_form" function can be used instead

Dependencies

Inputs

(required)

  • nuc_str = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
    • Isomeric/metastable state characters must always immediately follow the atomic mass characters. Isomeric state labels MUST either:
      • (1) be a single lower-case character OR
      • (2) begin with any non-numeric character and end with a number
    • Atomic mass numbers must be nonnegative integers OR the string "nat" (in which case no metastable states can be written)
    • Elemental symbols MUST begin with an upper-case character

Inputs

(optional)

  • include_Z = True/False determining whether the nuclide's atomic number Z will be printed as a subscript beneath the atomic mass

Outputs

  • LaTeX-formatted raw string of nuclide
Expand source code
def nuclide_plain_str_to_latex_str(nuc_str,include_Z=False):
    '''
    Description:
        Converts a plaintext string of a nuclide to a LaTeX-formatted raw string
        Note: if you already have the Z, A, and isomeric state information determined, the "nuclide_to_Latex_form" function can be used instead
    
    Dependencies:
        - `Element_Z_to_Sym` (only required if `include_Z = True`)
    
    Inputs:
        (required)
     
       - `nuc_str` = string to be converted; a huge variety of formats are supported, but they all must follow the following rules:
           + Isomeric/metastable state characters must always immediately follow the atomic mass characters.
               Isomeric state labels MUST either:
               - (1) be a single lower-case character OR 
               - (2) begin with any non-numeric character and end with a number
           + Atomic mass numbers must be nonnegative integers OR the string `"nat"` (in which case no metastable states can be written)
           + Elemental symbols MUST begin with an upper-case character
    
    Inputs:
       (optional)
        
       - `include_Z` = `True`/`False` determining whether the nuclide's atomic number Z will be printed as a subscript beneath the atomic mass
    
    Outputs:
        - LaTeX-formatted raw string of nuclide
    '''
    tex_str = r''
    
    # remove unwanted characters from provided string
    delete_characters_list = [' ', '-', '_']
    for dc in delete_characters_list:
        nuc_str = nuc_str.replace(dc,'')
    
    # determine which characters are letters versus numbers
    isalpha_list = []
    isdigit_list = []
    for c in nuc_str:
        isalpha_list.append(c.isalpha())
        isdigit_list.append(c.isdigit())
    
    symbol = ''
    mass = ''
    isost = ''
    
    # string MUST begin with either mass number or elemental symbol
    if isdigit_list[0] or nuc_str[0:3]=='nat': # mass first
        mass_first = True 
    else:
        mass_first = False 
    
    if mass_first:
        if nuc_str[0:3]=='nat':
            mass = 'nat'
            ci = 3
        else:
            ci = 0
            while isdigit_list[ci]:
                mass += nuc_str[ci]
                ci += 1
            mass = str(int(mass)) # eliminate any extra leading zeros
            # encountered a non-numeric character, end of mass
            # now, determine if metastable state is listed or if element is listed next
            # first, check to see if any other numerals are in string
            lni = 0 # last numeral index
            for i in range(ci,len(nuc_str)):
                if isdigit_list[i]: 
                    lni = i
            if lni != 0:
                # grab all characters between ci and last numeral as metastable state
                isost = nuc_str[ci:lni+1]
                ci = lni + 1
            else: # no more numerals in string, now check for single lower-case letter
                if isalpha_list[ci] and nuc_str[ci].islower():
                    isost = nuc_str[ci]
                    ci += 1
            
            # Now extract elemental symbol
            for i in range(ci,len(nuc_str)):
                if isalpha_list[i]:
                    symbol += nuc_str[i]
    
    else: # if elemental symbol is listed first
        if 'nat' in nuc_str:
            mass = 'nat'
            nuc_str = nuc_str.replace('nat','')
            
        ci = 0
        # Extract all characters before first number as the elemental symbol
        while nuc_str[ci].isalpha():
            symbol += nuc_str[ci]
            ci += 1
            
        # now, extract mass 
        if mass != 'nat':
            while nuc_str[ci].isdigit():
                mass += nuc_str[ci]
                ci += 1
                if ci == len(nuc_str):
                    break
            
            # lastly, extract isomeric state, if present
            if ci != len(nuc_str):
                isost = nuc_str[ci:]
        
    # treating the cases of lowercase-specified particles (n, d, t, etc.)
    if symbol == '' and isost != '':
        symbol = isost
        isost = ''
        
    # Now assemble LaTeX string for nuclides
    if include_Z:
        if symbol == 'n':
            Z = 0
        elif symbol == 'p' or symbol == 'd' or symbol == 't':
            Z = 1
        else:
            Z = Element_Sym_to_Z(symbol)
        Z = str(int(Z))
        tex_str = r"$^{{{}{}}}_{{{}}}$".format(mass,isost,Z) + "{}".format(symbol)
    else:
        tex_str = r"$^{{{}{}}}$".format(mass,isost) + "{}".format(symbol)
    
    return tex_str
def nuclide_to_Latex_form(Z, A, m='')

Description

Form a LaTeX-formatted string of a nuclide provided its information

Dependencies

Element_Z_to_Sym() (only required if inputed Z is not already an elemental symbol)

Inputs

  • Z = atomic number of nuclide (int, float, or string) or elemental symbol (string)
  • A = atomic mass of nuclide (int, float, or string) or string to go in place of A (ex. 'nat')
  • m = metastable state (D='', ground state); this will be appended to the end of A if not a string already, it will be converted into one and appended to 'm' (ex. 1 -> 'm1')

Outputs

  • LaTeX-formatted raw string of a nuclide, excellent for plot titles, labels, and auto-generated LaTeX documents
Expand source code
def nuclide_to_Latex_form(Z,A,m=''):
    '''
    Description:
        Form a LaTeX-formatted string of a nuclide provided its information
    
    Dependencies:
        `Element_Z_to_Sym` 
        (only required if inputed Z is not already an elemental symbol)
    
    Inputs:
        - `Z` = atomic number of nuclide (int, float, or string) or elemental symbol (string)
        - `A` = atomic mass of nuclide (int, float, or string) or string to go in place of A (ex. 'nat')
        - `m` = metastable state (D='', ground state); this will be appended to the end of A
              if not a string already, it will be converted into one and appended to 'm' (ex. 1 -> 'm1')
    
    Outputs:
        - LaTeX-formatted raw string of a nuclide, excellent for plot titles, labels, and auto-generated LaTeX documents
    '''
    if isinstance(A,(int,float)): A = str(int(A))
    if not isinstance(Z,str): symbol = Element_Z_to_Sym(int(Z))
    if isinstance(m,float): m = int(m)
    if isinstance(m,int): m = 'm' + str(m)
    latex_str = r"$^{{{}{}}}$".format(A,m) + "{}".format(symbol)
    return latex_str
def parse_DCHAIN_act_file(act_file_path)

Description: This code parses the .act file generated by DCHAIN

Inputs

  • path to a DCHAIN-generated .act file

Outputs

  • length R list of region numbers
  • length T list of measurement times (in sec) from start of irradiation
  • time of end of irradiation (in sec)
  • NumPy array of dimension RxTxNx11x2 of nuclide table data (N=max recorded table length)
  • NumPy array of dimension RxTxEx5x2 of gamma spectrum table data (E=number of energy groups of gamma spectra)
  • NumPy array of dimension RxTxNx12x2 of top-10 list table data
  • List containing 3 lists of column headers for the preceding three NumPy arrays
  • list of the below lists/arrays of summary information
    • NumPy array of dimension Rx7 of region-specific summary info: Beam current, beam energy, beam power, total neutron flux, region volume, irradiation time, region number
    • list of length 7 containing descriptions of the above items
    • NumPy array of dimension RxTx12x2 of region and time-specific summary info Rank, [nuclide, A/cc, A, %], [nuclide, P/cc, P, %], [nuclide, H, %] with values and absolute uncertainties
    • list of length 12 containing descriptions of the above items
Expand source code
def parse_DCHAIN_act_file(act_file_path):
    '''
    Description: 
         This code parses the .act file generated by DCHAIN
    
    Inputs:
        - path to a DCHAIN-generated .act file
    
    Outputs:
        - length R list of region numbers 
        - length T list of measurement times (in sec) from start of irradiation
        - time of end of irradiation (in sec)
        - NumPy array of dimension RxTxNx11x2 of nuclide table data (N=max recorded table length)
        - NumPy array of dimension RxTxEx5x2 of gamma spectrum table data (E=number of energy groups of gamma spectra)
        - NumPy array of dimension RxTxNx12x2 of top-10 list table data
        - List containing 3 lists of column headers for the preceding three NumPy arrays
        - list of the below lists/arrays of summary information
            + NumPy array of dimension Rx7 of region-specific summary info:
                Beam current, beam energy, beam power, total neutron flux, region volume, irradiation time, region number
            + list of length 7 containing descriptions of the above items
            + NumPy array of dimension RxTx12x2 of region and time-specific summary info
                Rank, [nuclide, A/cc, A, %], [nuclide, P/cc, P, %], [nuclide, H, %] with values and absolute uncertainties
            + list of length 12 containing descriptions of the above items
    '''
    # Extract file info
    f = open(act_file_path)
    lines = f.readlines()
    f.close()
    
    # Parse file to determine number of regions
    nreg = 0
    reg_nos = []
    for line in lines:
        if 'region number' in line:
            nreg += 1
            reg_nos.append(int(line[21:31]))
    
    # Parse file again to determine number of time steps
    current_reg_no = -1
    #irradiation_time = -1.0 # seconds
    end_of_irradiation_time = -1.0 # seconds
    ntimes = 0
    time_strs = []
    time_list_sec = [] # time steps in seconds
    time_list_sec_after_EOB = [] # time steps in seconds after EOB
    for line in lines:
        #if 'irradiation time' in line: irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33])
        if 'region number' in line: current_reg_no = int(line[21:31])
        if current_reg_no != reg_nos[0]: continue # only read times from first region
        if '--- output time ---' in line:
            ntimes += 1
            time_strs.append(line)
            time_list_sec.append(float(line[40:53]))
            time_list_sec_after_EOB.append(0.0)
        if ('--- output time ---' in line) and ('after the last shutdown' in line):
            time_list_sec_after_EOB[ntimes-1] = float(line[87:97])*time_str_to_sec_multiplier(line[99])
            if end_of_irradiation_time == -1:
                end_of_irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33]) - float(line[87:97])*time_str_to_sec_multiplier(line[99])
            
    for ti in range(len(time_list_sec_after_EOB)):
        if time_list_sec_after_EOB[ti]==0.0:
            time_list_sec_after_EOB[ti] = time_list_sec[ti] - end_of_irradiation_time
            
    # Extract "summary info" from file
    ri = -1 # region index
    ti = -1 # time index
    r_summary_info = np.empty((nreg,7), dtype='object') # (regionwise) initialize Rx7 array
    r_summary_info_description = ['region number','irradiation time [s]','region volume [cc]','neutron flux [n/cm^2/s]','beam power [MW]','beam energy [GeV]','beam current [mA]'] # (regionwise) initialize Rx7 array
    '''
    r_summary_info[i,j,k]
       i = region number
       j = category (see table below)
    index  meaning
       0   region number
       1   irradiation time [sec]
       2   region volume [cc]
       3   neutron flux [n/cm^2/s]
       4   beam power [MW]
       5   beam energy [GeV]
       6   beam current [mA] 
    '''
    rt_summary_info = np.empty((nreg,ntimes,12,2), dtype='object') # (region-and-timewise) initialize RxTx12x2 array
    rt_summary_info_description = ['total gamma flux [#/s/cc]','total gamma energy flux [MeV/s/cc]','annihilation gamma flux [#/s/cc]','gamma current underflow [#/s]','gamma current overflow [#/s]','total activity [Bq/cc]','total decay heat [W/cc]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','activated atoms [#/cc]','total gamma dose rate [uSV/h*m^2]'] # (region-and-timewise) initialize RxTx12 array
    '''
    rt_summary_info[i,j,k,m]
       i = region number
       j = output time step
       k = category (see table below)
       m = value (k=0) or absolute uncertainty (k=1)
    index  meaning
       0   total gamma flux [#/s/cc]
       1   total gamma energy flux [MeV/s/cc]
       2   annihilation gamma flux [#/s/cc]
       3   gamma current underflow [#/s] (gammas below lowest energy bin)
       4   gamma current overflow [#/s] (gammas above highest energy bin)
       5   total activity [Bq/cc]
       6   total decay heat [W/cc]
       7     beta decay heat [W/cc]
       8     gamma decay heat [W/cc]
       9     alpha decay heat [W/cc]
       10  activated atoms [#/cc]
       11  total gamma dose rate [uSV/h*m^2]
    '''
    for li in range(len(lines)):
        line = lines[li]
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
            
            # region-specific summary info
            r_summary_info[ri,0] = int(line[21:31])
            r_summary_info[ri,1] = float(lines[li-1][21:31])*time_str_to_sec_multiplier(lines[li-1][33])
            r_summary_info[ri,2] = float(lines[li-2][21:31])
            r_summary_info[ri,3] = float(lines[li-3][21:31])
            r_summary_info[ri,4] = float(lines[li-4][21:31])
            r_summary_info[ri,5] = float(lines[li-5][21:31])
            r_summary_info[ri,6] = float(lines[li-6][21:31])
            
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            
        # gamma info specific to region and time
        if 'total gamma-ray flux' in line:
            rt_summary_info[ri,ti,0,0] = float(line[38:49])
            rt_summary_info[ri,ti,1,0] = float(lines[li+1][38:49])
            rt_summary_info[ri,ti,2,0] = float(lines[li+2][38:49])
            rt_summary_info[ri,ti,0,1] = float(line[53:64])
            rt_summary_info[ri,ti,1,1] = float(lines[li+1][53:64])
            rt_summary_info[ri,ti,2,1] = float(lines[li+2][53:64])
            if 'group limitation' in lines[li+3]:
                rt_summary_info[ri,ti,3,0] = float(lines[li+3][91:101])
                rt_summary_info[ri,ti,4,0] = float(lines[li+3][64:74])
            else:
                rt_summary_info[ri,ti,3,0] = 0.0
                rt_summary_info[ri,ti,4,0] = 0.0
        
        if 'no gamma-ray' in line:
            rt_summary_info[ri,ti,0,0] = 0.0
            rt_summary_info[ri,ti,1,0] = 0.0
            rt_summary_info[ri,ti,2,0] = 0.0
            rt_summary_info[ri,ti,0,1] = 0.0
            rt_summary_info[ri,ti,1,1] = 0.0
            rt_summary_info[ri,ti,2,1] = 0.0
            rt_summary_info[ri,ti,3,0] = 0.0
            rt_summary_info[ri,ti,4,0] = 0.0
        
        # activation info specific to region and time
        if 'total activity' in line:
            rt_summary_info[ri,ti,5,0] = float(line[24:36])
            rt_summary_info[ri,ti,6,0] = float(lines[li+1][24:36])
            rt_summary_info[ri,ti,7,0] = float(lines[li+2][24:36])
            rt_summary_info[ri,ti,8,0] = float(lines[li+3][24:36])
            rt_summary_info[ri,ti,9,0] = float(lines[li+4][24:36])
            rt_summary_info[ri,ti,10,0]= float(lines[li+5][24:36])
            rt_summary_info[ri,ti,11,0]= float(lines[li+6][24:36])
            rt_summary_info[ri,ti,5,1] = float(line[40:52])
            rt_summary_info[ri,ti,6,1] = float(lines[li+1][40:52])
            rt_summary_info[ri,ti,7,1] = float(lines[li+2][40:52])
            rt_summary_info[ri,ti,8,1] = float(lines[li+3][40:52])
            rt_summary_info[ri,ti,9,1] = float(lines[li+4][40:52])
            rt_summary_info[ri,ti,10,1]= float(lines[li+5][40:52])
            rt_summary_info[ri,ti,11,1]= float(lines[li+6][40:52])
    
    summary_info = [r_summary_info, r_summary_info_description, rt_summary_info, rt_summary_info_description]
    
    # Extract major "blocks" (nuclides, gamma spec, top10 list) for each time step in each region
    act_block_text = np.empty((nreg,ntimes,3), dtype='object') # initialize RxTx3 array to hold character strings where final index 0=nuclides, 1=gamma-spec, and 2=top10-list
    ri = -1 # region index
    ti = -1 # time index
    qi = -1 # quantity index - 0=nuclides, 1=gamma-spec, and 2=top10-list
    max_array_len = [0,0,0] # maximum number of entries for a given quantity
    current_array_len = [0,0,0] # current number of entries for a given quantity
    for line in lines:
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            qi = 0 # reset quantity index
        if 'gamma-ray spectrum weighted by energy' in line:
            qi = 1
        if 'dominant nuclides (top 10)' in line:
            qi = 2
        if 'total' in line[:20] or line=='\n': # no longer reading info block
            if current_array_len[qi] > max_array_len[qi]: 
                max_array_len[qi] = current_array_len[qi]
            current_array_len[qi] = 0
            qi = -1 
            
        if qi < 0: continue # not in region of interest
        
        try:
            act_block_text[ri,ti,qi] += line 
        except:
            act_block_text[ri,ti,qi] = line 
            
        current_array_len[qi] += 1
        
    header_len = [3,4,3] # number of lines present in table header
    
    nuclides_produced = np.empty((nreg,ntimes,max_array_len[0]-header_len[0],11,2), dtype='object') # initialize RxTxNx11x2 array to hold nuclide information
    gamma_spectra     = np.empty((nreg,ntimes,max_array_len[1]-header_len[1], 5,2), dtype='object') # initialize RxTxNx5x2 array to hold gamma spec information
    top10_lists       = np.empty((nreg,ntimes,max_array_len[2]-header_len[2],12,2), dtype='object') # initialize RxTxNx12x2 array to hold top 10 list information
    
    column_headers = [ [],[],[] ]
    
    # Now, populate each array
    
    # Nuclides produced
    column_headers[0] = ['nuclide','atoms [#/cc]','activity [Bq/cc]','activity [Bq]','rate [%]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','total decay heat [W/cc]','half life [s]','dose-rate [uSv/h*m^2]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,0].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[0]: continue # in header lines
                ii = ei - header_len[0] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                rel_err = float(line[51:61])
                nuclides_produced[ri,ti,ii,0,0] = line[3:9]            # nuclide
                nuclides_produced[ri,ti,ii,1,0] = float(line[12:23].replace('      ','0.0'))    # atoms [#/cc]
                nuclides_produced[ri,ti,ii,2,0] = float(line[25:36].replace('      ','0.0'))    # activity [Bq/cc]
                nuclides_produced[ri,ti,ii,3,0] = float(line[38:49].replace('      ','0.0'))    # activity [Bq]
                nuclides_produced[ri,ti,ii,4,0] = float(line[61:68].replace('     ','0.0'))    # rate [%]
                nuclides_produced[ri,ti,ii,5,0] = float(line[70:80].replace('      ','0.0'))    # beta decay heat [W/cc]
                nuclides_produced[ri,ti,ii,6,0] = float(line[81:91].replace('      ','0.0'))    # gamma decay heat [W/cc]
                nuclides_produced[ri,ti,ii,7,0] = float(line[92:102].replace('      ','0.0'))   # alpha decay heat [W/cc]
                nuclides_produced[ri,ti,ii,8,0] = float(line[103:113].replace('      ','0.0'))  # total decay heat [W/cc]
                nuclides_produced[ri,ti,ii,9,0] = float(line[116:126].replace('stable','0.0')) # half life [s]
                if nuclides_produced[ri,ti,ii,9,0] == 0:
                    nuclides_produced[ri,ti,ii,10,0]= 0.0  # dose-rate [uSv/h*m^2]
                else:
                    nuclides_produced[ri,ti,ii,10,0]= float(line[128:138].replace('      ','0.0'))  # dose-rate [uSv/h*m^2]
                
                # absolute errors for corresponding values
                nuclides_produced[ri,ti,ii,1,1] = nuclides_produced[ri,ti,ii,1,0]*rel_err
                nuclides_produced[ri,ti,ii,2,1] = nuclides_produced[ri,ti,ii,2,0]*rel_err
                nuclides_produced[ri,ti,ii,3,1] = nuclides_produced[ri,ti,ii,3,0]*rel_err
                nuclides_produced[ri,ti,ii,4,1] = nuclides_produced[ri,ti,ii,4,0]*rel_err
                nuclides_produced[ri,ti,ii,5,1] = nuclides_produced[ri,ti,ii,5,0]*rel_err
                nuclides_produced[ri,ti,ii,6,1] = nuclides_produced[ri,ti,ii,6,0]*rel_err
                nuclides_produced[ri,ti,ii,7,1] = nuclides_produced[ri,ti,ii,7,0]*rel_err
                nuclides_produced[ri,ti,ii,8,1] = nuclides_produced[ri,ti,ii,8,0]*rel_err
                nuclides_produced[ri,ti,ii,9,1] = nuclides_produced[ri,ti,ii,9,0]*rel_err
                nuclides_produced[ri,ti,ii,10,1]= nuclides_produced[ri,ti,ii,10,0]*rel_err
                
    # Gamma-ray spectra
    column_headers[1] = ['group number','bin energy lower-bound [MeV]','bin energy upper-bound [MeV]','flux [#/s/cc]','energy flux [MeV/s/cc]']
    for ri in range(nreg):
        for ti in range(ntimes):
            if not act_block_text[ri,ti,1]: 
                gamma_spectra[ri,ti,:,0,0] = None  # group number
                gamma_spectra[ri,ti,:,1,0] = None  # bin energy lower-bound [MeV]
                gamma_spectra[ri,ti,:,2,0] = None  # bin energy upper-bound [MeV]
                gamma_spectra[ri,ti,:,3,0] = 0.0   # flux [#/s/cc]
                gamma_spectra[ri,ti,:,4,0] = 0.0   # energy flux [MeV/s/cc]
                gamma_spectra[ri,ti,:,3,1] = 0.0   # flux absolute error [#/s/cc]
                gamma_spectra[ri,ti,:,4,1] = 0.0   # energy flux absolute error [MeV/s/cc]
                continue
            table_text = act_block_text[ri,ti,1].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[1]: continue # in header lines
                ii = ei - header_len[1] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                gamma_spectra[ri,ti,ii,0,0] = int(line[1:4])       # group number
                gamma_spectra[ri,ti,ii,1,0] = float(line[17:25])   # bin energy lower-bound [MeV]
                gamma_spectra[ri,ti,ii,2,0] = float(line[7:15])    # bin energy upper-bound [MeV]
                gamma_spectra[ri,ti,ii,3,0] = float(line[27:38])   # flux [#/s/cc]
                gamma_spectra[ri,ti,ii,4,0] = float(line[40:51])   # energy flux [MeV/s/cc]
                gamma_spectra[ri,ti,ii,3,1] = gamma_spectra[ri,ti,ii,3,0]*float(line[53:64])   # flux absolute error [#/s/cc]
                gamma_spectra[ri,ti,ii,4,1] = gamma_spectra[ri,ti,ii,4,0]*float(line[53:64])   # energy flux absolute error [MeV/s/cc]
    
    # Top 10 lists
    column_headers[2] = ['rank','nuclide -  Activity ranking','activity [Bq/cc]','activity [Bq]','rate [%]','nuclide - Decay heat ranking','decay heat [W/cc]','decay heat [W]','rate [%]','nuclide - Dose rate ranking','dose-rate [uSv/h*m^2]','rate [%]']
    for ri in range(nreg):
        for ti in range(ntimes):
            if not act_block_text[ri,ti,2]: continue
            table_text = act_block_text[ri,ti,2].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[2]: continue # in header lines
                ii = ei - header_len[2] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                top10_lists[ri,ti,ii,0,0] = int(line[1:5])       # number/rank
                top10_lists[ri,ti,ii,1,0] = line[8:14]           # nuclide -  Activity ranking
                top10_lists[ri,ti,ii,2,0] = float(line[15:26])   # activity [Bq/cc]
                top10_lists[ri,ti,ii,3,0] = float(line[26:37])   # activity [Bq]
                top10_lists[ri,ti,ii,4,0] = float(line[48:55])   # rate [%]
                rel_err = float(line[37:48])
                top10_lists[ri,ti,ii,2,1] = top10_lists[ri,ti,ii,2,0]*rel_err   # activity absolute error [Bq/cc]
                top10_lists[ri,ti,ii,3,1] = top10_lists[ri,ti,ii,3,0]*rel_err   # activity absolute error [Bq]
                top10_lists[ri,ti,ii,4,1] = top10_lists[ri,ti,ii,4,0]*rel_err   # rate absolute error [%]
                
                top10_lists[ri,ti,ii,5,0] = line[60:66]          # nuclide - Decay heat ranking
                top10_lists[ri,ti,ii,6,0] = float(line[67:78])   # decay heat [W/cc]
                top10_lists[ri,ti,ii,7,0] = float(line[78:89])   # decay heat [W]
                top10_lists[ri,ti,ii,8,0] = float(line[100:107]) # rate [%]
                rel_err = float(line[89:100])
                top10_lists[ri,ti,ii,6,1] = top10_lists[ri,ti,ii,6,0]*rel_err   # decay heat absolute error [W/cc]
                top10_lists[ri,ti,ii,7,1] = top10_lists[ri,ti,ii,7,0]*rel_err   # decay heat absolute error [W]
                top10_lists[ri,ti,ii,8,1] = top10_lists[ri,ti,ii,8,0]*rel_err   # rate absolute error [%]
                
                top10_lists[ri,ti,ii,9,0] = line[112:118]        # nuclide - Dose rate ranking
                top10_lists[ri,ti,ii,10,0]= float(line[119:130]) # dose-rate [uSv/h*m^2]
                top10_lists[ri,ti,ii,11,0]= float(line[142:149]) # rate [%]
                rel_err = float(line[131:142])
                top10_lists[ri,ti,ii,10,1]= top10_lists[ri,ti,ii,10,0]*rel_err # dose-rate absolute error [uSv/h*m^2]
                top10_lists[ri,ti,ii,11,1]= top10_lists[ri,ti,ii,11,0]*rel_err # rate absolute error [%]
    
    return reg_nos, time_list_sec, time_list_sec_after_EOB, end_of_irradiation_time, nuclides_produced, gamma_spectra, top10_lists, column_headers, summary_info
def parse_DCHAIN_act_file_legacy(act_file_path)

Description

This code parses the .act file generated by DCHAIN (without uncertainty values)

Inputs

  • path to a DCHAIN-generated .act file

Outputs

  • length R list of region numbers
  • length T list of measurement times (in sec) from start of irradiation
  • time of end of irradiation (in sec)
  • NumPy array of dimension RxTxNx11 of nuclide table data (N=max recorded table lenth)
  • NumPy array of dimension RxTxNx5 of gamma spectrum table data
  • NumPy array of dimension RxTxNx12 of top-10 list table data
  • List containing 3 lists of column headers for the preceding three NumPy arrays
  • list of the below lists/arrays of summary information
    • NumPy array of dimension Rx7 of region-specific summary info
    • list of length 7 containing descriptions of the above items
    • NumPy array of dimension RxTx12 of region and time-specific summary info
    • list of length 12 containing descriptions of the above items
Expand source code
def parse_DCHAIN_act_file_legacy(act_file_path):
    '''
    Description:
       This code parses the .act file generated by DCHAIN (without uncertainty values)
    
    Inputs:
        - path to a DCHAIN-generated .act file
        
    Outputs:
        - length R list of region numbers 
        - length T list of measurement times (in sec) from start of irradiation
        - time of end of irradiation (in sec)
        - NumPy array of dimension RxTxNx11 of nuclide table data (N=max recorded table lenth)
        - NumPy array of dimension RxTxNx5 of gamma spectrum table data
        - NumPy array of dimension RxTxNx12 of top-10 list table data
        - List containing 3 lists of column headers for the preceding three NumPy arrays
        - list of the below lists/arrays of summary information
            - NumPy array of dimension Rx7 of region-specific summary info
            - list of length 7 containing descriptions of the above items
            - NumPy array of dimension RxTx12 of region and time-specific summary info
            - list of length 12 containing descriptions of the above items
    '''
    # Extract file info
    f = open(act_file_path)
    lines = f.readlines()
    f.close()
    
    # Parse file to determine number of regions
    nreg = 0
    reg_nos = []
    for line in lines:
        if 'region number' in line:
            nreg += 1
            reg_nos.append(int(line[21:31]))
    
    # Parse file again to determine number of time steps
    current_reg_no = -1
    #irradiation_time = -1.0 # seconds
    end_of_irradiation_time = -1.0 # seconds
    ntimes = 0
    time_strs = []
    time_list_sec = [] # time steps in seconds
    for line in lines:
        #if 'irradiation time' in line: irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33])
        if 'region number' in line: current_reg_no = int(line[21:31])
        if current_reg_no != reg_nos[0]: continue # only read times from first region
        if '--- output time ---' in line:
            ntimes += 1
            time_strs.append(line)
            time_list_sec.append(float(line[40:53]))
        if ('--- output time ---' in line) and ('after the last shutdown' in line) and end_of_irradiation_time == -1:
            end_of_irradiation_time = float(line[21:31])*time_str_to_sec_multiplier(line[33]) - float(line[88:97])*time_str_to_sec_multiplier(line[99])
            
    # Extract "summary info" from file
    ri = -1 # region index
    ti = -1 # time index
    r_summary_info = np.empty((nreg,7), dtype='object') # (regionwise) initialize Rx7 array
    r_summary_info_description = ['region number','irradiation time [s]','region volume [cc]','neutron flux [n/cm^2/s]','beam power [MW]','beam energy [GeV]','beam current [mA]'] # (regionwise) initialize Rx7 array
    '''
    index  meaning
       0   region number
       1   irradiation time [sec]
       2   region volume [cc]
       3   neutron flux [n/cm^2/s]
       4   beam power [MW]
       5   beam energy [GeV]
       6   beam current [mA] 
    '''
    rt_summary_info = np.empty((nreg,ntimes,12), dtype='object') # (region-and-timewise) initialize RxTx12 array
    rt_summary_info_description = ['total gamma flux [#/s/cc]','total gamma energy flux [MeV/s/cc]','annihilation gamma flux [#/s/cc]','gamma current underflow [#/s]','gamma current overflow [#/s]','total activity [Bq/cc]','total decay heat [W/cc]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','activated atoms [#/cc]','total gamma dose rate [uSV/h*m^2]'] # (region-and-timewise) initialize RxTx12 array
    '''
    index  meaning
       0   total gamma flux [#/s/cc]
       1   total gamma energy flux [MeV/s/cc]
       2   annihilation gamma flux [#/s/cc]
       3   gamma current underflow [#/s] (gammas below lowest energy bin)
       4   gamma current overflow [#/s] (gammas above highest energy bin)
       5   total activity [Bq/cc]
       6   total decay heat [W/cc]
       7     beta decay heat [W/cc]
       8     gamma decay heat [W/cc]
       9     alpha decay heat [W/cc]
       10  activated atoms [#/cc]
       11  total gamma dose rate [uSV/h*m^2]
    '''
    for li in range(len(lines)):
        line = lines[li]
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
            
            # region-specific summary info
            r_summary_info[ri,0] = int(line[21:31])
            r_summary_info[ri,1] = float(lines[li-1][21:31])*time_str_to_sec_multiplier(lines[li-1][33])
            r_summary_info[ri,2] = float(lines[li-2][21:31])
            r_summary_info[ri,3] = float(lines[li-3][21:31])
            r_summary_info[ri,4] = float(lines[li-4][21:31])
            r_summary_info[ri,5] = float(lines[li-5][21:31])
            r_summary_info[ri,6] = float(lines[li-6][21:31])
            
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            
        # gamma info specific to region and time
        if 'total gamma-ray flux' in line:
            rt_summary_info[ri,ti,0] = float(line[39:49])
            rt_summary_info[ri,ti,1] = float(lines[li+1][39:49])
            rt_summary_info[ri,ti,2] = float(lines[li+2][39:49])
            if 'group limitation' in lines[li+3]:
                rt_summary_info[ri,ti,3] = float(lines[li+3][91:101])
                rt_summary_info[ri,ti,4] = float(lines[li+3][64:74])
            else:
                rt_summary_info[ri,ti,3] = 0.0
                rt_summary_info[ri,ti,4] = 0.0
        
        # activation info specific to region and time
        if 'total activity' in line:
            rt_summary_info[ri,ti,5] = float(line[25:36])
            rt_summary_info[ri,ti,6] = float(lines[li+1][25:36])
            rt_summary_info[ri,ti,7] = float(lines[li+2][25:36])
            rt_summary_info[ri,ti,8] = float(lines[li+3][25:36])
            rt_summary_info[ri,ti,9] = float(lines[li+4][25:36])
            rt_summary_info[ri,ti,10]= float(lines[li+5][25:36])
            rt_summary_info[ri,ti,11]= float(lines[li+6][25:36])
    
    summary_info = [r_summary_info, r_summary_info_description, rt_summary_info, rt_summary_info_description]
    
    # Extract major "blocks" (nuclides, gamma spec, top10 list) for each time step in each region
    act_block_text = np.empty((nreg,ntimes,3), dtype='object') # initialize RxTx3 array to hold character strings where final index 0=nuclides, 1=gamma-spec, and 2=top10-list
    ri = -1 # region index
    ti = -1 # time index
    qi = -1 # quantity index - 0=nuclides, 1=gamma-spec, and 2=top10-list
    max_array_len = [0,0,0] # maximum number of entries for a given quantity
    current_array_len = [0,0,0] # current number of entries for a given quantity
    for line in lines:
        if 'region number' in line: 
            ri = find(int(line[21:31]),reg_nos)
        if '--- output time ---' in line: 
            ti = find(line,time_strs)
            qi = 0 # reset quantity index
        if 'gamma-ray spectrum weighted by energy' in line:
            qi = 1
        if 'dominant nuclides (top 10)' in line:
            qi = 2
        if 'total' in line[:20] or line=='\n': # no longer reading info block
            if current_array_len[qi] > max_array_len[qi]: 
                max_array_len[qi] = current_array_len[qi]
            current_array_len[qi] = 0
            qi = -1 
            
        if qi < 0: continue # not in region of interest
        
        try:
            act_block_text[ri,ti,qi] += line 
        except:
            act_block_text[ri,ti,qi] = line 
            
        current_array_len[qi] += 1
        
    header_len = [3,4,3] # number of lines present in table header
    
    nuclides_produced = np.empty((nreg,ntimes,max_array_len[0]-header_len[0],11), dtype='object') # initialize RxTxNx11 array to hold nuclide information
    gamma_spectra     = np.empty((nreg,ntimes,max_array_len[1]-header_len[1], 5), dtype='object') # initialize RxTxNx5 array to hold gamma spec information
    top10_lists       = np.empty((nreg,ntimes,max_array_len[2]-header_len[2],12), dtype='object') # initialize RxTxNx12 array to hold top 10 list information
    
    column_headers = [ [],[],[] ]
    
    # Now, populate each array
    
    # Nuclides produced
    column_headers[0] = ['nuclide','atoms [#/cc]','activity [Bq/cc]','activity [Bq]','rate [%]','beta decay heat [W/cc]','gamma decay heat [W/cc]','alpha decay heat [W/cc]','total decay heat [W/cc]','half life [s]','dose-rate [uSv/h*m^2]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,0].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[0]: continue # in header lines
                ii = ei - header_len[0] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                nuclides_produced[ri,ti,ii,0] = line[3:9]            # nuclide
                nuclides_produced[ri,ti,ii,1] = float(line[13:23])   # atoms [#/cc]
                nuclides_produced[ri,ti,ii,2] = float(line[26:36])   # activity [Bq/cc]
                nuclides_produced[ri,ti,ii,3] = float(line[38:48])   # activity [Bq]
                nuclides_produced[ri,ti,ii,4] = float(line[49:56].replace('     ','0.0'))   # rate [%]
                nuclides_produced[ri,ti,ii,5] = float(line[58:67])   # beta decay heat [W/cc]
                nuclides_produced[ri,ti,ii,6] = float(line[69:78])   # gamma decay heat [W/cc]
                nuclides_produced[ri,ti,ii,7] = float(line[80:89])   # alpha decay heat [W/cc]
                nuclides_produced[ri,ti,ii,8] = float(line[91:100])  # total decay heat [W/cc]
                nuclides_produced[ri,ti,ii,9] = float(line[104:113]) # half life [s]
                nuclides_produced[ri,ti,ii,10]= float(line[116:125]) # dose-rate [uSv/h*m^2]
                
    # Gamma-ray spectra
    column_headers[1] = ['group number','bin energy lower-bound [MeV]','bin energy upper-bound [MeV]','flux [#/s/cc]','energy flux [MeV/s/cc]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,1].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[1]: continue # in header lines
                ii = ei - header_len[1] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                gamma_spectra[ri,ti,ii,0] = int(line[1:4])       # group number
                gamma_spectra[ri,ti,ii,1] = float(line[17:25])   # bin energy lower-bound [MeV]
                gamma_spectra[ri,ti,ii,2] = float(line[7:15])    # bin energy upper-bound [MeV]
                gamma_spectra[ri,ti,ii,3] = float(line[28:38])   # flux [#/s/cc]
                gamma_spectra[ri,ti,ii,4] = float(line[41:51])   # energy flux [MeV/s/cc]
    
    # Top 10 lists
    column_headers[2] = ['rank','nuclide -  Activity ranking','activity [Bq/cc]','activity [Bq]','rate [%]','nuclide - Decay heat ranking','decay heat [W/cc]','decay heat [W]','rate [%]','nuclide - Dose rate ranking','dose-rate [uSv/h*m^2]','rate [%]']
    for ri in range(nreg):
        for ti in range(ntimes):
            table_text = act_block_text[ri,ti,2].split('\n')
            for ei in range(len(table_text)):
                if ei < header_len[2]: continue # in header lines
                ii = ei - header_len[2] # actual number index
                line = table_text[ei]
                if line=='' or line==None: continue # skip blank/nonexistent lines
                top10_lists[ri,ti,ii,0] = int(line[1:5])       # number/rank
                top10_lists[ri,ti,ii,1] = line[8:14]           # nuclide -  Activity ranking
                top10_lists[ri,ti,ii,2] = float(line[16:26])   # activity [Bq/cc]
                top10_lists[ri,ti,ii,3] = float(line[27:37])   # activity [Bq]
                top10_lists[ri,ti,ii,4] = float(line[38:43])   # rate [%]
                top10_lists[ri,ti,ii,5] = line[48:54]          # nuclide - Decay heat ranking
                top10_lists[ri,ti,ii,6] = float(line[56:66])   # decay heat [W/cc]
                top10_lists[ri,ti,ii,7] = float(line[67:77])   # decay heat [W]
                top10_lists[ri,ti,ii,8] = float(line[78:83])   # rate [%]
                top10_lists[ri,ti,ii,9] = line[88:94]          # nuclide - Dose rate ranking
                top10_lists[ri,ti,ii,10]= float(line[96:106])  # dose-rate [uSv/h*m^2]
                top10_lists[ri,ti,ii,11]= float(line[107:113]) # rate [%]
    
    return reg_nos, time_list_sec, end_of_irradiation_time, nuclides_produced, gamma_spectra, top10_lists, column_headers, summary_info
def parse_DCS_file_from_DCHAIN(filepath, relevancy_threshold=0.01, print_progress=False, nch_max=100)

Description

Parse a decay chain information file produced by DCHAIN-SP

Dependencies

import numpy as np

import time

Inputs

(required)

  • filepath = string, path to DCS file

Inputs

(optional, keyword)

  • print_progress = logical variable denoting whether time and significant nuclide info will be printed while scanning file (D=False)
  • nch_max = maximum number of chains per isotope (D=100)
  • relevancy_threshold = what fraction of total activity must a nuclide contribute to be deemed relevant

Outputs

dimension meaning for output array dimensions
R (n_reg) regions
T (ntsteps) time steps
N (nnuc_max) max number of nuclides
C (chni_max) maximum index of relevant chains
L (chln_max) maximum number of links per chain
  • 0) inventory = universal columns of DCS file [R,T,N,C,vi], vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
  • 1) l_chains = [R,T,N,C], length of listed chain
  • 2) prod_nuc = [R,T,N], strings of the nuclide being produced
  • 3) chn_indx = [R,T,N], lists of the chain indices printed
  • 4) link_nuc = [R,T,N,C,L], strings of the nuclides in each chain
  • 5) decay_mode = [R,T,N,C,L], strings of the decay modes each link undergoes to produce the next link
  • 6) link_dN_info = [R,T,N,C,L,di], extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total (only generated if these values are found in file, 'None' otherwise)
  • 7) end_of_irradiation_time = time of end of final irradiation step [seconds]
  • 8) notable_nuclides_names_by_region = list of lists (one per region) containing the relevant nuclides per region
  • 9) notable_nuclides_AvT_by_region = list of arrays (one per region, [T,N_rlv-nuc,3]) containing the time[s]/inventory[atm/cc]/activity[Bq/cc] data of relevant nuclides
Expand source code
def parse_DCS_file_from_DCHAIN(filepath,relevancy_threshold=0.01,print_progress=False,nch_max=100):
    '''
    Description:
        Parse a decay chain information file produced by DCHAIN-SP
    
    Dependencies:
        `import numpy as np`
        
        `import time`
    
    Inputs:
       (required)
       
       - `filepath` = string, path to DCS file
        
    Inputs:
       (optional, keyword)
       
       - `print_progress` = logical variable denoting whether time and significant nuclide info will be printed while scanning file (D=`False`)
       - `nch_max` = maximum number of chains per isotope (D=`100`)
       - `relevancy_threshold` = what fraction of total activity must a nuclide contribute to be deemed relevant
        
    Outputs:
       
       | dimension      |  meaning for output array dimensions |
       | :------------- | :-------------------------------- |
       |   R (n_reg)    | regions                           |
       |   T (ntsteps)  | time steps                        |
       |   N (nnuc_max) | max number of nuclides            |
       |   C (chni_max) | maximum index of relevant chains  |
       |   L (chln_max) | maximum number of links per chain |
       
        
        - 0) `inventory`    = universal columns of DCS file `[R,T,N,C,vi]`, vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
        - 1) `l_chains`     = `[R,T,N,C]`, length of listed chain
        - 2) `prod_nuc`     = `[R,T,N]`, strings of the nuclide being produced
        - 3) `chn_indx`     = `[R,T,N]`, lists of the chain indices printed
        - 4) `link_nuc`     = `[R,T,N,C,L]`, strings of the nuclides in each chain
        - 5) `decay_mode`   = `[R,T,N,C,L]`, strings of the decay modes each link undergoes to produce the next link
        - 6) `link_dN_info` = `[R,T,N,C,L,di]`, extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total (only generated if these values are found in file, 'None' otherwise)
        - 7) `end_of_irradiation_time` = time of end of final irradiation step [seconds]
        - 8) `notable_nuclides_names_by_region` = list of lists (one per region) containing the relevant nuclides per region
        - 9) `notable_nuclides_AvT_by_region`   = list of arrays (one per region, `[T,N_rlv-nuc,3]`) containing the time[s]/inventory[atm/cc]/activity[Bq/cc] data of relevant nuclides
    '''
    global start
    
    try:
        start_time = start
    except:
        start_time = time.time()
    start = start_time
    
    
    print('Processing the *.DCS decay chain file...     ({:0.2f} seconds elapsed)'.format(time.time()-start))
    
    # Extract text from file
    f = open(filepath)
    lines = f.readlines()
    f.close()
    
    # Determine if extra data is written per isotope by querying whether an isotope's name or blank spaces are present in the line immediately below the first nuclide
    if len(lines[9][3:9].strip())==0: 
        extra_chain_data_present = True
    else:
        extra_chain_data_present = False 
    
    # First, scan for regions
    n_reg = 0
    reg_nos = []
    reg_labels = []
    for line in lines:
        if 'c<>-<>   no.' in line:
            n_reg += 1
            reg_nos.append(int(line[12:19]))
        if 'c<>-<>   region label :' in line:
            reg_labels.append(line[23:52].strip())
    print('{} regions found...     ({:0.2f} seconds elapsed)'.format(n_reg,time.time()-start))
    
    
    # Then, scan for time steps.  Need all individual times from beginning and time of end of irradiation.
    ntsteps = 0
    wtimes = [] # written times since start of calculations in seconds (so, the times at the end of each time step) 
    beam_state = [] # in each time step, 1 if beam on, 0 if beam off
    end_of_irradiation_time = 0.0 # time (in seconds since start of irradiation) at which beam was switched off for the final time
    end_irr_time_located = False
    for line in lines:
        if ' --- during' in line:
            if 'irradiation' in line:
                beam_state.append(1)
            elif 'cooling' in line:
                beam_state.append(0)
            else:
                beam_state.append(None)
                print('found weird beam condition')
        elif ' --- output time' in line:
            ntsteps += 1
            t = np.float(line[39:53])
            wtimes.append(t)
            if 'after the last shutdown:' in line and not end_irr_time_located:
                taeoi_val = np.float(line[88:97])
                taeoi_unit = line[99]
                taeoi = taeoi_val*(time_str_to_sec_multiplier(taeoi_unit))
                end_of_irradiation_time = t - taeoi
                end_irr_time_located = True
        elif 'end of irradiation and decay calculation for this region' in line:
            break
    wtimes = np.array(wtimes)
    
    pstr = '{} time steps found\nend of irradiation at t = {:g} sec ({})\nend of calculation at t = {:g} sec ({})...                ({:0.2f} seconds elapsed)'.format(
            ntsteps,end_of_irradiation_time,seconds_to_ydhms(end_of_irradiation_time),wtimes[-1],seconds_to_ydhms(wtimes[-1]),time.time()-start)
    print(pstr)
    
    
    
    # Next, scan for other maximum limiting dimensions
    nnuc_max = 0 # maximum number of nuclides listed in a time step
    chni_max = 0 # highest index of a relevant chain found
    chln_max = 0 # maximum number of links (nuclides) found in any chain
    
    current_tstep_nnuc = 0 # number of nuclides in current time step
    chni = 0 # chain number index
    chln = 0 # chain length
    
    for line in lines:
        if len(line) < 5: continue
        
        if ' --- output time' in line or 'end of irradiation' in line:
            if current_tstep_nnuc > nnuc_max: nnuc_max = current_tstep_nnuc
            current_tstep_nnuc = 0 # reset count of nuclides in time step
            
        if len(line[3:9].strip())!=0 and line[11]=='(': # first chain entry of a nuclide
            current_tstep_nnuc += 1
            
        if line[11]=='(': # all chains have this in common
            chni = int(line[12:16])
            if chni > chni_max: chni_max = chni 
            chln = 1 + line.count(')->')
            if chln > chln_max: chln_max = chln 
    
    pstr =  '{} = maximum number of nuclides listed in a single time step\n'.format(nnuc_max)
    pstr += '{} = highest index found of all relevant chains\n'.format(chni_max)
    pstr += '{} = length of longest chain listed...                          ({:0.2f} seconds elapsed)'.format(chln_max,time.time()-start)
    print(pstr)
    
    
    # Construct arrays to hold decay chain information
    #   R (n_reg)    regions
    #   T (ntsteps)  time steps
    #   N (nnuc_max) max number of nuclides
    #   C (chni_max) maximum index of relevant chains
    #   L (chln_max) maximum number of links per chain
    
    inventory = np.zeros((n_reg,ntsteps,nnuc_max,chni_max,5)) # universal columns of DCS file [R,T,N,C,vi], vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
    l_chains  = np.zeros((n_reg,ntsteps,nnuc_max,chni_max)) # [R,T,N,C], length of listed chain
    prod_nuc  = np.empty((n_reg,ntsteps,nnuc_max), dtype='object') # [R,T,N], strings of the nuclide being produced
    chn_indx  = np.empty((n_reg,ntsteps,nnuc_max), dtype='object') # [R,T,N], lists of the chain indices printed
    link_nuc  = np.empty((n_reg,ntsteps,nnuc_max,chni_max,chln_max), dtype='object') # [R,T,N,C,L], strings of the nuclides in each chain
    decay_mode= np.empty((n_reg,ntsteps,nnuc_max,chni_max,chln_max), dtype='object') # [R,T,N,C,L], strings of the decay modes each link undergoes to produce the next link
    nuc_relvnt= np.empty((n_reg,ntsteps,nnuc_max), dtype='object') # [R,T,N], True/False denoting whether a nuclide meets the relevancy threshold in each time step
    if extra_chain_data_present:
        link_dN_info = np.zeros((n_reg,ntsteps,nnuc_max,chni_max,chln_max,3)) # [R,T,N,C,L,di], extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total
        col_strs = ['dN_Beam','dN_Decay/nx','dN_Total']
        nexcol = len(col_strs)
    else:
        link_dN_info = None 
    
    # Populate these arrays
    ri = None # region index
    ti = None # time step index
    ni = None # nuclide index
    ci = None # chain index (1 lower than Fortran value)
    
    # character column and spacing numbers for decay chains
    ch_sci = 95 # chain start column index
    dc_sci = 105 # column index of first decay mode listing
    vl_sci = 94 # column index of first extra decay chain value
    link_gap_sts = 17 # number of characters between start of one link and the next
    
    for line in lines:
        if len(line) < 5: continue
        
        if 'c<>-<>   no.' in line: # entering new region
            ri = find(int(line[12:19]),reg_nos)
            continue 
        elif ' --- output time' in line: # entering new time step
            t = np.float(line[39:53])
            ti = find(t,wtimes) 
            ni = -1 # reset nuclide index
            continue 
        elif len(line[3:9].strip())!=0 and line[11]=='(': # entering new output nuclide
            ni += 1
            prod_nuc[ri,ti,ni] = line[3:9]
            
        if line[11]=='(': # if line contains a chain
            ci = int(line[12:16]) - 1 # chain index
            if not chn_indx[ri,ti,ni]:
                chn_indx[ri,ti,ni] = [ci]
            else:
                chn_indx[ri,ti,ni].append(ci)
            col_vals = line[25:92].strip().split()
            for vi in range(len(col_vals)):
                inventory[ri,ti,ni,ci,vi] = np.float(col_vals[vi])
            chln = 1 + line.count(')->')
            l_chains[ri,ti,ni,ci] = chln 
            for li in range(chln):
                nci1 = ch_sci + li*link_gap_sts
                nci2 = nci1 + 6
                link_nuc[ri,ti,ni,ci,li] = line[nci1:nci2]
                if li != chln:
                    dci1 = dc_sci + li*link_gap_sts
                    dci2 = dci1 + 2
                    decay_mode[ri,ti,ni,ci,li] = line[dci1:dci2]
            continue
        
        if len(line[3:9].strip())==0 and extra_chain_data_present:
            vi = None 
            for i in range(nexcol):
                if col_strs[i] in line: 
                    vi = i 
            for li in range(chln):
                vci1 = vl_sci + li*link_gap_sts
                vci2 = vci1 + 14
                if len(line[vci1:vci2].strip())==0:
                    val = 0
                else:
                    val = np.float(line[vci1:vci2])
                link_dN_info[ri,ti,ni,ci,li,vi] = val
    
    
    
    # Now extract results from the data arrays
    print('\nNow processing decay chain results...        ({:0.2f} seconds elapsed)'.format(time.time()-start)) 
    
    notable_nuclides_AvT_by_region = [] # list of arrays (one per region) containing the time/inventory/activity data of relevant nuclides
    notable_nuclides_names_by_region = [] # list of lists (one per region) containing the relevant nuclides per region
    
    for ri in range(n_reg):
        print('Region no. {} ({})'.format(reg_nos[ri],reg_labels[ri]))
        relevant_nuclides = []
        for ti in range(ntsteps):
            t = wtimes[ti]
            if print_progress:
                if t > end_of_irradiation_time:
                    tai = t - end_of_irradiation_time
                    print('\tAt time {:g} sec ({}), which is {:g} sec ({}) after end of final irradiation'.format(t,seconds_to_ydhms(t),tai,seconds_to_ydhms(tai)))
                else:
                    print('\tAt time {:g} sec ({})'.format(t,seconds_to_ydhms(t)))
             
            # First, get total information for the time step
            N0 = [] # inventory at start of time step
            N1 = [] # inventory at end of time step
            A0 = [] # activity at start of time step
            A1 = [] # activity at end of time step
            
            for ni in range(nnuc_max):
                if not prod_nuc[ri,ti,ni]: continue # skip empty nuclide indices
                chain_indices = chn_indx[ri,ti,ni]
                ci0 = chain_indices[0] # First chain
                ci1 = chain_indices[-1] # Last (nonzero) chain
                N0.append(inventory[ri,ti,ni,ci0,0])
                N1.append(inventory[ri,ti,ni,ci1,2])
                lam = inventory[ri,ti,ni,ci0,3]/inventory[ri,ti,ni,ci0,2]
                A0.append(lam*inventory[ri,ti,ni,ci0,0])
                A1.append(inventory[ri,ti,ni,ci1,3])
              
            N0_tot_tstep = np.sum(N0) # total inventory at start of time step
            N1_tot_tstep = np.sum(N1) # total inventory at end of time step
            A0_tot_tstep = np.sum(A0) # total activity at start of time step
            A1_tot_tstep = np.sum(A1) # total activity at end of time step
            
            # Determine which nuclides meet the relevancy threshold
            pstr = ('\t\tRelevant radionuclides include:\n')
            
            nii = -1
            for ni in range(nnuc_max):
                if not prod_nuc[ri,ti,ni]: continue # skip empty nuclide indices
                nii += 1
                A1_fract_threshold = relevancy_threshold # Nuclide must be responsible for at least 0.1% total activity at end of time step
                A1_nuc_frac = A1[nii]/A1_tot_tstep
                if A1_nuc_frac > A1_fract_threshold: 
                    nuc_relvnt[ri,ti,ni] = True
                    if prod_nuc[ri,ti,ni] not in relevant_nuclides: relevant_nuclides.append(prod_nuc[ri,ti,ni])
                    pstr += ('\t\t  - {} with {:0.2f}% total activity\n'.format(prod_nuc[ri,ti,ni],100*A1_nuc_frac))
                else:
                    nuc_relvnt[ri,ti,ni] = False
            
            if print_progress:
                print(pstr)
                
        n_relevant_nuclides = len(relevant_nuclides)
        
        # now collect activity of each nuclide at each time step
        relv_nuc_inv  = np.zeros((ntsteps,n_relevant_nuclides,3)) # [T,rlvN,3], time[s]/inventory[atm/cc]/activity[Bq/cc] for relevant nuclides
        
        for ti in range(ntsteps):
            t = wtimes[ti]
            relv_nuc_inv[ti,:,0] = t
            for ni in range(nnuc_max):
                if not prod_nuc[ri,ti,ni]: continue # skip empty nuclide indices
                if prod_nuc[ri,ti,ni] not in relevant_nuclides: continue # only want nuclides which are at some point relevant
                chain_indices = chn_indx[ri,ti,ni]
                ci1 = chain_indices[-1]
                
                #if nuclide is relevant, find its index among the relevant ones
                rni = find(prod_nuc[ri,ti,ni],relevant_nuclides)
                relv_nuc_inv[ti,rni,1] = inventory[ri,ti,ni,ci1,2]
                relv_nuc_inv[ti,rni,2] = inventory[ri,ti,ni,ci1,3]
        
        notable_nuclides_names_by_region.append(relevant_nuclides)
        notable_nuclides_AvT_by_region.append(relv_nuc_inv)
        
    
    return inventory, l_chains, prod_nuc, chn_indx, link_nuc, decay_mode, link_dN_info, end_of_irradiation_time, notable_nuclides_names_by_region, notable_nuclides_AvT_by_region
def parse_dtrk_file(path_to_dtrk_file, return_metadata=False)

Description

Parses the output file of a T-Track tally generated by PHITS. Note that this specific function assumes that the T-Track tally was one automatically generated by and corresponding to a T-Dchain tally but in principle works with any T-Track tally. This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.

Inputs

  • path_to_dtrk_file = path to the T-Track tally output file to be parsed
  • return_metadata = Boolean indicating whether additional information is outputted with the flux (D=False)

Outputs

  • flux = a RxEx4 array containing regionwise fluxes [Elower/Eupper/flux/abs_error]
  • dtrk_metadata (only returned if return_metadata=True) = list of length two
    • dtrk_metadata[0] = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
    • dtrk_metadata[1] = string denoting mesh type as either 'reg', 'xyz', or 'tet'
Expand source code
def parse_dtrk_file(path_to_dtrk_file,return_metadata=False):
    '''
    Description:
        Parses the output file of a T-Track tally generated by PHITS.  Note that this specific function assumes that the T-Track 
        tally was one automatically generated by and corresponding to a T-Dchain tally but in principle works with any T-Track tally.
        This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.
        
    Inputs:
        - `path_to_dtrk_file` = path to the T-Track tally output file to be parsed
        - `return_metadata` = Boolean indicating whether additional information is outputted with the flux (D=`False`)
        
    Outputs:
        - `flux` = a RxEx4 array containing regionwise fluxes [Elower/Eupper/flux/abs_error]
        - `dtrk_metadata` (only returned if `return_metadata=True`) = list of length two
               - `dtrk_metadata[0]` = string denoting axis type 'eng' (old full format) or 'dchain' (new reduced format)
               - `dtrk_metadata[1]` = string denoting mesh type as either 'reg', 'xyz', or 'tet'
    '''
    
    # Extract text from file
    f = open(path_to_dtrk_file)
    file_text = f.read()
    lines = file_text.split('\n')
    f.close()
    
    # Determine geometry type (mesh = reg, xyz, or tet)
    for line in lines:
        if 'mesh =' in line:
            meshtype = line.replace('mesh =','').strip().split()[0]
            break 
    
    # Determine if original or reduced format (axis = eng or axis = dchain)
    for line in lines:
        if 'axis =' in line:
            axistype = line.replace('axis =','').strip().split()[0]
            break 
    # Double check 
    for li, line in enumerate(lines):
        if li>500: break
        if '#  e-lower      e-upper      neutron     r.err ' in line: 
            axistype='eng'
            break
    
    dtrk_metadata = [axistype,meshtype]
    
    # Determine number of regions
    if axistype=='eng':
        nreg = file_text.count('#   no. =')
    elif axistype=='dchain':
        for li, line in reversed(list(enumerate(lines))):
            #print(line)
            if '0    0   0.0000E+00  0.0000' in line:
                nreg = int(lines[li-1].split()[0])
                break 
    
    if axistype=='dchain':
        nEbins = 1968
    else:
        for line in lines:
            if 'ne =' in line:
                nEbins = int(line.replace('ne =','').strip().split()[0])
                break
    
    flux = np.zeros((nreg,nEbins,4))
    
    if axistype=='eng':
        in_flux_lines = False
        ei = 0
        ri = -1
        
        for line in lines:
            if '#   no. =' in line:
                ri += 1
            if '#  e-lower      e-upper      neutron     r.err ' in line:
                in_flux_lines = True 
                continue 
            if in_flux_lines:
                flux[ri,ei,:] = [np.float(x) for x in line.split()]
                flux[ri,ei,3] = flux[ri,ei,3]*flux[ri,ei,2] # convert relative error to absolute error
                ei += 1
                if ei == nEbins:
                    in_flux_lines = False 
                    ei = 0
    elif axistype=='dchain':
        ebins = [20.0] + ECCO1968_Ebins(1968)
        ebins = ebins[::-1]
        in_flux_lines = False
        for line in lines:
            if '# num ie flux r.err' in line:
                in_flux_lines = True 
                continue 
            if '0    0   0.0000E+00  0.0000' in line:
                in_flux_lines = False
                break 
            if in_flux_lines:
                vals = line.split() 
                ri = int(vals[0])-1
                ei = int(vals[1])-1
                fval = np.float(vals[2])
                ferr = np.float(vals[3])
                
                flux[ri,ei,0] = ebins[ei]
                flux[ri,ei,1] = ebins[ei+1]
                flux[ri,ei,2] = fval
                flux[ri,ei,3] = fval*ferr 
                
    if return_metadata:
        return flux, dtrk_metadata
    else:
        return flux
def parse_dyld_files(path_to_dyld_file, iredufmt=None)

Description

Parses the output files of a T-Yield tally generated by PHITS with axis=dchain. This function assumes that the T-Yield tally was one automatically generated by and corresponding to a T-Dchain tally (axis=dchain). This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.

Inputs

  • path_to_dyld_file = path to the T-Yield tally output file to be parsed (the *_err.dyld file of the same name is automatically searched for and read, if present)
  • iredufmt = (DEPRICATED; this is now determined automatically) integer 1 or 0 specifying how the xyz meshes are ordered relative to the internal region numbers. In the new format (1), region indices are incremented as x->y->z (x=innermost loop); this is reversed in the old format (0). Ultimately, this corresponds to the same iredufmt parameter in PHITS/DCHAIN, 1='new' and 0='old'. This variable is only used for xyz meshes where this ordering matters.

Outputs

  • yields = a RxNx2 array containing regionwise yields (and their absolute uncertainties) for all nuclides produced in T-Yield
  • nuclide_names_yld = a length N list of all nuclide names in order
Expand source code
def parse_dyld_files(path_to_dyld_file,iredufmt=None):
    '''
    Description:
        Parses the output files of a T-Yield tally generated by PHITS with axis=dchain.  This function assumes
        that the T-Yield tally was one automatically generated by and corresponding to a T-Dchain tally (axis=dchain).
        This works for region, xyz, and tetrahedral mesh geometries in either the original or reduced format.
        
    Inputs:
        - `path_to_dyld_file` = path to the T-Yield tally output file to be parsed 
                           (the *_err.dyld file of the same name is automatically searched for and read, if present)
        - `iredufmt` = (DEPRICATED; this is now determined automatically)
                   integer 1 or 0 specifying how the xyz meshes are ordered relative to the internal region numbers.
                   In the new format (1), region indices are incremented as x->y->z (x=innermost loop); this is reversed in the old format (0).
                   Ultimately, this corresponds to the same iredufmt parameter in PHITS/DCHAIN, 1='new' and 0='old'.
                   This variable is only used for xyz meshes where this ordering matters.
        
    Outputs:
        - `yields` = a RxNx2 array containing regionwise yields (and their absolute uncertainties) for all nuclides produced in T-Yield
        - `nuclide_names_yld` = a length N list of all nuclide names in order
    '''
    
    # Extract text from file
    f = open(path_to_dyld_file)
    file_text = f.read()
    lines = file_text.split('\n')
    f.close()
    
    # determine if in reduced format
    iredufmt=0
    for li, line in enumerate(lines):
        if "# num nucleusID yield r.err" in line:
            iredufmt=1
            break
        if "isotope production #" in line:
            iredufmt=0
            break
    
    
    # Get error data if available
    if iredufmt==0:
        try:
            f_err = open(path_to_dyld_file.replace('.dyld','_err.dyld'))
            file_text_err = f_err.read()
            lines_err = file_text_err.split('\n')
            f_err.close()
            err_dyld_found = True
        except:
            file_text_err = None
            lines_err = None
            err_dyld_found = False
    
    # Determine geometry type (mesh = reg, xyz, or tet)
    for line in lines:
        if 'mesh =' in line:
            meshtype = line.replace('mesh =','').strip().split()[0]
            break 
    
    # If xyz mesh, need to find mesh dimensions:
    if meshtype=='xyz':
        for line in lines:
            if 'nx =' in line:
                nx = int(line.replace('nx =','').strip().split()[0])
            elif 'ny =' in line:
                ny = int(line.replace('ny =','').strip().split()[0])
            elif 'nz =' in line:
                nz = int(line.replace('nz =','').strip().split()[0])
                break 
    
    # Find starting line
    for li, line in enumerate(lines):
        if 'nuclear yield (or production)' in line:
            li_start = li 
            break 
    
    if iredufmt==1:
        # Count number of nuclides present in whole file
        nreg = 0 
        nuc_id_list = []
        for li, line in enumerate(lines):
            if li <= li_start+3: continue # in header
            vals = line.strip().split()
            if int(vals[0])==0: break # reached end
            if int(vals[0])>nreg: nreg = int(vals[0])
            zzzaaam = int(vals[1])
            if zzzaaam not in nuc_id_list: nuc_id_list.append(zzzaaam)
        
        nnuc = len(nuc_id_list)
        yields = np.zeros((nreg,nnuc,2))
        nuclide_names_yld = []
        
        # Get names
        nuc_id_list = sorted(nuc_id_list)
        for id in nuc_id_list:
            nuclide_names_yld.append(ZAM_to_Dname(id)) 
        
        # Get values
        for li, line in enumerate(lines):
            if li <= li_start+3: continue # in header
            vals = line.strip().split()
            if int(vals[0])==0: break # reached end
            ri = int(vals[0]) - 1
            zzzaaam = int(vals[1])
            ni = nuc_id_list.index(zzzaaam)
            yields[ri,ni,0] = np.float(vals[2])
            yields[ri,ni,1] = np.float(vals[3])*np.float(vals[2])
              
    else: # old ''traditional'' format
        # Count number of nuclides present in whole file
        nnuc = 0
        for li, line in enumerate(lines):
            if li <= li_start+2: continue # skip header lines
            if 'isotope production' in line: 
                N_bounds = line.strip().split('=')[-1].split()
                N_bounds = [int(i) for i in N_bounds]
                nnuc += N_bounds[1] - N_bounds[0] + 1
        
        # Determine number of regions
        nreg = 0
        for li, line in enumerate(lines):
            if li <= li_start+4: continue # skip header lines
            if len(line) < 2: break # reached end of first element block 
            nreg += 1
        
        yields = np.zeros((nreg,nnuc,2))
        nuclide_names_yld = []
        
        # Extract yield data
        ni = 0 # nuclide index
        ni_newstart = 0
        ri = 0 # region index
        for li, line in enumerate(lines):
            if li <= li_start+2: continue # skip header lines
            if len(line) < 2: continue # skip line breaks
            
            if 'isotope production' in line: 
                # extract Z and A info
                Z = int(line.strip().split('-')[0])
                N_bounds = line.strip().split('=')[-1].split()
                N_bounds = [int(i) for i in N_bounds]
                N_list = []
                for i in range(N_bounds[1]-N_bounds[0]+1):
                    N_list.append(N_bounds[0]+i)
                nisotopes = len(N_list)
                A_list = [N+Z for N in N_list]
                ni_newstart = len(nuclide_names_yld)
                for A in A_list:
                    ZAM = 10*A + 10000*Z
                    nuclide_names_yld.append(ZAM_to_Dname(ZAM)) 
                on_buffer_line = True
                continue 
            
            if on_buffer_line:
                ri = 0
                on_buffer_line = False 
                continue 
                  
            if '# Information for Restart Calculation' in line: break # reached end of useful info
            
            # Only lines making it to this point will be ones with region and yield data
            vals = line.strip().split()
            if meshtype=='xyz':
                yvals = vals[3:]
                if err_dyld_found: yvals_rerr = lines_err[li].strip().split()[3:]
                jx,jy,jz = int(vals[0]),int(vals[1]),int(vals[2])
                rii = jz + (jy-1)*nz + (jx-1)*(nz*ny)
                #if iredufmt==1:
                #    rii = jx + (jy-1)*nx + (jz-1)*(nx*ny) 
                #else:
                #    rii = jz + (jy-1)*nz + (jx-1)*(nz*ny)
            else:
                yvals = vals[1:]
                if err_dyld_found: yvals_rerr = lines_err[li].split()[1:]
                rii = ri 
            
            for i in range(nisotopes):
                yields[rii,ni_newstart+i,0] = np.float(yvals[i])
                if err_dyld_found: yields[rii,ni_newstart+i,1] = np.float(yvals_rerr[i])*yields[rii,ni_newstart+i,0]
                
            ri += 1
        
    return yields, nuclide_names_yld
def plot_top10_nuclides(dchain_output, rank_val='activity', xaxis_val='time', xaxis_type='indices', regions=None, region_indices=None, times=None, time_indices=None, rank_cutoff=10, xscale='linear')

Description

Generate a nice plot illustrating dominant nuclides as a function of time or region

Dependencies

Inputs

(required)

  • dchain_output = dictionary output from the process_dchain_simulation_output for a simulation

Inputs

(optional, keyword)

  • rank_val = which top 10 list is selected. (D='activity', options include 'activity', 'decay_heat', and 'gamma_dose')
  • xaxis_val = value to be plotted on x-axis; can be either "time" (default) or "region"
  • xaxis_type = space xaxis entries either equally by "indices" (default) or realistically by "values"
  • regions = list of region numbers (or individual value) to generate plots for (D=None, plotting all regions)
  • region_indices = same as above but uses indices rather than region numbers; this has higher priority if specified (D=None, plot all regions)
  • times = list of times (from start in seconds) (or individual value) to generate plots for (D=None, plotting all times)
  • time_indices = same as above but uses indices rather than time values; this has higher priority if specified (D=None, plot all times)
  • rank_cutoff = highest rank (or number of ranks) to be displayed (D=10, cannot be any greater than 10)
  • xscale = string specifying scale of x-axis, either 'linear' (default) or 'log'

Outputs

  • fig_list = list of figures which can be plotted.
Expand source code
def plot_top10_nuclides(dchain_output,rank_val='activity',xaxis_val='time',xaxis_type='indices',regions=None,region_indices=None,times=None,time_indices=None,rank_cutoff=10,xscale='linear'):
    '''
    Description:
        Generate a nice plot illustrating dominant nuclides as a function of time or region
    
    Dependencies:
        - `import numpy as np`
        - `import matplotlib.pyplot as plt`
        - `process_dchain_simulation_output`
    
    Inputs:
        (required)
        
        - `dchain_output` = dictionary output from the process_dchain_simulation_output for a simulation
        
    Inputs:
        (optional, keyword)
       
        - `rank_val` = which top 10 list is selected. (D=`'activity'`, options include `'activity'`, `'decay_heat'`, and `'gamma_dose'`)
        - `xaxis_val` = value to be plotted on x-axis; can be either `"time"` (default) or `"region"`
        - `xaxis_type` = space xaxis entries either equally by `"indices"` (default) or realistically by `"values"`
        - `regions` = list of region numbers (or individual value) to generate plots for (D=`None`, plotting all regions)
        - `region_indices` = same as above but uses indices rather than region numbers; this has higher priority if specified (D=`None`, plot all regions)
        - `times` = list of times (from start in seconds) (or individual value) to generate plots for (D=`None`, plotting all times)
        - `time_indices` = same as above but uses indices rather than time values; this has higher priority if specified (D=`None`, plot all times)
        - `rank_cutoff` = highest rank (or number of ranks) to be displayed (D=`10`, cannot be any greater than 10)
        - `xscale` = string specifying scale of x-axis, either `'linear'` (default) or `'log'`
        
    Outputs:
        - `fig_list` = list of figures which can be plotted.
    '''
    
    max_nregs = len(dchain_output['region']['numbers'])
    all_regs = dchain_output['region']['numbers']
    max_ntimes = len(dchain_output['time']['from_start_sec'])
    all_times = dchain_output['time']['from_start_sec']
    
    if not regions and not region_indices:
        regions = dchain_output['region']['numbers']
        region_indices  = range(len(regions))
    elif region_indices:
        if isinstance(region_indices, list):
            regions = []
            for i in region_indices:
                if i >= max_nregs:
                    print('region index {} greater than number of regions {}, skipping...'.format(i,max_nregs))
                    region_indices.remove(i)
                    continue
                regions.append(dchain_output['region']['numbers'][i])
        else:
            if region_indices < max_nregs:
                regions = [dchain_output['region']['numbers'][region_indices]]
                region_indices = [region_indices]
            else:
                print('Single provided region index {} is out of bounds of total number of regions {}, aborting...'.format(region_indices,max_nregs))
                return None
    elif regions:
        if isinstance(regions, list):
            region_indices = []
            for i in regions:
                if i not in all_regs:
                    print('region {} is not contained in list of region numbers, skipping...'.format(i))
                    regions.remove(i)
                    continue
                region_indices.append(dchain_output['region']['numbers'].index(i))
        else:
            if regions in all_regs:
                region_indices = [dchain_output['region']['numbers'].index(regions)]
                regions = [regions]
            else:
                print('Single provided region {} is not in the simulated region numbers, aborting...'.format(region_indices))
                return None
    
    if not times and not time_indices:
        times = dchain_output['time']['from_start_sec']
        time_indices  = range(len(times))
    elif time_indices:
        if isinstance(time_indices, list):
            times = []
            for i in time_indices:
                if i >= max_ntimes:
                    print('time index {} greater than number of times {}, skipping...'.format(i,max_ntimes))
                    time_indices.remove(i)
                    continue
                times.append(dchain_output['time']['from_start_sec'][i])
        else:
            if time_indices < max_ntimes:
                times = [dchain_output['time']['from_start_sec'][time_indices]]
                time_indices = [time_indices]
            else:
                print('Single provided time index {} is out of bounds of total number of times {}, aborting...'.format(time_indices,max_ntimes))
                return None
    elif times:
        if isinstance(times, list):
            time_indices = []
            for i in times:
                if i not in all_times:
                    print('time {} is not contained in list of times, skipping...'.format(i))
                    times.remove(i)
                    continue
                time_indices.append(dchain_output['time']['from_start_sec'].index(i))
        else:
            if times in all_times:
                time_indices = [dchain_output['time']['from_start_sec'].index(times)]
                times = [times]
            else:
                print('Single provided time {} is not in the outputted times, aborting...'.format(time_indices))
                return None
            
    if rank_val=='photon_dose':
        rank_val = 'gamma_dose'
    if rank_val not in ['activity','decay_heat','gamma_dose']:
        print("rank_val must be either 'activity', 'decay_heat', or 'gamma_dose', aborting...")
        return None
    
    if xaxis_val not in ['time','region']:
        print("xaxis_val must be either 'time' or 'region', aborting...")
        return None
    
    if xaxis_type=='index': xaxis_type = 'indices'
    if xaxis_type=='value': xaxis_type = 'values'
    if xaxis_type not in ['indices','values']:
        print("xaxis_val must be either 'indices' or 'values', aborting...")
        return None
    
    fig_list = []
    ax_list = []
    figi = 0
    
    if xaxis_val=='time':
        major_indices = region_indices
        minor_indices = time_indices
        major_values = regions 
        minor_values = times
        xstr = 'Time'
    else:
        major_indices = time_indices
        minor_indices = region_indices
        major_values = times
        minor_values = regions
        xstr = 'Region'
        
    if xaxis_type=='indices':
        xdata = minor_indices
        xstr += ' (index)'
    else:
        xdata = minor_values
        
    for majori in major_indices:
        figi += 1
        # Assemble list of all ranked nuclides
        nuclides = []
        for minori in minor_indices:
            
            if xaxis_val=='time':
                ri = majori 
                ti = minori
            else:
                ri = minori 
                ti = majori
            
            for tti in range(len(dchain_output['top10'][rank_val]['nuclide'][ri][ti,:])):
                if (dchain_output['top10'][rank_val]['nuclide'][ri][ti,tti]!=None) and (dchain_output['top10'][rank_val]['nuclide'][ri][ti,tti] not in nuclides) and (dchain_output['top10'][rank_val]['rank'][ri][ti,tti]<=rank_cutoff):
                    nuclides.append(dchain_output['top10'][rank_val]['nuclide'][ri][ti,tti])
        # Now assemble plot for each nuclide
        plot_dicts = []
        for nuclide in nuclides:
            ni = nuclides.index(nuclide)
            ydata = []
            for minori in minor_indices:
                if xaxis_val=='time':
                    ri = majori 
                    ti = minori
                else:
                    ri = minori 
                    ti = majori
                if nuclide in dchain_output['top10'][rank_val]['nuclide'][ri][ti,:]:
                    tti = dchain_output['top10'][rank_val]['nuclide'][ri][ti,:].tolist().index(nuclide)
                    #tti = np.where(dchain_output['top10'][rank_val]['nuclide'][ri][ti,:]==nuclide)
                    if dchain_output['top10'][rank_val]['rank'][ri][ti,tti]<=rank_cutoff:
                        ydata.append( 1 + rank_cutoff - dchain_output['top10'][rank_val]['rank'][ri][ti,tti] )
                    else:
                        ydata.append(np.NaN)
                else:
                    ydata.append(np.NaN)
            tex_name = nuclide_plain_str_to_latex_str(nuclide)
            # dict = {'xdata':xdata,'ydata':ydata,'marker':tex_name,'markersize':30,'color':colors_list_12(ni%12)}
            dict = {'xdata':xdata,'ydata':ydata,'marker':tex_name,'markersize':30}
            plot_dicts.append(dict)
        # Now generate plot
        
        if xaxis_val=='time':
            title_str = 'Top {} nuclides by {} in region {}'.format(rank_cutoff,rank_val.replace('_',' '),major_values[major_indices.index(majori)])
        else:
            title_str = 'Top {} nuclides by {} at t = {} seconds'.format(rank_cutoff,rank_val.replace('_',' '),major_values[major_indices.index(majori)])
        ystr = 'Rank'
        
        
        
        # For public version, just make this a basic plot rather than using my complicated personal plotting function
        fig1 = plt.figure()
        ax1 = plt.subplot(111)
        
        for entry in plot_dicts:
            ax1.plot(entry['xdata'],entry['ydata'],marker=entry['marker'],markersize=entry['markersize'],ls='')
        
        plt.xlabel(xstr,fontsize=14)
        plt.ylabel(ystr,fontsize=14)
        plt.xscale(xscale)
        fig1.tight_layout()
        fig1.set_size_inches(0.2+6.3*(len(plot_dicts[0]['xdata'])/12),6.5*(rank_cutoff/10)+0.1*(10-rank_cutoff))
        
        '''
        fig1, ax1 = fancy_plot(
                               xdata_lists=None, 
                               ydata_lists=None, 
                               dictionaries=plot_dicts,
                               figi=figi,
                               title_str=title_str,
                               x_label_str=xstr,  
                               y_label_str=ystr,
                               x_scale=xscale,
                               y_scale='linear',
                               fig_height_inch=6.5*(rank_cutoff/10)+0.1*(10-rank_cutoff)
                               )
        '''
        
        ax1.set_yticks(range(1,rank_cutoff+1))
        ax1.set_yticklabels([str(i) for i in range(rank_cutoff,0,-1)])
        
        if xaxis_type=='indices':
            ax1.set_xticks(minor_indices)
            ax1.set_xticklabels([str(i) for i in minor_indices])
        
        plt.grid(b=True, which='major', linestyle='-', alpha=0)#0.25)
        plt.grid(b=True, which='minor', linestyle='-', alpha=0)#0.10)
        
        fig_list.append(fig1)
        ax_list.append(ax1)
        
    
    return fig_list #, ax_list
def process_dchain_simulation_output(simulation_folder_path, simulation_basename, dtrk_filepath=None, dyld_filepath=None, process_DCS_file=False)

Description

This is intended to be a single master function for processing DCHAIN output.

Dependencies

from munch import *

Inputs

  • simulation_folder_path = text string of path to folder containing simulation output (should end with / or )
  • simulation_basename = common string of the DCHAIN simulations; output files are named THIS_NAME.*
  • dtrk_filepath = file path to *.dtrk file, only necessary if it has a different basename and there are multiple *.dtrk files in the folder
  • dyld_filepath = file path to *.dyld files, only necessary if it has a different basename and there are multiple *.dyld files in the folder
  • process_DCS_file (optional) = Boolean variable specifying whether the DCS file should be processed too. (D=False)

Outputs

  • dchain_output = a dictionary object containing all information from DCHAIN's output files. See the keys breakdown below.

    Technically, it tries to return a "munchify" object which can be used both exactly like a dictionary but also provides attribute-style access like a namedtuple or Class object such as dchain_output.time.of_EOB_sec rather than the dictionary style dchain_output['time']['of_EOB_sec']

dchain_output dictionary structure:

# Notation for output array dimensions
#   R  regions
#   T  time steps
#   N  max number of nuclides found in a single region
#   E  number of gamma energy bins
# le10 for top 10 lists, a number <= 10

{
dchain_output = {
'time':{                            #  ~  Time information
    'from_start_sec'                # [T] list of times from start time [sec]
    'from_EOB_sec'                  # [T] times from end of final bombardment [sec]
    'of_EOB_sec'                    #     scalar time of end of final bombardment [sec]
    }

'region':{                          #  ~  Information which only varies with region
    'numbers'                       # [R] region numbers
    'number'                        # [R] region numbers
    'irradiation_time_sec'          # [R] irradiation time per region
    'volume'                        # [R] volume in [cc] per region
    'neutron_flux'                  # [R] neutron flux in [n/cm^2/s] per region
    'beam_power_MW'                 # [R] beam power in [MW] per region
    'beam_energy_GeV'               # [R] beam energy in [GeV] per region
    'beam_current_mA'               # [R] beam current in [mA] per region
    }

'nuclides':{                        #  ~  Main nuclide results from *.act file
    'names'                         # [R][N] names of nuclides produced in each region 
    'TeX_names'                     # [R][N] LaTeX-formatted names of nuclides produced
    'ZZZAAAM'                       # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                                    #        (ground state m=0, metastable m=1,2,etc.)
    'half_life'                     # [R][N] half lives of nuclides produced [sec]
    'inventory':{'value'            # [R][T,N] atoms [#/cc]
                 'error'}           # [R][T,N] atoms [#/cc]
    'activity':{'value'             # [R][T,N] activity [Bq/cc]
                'error'}            # [R][T,N] activity [Bq/cc]
    'dose_rate':{'value'            # [R][T,N] dose-rate [uSv/h*m^2]
                 'error'}           # [R][T,N] dose-rate [uSv/h*m^2]
    'decay_heat':{                  
        'total':{'value'            # [R][T,N] total decay heat [W/cc]
                 'error'}           # [R][T,N] total decay heat [W/cc]
        'beta':{'value'             # [R][T,N] beta decay heat [W/cc]
                'error'}            # [R][T,N] beta decay heat [W/cc]
        'gamma':{'value'            # [R][T,N] gamma decay heat [W/cc]
                 'error'}           # [R][T,N] gamma decay heat [W/cc]
        'alpha':{'value'            # [R][T,N] alpha decay heat [W/cc]
                 'error'}           # [R][T,N] alpha decay heat [W/cc]
        }                          
    'column_headers'                # Length 7 list of the *.act columns' descriptions
    'total':{                       #  ~  Total values summed over all nuclides
        'activity':{'value'         # [R][T] total activity [Bq/cc]
                    'error'}        # [R][T] total activity [Bq/cc]
        'decay_heat':{'value'       # [R][T] total decay heat [W/cc]
                      'error'}      # [R][T] total decay heat [W/cc]
        'beta_heat':{'value'        # [R][T] total beta decay heat [W/cc]
                     'error'}       # [R][T] total beta decay heat [W/cc]
        'gamma_heat':{'value'       # [R][T] total gamma decay heat [W/cc]
                      'error'}      # [R][T] total gamma decay heat [W/cc]
        'alpha_heat':{'value'       # [R][T] total alpha decay heat [W/cc]
                      'error'}      # [R][T] total alpha decay heat [W/cc]
        'activated_atoms':{'value'  # [R][T] total activated atoms [#/cc]
                           'error'} # [R][T] total activated atoms [#/cc]
        'gamma_dose_rate':{'value'  # [R][T] total gamma dose rate [uSV/h*m^2]
                           'error'} # [R][T] total gamma dose rate [uSV/h*m^2]
        }
    }

'gamma':{                           #  ~  Gamma spectra and totals
    'spectra':{
        'group_number'              # [R][T,E] group number
        'E_lower'                   # [R][T,E] bin energy lower-bound [MeV]
        'E_upper'                   # [R][T,E] bin energy upper-bound [MeV]
        'flux':{'value'             # [R][T,E] flux [#/s/cc]
                'error'}            # [R][T,E] flux [#/s/cc]
        'energy_flux':{'value'      # [R][T,E] energy flux [MeV/s/cc]
                       'error'}     # [R][T,E] energy flux [MeV/s/cc]
        }                          
    'total_flux':{'value'           # [R][T] total gamma flux [#/s/cc]
                  'error'}          # [R][T] total gamma flux [#/s/cc]
    'total_energy_flux':{'value'    # [R][T] total gamma energy flux [MeV/s/cc]
                         'error'}   # [R][T] total gamma energy flux [MeV/s/cc]
    'annihilation_flux':{'value'    # [R][T] annihilation gamma flux [#/s/cc]
                         'error'}   # [R][T] annihilation gamma flux [#/s/cc]
    'current_underflow':{'value'    # [R][T] gamma current underflow [#/s]
                         'error'}   # no error reported
    'current_overflow':{'value'     # [R][T] gamma current overflow [#/s]
                        'error'}    # no error reported
    }

'top10':{                           #  ~  Top 10 lists from *.act file
    'activity':{
        'rank'                      # [R][T,le10] rank
        'nuclide'                   # [R][T,le10] nuclide name
        'value'                     # [R][T,le10] activity [Bq/cc]
        'error'                     # [R][T,le10] activity [Bq/cc]
        'percent'                   # [R][T,le10] percent of total activity
        }                         
    'decay_heat':{                 
        'rank'                      # [R][T,le10] rank
        'nuclide'                   # [R][T,le10] nuclide name
        'value'                     # [R][T,le10] decay heat [W/cc]
        'error'                     # [R][T,le10] decay heat [W/cc]
        'percent'                   # [R][T,le10] percent of total decay heat
        }                         
    'gamma_dose':{                 
        'rank':                     # [R][T,le10] rank
        'nuclide'                   # [R][T,le10] nuclide name
        'value'                     # [R][T,le10] dose-rate [uSv/h*m^2]
        'error'                     # [R][T,le10] dose-rate [uSv/h*m^2]
        'percent'                   # [R][T,le10] percent of total gamma dose rate
        }
    }

'number_of':{                       #  ~  Maximum values of R, T, N, and E
    'regions'                       #  R  = total number of regions
    'time_steps'                    #  T  = total number of time steps
    'max_nuclides_in_any_region'    #  N  = maximum unique nuclides found in any region
    'gamma_energy_bins'             #  E  = number of gamma energy bins (default=42)
    }

}

if process_dtrk_file:
dchain_output.update({
'neutron':{                         #  ~  Neutron spectra and totals
    'spectra':{                     #  -  Actual values used in DCHAIN
        'E_lower'                   # [R][E] bin energy lower-bound [MeV]
        'E_upper'                   # [R][E] bin energy upper-bound [MeV]
        'flux':{'value'             # [R][E] neutron flux [#/s/cm^2]
                'error'}            # [R][E] neutron flux [#/s/cm^2]
        }                          
    'total_flux':{'value'           # [R] total neutron flux [#/s/cm^2]
                  'error'}          # [R] total neutron flux [#/s/cm^2]
    'unit_spectra':{                #  -  Flux per unit source particle
        'E_lower'                   # [R][E] bin energy lower-bound [MeV]
        'E_upper'                   # [R][E] bin energy upper-bound [MeV]
        'flux':{'value'             # [R][E] neutron flux [#/s/cm^2/s.p.]
                'error'}            # [R][E] neutron flux [#/s/cm^2/s.p.]
        }                          
    }
})

if process_dyld_files:
dchain_output.update({
'yields':{                          #  ~  Yield spectra
    'all_names'                     # [N] names of all nuclides produced 
    'names'                         # [R][N] names of nuclides produced in each region 
    'TeX_names'                     # [R][N] LaTeX-formatted names of nuclides produced
    'ZZZAAAM'                       # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                                    #        (ground state m=0, metastable m=1,2,etc.)
    'rate':{                        #  -  Actual values used in DCHAIN (100% beam power)
           'value'                  # [R][E] nuclide yield rate [#/s/cm^3]
           'error'                  # [R][E] nuclide yield rate [#/s/cm^3]
        }                          
    'unit_spectra':{                #  -  Yields per unit source particle
           'value'                  # [R][E] nuclide yield rate [#/s.p.]
           'error'                  # [R][E] nuclide yield rate [#/s.p.]
        }                          
    }
})

if process_DCS_file: # add extra information
    # Notation for output array dimensions
    #   R  (n_reg)    regions
    #   Td (ntsteps)  time steps in DCS file (usually differs from that of *.act file!)
    #   Nd (nnuc_max) max number of nuclides (this index differs from the *.act N index)
    #   C  (chni_max) maximum index of relevant chains
    #   L  (chln_max) maximum number of links per chain

dchain_output.update({

'DCS':{
    'time':{
        'from_start_sec'            # [Td] list of times from start time [sec]
        'from_EOB_sec'              # [Td] times from end of final bombardment [sec]
        'of_EOB_sec'                #      scalar time of end of final bombardment [sec]
        }

    'number_of':{                   #  ~  Maximum values of R, Td, Nd, C, and L
        'regions'                   #  R  = total number of regions
        'time_steps'                #  Td = total number of time steps
        'max_nuclides'              #  Nd = max number of end nuclides in any time step
        'max_number_of_chains'      #  C  = highest index of a relevant chain found
        'max_chain_length'          #  L  = max number of links (nuclides) in any chain
        }

    'end_nuclide':{                 #  ~  Informtaion on nuclides ending each chain 
        'names'                     # [R][Td,Nd] nuclide names
        'inventory':{             
            'N_previous'            # [R][Td,Nd,C] N in previous time step [atoms/cc]
            'N_now'                 # [R][Td,Nd,C] N in current time step [atoms/cc]
            'dN'                    # [R][Td,Nd,C] change in N of end nuclide from
                                    #              prev. to current time step [atoms/cc]
            }                      
        'activity':{                
            'A_previous'            # [R][Td,Nd,C] A in previous time step [Bq/cc]
            'A_now'                 # [R][Td,Nd,C] A in the current time step [Bq/cc] 
            'dA'                    # [R][Td,Nd,C] change in A of end nuclide from 
                                    #              prev. to current time step [Bq/cc]
            }
        }

    'chains':{                      #  ~  Chains, links, and their contributions
        'indices_of_printed_chains' # [R][Td,Nd]     list of chain indices printed to
                                    #                *.dcs, valid values of C index
        'length'                    # [R][Td,Nd,C]   length of listed chain, L max index
        'link_nuclides'             # [R][Td,Nd,C,L] strings of nuclides in each chain
        'link_decay_modes'          # [R][Td,Nd,C,L] strings of decay modes each link
                                    #                undergoes to produce the next link
        'link_dN':{                 # (only filled if values in file, 'None' otherwise)
            'beam'                  # [R][Td,Nd,C,L] beam contribution to dN per link
            'decay_nrxn'            # [R][Td,Nd,C,L] decay/neutron rxn dN contribution
            'total'                 # [R][Td,Nd,C,L] total contribution to dN per link 
            }
        }

    'relevant_nuclides':{           #  ~  A vs t of nuclides over relevancy threshold
        'names'                     # [R]        list of relevant nuclides per region
        'times'                     # [R][Td,Nd] time [s]
        'inventory'                 # [R][Td,Nd] inventory [atm/cc]
        'activity'                  # [R][Td,Nd] activity [Bq/cc]
        } 
    }
})
Expand source code
def process_dchain_simulation_output(simulation_folder_path,simulation_basename,dtrk_filepath=None,dyld_filepath=None,process_DCS_file=False):
    '''
    Description:
        This is intended to be a single master function for processing DCHAIN output.
        
    Dependencies:
        from munch import *
    
    Inputs:
        - `simulation_folder_path` = text string of path to folder containing simulation output (should end with / or \)
        - `simulation_basename` = common string of the DCHAIN simulations; output files are named THIS_NAME.*
        - `dtrk_filepath` = file path to \*.dtrk file, only necessary if it has a different basename and there are multiple \*.dtrk files in the folder
        - `dyld_filepath` = file path to \*.dyld files, only necessary if it has a different basename and there are multiple \*.dyld files in the folder
        - `process_DCS_file` (optional) = Boolean variable specifying whether the DCS file should be processed too. (D=False)
        
    Outputs:
        - `dchain_output` = a dictionary object containing all information from DCHAIN's output files.  See the keys breakdown below.
                       
            Technically, it tries to return a "munchify" object which can be used both exactly like a dictionary but also 
            provides attribute-style access like a namedtuple or Class object such as `dchain_output.time.of_EOB_sec` rather
            than the dictionary style `dchain_output['time']['of_EOB_sec']`
        
   
    dchain_output dictionary structure:
    --------
        # Notation for output array dimensions
        #   R  regions
        #   T  time steps
        #   N  max number of nuclides found in a single region
        #   E  number of gamma energy bins
        # le10 for top 10 lists, a number <= 10
    
        {
        dchain_output = {
        'time':{                            #  ~  Time information
            'from_start_sec'                # [T] list of times from start time [sec]
            'from_EOB_sec'                  # [T] times from end of final bombardment [sec]
            'of_EOB_sec'                    #     scalar time of end of final bombardment [sec]
            }                              
        
        'region':{                          #  ~  Information which only varies with region
            'numbers'                       # [R] region numbers
            'number'                        # [R] region numbers
            'irradiation_time_sec'          # [R] irradiation time per region
            'volume'                        # [R] volume in [cc] per region
            'neutron_flux'                  # [R] neutron flux in [n/cm^2/s] per region
            'beam_power_MW'                 # [R] beam power in [MW] per region
            'beam_energy_GeV'               # [R] beam energy in [GeV] per region
            'beam_current_mA'               # [R] beam current in [mA] per region
            }                              
                                            
        'nuclides':{                        #  ~  Main nuclide results from *.act file
            'names'                         # [R][N] names of nuclides produced in each region 
            'TeX_names'                     # [R][N] LaTeX-formatted names of nuclides produced
            'ZZZAAAM'                       # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                                            #        (ground state m=0, metastable m=1,2,etc.)
            'half_life'                     # [R][N] half lives of nuclides produced [sec]
            'inventory':{'value'            # [R][T,N] atoms [#/cc]
                         'error'}           # [R][T,N] atoms [#/cc]
            'activity':{'value'             # [R][T,N] activity [Bq/cc]
                        'error'}            # [R][T,N] activity [Bq/cc]
            'dose_rate':{'value'            # [R][T,N] dose-rate [uSv/h*m^2]
                         'error'}           # [R][T,N] dose-rate [uSv/h*m^2]
            'decay_heat':{                  
                'total':{'value'            # [R][T,N] total decay heat [W/cc]
                         'error'}           # [R][T,N] total decay heat [W/cc]
                'beta':{'value'             # [R][T,N] beta decay heat [W/cc]
                        'error'}            # [R][T,N] beta decay heat [W/cc]
                'gamma':{'value'            # [R][T,N] gamma decay heat [W/cc]
                         'error'}           # [R][T,N] gamma decay heat [W/cc]
                'alpha':{'value'            # [R][T,N] alpha decay heat [W/cc]
                         'error'}           # [R][T,N] alpha decay heat [W/cc]
                }                          
            'column_headers'                # Length 7 list of the *.act columns' descriptions
            'total':{                       #  ~  Total values summed over all nuclides
                'activity':{'value'         # [R][T] total activity [Bq/cc]
                            'error'}        # [R][T] total activity [Bq/cc]
                'decay_heat':{'value'       # [R][T] total decay heat [W/cc]
                              'error'}      # [R][T] total decay heat [W/cc]
                'beta_heat':{'value'        # [R][T] total beta decay heat [W/cc]
                             'error'}       # [R][T] total beta decay heat [W/cc]
                'gamma_heat':{'value'       # [R][T] total gamma decay heat [W/cc]
                              'error'}      # [R][T] total gamma decay heat [W/cc]
                'alpha_heat':{'value'       # [R][T] total alpha decay heat [W/cc]
                              'error'}      # [R][T] total alpha decay heat [W/cc]
                'activated_atoms':{'value'  # [R][T] total activated atoms [#/cc]
                                   'error'} # [R][T] total activated atoms [#/cc]
                'gamma_dose_rate':{'value'  # [R][T] total gamma dose rate [uSV/h*m^2]
                                   'error'} # [R][T] total gamma dose rate [uSV/h*m^2]
                }
            }
        
        'gamma':{                           #  ~  Gamma spectra and totals
            'spectra':{
                'group_number'              # [R][T,E] group number
                'E_lower'                   # [R][T,E] bin energy lower-bound [MeV]
                'E_upper'                   # [R][T,E] bin energy upper-bound [MeV]
                'flux':{'value'             # [R][T,E] flux [#/s/cc]
                        'error'}            # [R][T,E] flux [#/s/cc]
                'energy_flux':{'value'      # [R][T,E] energy flux [MeV/s/cc]
                               'error'}     # [R][T,E] energy flux [MeV/s/cc]
                }                          
            'total_flux':{'value'           # [R][T] total gamma flux [#/s/cc]
                          'error'}          # [R][T] total gamma flux [#/s/cc]
            'total_energy_flux':{'value'    # [R][T] total gamma energy flux [MeV/s/cc]
                                 'error'}   # [R][T] total gamma energy flux [MeV/s/cc]
            'annihilation_flux':{'value'    # [R][T] annihilation gamma flux [#/s/cc]
                                 'error'}   # [R][T] annihilation gamma flux [#/s/cc]
            'current_underflow':{'value'    # [R][T] gamma current underflow [#/s]
                                 'error'}   # no error reported
            'current_overflow':{'value'     # [R][T] gamma current overflow [#/s]
                                'error'}    # no error reported
            }
        
        'top10':{                           #  ~  Top 10 lists from *.act file
            'activity':{
                'rank'                      # [R][T,le10] rank
                'nuclide'                   # [R][T,le10] nuclide name
                'value'                     # [R][T,le10] activity [Bq/cc]
                'error'                     # [R][T,le10] activity [Bq/cc]
                'percent'                   # [R][T,le10] percent of total activity
                }                         
            'decay_heat':{                 
                'rank'                      # [R][T,le10] rank
                'nuclide'                   # [R][T,le10] nuclide name
                'value'                     # [R][T,le10] decay heat [W/cc]
                'error'                     # [R][T,le10] decay heat [W/cc]
                'percent'                   # [R][T,le10] percent of total decay heat
                }                         
            'gamma_dose':{                 
                'rank':                     # [R][T,le10] rank
                'nuclide'                   # [R][T,le10] nuclide name
                'value'                     # [R][T,le10] dose-rate [uSv/h*m^2]
                'error'                     # [R][T,le10] dose-rate [uSv/h*m^2]
                'percent'                   # [R][T,le10] percent of total gamma dose rate
                }
            }
        
        'number_of':{                       #  ~  Maximum values of R, T, N, and E
            'regions'                       #  R  = total number of regions
            'time_steps'                    #  T  = total number of time steps
            'max_nuclides_in_any_region'    #  N  = maximum unique nuclides found in any region
            'gamma_energy_bins'             #  E  = number of gamma energy bins (default=42)
            }
        
        }
        
        if process_dtrk_file:
        dchain_output.update({
        'neutron':{                         #  ~  Neutron spectra and totals
            'spectra':{                     #  -  Actual values used in DCHAIN
                'E_lower'                   # [R][E] bin energy lower-bound [MeV]
                'E_upper'                   # [R][E] bin energy upper-bound [MeV]
                'flux':{'value'             # [R][E] neutron flux [#/s/cm^2]
                        'error'}            # [R][E] neutron flux [#/s/cm^2]
                }                          
            'total_flux':{'value'           # [R] total neutron flux [#/s/cm^2]
                          'error'}          # [R] total neutron flux [#/s/cm^2]
            'unit_spectra':{                #  -  Flux per unit source particle
                'E_lower'                   # [R][E] bin energy lower-bound [MeV]
                'E_upper'                   # [R][E] bin energy upper-bound [MeV]
                'flux':{'value'             # [R][E] neutron flux [#/s/cm^2/s.p.]
                        'error'}            # [R][E] neutron flux [#/s/cm^2/s.p.]
                }                          
            }
        })
        
        if process_dyld_files:
        dchain_output.update({
        'yields':{                          #  ~  Yield spectra
            'all_names'                     # [N] names of all nuclides produced 
            'names'                         # [R][N] names of nuclides produced in each region 
            'TeX_names'                     # [R][N] LaTeX-formatted names of nuclides produced
            'ZZZAAAM'                       # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                                            #        (ground state m=0, metastable m=1,2,etc.)
            'rate':{                        #  -  Actual values used in DCHAIN (100% beam power)
                   'value'                  # [R][E] nuclide yield rate [#/s/cm^3]
                   'error'                  # [R][E] nuclide yield rate [#/s/cm^3]
                }                          
            'unit_spectra':{                #  -  Yields per unit source particle
                   'value'                  # [R][E] nuclide yield rate [#/s.p.]
                   'error'                  # [R][E] nuclide yield rate [#/s.p.]
                }                          
            }
        })
        
        if process_DCS_file: # add extra information
            # Notation for output array dimensions
            #   R  (n_reg)    regions
            #   Td (ntsteps)  time steps in DCS file (usually differs from that of *.act file!)
            #   Nd (nnuc_max) max number of nuclides (this index differs from the *.act N index)
            #   C  (chni_max) maximum index of relevant chains
            #   L  (chln_max) maximum number of links per chain
            
        dchain_output.update({
    
        'DCS':{
            'time':{
                'from_start_sec'            # [Td] list of times from start time [sec]
                'from_EOB_sec'              # [Td] times from end of final bombardment [sec]
                'of_EOB_sec'                #      scalar time of end of final bombardment [sec]
                }
            
            'number_of':{                   #  ~  Maximum values of R, Td, Nd, C, and L
                'regions'                   #  R  = total number of regions
                'time_steps'                #  Td = total number of time steps
                'max_nuclides'              #  Nd = max number of end nuclides in any time step
                'max_number_of_chains'      #  C  = highest index of a relevant chain found
                'max_chain_length'          #  L  = max number of links (nuclides) in any chain
                }
            
            'end_nuclide':{                 #  ~  Informtaion on nuclides ending each chain 
                'names'                     # [R][Td,Nd] nuclide names
                'inventory':{             
                    'N_previous'            # [R][Td,Nd,C] N in previous time step [atoms/cc]
                    'N_now'                 # [R][Td,Nd,C] N in current time step [atoms/cc]
                    'dN'                    # [R][Td,Nd,C] change in N of end nuclide from
                                            #              prev. to current time step [atoms/cc]
                    }                      
                'activity':{                
                    'A_previous'            # [R][Td,Nd,C] A in previous time step [Bq/cc]
                    'A_now'                 # [R][Td,Nd,C] A in the current time step [Bq/cc] 
                    'dA'                    # [R][Td,Nd,C] change in A of end nuclide from 
                                            #              prev. to current time step [Bq/cc]
                    }
                }
                   
            'chains':{                      #  ~  Chains, links, and their contributions
                'indices_of_printed_chains' # [R][Td,Nd]     list of chain indices printed to
                                            #                *.dcs, valid values of C index
                'length'                    # [R][Td,Nd,C]   length of listed chain, L max index
                'link_nuclides'             # [R][Td,Nd,C,L] strings of nuclides in each chain
                'link_decay_modes'          # [R][Td,Nd,C,L] strings of decay modes each link
                                            #                undergoes to produce the next link
                'link_dN':{                 # (only filled if values in file, 'None' otherwise)
                    'beam'                  # [R][Td,Nd,C,L] beam contribution to dN per link
                    'decay_nrxn'            # [R][Td,Nd,C,L] decay/neutron rxn dN contribution
                    'total'                 # [R][Td,Nd,C,L] total contribution to dN per link 
                    }
                }
            
            'relevant_nuclides':{           #  ~  A vs t of nuclides over relevancy threshold
                'names'                     # [R]        list of relevant nuclides per region
                'times'                     # [R][Td,Nd] time [s]
                'inventory'                 # [R][Td,Nd] inventory [atm/cc]
                'activity'                  # [R][Td,Nd] activity [Bq/cc]
                } 
            }
        })
    
    '''
    global start
    
    try:
        start_time = start
    except:
        start_time = time.time()
    start = start_time
    
    if simulation_basename[-3:] == '.in': simulation_basename = simulation_basename[:-3]
    simulation_file_basic_path = simulation_folder_path + simulation_basename # only need to add output file extension
    
    act_file = simulation_file_basic_path + '.act'
    dcs_file = simulation_file_basic_path + '.dcs'
    
    process_dtrk_file = True
    if dtrk_filepath: # DTRK file manually provided
        dtrk_file = dtrk_filepath
        if not os.path.exists(dtrk_file):
            print('    Provided .dtrk file could not be found: {}'.format(dtrk_filepath))
            process_dtrk_file = False
    else: # automatically find DTRK file
        dtrk_file = simulation_file_basic_path + '.dtrk'
        if not os.path.exists(dtrk_file):
            # look for any files with the .dtrk extension in the simulation folder path
            valid_files = []
            valid_filepaths = []
            for file in os.listdir(simulation_folder_path):
                if file.endswith(".dtrk"):
                    valid_files.append(file)
                    valid_filepaths.append(os.path.join(simulation_folder_path, file))
            if len(valid_files)>0:
                print('    Could not find default .dtrk file {}, using {} in same directory instead.'.format(simulation_basename + '.dtrk',valid_files[0]))
                dtrk_file = valid_filepaths[0]
            else:
                print('    No .dtrk files could not be found in provided simulation folder: {}'.format(simulation_folder_path))
                process_dtrk_file = False
                
    if process_dtrk_file:
        neutron_flux, dtrk_metadata = parse_dtrk_file(dtrk_file,return_metadata=True)
    
    process_dyld_file = True
    if dyld_filepath: # dyld file manually provided
        dyld_file = dyld_filepath
        if not os.path.exists(dyld_file):
            print('    Provided .dyld file could not be found: {}'.format(dyld_filepath))
            process_dyld_file = False
    else: # automatically find dyld file
        dyld_file = simulation_file_basic_path + '.dyld'
        if not os.path.exists(dyld_file):
            # look for any files with the .dyld extension in the simulation folder path
            valid_files = []
            valid_filepaths = []
            for file in os.listdir(simulation_folder_path):
                if file.endswith(".dyld"):
                    valid_files.append(file)
                    valid_filepaths.append(os.path.join(simulation_folder_path, file))
            if len(valid_files)>0:
                print('    Could not find default .dyld file {}, using {} in same directory instead.'.format(simulation_basename + '.dyld',valid_files[0]))
                dyld_file = valid_filepaths[0]
            else:
                print('    No .dyld files could not be found in provided simulation folder: {}'.format(simulation_folder_path))
                process_dyld_file = False
    
    print('{:<50}     ({:0.2f} seconds elapsed)'.format('    Parsing DCHAIN activation file...',time.time()-start))
    
    parse_DCHAIN_act_file_OUTPUT = parse_DCHAIN_act_file(act_file)
    reg_nos           = parse_DCHAIN_act_file_OUTPUT[0]  # length R list of region numbers 
    time_list_sec     = parse_DCHAIN_act_file_OUTPUT[1]  # length T list of measurement times (in sec) from start of irradiation
    time_list_sec_after_EOB = parse_DCHAIN_act_file_OUTPUT[2] # length T list of measurement times (in sec) from end of last irradiation
    irradiation_end_t = parse_DCHAIN_act_file_OUTPUT[3]  # time of end of irradiation (in sec)
    nuclides_produced = parse_DCHAIN_act_file_OUTPUT[4]  # NumPy array of dimension RxTxNx11x2 of nuclide table data (N=max recorded table lenth) (last index 0=value, 1=abs error) [0='nuclide',1='atoms [#/cc]',2='activity [Bq/cc]',3='activity [Bq]',4='rate [%]',5='beta decay heat [W/cc]',6='gamma decay heat [W/cc]',7='alpha decay heat [W/cc]',8='total decay heat [W/cc]',9='half life [s]',10='dose-rate [uSv/h*m^2]']
    gamma_spectra     = parse_DCHAIN_act_file_OUTPUT[5]  # NumPy array of dimension RxTxEx5x2 of gamma spectrum table data (last index 0=value, 1=abs error) [0='group number',1='bin energy lower-bound [MeV]',2='bin energy upper-bound [MeV]',3='flux [#/s/cc]',4='energy flux [MeV/s/cc]']
    top10_lists       = parse_DCHAIN_act_file_OUTPUT[6]  # NumPy array of dimension RxTxNx12x2 of top-10 list table data (last index 0=value, 1=abs error) [0='rank',1='nuclide -  Activity ranking',2='activity [Bq/cc]',3='activity [Bq]',4='rate [%]',5='nuclide - Decay heat ranking',6='decay heat [W/cc]',7='decay heat [W]',8='rate [%]',9='nuclide - Dose rate ranking',10='dose-rate [uSv/h*m^2]',11='rate [%]']
    column_headers    = parse_DCHAIN_act_file_OUTPUT[7]  # List containing 3 lists of column headers for the preceding three NumPy arrays
    summary_info      = parse_DCHAIN_act_file_OUTPUT[8]  # list of the below lists/arrays of summary information
    r_summary_info              = summary_info[0]  # NumPy array of dimension Rx7 of region-specific summary info [0='region number',1='irradiation time [s]',2='region volume [cc]',3='neutron flux [n/cm^2/s]',4='beam power [MW]',5='beam energy [GeV]',6='beam current [mA]']
    r_summary_info_description  = summary_info[1]  # list of length 7 containing descriptions of the above items [0='region number',1='irradiation time [s]',2='region volume [cc]',3='neutron flux [n/cm^2/s]',4='beam power [MW]',5='beam energy [GeV]',6='beam current [mA]']
    rt_summary_info             = summary_info[2]  # NumPy array of dimension RxTx12x2 of region and time-specific summary info (last index 0=value, 1=abs error) [0='total gamma flux [#/s/cc]',1='total gamma energy flux [MeV/s/cc]',2='annihilation gamma flux [#/s/cc]',3='gamma current underflow [#/s]',4='gamma current overflow [#/s]',5='total activity [Bq/cc]',6='total decay heat [W/cc]',7='beta decay heat [W/cc]',8='gamma decay heat [W/cc]',9='alpha decay heat [W/cc]',10='activated atoms [#/cc]',11='total gamma dose rate [uSV/h*m^2]']
    rt_summary_info_description = summary_info[3]  # list of length 12 containing descriptions of the above items [0='total gamma flux [#/s/cc]',1='total gamma energy flux [MeV/s/cc]',2='annihilation gamma flux [#/s/cc]',3='gamma current underflow [#/s]',4='gamma current overflow [#/s]',5='total activity [Bq/cc]',6='total decay heat [W/cc]',7='beta decay heat [W/cc]',8='gamma decay heat [W/cc]',9='alpha decay heat [W/cc]',10='activated atoms [#/cc]',11='total gamma dose rate [uSV/h*m^2]']
    
    
    print('{:<50}     ({:0.2f} seconds elapsed)'.format('    Restructuring nuclide data table array...',time.time()-start))
    
    generate_nuclide_time_profiles_OUTPUT = generate_nuclide_time_profiles(nuclides_produced)
    nuclide_names        = generate_nuclide_time_profiles_OUTPUT[0]  # List of length R of lists containing names of nuclides produced in each region 
    LaTeX_nuclide_names  = generate_nuclide_time_profiles_OUTPUT[1]  # List of length R of lists containing LaTeX-formatted names of nuclides produced in each region 
    nuclide_ZAM_vals     = generate_nuclide_time_profiles_OUTPUT[2]  # List of length R of lists containing ZZZAAAM values (=10000*Z+10*A+M) of nuclides produced in each region (ground state m=0, metastable m=1,2,etc.)
    nuclide_half_lives   = generate_nuclide_time_profiles_OUTPUT[3]  # List of length R of lists containing half lives of nuclides produced in each region (in seconds)
    nuclide_info         = generate_nuclide_time_profiles_OUTPUT[4]  # List of length R of NumPy arrays of dimension TxNx7x2 of nuclide info (last index 0=value, 1=abs error) [0='atoms [#/cc]',1='activity [Bq/cc]','2=beta decay heat [W/cc]','3=gamma decay heat [W/cc]','4=alpha decay heat [W/cc]','5=total decay heat [W/cc]','6=dose-rate [uSv/h*m^2]']
    nuclide_info_headers = generate_nuclide_time_profiles_OUTPUT[5]  # List of length 7 containing text descriptions of the 7 columns of the info arrays [0='atoms [#/cc]',1='activity [Bq/cc]','2=beta decay heat [W/cc]','3=gamma decay heat [W/cc]','4=alpha decay heat [W/cc]','5=total decay heat [W/cc]','6=dose-rate [uSv/h*m^2]']
    
    
    if process_dyld_file:
        if process_dtrk_file:
            if dtrk_metadata[0]=='dchain':
                iredufmt=1
            else: # dtrk_metadata[0]=='eng'
                iredufmt=0
        else:
            iredufmt=0
        yields, nuclide_names_yld = parse_dyld_files(dyld_file,iredufmt=iredufmt)
        
        # Now reformat for more user-friendly output values
        # regionwise values
        beam_fluxs = (6.2415064e15)*r_summary_info[:,6] # particles/sec (converted from mA)
        reg_vols = r_summary_info[:,2]
        nregs,nnuc = np.shape(yields)[0],np.shape(yields)[1]
        reg_yld_names = []
        reg_yld_texnames = []
        reg_yld_zam = []
        yield_values_by_reg = [[],[],[],[]] # yield, yield abs err, unit yield, unit yield abs err
        for ri in range(nregs):
            for ni in range(nnuc):
                if ni==0:
                    reg_yld_names.append([])
                    reg_yld_texnames.append([])
                    reg_yld_zam.append([])
                    for j in range(4):
                        yield_values_by_reg[j].append([])
                if yields[ri,ni,0] != 0.0:
                    reg_yld_names[-1].append(nuclide_names_yld[ni])
                    reg_yld_texnames[-1].append(Dname_to_Latex(nuclide_names_yld[ni]))
                    reg_yld_zam[-1].append(Dname_to_ZAM(nuclide_names_yld[ni]))
                    yield_values_by_reg[0][-1].append(beam_fluxs[ri]*yields[ri,ni,0]/reg_vols[ri])
                    yield_values_by_reg[1][-1].append(beam_fluxs[ri]*yields[ri,ni,1]/reg_vols[ri])
                    yield_values_by_reg[2][-1].append(yields[ri,ni,0])
                    yield_values_by_reg[3][-1].append(yields[ri,ni,1])
    
    
    
    if process_DCS_file:
        # Control parameters
        relevancy_threshold=0.01 # fraction of total activity an isotope must be at any time step in DCS file to be placed in the "relevant" array
        
        fcn_out = parse_DCS_file_from_DCHAIN(dcs_file,relevancy_threshold=relevancy_threshold)
        
        # Notation for output array dimensions
        #   R (n_reg)    regions
        #   T (ntsteps)  time steps
        #   N (nnuc_max) max number of nuclides
        #   C (chni_max) maximum index of relevant chains
        #   L (chln_max) maximum number of links per chain
        
        inventory  = fcn_out[0] # universal columns of DCS file [R,T,N,C,vi], vi: 0=N_i-1/V, 1=dN/V, 2=N_i/V, 3=A_i/V, 4=A_i
        l_chains   = fcn_out[1] # [R,T,N,C], length of listed chain
        prod_nuc   = fcn_out[2] # [R,T,N], strings of the nuclide being produced
        chn_indx   = fcn_out[3] # [R,T,N], lists of the chain indices printed
        link_nuc   = fcn_out[4] # [R,T,N,C,L], strings of the nuclides in each chain
        decay_mode = fcn_out[5] # [R,T,N,C,L], strings of the decay modes each link undergoes to produce the next link
        link_dN_info=fcn_out[6] # [R,T,N,C,L,di], extra dN info di: 0=dN_Beam, 1=dN_Decay/nrxn, 2=dN_Total (only generated if these values are found in file, 'None' otherwise)
        end_of_irradiation_time = fcn_out[7] # time of end of final irradiation step [seconds]
        notable_nuclides_names_by_region = fcn_out[8] # list of lists (one per region) containing the relevant nuclides per region
        notable_nuclides_AvT_by_region   = fcn_out[9] # list of arrays (one per region, [T,N_rlv-nuc,3]) containing the time[s]/inventory[atm/cc]/activity[Bq/cc] data of relevant nuclides
        
        n_reg,ntsteps,nnuc_max,chni_max,chln_max = np.shape(decay_mode)
        
        relevant_nuclides = notable_nuclides_names_by_region[0]
        n_relevant_nuclides = len(relevant_nuclides)
        relv_nuc_inv = notable_nuclides_AvT_by_region[0]
        
        t_from_start = relv_nuc_inv[:,0,0]
        t_after_irrad_sec = ((relv_nuc_inv[:,0,0]-end_of_irradiation_time))
    
    nreg = len(reg_nos)
    rilist = range(nreg)
    #regionwise_gamma_spectra = [gamma_spectra[ri,:,:,:,:] for ri in range(nreg)]
    #rw_totgflux_val = [rt_summary_info[ri,:,0,0] for ri in range(nreg)]
    #rw_totgflux_ae = [rt_summary_info[ri,:,0,1] for ri in range(nreg)]
    #rw_totegflux = [rt_summary_info[ri,:,1,:] for ri in range(nreg)]
    
    
    # Notation for output array dimensions
    #   R  regions
    #   T  time steps
    #   N  max number of nuclides found in a single region
    #   E  number of gamma energy bins
    # le10 for top 10 lists, a number <= 10
    
    dchain_output = {
        'time':{                                              #  ~  Time information
            'from_start_sec':time_list_sec,                   # [T] list of times from start time [sec]
            'from_EOB_sec':time_list_sec_after_EOB,           # [T] list of times from end of final bombardment [sec]
            'of_EOB_sec':irradiation_end_t,                   #     scalar time marking end of final bombardment [sec]
            },
        
        'region':{                                            #  ~  Information which only varies with region
            'numbers':reg_nos,                                # [R] list of all region numbers
            'number': r_summary_info[:,0],                    # [R] list of all region numbers
            'irradiation_time_sec': r_summary_info[:,1],      # [R] list of irradiation time per region
            'volume': r_summary_info[:,2],                    # [R] list of volume in cc per region
            'neutron_flux': r_summary_info[:,3],              # [R] list of neutron flux in n/cm^2/s per region
            'beam_power_MW': r_summary_info[:,4],             # [R] list of beam power in MW per region
            'beam_energy_GeV': r_summary_info[:,5],           # [R] list of beam energy in GeV per region
            'beam_current_mA': r_summary_info[:,6]            # [R] list of beam current in mA per region
            },
        
        'nuclides':{                                          #  ~  Main nuclide results from *.act file
            'names':nuclide_names,                            # [R][N] list of lists containing names of nuclides produced in each region 
            'TeX_names':LaTeX_nuclide_names,                  # [R][N] list of lists containing LaTeX-formatted names of nuclides produced in each region 
            'ZZZAAAM':nuclide_ZAM_vals,                       # [R][N] list of lists containing ZZZAAAM values (=10000*Z+10*A+M) of nuclides produced in each region (ground state m=0, metastable m=1,2,etc.)
            'half_life':nuclide_half_lives,                   # [R][N] list of lists containing half lives of nuclides produced in each region (in seconds)
            'inventory':{'value':[nuclide_info[ri][:,:,0,0] for ri in rilist],       # [R][T,N] atoms [#/cc]
                         'error':[nuclide_info[ri][:,:,0,1] for ri in rilist]},      # [R][T,N] atoms [#/cc]
            'activity':{'value':[nuclide_info[ri][:,:,1,0] for ri in rilist],        # [R][T,N] activity [Bq/cc]
                        'error':[nuclide_info[ri][:,:,1,1] for ri in rilist]},       # [R][T,N] activity [Bq/cc]
            'dose_rate':{'value':[nuclide_info[ri][:,:,6,0] for ri in rilist],       # [R][T,N] dose-rate [uSv/h*m^2]
                         'error':[nuclide_info[ri][:,:,6,1] for ri in rilist]},      # [R][T,N] dose-rate [uSv/h*m^2]
            'decay_heat':{
                'total':{'value':[nuclide_info[ri][:,:,5,0] for ri in rilist],       # [R][T,N] total decay heat [W/cc]
                         'error':[nuclide_info[ri][:,:,5,1] for ri in rilist]},      # [R][T,N] total decay heat [W/cc]
                'beta':{'value':[nuclide_info[ri][:,:,2,0] for ri in rilist],        # [R][T,N] beta decay heat [W/cc]
                        'error':[nuclide_info[ri][:,:,2,1] for ri in rilist]},       # [R][T,N] beta decay heat [W/cc]
                'gamma':{'value':[nuclide_info[ri][:,:,3,0] for ri in rilist],       # [R][T,N] gamma decay heat [W/cc]
                         'error':[nuclide_info[ri][:,:,3,1] for ri in rilist]},      # [R][T,N] gamma decay heat [W/cc]
                'alpha':{'value':[nuclide_info[ri][:,:,4,0] for ri in rilist],       # [R][T,N] alpha decay heat [W/cc]
                         'error':[nuclide_info[ri][:,:,4,1] for ri in rilist]},      # [R][T,N] alpha decay heat [W/cc]
                },
            'column_headers':nuclide_info_headers, # List of length 7 containing text descriptions of the 7 columns of the info arrays [0='atoms [#/cc]',1='activity [Bq/cc]','2=beta decay heat [W/cc]','3=gamma decay heat [W/cc]','4=alpha decay heat [W/cc]','5=total decay heat [W/cc]','6=dose-rate [uSv/h*m^2]']
            'total':{                                                                       #  ~  Total values summed over all nuclides 
                'activity':{'value':[rt_summary_info[ri,:,5,0] for ri in rilist] ,          # [R][T] total activity [Bq/cc]
                            'error':[rt_summary_info[ri,:,5,1] for ri in rilist]},          # [R][T] total activity [Bq/cc]
                'decay_heat':{'value':[rt_summary_info[ri,:,6,0] for ri in rilist] ,        # [R][T] total decay heat [W/cc]
                              'error':[rt_summary_info[ri,:,6,1] for ri in rilist]},        # [R][T] total decay heat [W/cc]
                'beta_heat':{'value':[rt_summary_info[ri,:,7,0] for ri in rilist] ,         # [R][T] total beta decay heat [W/cc]
                             'error':[rt_summary_info[ri,:,7,1] for ri in rilist]},         # [R][T] total beta decay heat [W/cc]
                'gamma_heat':{'value':[rt_summary_info[ri,:,8,0] for ri in rilist] ,        # [R][T] total gamma decay heat [W/cc]
                              'error':[rt_summary_info[ri,:,8,1] for ri in rilist]},        # [R][T] total gamma decay heat [W/cc]
                'alpha_heat':{'value':[rt_summary_info[ri,:,9,0] for ri in rilist] ,        # [R][T] total alpha decay heat [W/cc]
                              'error':[rt_summary_info[ri,:,9,1] for ri in rilist]},        # [R][T] total alpha decay heat [W/cc]
                'activated_atoms':{'value':[rt_summary_info[ri,:,10,0] for ri in rilist],   # [R][T] total activated atoms [#/cc]
                                   'error':[rt_summary_info[ri,:,10,1] for ri in rilist]},  # [R][T] total activated atoms [#/cc]
                'gamma_dose_rate':{'value':[rt_summary_info[ri,:,11,0] for ri in rilist],   # [R][T] total gamma dose rate [uSV/h*m^2]
                                   'error':[rt_summary_info[ri,:,11,1] for ri in rilist]}   # [R][T] total gamma dose rate [uSV/h*m^2]
                }
            },
        
        
        'gamma':{                                                                                              #  ~  Gamma spectra and totals
            'spectra':{
                'group_number':[gamma_spectra[ri,:,:,0,0] for ri in rilist],                                   # [R][T,E] group number
                'E_lower':[gamma_spectra[ri,:,:,1,0] for ri in rilist],                                        # [R][T,E] bin energy lower-bound [MeV]
                'E_upper':[gamma_spectra[ri,:,:,2,0] for ri in rilist],                                        # [R][T,E] bin energy upper-bound [MeV]
                'flux':{'value':[gamma_spectra[ri,:,:,3,0] for ri in rilist],                                  # [R][T,E] flux [#/s/cc]
                        'error':[gamma_spectra[ri,:,:,3,0]*gamma_spectra[ri,:,:,3,1] for ri in rilist]},       # [R][T,E] flux [#/s/cc]
                'energy_flux':{'value':[gamma_spectra[ri,:,:,4,0] for ri in rilist],                           # [R][T,E] energy flux [MeV/s/cc]
                               'error':[gamma_spectra[ri,:,:,4,0]*gamma_spectra[ri,:,:,4,1] for ri in rilist]} # [R][T,E] energy flux [MeV/s/cc]
                },
            'total_flux':{'value':[rt_summary_info[ri,:,0,0] for ri in rilist],                                # [R][T] total gamma flux [#/s/cc]
                          'error':[rt_summary_info[ri,:,0,1] for ri in rilist]},                               # [R][T] total gamma flux [#/s/cc]
            'total_energy_flux':{'value':[rt_summary_info[ri,:,1,0] for ri in rilist] ,                        # [R][T] total gamma energy flux [MeV/s/cc]
                                 'error':[rt_summary_info[ri,:,1,1] for ri in rilist]},                        # [R][T] total gamma energy flux [MeV/s/cc]
            'annihilation_flux':{'value':[rt_summary_info[ri,:,2,0] for ri in rilist] ,                        # [R][T] annihilation gamma flux [#/s/cc]
                                 'error':[rt_summary_info[ri,:,2,1] for ri in rilist]},                        # [R][T] annihilation gamma flux [#/s/cc]
            'current_underflow':{'value':[rt_summary_info[ri,:,3,0] for ri in rilist] ,                        # [R][T] gamma current underflow [#/s]
                                 'error':[rt_summary_info[ri,:,3,1] for ri in rilist]}, # no error reported
            'current_overflow':{'value':[rt_summary_info[ri,:,4,0] for ri in rilist],                          # [R][T] gamma current overflow [#/s]
                                'error':[rt_summary_info[ri,:,4,1] for ri in rilist]}   # no error reported
            },
        
        'top10':{                                                          #  ~  Top 10 lists from *.act file
            'activity':{ 
                'rank':[top10_lists[ri,:,:,0,0] for ri in rilist],         # [R][T,le10] rank
                'nuclide':[top10_lists[ri,:,:,1,0] for ri in rilist],      # [R][T,le10] nuclide name
                'value':[top10_lists[ri,:,:,2,0] for ri in rilist],        # [R][T,le10] activity [Bq/cc]
                'error':[top10_lists[ri,:,:,2,1] for ri in rilist],        # [R][T,le10] activity [Bq/cc]
                'percent':[top10_lists[ri,:,:,4,0] for ri in rilist],      # [R][T,le10] percent of total activity
                },
            'decay_heat':{
                'rank':[top10_lists[ri,:,:,0,0] for ri in rilist],         # [R][T,le10] rank
                'nuclide':[top10_lists[ri,:,:,5,0] for ri in rilist],      # [R][T,le10] nuclide name
                'value':[top10_lists[ri,:,:,6,0] for ri in rilist],        # [R][T,le10] decay heat [W/cc]
                'error':[top10_lists[ri,:,:,6,1] for ri in rilist],        # [R][T,le10] decay heat [W/cc]
                'percent':[top10_lists[ri,:,:,8,0] for ri in rilist],      # [R][T,le10] percent of total decay heat
                },
            'gamma_dose':{
                'rank':[top10_lists[ri,:,:,0,0] for ri in rilist],         # [R][T,le10] rank
                'nuclide':[top10_lists[ri,:,:,9,0] for ri in rilist],      # [R][T,le10] nuclide name
                'value':[top10_lists[ri,:,:,10,0] for ri in rilist],       # [R][T,le10] dose-rate [uSv/h*m^2]
                'error':[top10_lists[ri,:,:,10,1] for ri in rilist],       # [R][T,le10] dose-rate [uSv/h*m^2]
                'percent':[top10_lists[ri,:,:,11,0] for ri in rilist],     # [R][T,le10] percent of total gamma dose rate
                }
            },
        
        'number_of':{                                                      #  ~  Maximum values of R, T, N, and E
            'regions':nreg,                                                #  R  = total number of regions
            'time_steps':len(time_list_sec),                               #  T  = total number of time steps
            'max_nuclides_in_any_region':np.shape(nuclides_produced)[2],   #  N  = maximum unique nuclides found in any region
            'gamma_energy_bins':np.shape(gamma_spectra)[2]                 #  E  = number of gamma energy bins (default=42)
            }
        
        }
    
    if process_dtrk_file:
        dflux_norm = []
        for ri in rilist:
            if np.sum(neutron_flux[ri,:,2]) != 0:
                dflux_norm.append(r_summary_info[ri,3]/np.sum(neutron_flux[ri,:,2]))
            else:
                dflux_norm.append(0.0)
        #dflux_norm = [r_summary_info[ri,3]/np.sum(neutron_flux[ri,:,2]) for ri in rilist] # normalize unit flux to total flux
        dchain_output.update({
            'neutron':{
                'spectra':{                                                                                         # Actual values used by DCHAIN for rate calcs
                    'E_lower':[neutron_flux[ri,:,0] for ri in rilist],                                              # [R][E] bin energy lower-bound [MeV]
                    'E_upper':[neutron_flux[ri,:,1] for ri in rilist],                                              # [R][E] bin energy upper-bound [MeV]
                    'flux':{'value':[neutron_flux[ri,:,2]*dflux_norm[ri] for ri in rilist],                         # [R][E] neutron flux [#/s/cm^2]
                            'error':[neutron_flux[ri,:,3]*dflux_norm[ri] for ri in rilist]},                        # [R][E] neutron flux [#/s/cm^2]
                    },
                'total_flux':{'value':[np.sum(neutron_flux[ri,:,2])*dflux_norm[ri] for ri in rilist],               # [R] total neutron flux [#/s/cm^2]
                              'error':[np.sqrt(np.sum(neutron_flux[ri,:,3]**2))*dflux_norm[ri] for ri in rilist]},  # [R] total neutron flux [#/s/cm^2]
                'unit_spectra':{                                                         # Raw T-Track output from PHITS  
                    'E_lower':[neutron_flux[ri,:,0] for ri in rilist],                   # [R][E] bin energy lower-bound [MeV]
                    'E_upper':[neutron_flux[ri,:,1] for ri in rilist],                   # [R][E] bin energy upper-bound [MeV]
                    'flux':{'value':[neutron_flux[ri,:,2] for ri in rilist],             # [R][E] neutron flux [#/s/cm^2]
                            'error':[neutron_flux[ri,:,3] for ri in rilist]},            # [R][E] neutron flux [#/s/cm^2]
                    },
                }
            })
    
    if process_dyld_file:
        dchain_output.update({
            'yields':{                                              #  ~  Yield spectra
                'all_names':nuclide_names_yld,                      # [N] names of nuclides produced in all regions
                'names':reg_yld_names,                              # [R][N] names of nuclides produced in each region 
                'TeX_names':reg_yld_texnames,                       # [R][N] LaTeX-formatted names of nuclides produced
                'ZZZAAAM':reg_yld_zam,                              # [R][N] ZZZAAAM values (=10000Z+10A+M) of nuclides 
                'rate':{'value':yield_values_by_reg[0],             # [R][E] nuclide yield rate [#/s/cm^3]
                        'error':yield_values_by_reg[1]},            # [R][E] nuclide yield rate [#/s/cm^3]
                'unit_rate':{'value':yield_values_by_reg[2],        # [R][E] unit nuclide yield rate [#/s.p.]
                             'error':yield_values_by_reg[3]}        # [R][E] unit nuclide yield rate [#/s.p.]
                }
            })
    
    if process_DCS_file: # add extra information
        # Notation for output array dimensions
        #   R  (n_reg)    regions
        #   Td (ntsteps)  time steps in DCS file (usually different from that of ACT file!)
        #   Nd (nnuc_max) max number of nuclides (this index differs from the ACT N index)
        #   C  (chni_max) maximum index of relevant chains
        #   L  (chln_max) maximum number of links per chain
        
        dchain_output.update({'DCS':{
            'time':{
                'from_start_sec':t_from_start,         # [Td] list
                'from_EOB_sec':t_after_irrad_sec,      # [Td] list
                'of_EOB_sec':irradiation_end_t         #      scalar
                },
            
            'number_of':{                              #  ~  Maximum values of R, Td, Nd, C, and L
                'regions':n_reg,                       #  R  = total number of regions
                'time_steps':ntsteps,                  #  Td = total number of time steps
                'max_nuclides':nnuc_max,               #  Nd = maximum number of nuclides listed in a time step
                'max_number_of_chains':chni_max,       #  C  = highest index of a relevant chain found
                'max_chain_length':chln_max            #  L  = maximum number of links (nuclides) found in any chain
                },
            
            'end_nuclide':{
                'names':[prod_nuc[ri,:,:] for ri in rilist],                  # [R][Td,Nd] nuclide names
                
                'inventory':{
                    'N_previous':[inventory[ri,:,:,:,0] for ri in rilist],    # [R][Td,Nd,C] inventory of end nuclide in previous time step [atoms/cc]
                    'N_now':[inventory[ri,:,:,:,2] for ri in rilist],         # [R][Td,Nd,C] inventory of end nuclide in the current time step [atoms/cc]
                    'dN':[inventory[ri,:,:,:,1] for ri in rilist]             # [R][Td,Nd,C] change in inventory of end nuclide from the previous to the current time step [atoms/cc]
                    },
                
                'activity':{
                    'A_previous':[inventory[ri,:,:,:,0]*(inventory[ri,:,:,:,3]/inventory[ri,:,:,:,2]) for ri in rilist], # [R][Td,Nd,C] activity of end nuclide in previous time step [Bq/cc]
                    'A_now':[inventory[ri,:,:,:,3] for ri in rilist],                                                    # [R][Td,Nd,C] activity of end nuclide in the current time step [Bq/cc] 
                    'dA':[inventory[ri,:,:,:,1]*(inventory[ri,:,:,:,3]/inventory[ri,:,:,:,2]) for ri in rilist]          # [R][Td,Nd,C] change in activity of end nuclide from the previous to the current time step [Bq/cc]
                    }
                },
                
                
            'chains':{
                'indices_of_printed_chains':[chn_indx[ri,:,:] for ri in rilist],           # [R][Td,Nd]     lists of the chain indices printed
                'length':[l_chains[ri,:,:,:] for ri in rilist],                            # [R][Td,Nd,C]   length of listed chain
                'link_nuclides':[link_nuc[ri,:,:,:,:] for ri in rilist],                   # [R][Td,Nd,C,L] strings of the nuclides in each chain
                'link_decay_modes':[decay_mode[ri,:,:,:,:] for ri in rilist],              # [R][Td,Nd,C,L] strings of the decay modes each link undergoes to produce the next link
                'link_dN':{
                    'beam':[None if link_dN_info==None else link_dN_info[ri,:,:,:,:,0] for ri in rilist],                  # [R][Td,Nd,C,L] beam contribution to dN from each link (only generated if these values are found in file, 'None' otherwise)
                    'decay_nrxn':[None if link_dN_info==None else link_dN_info[ri,:,:,:,:,1] for ri in rilist],            # [R][Td,Nd,C,L] decay + neutron rxn contribution to dN from each link (only generated if these values are found in file, 'None' otherwise)
                    'total':[None if link_dN_info==None else link_dN_info[ri,:,:,:,:,2] for ri in rilist]                  # [R][Td,Nd,C,L] total contribution to dN from each link (only generated if these values are found in file, 'None' otherwise)
                    }
                },
            
            'relevant_nuclides':{
                'names':notable_nuclides_names_by_region,                                  # [R]        list of relevant nuclides per region
                'times':[notable_nuclides_AvT_by_region[ri][:,:,0] for ri in rilist],      # [R][Td,Nd] time [s]
                'inventory':[notable_nuclides_AvT_by_region[ri][:,:,1] for ri in rilist],  # [R][Td,Nd] inventory [atm/cc]
                'activity':[notable_nuclides_AvT_by_region[ri][:,:,2] for ri in rilist]    # [R][Td,Nd] activity [Bq/cc]
                }
                
            }})
        
    
    # https://github.com/Infinidat/munch
    try:
        dchain_output = munchify(dchain_output)
    except:
        print("munchify failed.  Returned object is a conventional dictionary rather than a munchify object.")
    
    return dchain_output
def retrieve_rxn_xs_from_lib(libfile, target, reaction=None, product=None)

Description

Provided a DCHAIN neutron rxn cross section library file and sufficient information about a reaction, return that reaction's cross section.

Dependencies

rxn_to_dchain_str()

ECCO1968_Ebins()

Inputs

  • libfile = string of file path to data library file to be searched
  • target = string in general format of target nuclide
  • reaction = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X) if reaction = 'tot' or 'total', the summed total transmutation xs (reactions which change the target's nuclide species) is provided and input for product is ignored; this behavior is also assumed when missing both reaction and product information
  • product = (optional) string in general format of product nuclide (if omitted, sum of all isomeric states is assumed; if provided product but not isomeric state, ground state is assumed)

Outputs

  • xs = a 2x1968 numpy array containing energy [eV] (i=0) and cross sections [b] (i=1) for all 1968 ECCO bins
  • rxn_tex_str = a LaTeX-formatted string of the reaction
Expand source code
def retrieve_rxn_xs_from_lib(libfile,target,reaction=None,product=None):
    '''
    Description:
        Provided a DCHAIN neutron rxn cross section library file and sufficient information
        about a reaction, return that reaction's cross section.
    
    Dependencies:
        `rxn_to_dchain_str` 
        
        `ECCO1968_Ebins`
    
    Inputs:
        - `libfile` = string of file path to data library file to be searched
        - `target` = string in general format of target nuclide
        - `reaction` = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
                   if `reaction = 'tot'` or `'total'`, the summed total transmutation xs (reactions which change the target's nuclide species) is provided and input for product is ignored; 
                   this behavior is also assumed when missing both reaction and product information
        - `product` = (optional) string in general format of product nuclide (if omitted, sum of all isomeric states is assumed; if provided product but not isomeric state, ground state is assumed)
        
    Outputs:
        - `xs` = a 2x1968 numpy array containing energy [eV] (i=0) and cross sections [b] (i=1) for all 1968 ECCO bins
        - `rxn_tex_str` = a LaTeX-formatted string of the reaction
    '''
    
    datafolder, lib = os.path.split(libfile.replace('_n_act_xs_lib',''))
    
    n_occurances = 0
    rxn_locations= []
    rxn_listings = []
    xs_data_raw  = []
    
    # determine if rxn or product is provided
    if (not reaction and not product) or (reaction=='tot' or reaction=='total'): # only target provided
        #print('Total transmutation cross section of {}'.format(target))
        calc_total_xs = True 
    else:
        calc_total_xs = False
        
    if calc_total_xs: # only concerned with target, not target or reaction
        # First, assemble reaction string
        rt = rxn_to_dchain_str(target,None,target)[:10] # only concerned with target + '(N,'
        rt_alt = None # alternate string to cover ground state when 'g' is present in product too
        catalog_rxn_text = rt[0] + rt[1:].lower()
        catalog_rxn_text_alt = None
        # make pretty Latex str of reaction too
        target_str = rt[0]  + rt[1:7].lower()
        not_symbol = r'$\neg$' # '!'
        rxn_str    = '(n,*)'
        tex_target = nuclide_plain_str_to_latex_str(target_str)
        if target_str[-2:] == '  ':
            tex_product = nuclide_plain_str_to_latex_str(target_str[:-2]+'g ')
        else:
            tex_product = tex_target
        rxn_tex_str = tex_target + rxn_str + not_symbol + tex_product
    else:
        # First, assemble reaction string
        rt = rxn_to_dchain_str(target,reaction,product)
        rt_alt = None # alternate string to cover ground state when 'g' is present in product too
        if product:
            if rt[-2] == ' ': rt_alt = rt[:-2] + 'G '
        # Format string found in the catalog file
        catalog_rxn_text = rt[0] + rt[1:14].lower() + rt[14] + rt[15:].lower()
        catalog_rxn_text_alt = None
        if rt_alt: catalog_rxn_text_alt = rt_alt[0] + rt_alt[1:14].lower() + rt_alt[14] + rt_alt[15:].lower()
        # make pretty Latex str of reaction too
        target_str = rt[0]  + rt[1:7].lower()
        product_str= rt[14] + rt[15:22].lower()
        rxn_str    = rt[7:14].lower().replace(' ','')
        if 'a' in rxn_str: rxn_str = rxn_str.replace('a',r'$\alpha$')
        if 'g' in rxn_str: rxn_str = rxn_str.replace('g',r'$\gamma$')
        if 'he3' in rxn_str: rxn_str = rxn_str.replace('he3',r'$^3$He')
        tex_target = nuclide_plain_str_to_latex_str(target_str)
        tex_product= nuclide_plain_str_to_latex_str(product_str)
        rxn_tex_str = tex_target + rxn_str + tex_product
    
    if lib[0]=='h': # in hybrid_lib_dchain_names: 
        hlib = True 
    else:
        hlib = False
    catalog_file = libfile.replace('_n_act_xs_lib','_n_reaction_list.txt')
    library_file = libfile
    if os.path.isfile(catalog_file): # catalog file exists
        search_file = catalog_file
        search_text = catalog_rxn_text
        search_text_alt = catalog_rxn_text_alt
        using_catalog_file = True
    elif os.path.isfile(library_file): # library file exists
        search_file = library_file
        search_text = rt
        search_text_alt = rt_alt
        using_catalog_file = False
    else:
        print('\t{} library files not found.'.format(lib))
        return None
    with open(search_file) as f:
        asterisk_counter = -1
        for num, line in enumerate(f, 1):
            if '*' in line: asterisk_counter += 1
            if search_text in line or (rt_alt and search_text_alt in line):
                n_occurances += 1
                if using_catalog_file:
                    rxn_locations.append(num-2) # index of reaction in reaction lib
                    rxn_listings.append(line.replace('\n',''))
                else: # searching through actual library file
                    rxn_locations.append(asterisk_counter) # index of reaction in reaction lib
                    rxn_listings.append(line[20:41])
    
    if n_occurances == 0:
        print('Reaction "{}" not found in library "{}".'.format(catalog_rxn_text,lib))
        return None, 'null'
    if n_occurances > 1:
        if calc_total_xs:
            print('   Multiple reactions found.  The sum of all will be used.')
        else:
            print('   Multiple product isomers for reaction found.  The sum of all isomeric states will be used.')
        for j in range(n_occurances):
            print('\t{}   (lib entry index {} of {})'.format(rxn_listings[j],rxn_locations[j],lib))
    
    if hlib:
        print('Hybrid library {} uses data from {} for reaction {}.'.format(lib,rxn_listings[0][26:],catalog_rxn_text))
    
    # State library availability
    #if hlib and n_occurances>0: # this is a hybrid library
    #    src_lib  = rxn_listings[libi][0][26:]
    #    print('\t{}   {} (from {})'.format(lib,n_occurances,src_lib))
    #    lib_colors[libi] = lib_colors[lib_names.index(src_lib)]
    #else: # normal library
    #    print('\t{}   {}'.format(lib,n_occurances[libi]))
        
    #if n_occurances[libi]>0 and libi in libis_to_compare: n_plotable_lines += 1
    
    if not os.path.isfile(library_file):
        print('\t{} library file not found.'.format(lib))
        return None, 'null'
    if n_occurances == 0:
        print('\t\tCross section not present in this library, skipping')
        return None, 'null'
    entry_index = -1
    with open(library_file) as f:
        lines = f.readlines()
        for line in lines:
            if '*' in line: entry_index += 1
            if entry_index in rxn_locations: # reached bookmarked entry index of interest for this lib
                found_entry = True
                enti = rxn_locations.index(entry_index)
                
                if '*' in line:
                    if rxn_listings[enti][:22].upper() not in line: # double check that entry is correct
                        print('Library index mismatch!')
                        found_entry = False
                    xs_vals = []
                    entry_li = -1
                if found_entry:
                    entry_li += 1
                    if entry_li == 0: # first line
                        nEbins = int(line[12:18])
                    elif entry_li == 1 or entry_li == 2: # skip descriptive lines
                        continue 
                    else:
                        xs_vals += [np.float(xsi) for xsi in line.replace('\n','').split()]
                        
                    if len(xs_vals) == nEbins:
                        if len(xs_data_raw)==0: # need to initialize entry
                            xs_data_raw = np.zeros(1968)
                        xs_data_raw[:nEbins] += np.array(xs_vals)
    
    # Now pretty up the results to be provided to the user
    xs = np.zeros((2,1968))
    xs[0,:] = (1e6)*np.array(ECCO1968_Ebins(1968)[::-1]) # all energy bins in increasing order in eV
    # now flip order of lists to have them in energy increasing order
    for i in range(len(xs_data_raw)): 
        xs[1,1967-i] = xs_data_raw[i]
        
    return xs, rxn_tex_str
def rxn_to_dchain_str(target, reaction=None, product=None)

Desription

Provided a target nuclide and reaction, and optionally a target, generate a reaction string in the format used in DCHAIN's nrxn libs

Dependencies

nuclide_plain_str_ZZZAAAM()

ZZZAAAM_to_dchain_xs_lib_str()

Inputs

  • target = string in general format of target nuclide
  • Note: at least one of the below options must be provided.
    • reaction = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
    • product = (optional) string in general format of product nuclide (if omitted, product in ground state is assumed)

Outputs

  • rxn_dchain_str = string formatted identically as that found in DCHAIN's neutron reaction cross section libraries
Expand source code
def rxn_to_dchain_str(target,reaction=None,product=None):
    '''
    Desription:
        Provided a target nuclide and reaction, and optionally a target, generate a reaction string in the format used in DCHAIN's nrxn libs
    
    Dependencies:
        `nuclide_plain_str_ZZZAAAM`
        
        `ZZZAAAM_to_dchain_xs_lib_str`
    
    Inputs:
        - `target` = string in general format of target nuclide
        - Note: at least one of the below options must be provided.
             - `reaction` = (optional) either an int MT number (ENDF6 format) or a string of the ejectiles from the neutron reaction (case-insensitive), the "X" in (N,X)
             - `product` = (optional) string in general format of product nuclide (if omitted, product in ground state is assumed)
    
    Outputs:
        - `rxn_dchain_str` = string formatted identically as that found in DCHAIN's neutron reaction cross section libraries
    '''
    
    if not reaction and not product:
        print('Warning: no reaction or product provided with target {}.'.format(target))
        ZZZAAAM = nuclide_plain_str_ZZZAAAM(target)
        target_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM) 
        dstr = target_dstr + '(N,'
        return dstr
    
    
    ZZZAAAM = nuclide_plain_str_ZZZAAAM(target)
    target_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM)
    if product: # if not None (product is provided)
        ZZZAAAM_prod = nuclide_plain_str_ZZZAAAM(product)
        product_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM_prod)        
    
    react = ['   ', '   ', '   ', '   ', 'N  ', '   ', '   ', '   ', '   ', '   ', '   ', '2ND', '   ', '   ', '   ', '   ', '2N ', '3N ', 'FIS', '   ', 
             '   ', '   ', 'NA ', 'N3A', '2NA', '3NA', '   ', '   ', 'NP ', 'N2A', '2N2', '   ', 'ND ', 'NT ', 'NE ', 'ND2', 'NT2', '4N ', '   ', '   ', 
             '   ', '2NP', '3NP', '   ', 'N2P', 'NPA', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', 
             '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', 
             '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', '   ', 
             '   ', '   ', 'G  ', 'P  ', 'D  ', 'T  ', 'HE3', 'A  ', '2A ', '3A ', '   ', '2P ', 'PA ', 'T2A', 'D2A', 'PD ', 'PT ', 'DA ', '   ', '   ']
    dZ = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -6, -2, -2, 0, 0, -1, -4, -4, 0, -1, -1, -2, -5, -5, 0, 0, 0, 0, -1, -1,
          0, -2, -3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -2, -2, -4, -6, 0, -2, -3, -5, -5, -2, -2, -3, 0, 0]
    dA = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -1, -2, 0, 0, 0, 0, -4, -12, -5, -6, 0, 0, -1, -8, -9, 0, -2, -3, -3, -10, -11, -3, 0, 0, 0, -2,
          -3, 0, -2, -5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, -1, -2, -2, -3, -7, -11, 0, -1, -4, -10, -9, -2, -3, -5, 0, 0]
    
    if reaction: # reaction is provided
        # Get reaction from MT / check if valid reaction
        if isinstance(reaction, float): reaction = int(reaction)
        if isinstance(reaction, int):
            MT = reaction
            rxn = react[MT]
        else:
            rxn = "{:3}".format(reaction.upper())
            if rxn not in react:
                print('Reaction "{}" not in reaction list for DCHAIN, check formatting or enter ENDF MT number (0-119)'.format(rxn))
                return None 
            else:
                MT = react.index(rxn)
    
    if not product: # need to figure out product from parent and rxn, assume ground state (but omit final isomeric characters from string in case xslib has other states too)
        ZAM_str = str(ZZZAAAM)
        M = 0
        A = int(ZAM_str[-4:-1]) + dA[MT]
        Z = int(ZAM_str[:-4]) + dZ[MT]
        ZZZAAAM_prod = 10000*Z + 10*A + M 
        product_dstr = ZZZAAAM_to_dchain_xs_lib_str(ZZZAAAM_prod)[:-2]
        if MT == 18: product_dstr = '' # fission
    
    if not reaction: # need to find missing reaction from target and product, isomeric states are ignored
        ZAM_str = str(ZZZAAAM)
        A_tar = int(ZAM_str[-4:-1])
        Z_tar = int(ZAM_str[:-4]) 
        ZAM_str_prod = str(ZZZAAAM_prod)
        A_prod = int(ZAM_str_prod[-4:-1])
        Z_prod = int(ZAM_str_prod[:-4]) 
        rxn_dA = A_prod - A_tar 
        rxn_dZ = Z_prod - Z_tar 
        matching_dA = [index for index,value in enumerate(dA) if value == rxn_dA]
        matching_dZ = [index for index,value in enumerate(dZ) if value == rxn_dZ]
        MT = set(matching_dA).intersection(matching_dZ).pop() # gets the value which is common between the two lists, in this case the index of the reaction
        rxn = react[MT]
    
    if reaction and product: # check to make sure they are compatible
        # if mismatch occurs, assume product is more likely correct than rxn
        ZAM_str = str(ZZZAAAM)
        A_tar = int(ZAM_str[-4:-1])
        Z_tar = int(ZAM_str[:-4]) 
        ZAM_str_prod = str(ZZZAAAM_prod)
        A_prod = int(ZAM_str_prod[-4:-1])
        Z_prod = int(ZAM_str_prod[:-4]) 
        rxn_dA = A_prod - A_tar 
        rxn_dZ = Z_prod - Z_tar 
        matching_dA = [index for index,value in enumerate(dA) if value == rxn_dA]
        matching_dZ = [index for index,value in enumerate(dZ) if value == rxn_dZ]
        if A_tar==A_prod and Z_tar==Z_prod:
            MT_calc = 4
        else:
            MT_calc = set(matching_dA).intersection(matching_dZ).pop() # gets the value which is common between the two lists, in this case the index of the reaction
        rxn_calc = react[MT_calc]
        if rxn_calc != rxn:
            print('Warning, mismatch between reaction (N,{}) and product ({}) with target {}; assuming product ({}) is correct and reaction should be (N,{}) instead.'.format(rxn.strip(),product,target,product_dstr,rxn_calc.strip()))
            rxn = rxn_calc
        
    dstr = target_dstr + '(N,{})'.format(rxn) + product_dstr
    
    return dstr
def seconds_to_dhms(t_sec)

Description

Provide a time in seconds and obtain a string with the time in days, hours, minutes, and seconds

Inputs

  • t_sec = a time in seconds (float or int)

Outputs

  • time_str = string containing the time prettily formatted in d/h/m/s format
Expand source code
def seconds_to_dhms(t_sec):
    '''
    Description:
        Provide a time in seconds and obtain a string with the time in days, hours, minutes, and seconds
    
    Inputs:
        - `t_sec` = a time in seconds (float or int)
    
    Outputs:
        - `time_str` = string containing the time prettily formatted in d/h/m/s format
    
    '''
    m, s = divmod(t_sec, 60)
    h, m = divmod(m, 60)
    d, h = divmod(h, 24)
    
    if d != 0:
        time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
    elif h != 0:
        time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
    elif m != 0:
        time_str = "{:0.0f}m {:0.2f}s".format(m,s)
    elif s != 0:
        time_str = "{:0.2f}s".format(s)
    else:
        time_str = ""
        
    return time_str
def seconds_to_ydhms(t_sec)

Description

Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds

Inputs

  • t_sec = a time in seconds (float or int)

Outputs

  • time_str = string containing the time prettily formatted in y/d/h/m/s format
Expand source code
def seconds_to_ydhms(t_sec):
    '''
    Description:
        Provide a time in seconds and obtain a string with the time in years, days, hours, minutes, and seconds
    
    Inputs:
        - `t_sec` = a time in seconds (float or int)
    
    Outputs:
        - `time_str` = string containing the time prettily formatted in y/d/h/m/s format
    
    '''
    m, s = divmod(t_sec, 60)
    h, m = divmod(m, 60)
    d, h = divmod(h, 24)
    y, d = divmod(d, 365)
    
    if y>=4 : # if leap year occurred
        n_leap_years = int(y/4)
        d = d-n_leap_years
    
    if y != 0:
        time_str = "{:0.0f}y {:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(y,d,h,m,s)
    elif d != 0:
        time_str = "{:0.0f}d {:0.0f}h {:0.0f}m {:0.2f}s".format(d,h,m,s)
    elif h != 0:
        time_str = "{:0.0f}h {:0.0f}m {:0.2f}s".format(h,m,s)
    elif m != 0:
        time_str = "{:0.0f}m {:0.2f}s".format(m,s)
    elif s != 0:
        time_str = "{:0.2f}s".format(s)
    else:
        time_str = ""
        
    return time_str
def time_str_to_sec_multiplier(time_str)

Description

Provide a time unit and this function provides what those time units need to be multiplied by to obtain seconds.

Inputs

  • time_str = string containing time units character(s) [s,m,h,d,y,ms,us,ns,ps,fs]

Outputs

  • m = multiplier to convert a time of the supplied units to seconds
Expand source code
def time_str_to_sec_multiplier(time_str):
    '''
    Description:
        Provide a time unit and this function provides what those time units need to be multiplied by to obtain seconds.
    
    Inputs:
        - `time_str` = string containing time units character(s) [s,m,h,d,y,ms,us,ns,ps,fs]
    
    Outputs:
        - `m` = multiplier to convert a time of the supplied units to seconds
    
    '''
    try:
        if time_str == 's':
            m = 1
        elif time_str == 'm':
            m = 60
        elif time_str == 'h':
            m = 60*60
        elif time_str == 'd':
            m = 60*60*24
        elif time_str == 'y':
            m = 60*60*24*365.25
        elif time_str == 'ms':
            m = 1e-3
        elif time_str == 'us':
            m = 1e-6
        elif time_str == 'ns':
            m = 1e-9
        elif time_str == 'ps':
            m = 1e-12
        elif time_str == 'fs':
            m = 1e-15
        return m
    except:
        print('"{}" is not a valid time unit; please use one of the following: [s,m,h,d,y,ms,us,ns,ps,fs]'.format(time_str))
        return None