From eee5d128c56a4b9bfb8bfb955c9046808efd286d Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Thu, 7 May 2026 16:33:50 -0500 Subject: [PATCH 01/13] Expanding to UKDD decay libraries --- src/DataLib/EAFLib.C | 35 ++- tools/ALARAJOYWrapper/README.md | 40 ++- tools/ALARAJOYWrapper/preprocess_fendl3.py | 35 ++- tools/ALARAJOYWrapper/reaction_data.py | 282 +++++++++++++++++---- 4 files changed, 313 insertions(+), 79 deletions(-) diff --git a/src/DataLib/EAFLib.C b/src/DataLib/EAFLib.C index 616843fb2..12ba5f8b5 100644 --- a/src/DataLib/EAFLib.C +++ b/src/DataLib/EAFLib.C @@ -58,7 +58,7 @@ void EAFLib::extract(char* input, float* value) char section[32]; /* find beginning of exponent */ - int expStart = strcspn(input,"+-"); + int expStart = strcspn(input+1,"+-") + 1; /* extract mantissa portion to string variable */ strncpy(section,input,expStart); @@ -397,14 +397,28 @@ void EAFLib::getDecayInfo() { char buffer[MAXLINELENGTH]; - int junkInt,i; + int MT; - /* read number of initial comment lines */ - inDecay >> junkInt; + std::streampos lineStart; - /* read end of this line and comment lines */ - for (i=0;i<=junkInt;i++) - inDecay.getline(buffer,MAXLINELENGTH); + while (true) + { + lineStart = inDecay.tellg(); + if (!inDecay.getline(buffer, MAXLINELENGTH)) break; + + int len = (int)strlen(buffer); + if (len < 75) continue; + + char mtField[4] = {0}; + strncpy(mtField, buffer + 72, 3); + MT = atoi(mtField); + + if (MT == 457) + { + inDecay.seekg(lineStart); + break; + } + } decayKza = new int[MAXEAFDCYMODES]; memCheck(decayKza,"EAFLib::getDecayInfo(...): decayKza"); @@ -438,8 +452,11 @@ int EAFLib::getDecayData() /* scan for first 457 card */ inDecay.getline(buffer,MAXLINELENGTH); /* extract the MT number of this line */ - buffer[75] = '\0'; - MT = atoi(buffer+72); + int len = (int)strlen(buffer); + if (len < 75) { MT = 0; continue; } + char mtField[4] = {0}; + strncpy(mtField, buffer + 72, 3); + MT = atoi(mtField); } while (MT != 457 && !inDecay.eof()); if (inDecay.eof()) diff --git a/tools/ALARAJOYWrapper/README.md b/tools/ALARAJOYWrapper/README.md index 77c5f30fb..d1053712f 100644 --- a/tools/ALARAJOYWrapper/README.md +++ b/tools/ALARAJOYWrapper/README.md @@ -1,6 +1,6 @@ -# FENDL3 Preprocessor with NJOY Wrapping for ALARA (*ALARAJOYWrapper*) +# FENDL3.2x Preprocessor with NJOY Wrapping for ALARA (*ALARAJOYWrapper*) -This preprocessor is designed to update ALARA's data input capabilities for updated FENDL3.2x data sets formatted as TENDL (ENDF-6) files. Unlike previous versions of [FENDL](https://www-nds.iaea.org/fendl_library/websites/fendl32b/) (Fusion Evaluated Nuclear Data Library), which have available groupwise cross-section data for neutron activation, as is required for ALARA's functionality, FENDL3 data requires conversion to that format. +This preprocessor is designed to update ALARA's data input capabilities for updated FENDL3.2x data sets formatted as TENDL (ENDF-6) files. Unlike previous versions of [FENDL](https://www-nds.iaea.org/fendl/) (Fusion Evaluated Nuclear Data Library), which have available groupwise cross-section data for neutron activation, as is required for ALARA's functionality, FENDL3.2x data requires conversion to that format. This preprocessor uses [NJOY 2016](https://github.com/njoy/NJOY2016) Nuclear Data Processing System to produce requisite pointwise data (PENDF) to subsequntly convert to the Vitamin-J 175 energy group groupwise format (GENDF) for activation cross-sections and to handle the processed data to be fed back to ALARA. For pointwise conversion, ALARAJOYWrapper uses the NJOY modules MODER, RECONR, BROADR, UNRESR, and GASPR, and for the ultimate conversion to the groupwise format, GROUPR. Note that in order to properly handle isomeric reactions, NJOY 2016 has to be built with from the commit `1dbce787` or newer of the `develop` branch, which includes the necessary support for holistic isomer handling. @@ -21,19 +21,39 @@ This preprocessor uses [NJOY 2016](https://github.com/njoy/NJOY2016) Nuclear Dat * [Pandas](https://pandas.pydata.org/docs/getting_started/install.html) - Domain-specific packages * [ENDFtk](https://github.com/njoy/ENDFtk) - * [NJOY 2016](https://github.com/njoy/NJOY2016) (built with `develop` branch) + * [endf-parserpy](https://endf-parserpy.readthedocs.io/en/latest/index.html) + * [NJOY 2016](https://github.com/njoy/NJOY2016) (at Git hash `1dbce787`) -## Usage -This preprocessor can and should be used independently of any ALARA run. While the data format produced from ALARAJOYWrapper is necessary to run `convert_lib` on ALARA to convert TENDL data to an ALARA binary, once the user has a space-delimited DSV file produced from this preprocessor, usage of this preprocessor is not strictly necessary for the particular isotopes processed by the user. -ALARAJOYWrapper is designed to produce a space-delimited DSV containing data that can be directly read by the ALARA data library, ALARAJOY, without any further processing. +## Data Acquisition + +Activation/inventory solvers such as ALARA require both neutron activation cross-sections and decay data. ALARAJOYWrapper is designed with the specific intent for processing FENDL3.2x data in a format readable by ALARA, corresponding to TENDL-2017 activation cross-sections and UKDD-2020 decay data. Nevertheless, to create custom ALARA libraries, any combination of TENDL and EAF or UKDD data can be supplied to ALARAJOYWrapper. + +Many decay data libraries are distributed in repositories containing individual data files for each nuclide in the library. ALARA requires a single decay data file, so ALARAJOYWrapper can also compile these data libraries into a single file referable by `decayFname` (see _Application of Processed Data to ALARA Data Conversion Methods_ below). + +- Activation Cross-Sections + * [TENDL](https://tendl.imperial.ac.uk/) + - Usable with any TENDL release + - For FENDL3.2x processing, [TENDL 2017](https://tendl.imperial.ac.uk/tendl_2017/tendl2017.html) is the standard version. + +- Decay Data + * EAF + - [EAF-2010](https://git.oecd-nea.org/fispact/nuclear_data/EAF2010data.tar.bz2) (Must separate out ` eaf_dec_20100.0*` from the rest of the files after unzipping) + - [EAF-4.1](https://nds.iaea.org/fendl20/fen-decay.htm) (FENDL2.x decay library) + * UKDD + - [UKDD-20](https://www.oecd-nea.org/dbdata/fispact/decay2020.tar.bz2) (FENDL3.2x decay library) + - [UKDD-12](https://git.oecd-nea.org/fispact/nuclear_data/decay.tar.bz2) + + +## Usage +This preprocessor can and should be used independently of any ALARA run. While the data format produced from ALARAJOYWrapper is necessary to run `convert_lib` on ALARA to convert TENDL data to an ALARA binary, once the user has a space-delimited DSV file produced from this preprocessor, usage of this script is not strictly necessary for the particular isotopes processed by the user. -To run this preprocessor, the user must first have acquired TENDL files for each isotope from a selected [TENDL distribution](https://tendl.imperial.ac.uk/). While this tool is designed specifically for the purpose of making FENDL3.2x activation data available to be used in ALARA, the formatting of TENDL 2017 is not unique to that release, and users can apply any version of TENDL desired from the Imperial College London TENDL database to be processed in the ALARAJOY format. Otherwise, [TENDL 2017](https://tendl.imperial.ac.uk/tendl_2017/tendl2017.html) is the source for FENDL3.2x neutron activation data and is the standard intended input for this tool. All TENDL files to be processed must be in the same directory as each other to be properly identified by ALARAJOYWrapper. +ALARAJOYWrapper is designed to produce a space-delimited DSV containing cross-data that can be directly read by the ALARA data library, ALARAJOY, without any further processing. Running ALARAJOYWrapper can be done with one Python command: ``` -python preprocess_fendl3.py -f /path/to/fendl3_data_dir/ -d /path/to/eaf_decay_library/ -a +python preprocess_fendl3.py -f /path/to/fendl3_data_dir/ -d /path/to/eaf_decay_library/ decay_library_type -a ``` To read in detail about each of these arguments, call this command: ``` @@ -42,7 +62,7 @@ python preprocess_fendl3.py -h ## Data Output -Running `preprocess_fendl3.py` will return the file path to the resultant space-delimited DSV file containing transmutation reaction pathways for the neutron activation of the given isotope(s). Each row in the DSV represents a different reaction, and contains the following data needed by ALARA for a library conversion: +Running `preprocess_fendl3.py` will produce two file paths. The first is to the compiled decay data file, which will either be identical to the input for the first argument in `-d` if a pre-compiled decay library is being used or to the newly produced compiled decay library. The second is to the resultant space-delimited DSV file containing transmutation reaction pathways for the neutron activation of the given isotope(s). Each row in the DSV represents a different reaction, and contains the following data needed by ALARA for a library conversion: - Parent KZA: Unique isotope identifier for the parent isotope in the format **ZZAAAM**, where ZZ is the isotope's atomic number, AAA is the mass number, and M is the isomeric state (0 if non-excited). - Daughter KZA: Unique isotope identifier of the daughter isotope produced from a particular transmutation reaction in the format **ZZAAAM**. @@ -69,7 +89,7 @@ Data library conversion to ALARA binary libraries is done with the `convert_lib` wherein: - `ajoylib` is the library value for ALARAJOY - `transFname` is the file path to the DSV containing the preprocessed FENDL3 transmutation data -- `decayFname` is the file path to EAF decay data. +- `decayFname` is the file path to the compiled EAF or UKDD decay library, which may be produced by ALARAJOYWrapper. - `alaraFname` is the file path template for resultant ALARA data libraries (.lib, .idx. .gam, .gdx) The ALARA run for library conversion should precede and be independent from subsequent activation calculations with ALARA, as specified in the [ALARA Users' Guide](https://svalinn.github.io/ALARA/usersguide/index.html): diff --git a/tools/ALARAJOYWrapper/preprocess_fendl3.py b/tools/ALARAJOYWrapper/preprocess_fendl3.py index c195d6576..5542a6a4b 100644 --- a/tools/ALARAJOYWrapper/preprocess_fendl3.py +++ b/tools/ALARAJOYWrapper/preprocess_fendl3.py @@ -11,15 +11,14 @@ def args(): parser = argparse.ArgumentParser() parser.add_argument( - '--decay_lib', '-d', required=True, nargs=1, + '--decay_lib', '-d', required=True, nargs=2, help=(''' - Required argument to direct ALARAJOYWrapper to an EAF decay - library or directory containing EAF decay files for - individual nuclides. Necessary for cross-referencing short- - lived isomeric daughters against known half-life data. - - Note: If using --decay_lib to direct to a directory of EAF - decay files, all files must have the extension ".dat". + Required argument pair to direct ALARAJOYWrapper to a decay data + library. The first part of the argument is the path to either + an EAF or UKDD pre-compiled decay file or a repository + containing multiple decay files to be compiled internally. See + the README.md for access to various decay library + distributions. ''') ) parser.add_argument( @@ -99,7 +98,7 @@ def process_pendf( def process_gendf( njoy_groupr_input, material_id, MTs, mt_dict, - temperature, pKZA, isomer_dict, all_rxns, eaf_nucs + temperature, pKZA, isomer_dict, all_rxns, radionucs ): """ Prepare and run NJOY run with GROUPR and iteratively extract cross-section @@ -134,7 +133,7 @@ def process_gendf( } } } - eaf_nucs (dict): Dictionary keyed by all radionuclides in the EAF + radionucs (dict): Dictionary keyed by all radionuclides in the EAF decay library, with values of their half-lives. Returns: @@ -165,7 +164,7 @@ def process_gendf( if gendf_MTs: all_rxns = tp.iterate_MTs( gendf_MTs, mt_dict, xs_line_dict, pKZA, - all_rxns, eaf_nucs, isomer_dict, gendf_path + all_rxns, radionucs, isomer_dict, gendf_path ) print(f'Finished processing {element}-{A}') @@ -331,7 +330,12 @@ def main(): temperature = args().temperature[0] mt_dict = rxd.process_mt_data(rxd.load_mt_table(dir / 'mt_table.csv')) - eaf_nucs = rxd.find_eaf_ref_data(Path(args().decay_lib[0])) + decay_path, decay_lib_type = args().decay_lib + decay_path = Path(decay_path) + if decay_path.is_dir(): + decay_path = rxd.compile_decay_lib(decay_path, decay_lib_type, dir) + + radionucs = rxd.find_radionucs_from_decay_lib(decay_path) all_rxns = defaultdict(lambda: defaultdict(dict)) for file_properties in tp.search_for_files(search_dir): @@ -356,7 +360,7 @@ def main(): if not njoy_prep_error: all_rxns = process_gendf( njt.groupr_input, material_id, MTs, mt_dict, - temperature, pKZA, isomer_dict, all_rxns, eaf_nucs + temperature, pKZA, isomer_dict, all_rxns, radionucs ) else: @@ -375,7 +379,10 @@ def main(): dsv_path = dir / 'cumulative_gendf_data.dsv' write_dsv(dsv_path, gas_filtered) - print(f'Reaction data saved to: {dsv_path}') + + print('-' * 20) + print(f'Compiled decay data: {decay_path}') + print(f'Reaction cross-sections: {dsv_path}') if __name__ == '__main__': main() \ No newline at end of file diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index aa590d335..6444f81be 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -1,6 +1,12 @@ import csv from numpy import array import pandas as pd +from endf_parserpy import EndfParserPy +from endf_parserpy.interpreter.endf_utils import ( + BlankLineError, UnexpectedEndOfInputError +) +import warnings +from pathlib import Path # Define a dictionary containing all of the pathways for neutron and proton # changes in a nucleus following neutron activation @@ -21,6 +27,8 @@ 'kza' : [10010, 10020, 10030, 20030, 20040], 'total_mt' : range(203, 207 + 1) }) +DECAY_MT = 457 +TEND_RECORD = ' ' * 68 + '-1 0 0 0' # Track edge cases of unquantifiable MT reaction types spec_reactions = [ @@ -252,13 +260,188 @@ def process_mt_data(mt_dict): return mt_dict -def eaf_float(num_str): +def resolve_ukdd_inconsistencies(decay_parser, decay_file): + """ + Check formatting of a single-nuclide UKDD decay file. Expected formatting + inconsistencies resolvable by removing blank lines and/or inserting + missing TEND records. + + Arguments: + decay_parser (endf_parserpy.interpreter.endf_parser.EndfParserPy): + EndfParserPy parser object. + decay_file (pathlib._local.PosixPath): Path to a single-nuclide UKDD + decay file. + + Returns: + decay_data (dict): Dictionary containing all parsed decay data from a + single-nuclide UKDD decay file. + """ + + decay_MF = 8 + while True: + try: + return decay_parser.parsefile(decay_file)[decay_MF][DECAY_MT] + + except BlankLineError as B: + blank_line = int(str(B).split()[1]) + with open(decay_file, 'r') as f: + lines = f.readlines() + del lines[blank_line] + + with open(decay_file, 'w') as f: + f.writelines(lines) + + except UnexpectedEndOfInputError: + with open(decay_file, 'a') as f: + f.write('\n' + TEND_RECORD) + + # NEA Gitlab distribution of UKDD-20 contains a binary reference file + # unreadable by the EndfParserPy. Empty dictionary will be returned + # and file will be passed over. + except UnicodeDecodeError as U: + warnings.warn( + f'Invalid file {decay_file} causing "{U}" error. Skipping.' + ) + return dict() + +def append_to_compiled_lib( + decay_file, compiled_file, decay_lib_type, last_file=False +): + """ + Append the contents of a single- or multi-nuclide decay file to a compiled + file for the whole library. + + Arguments: + decay_file (pathlib._local.PosixPath): Path to a single-nuclide UKDD + or EAF decay file. + compiled_file (pathlib._local.PosixPath): Path to the compiled decay + library file. + decay_lib_type (str): Tag for one of the two decay library types that + can be processed by ALARAJOYWrapper; either 'ukdd' or 'eaf'. + last_file (bool, optional): Boolean flag for to mark the final nuclide + file in the decay library. Only needed for UKDD decay libraries. + (Defaults to False) + + Returns: + None + """ + + with open(decay_file, 'r') as f: + lines = f.readlines() + + # Remove TEND record from individual files for valid compilation except + # for final file in iteration. TEND flags needed initially for individual + # UKDD file restructuring to meet ALARA readable format, but cannot be + # present in the middle of a compiled decay file containing multiple + # nuclides + if not last_file and decay_lib_type == 'ukdd': + lines = [ + line[:81] for line in lines + if TEND_RECORD not in line + ] + + with open(compiled_file, 'a') as f: + f.writelines(lines) + +def ensure_line_length_compliance(compiled_file): + """ + Split lines that may have been joined together without a newline separator + during file compilation. + + Arguments: + compiled_file (pathlib._local.PosixPath): Path to the compiled decay + library file. + + Returns: + lines (list of str): List of all lines in the compiled decay library, + each with a maximum of 81 characters by ENDF/B formatting + conventions. + """ + + max_line_len = 81 + max_line_idx = max_line_len - 1 + + with open(compiled_file, 'r') as f: + lines = f.readlines() + for i, line in enumerate(lines): + if len(line) > max_line_len: + lines[i] = f'{line[:max_line_idx]}\n{line[max_line_idx:]}' + + return lines + +def compile_decay_lib(decay_dir, decay_lib_type, dir): + """ + Iteratively compile the data from individual-nuclide decay files into a + single file in ascending order of KZA. Can either be an EAF or UKDD + decay library. + + Arguments: + decay_dir (pathlib._local.PosixPath): Filepath to the directory + exclusively containing decay data files for either an EAF or UKDD + decay library. + decay_lib_type (str): Identifier tag for either EAF or UKDD data. + dir (pathlib._local.PosixPath): Path to the current working directory + (CWD) from which the command was called. + + Returns: + compiled_file (pathlib._local.PosixPath): Path to the compiled decay + library file. + """ + + compiled_file = dir / f'{decay_dir}_compiled' + compiled_file.unlink(missing_ok=True) + + ukdd_options = ['ukdd', 'ukaeadd', 'decay_2020', 'decay_2012'] + if decay_lib_type.lower() in ukdd_options: + decay_lib_type = 'ukdd' + all_nucs = dict() + for ukdd_file in decay_dir.iterdir(): + decay_parser = EndfParserPy() + decay_data = resolve_ukdd_inconsistencies(decay_parser, ukdd_file) + if decay_data: + decay_data['filepath'] = ukdd_file + kza = int(f'{int(decay_data['ZA'])}{int(decay_data['LIS'])}') + all_nucs[kza] = decay_data + + for i, kza in enumerate(sorted(all_nucs)): + last_file = False + if i == len(all_nucs) - 1: + last_file = True + + append_to_compiled_lib( + all_nucs[kza]['filepath'], + compiled_file, + decay_lib_type, + last_file + ) + + elif 'eaf' in decay_lib_type.lower(): + for file in decay_dir.iterdir(): + append_to_compiled_lib( + file, compiled_file, decay_lib_type + ) + + else: + raise ValueError( + 'Invalid library type. Must be either an EAF or UKDD release.' + ) + + lines = ensure_line_length_compliance(compiled_file) + with open(compiled_file, 'w') as f: + f.writelines(lines) + + print( + f'Compiled {decay_lib_type.upper()} decay libary to {compiled_file}.' + ) + return compiled_file + +def standardize_float(num_str): """ Process non-uniformly formatted scientific notation numbers from parsed - EAF data to standard floating point numbers. + decay data to standard floating point numbers. Arguments: - num_str (str): Number from an EAF file with non-uniform formatting. + num_str (str): Number from an decay file with non-uniform formatting. Returns: num (float): Reformatted number to be able to be properly converted to @@ -293,63 +476,70 @@ def get_MT_from_line(line): return int(line[72:75]) -def find_eaf_ref_data(eaf_path): +def find_radionucs_from_decay_lib(compiled_decay_lib): """ - Parse through an EAF file to build a dictionary keyed by all radionuclides - with their respective half-lives as the values. Modeled after the - file parsing methods in ALARA/src/DataLib/EAFLib.C. + Parse through an pre-compiled decay library file to build a dictionary + keyed by all radionuclides with their respective half-lives as the + values. Modeled after the file parsing methods in + ALARA/src/DataLib/EAFLib.C. Arguments: - eaf_path (pathlib._local.PosixPath): Filepath to an EAF decay library - or directory containing individual nuclide-decay data files - formatted with a ".dat" extension. + compiled_decay_lib (pathlib._local.PosixPath): Filepath to a pre- + compiled decay library. Must be either an EAF decay library (i.e. + EAF4.1, EAF2010, etc.) or a UKDD decay library (UKDD-12, UKDD-20). Returns: - eaf_nucs (dict): Dictionary keyed by all radionuclides in the EAF - decay library, with values of their half-lives. + radionucs (dict): Dictionary keyed by all radionuclides in the decay + library, with values of their half-lives. """ radionucs = {} - decay_MT = 457 - - for eaf in (eaf_path.glob('*.dat') if eaf_path.is_dir() else [eaf_path]): - with open(eaf, 'r') as f: - - # Read header and skip introductory comment lines - first_line = f.readline().strip() - try: - n_comment_lines = int(first_line.split()[0]) - except ValueError: - n_comment_lines = 1 + with open(compiled_decay_lib, 'r') as f: + + # Read header and skip introductory comment lines + first_line = f.readline().strip() + try: + n_comment_lines = int(first_line.split()[0]) + except ValueError: + n_comment_lines = 1 - for _ in range(n_comment_lines): - f.readline() + for _ in range(n_comment_lines): + f.readline() - _in_decay_block = False - line = f.readline() + _in_decay_block = False + line = f.readline() - while line: + while line: - # Radionuclide processing + try: MT = get_MT_from_line(line) + + # Certain file headers may not be full 81 character lines + # containing an MT value. Does not affect decay library parsing + # here or in ALARA. + except ValueError as V: + if str(V) == "invalid literal for int() with base 10: ''": + pass + else: + raise V + + if not _in_decay_block and MT == DECAY_MT: + _in_decay_block = True + + # Parse nuclide KZA + za = int(standardize_float(line[:11])) + M = int(line[33:44].strip()) + kza = za * 10 + M + + # Read half-life (next line) + line = f.readline() + thalf = standardize_float(line[:11]) + if thalf > 0: + radionucs[kza] = thalf - if not _in_decay_block and MT == decay_MT: - _in_decay_block = True - - # Parse nuclide KZA - za = int(eaf_float(line[:11])) - M = int(line[33:44].strip()) - kza = za * 10 + M - - # Read half-life (next line) - line = f.readline() - thalf = eaf_float(line[:11]) - if thalf > 0: - radionucs[kza] = thalf - - elif MT != decay_MT: - _in_decay_block = False + elif MT != DECAY_MT: + _in_decay_block = False - line = f.readline() + line = f.readline() return radionucs \ No newline at end of file From 2a0d1797fe52455dc446f42fc5cfff8e59c350e4 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Mon, 11 May 2026 15:15:12 -0500 Subject: [PATCH 02/13] Discarding ground-state nuclides without known decay data --- tools/ALARAJOYWrapper/preprocess_fendl3.py | 12 ++++---- tools/ALARAJOYWrapper/reaction_data.py | 34 ++++++++++++++-------- tools/ALARAJOYWrapper/tendl_processing.py | 22 +++++++------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/tools/ALARAJOYWrapper/preprocess_fendl3.py b/tools/ALARAJOYWrapper/preprocess_fendl3.py index 5542a6a4b..8c67ee22e 100644 --- a/tools/ALARAJOYWrapper/preprocess_fendl3.py +++ b/tools/ALARAJOYWrapper/preprocess_fendl3.py @@ -98,7 +98,7 @@ def process_pendf( def process_gendf( njoy_groupr_input, material_id, MTs, mt_dict, - temperature, pKZA, isomer_dict, all_rxns, radionucs + temperature, pKZA, isomer_dict, all_rxns, all_nucs ): """ Prepare and run NJOY run with GROUPR and iteratively extract cross-section @@ -133,8 +133,8 @@ def process_gendf( } } } - radionucs (dict): Dictionary keyed by all radionuclides in the EAF - decay library, with values of their half-lives. + all_nucs (dict): Dictionary keyed by all nuclide KZAs in the decay + library, with values of their half-lives (-1 for stable nuclides). Returns: all_rxns (collections.defaultdict): Updated dictionary for all @@ -164,7 +164,7 @@ def process_gendf( if gendf_MTs: all_rxns = tp.iterate_MTs( gendf_MTs, mt_dict, xs_line_dict, pKZA, - all_rxns, radionucs, isomer_dict, gendf_path + all_rxns, all_nucs, isomer_dict ) print(f'Finished processing {element}-{A}') @@ -335,7 +335,7 @@ def main(): if decay_path.is_dir(): decay_path = rxd.compile_decay_lib(decay_path, decay_lib_type, dir) - radionucs = rxd.find_radionucs_from_decay_lib(decay_path) + all_nucs = rxd.find_nucs_from_decay_lib(decay_path) all_rxns = defaultdict(lambda: defaultdict(dict)) for file_properties in tp.search_for_files(search_dir): @@ -360,7 +360,7 @@ def main(): if not njoy_prep_error: all_rxns = process_gendf( njt.groupr_input, material_id, MTs, mt_dict, - temperature, pKZA, isomer_dict, all_rxns, radionucs + temperature, pKZA, isomer_dict, all_rxns, all_nucs ) else: diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 6444f81be..9214b2f9b 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -3,7 +3,7 @@ import pandas as pd from endf_parserpy import EndfParserPy from endf_parserpy.interpreter.endf_utils import ( - BlankLineError, UnexpectedEndOfInputError + BlankLineError, UnexpectedEndOfInputError, UnexpectedControlRecordError ) import warnings from pathlib import Path @@ -280,7 +280,7 @@ def resolve_ukdd_inconsistencies(decay_parser, decay_file): decay_MF = 8 while True: try: - return decay_parser.parsefile(decay_file)[decay_MF][DECAY_MT] + return decay_parser.parsefile(decay_file)#[decay_MF][DECAY_MT] except BlankLineError as B: blank_line = int(str(B).split()[1]) @@ -396,9 +396,15 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): decay_lib_type = 'ukdd' all_nucs = dict() for ukdd_file in decay_dir.iterdir(): + if '.DS_Store' in str(ukdd_file): + ukdd_file.unlink() + continue + decay_parser = EndfParserPy() - decay_data = resolve_ukdd_inconsistencies(decay_parser, ukdd_file) - if decay_data: + parsed_file = resolve_ukdd_inconsistencies(decay_parser, ukdd_file) +# decay_parser.writefile(ukdd_file, parsed_file, exclude=(1,), overwrite=True) + if parsed_file: + decay_data = parsed_file[8][457] decay_data['filepath'] = ukdd_file kza = int(f'{int(decay_data['ZA'])}{int(decay_data['LIS'])}') all_nucs[kza] = decay_data @@ -476,7 +482,7 @@ def get_MT_from_line(line): return int(line[72:75]) -def find_radionucs_from_decay_lib(compiled_decay_lib): +def find_nucs_from_decay_lib(compiled_decay_lib): """ Parse through an pre-compiled decay library file to build a dictionary keyed by all radionuclides with their respective half-lives as the @@ -489,11 +495,11 @@ def find_radionucs_from_decay_lib(compiled_decay_lib): EAF4.1, EAF2010, etc.) or a UKDD decay library (UKDD-12, UKDD-20). Returns: - radionucs (dict): Dictionary keyed by all radionuclides in the decay - library, with values of their half-lives. + all_nucs (dict): Dictionary keyed by all nuclide KZAs in the decay + library, with values of their half-lives (-1 for stable nuclides). """ - radionucs = {} + all_nucs = {} with open(compiled_decay_lib, 'r') as f: # Read header and skip introductory comment lines @@ -530,16 +536,20 @@ def find_radionucs_from_decay_lib(compiled_decay_lib): za = int(standardize_float(line[:11])) M = int(line[33:44].strip()) kza = za * 10 + M + stability = int(line[44:56].strip()) # 0->unstable , 1->stable # Read half-life (next line) line = f.readline() - thalf = standardize_float(line[:11]) - if thalf > 0: - radionucs[kza] = thalf + if stability == 0: + thalf = standardize_float(line[:11]) + all_nucs[kza] = thalf + + else: + all_nucs[kza] = -1 elif MT != DECAY_MT: _in_decay_block = False line = f.readline() - return radionucs \ No newline at end of file + return all_nucs \ No newline at end of file diff --git a/tools/ALARAJOYWrapper/tendl_processing.py b/tools/ALARAJOYWrapper/tendl_processing.py index c8bfe8b93..0f25f26f2 100644 --- a/tools/ALARAJOYWrapper/tendl_processing.py +++ b/tools/ALARAJOYWrapper/tendl_processing.py @@ -384,16 +384,16 @@ def incrementally_deexcite_isomer(M, dKZA, eaf_nucs): return dKZA + trial_M def iterate_MTs( - MTs, mt_dict, xs_line_dict, pKZA, all_rxns, eaf_nucs, isomer_dict, gendf_path + MTs, mt_dict, xs_line_dict, pKZA, all_rxns, all_nucs, isomer_dict ): """ Iterate through all of the MTs present in a given GENDF file to extract the necessary data to be able to run ALARA. For isomeric daughters with an excited state less than 10 that do not have known half-lives (determined by the keys of radionucs, itself derived from the provided - EAF decay library), this function assumes a infinitesimal half-life - with discrete deexcitations to the next lowest isomeric state, until - reaching a level with a known half-life. As such, the cross-sections + decay library), this function assumes a infinitesimal half-life with + discrete deexcitations to the next lowest isomeric state, until + reaching a level with a known half-life or the ground state. As such, the cross-sections for these isomer daughters are accumulated to the appropriate isomeric state cross-sections by energy group. @@ -415,16 +415,14 @@ def iterate_MTs( } } } - eaf_nucs (dict): Dictionary keyed by all radionuclides in the EAF - decay library, with values of their half-lives. + all_nucs (dict): Dictionary keyed by all nuclide KZAs in the decay + library, with values of their half-lives (-1 for stable nuclides). isomer_dict (collections.defaultdict): Dictionary keyed by reaction type (MT), with each MT containing a subdictionary of the MF from which the isomeric pathways are extracted. At the lowest MT/MF level has a list of all isomeric states of possible daughter nuclides for which there are cross-section data in the original TENDL file. - gendf_path (pathlib._local.PosixPath): Path to the GENDF file from - which to extract groupwise cross-sections. Returns: all_rxns (collections.defaultdict): Updated dictionary for all @@ -460,7 +458,7 @@ def iterate_MTs( if M > 0: emitted += '*' - if dKZA in eaf_nucs or M == 0: + if dKZA in all_nucs: all_rxns[pKZA][dKZA][str(MT) + '*' * M] = { 'emitted' : emitted, 'xsections' : sigmas @@ -472,9 +470,13 @@ def iterate_MTs( if M > 1: dKZA = incrementally_deexcite_isomer( - M, dKZA, eaf_nucs + M, dKZA, all_nucs ) + # Skip daughters without decay data + if dKZA not in all_nucs: + continue + if dKZA not in all_rxns[pKZA]: all_rxns[pKZA][dKZA] = defaultdict(dict) From a3890b6d60fff5daab89fb250b383a6a531c89f5 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Tue, 12 May 2026 14:43:31 -0500 Subject: [PATCH 03/13] LISO instead of LIS --- tools/ALARAJOYWrapper/reaction_data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 9214b2f9b..b7a68d68d 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -402,11 +402,10 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): decay_parser = EndfParserPy() parsed_file = resolve_ukdd_inconsistencies(decay_parser, ukdd_file) -# decay_parser.writefile(ukdd_file, parsed_file, exclude=(1,), overwrite=True) if parsed_file: decay_data = parsed_file[8][457] decay_data['filepath'] = ukdd_file - kza = int(f'{int(decay_data['ZA'])}{int(decay_data['LIS'])}') + kza = int(f'{int(decay_data['ZA'])}{int(decay_data['LISO'])}') all_nucs[kza] = decay_data for i, kza in enumerate(sorted(all_nucs)): From 07c2167e5276667725f866bf7b2718c8753b44de Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Tue, 12 May 2026 15:33:17 -0500 Subject: [PATCH 04/13] Resolving isomer mishandling --- tools/ALARAJOYWrapper/reaction_data.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index b7a68d68d..7c11e2676 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -329,17 +329,6 @@ def append_to_compiled_lib( with open(decay_file, 'r') as f: lines = f.readlines() - # Remove TEND record from individual files for valid compilation except - # for final file in iteration. TEND flags needed initially for individual - # UKDD file restructuring to meet ALARA readable format, but cannot be - # present in the middle of a compiled decay file containing multiple - # nuclides - if not last_file and decay_lib_type == 'ukdd': - lines = [ - line[:81] for line in lines - if TEND_RECORD not in line - ] - with open(compiled_file, 'a') as f: f.writelines(lines) @@ -405,7 +394,7 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): if parsed_file: decay_data = parsed_file[8][457] decay_data['filepath'] = ukdd_file - kza = int(f'{int(decay_data['ZA'])}{int(decay_data['LISO'])}') + kza = int(decay_data['ZA']) * 10 + int(decay_data['LISO']) all_nucs[kza] = decay_data for i, kza in enumerate(sorted(all_nucs)): From 871521fed08e1abf9003dea9cbe0f50d1745339d Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Tue, 26 May 2026 10:05:06 -0500 Subject: [PATCH 05/13] EAFLib parse_MT update --- src/DataLib/EAFLib.C | 93 +++++++++++++++++++++++++++----------------- src/DataLib/EAFLib.h | 2 +- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/DataLib/EAFLib.C b/src/DataLib/EAFLib.C index 12ba5f8b5..678739119 100644 --- a/src/DataLib/EAFLib.C +++ b/src/DataLib/EAFLib.C @@ -393,32 +393,45 @@ int EAFLib::getTransData() /* read the head of the file and get the following info: * - Title * and then advance to first isotope */ -void EAFLib::getDecayInfo() +int EAFLib::parse_MT(const char* buffer) { + int MT_start = 72; + int MT_length = 3; + + if (strlen(buffer) < MT_start + MT_length) return -1; + + char mtField[4] = {0}; + strncpy(mtField, buffer + MT_start, MT_length); + return atoi(mtField); +} + + void EAFLib::getDecayInfo() { char buffer[MAXLINELENGTH]; - int MT; + int MT = -1; std::streampos lineStart; - while (true) - { - lineStart = inDecay.tellg(); - if (!inDecay.getline(buffer, MAXLINELENGTH)) break; + lineStart = inDecay.tellg(); + if (inDecay.getline(buffer, MAXLINELENGTH)) + { + MT = parse_MT(buffer); + } + + while (inDecay && MT != 457) + { + lineStart = inDecay.tellg(); - int len = (int)strlen(buffer); - if (len < 75) continue; + if (!inDecay.getline(buffer, MAXLINELENGTH)) + break; - char mtField[4] = {0}; - strncpy(mtField, buffer + 72, 3); - MT = atoi(mtField); + MT = parse_MT(buffer); + } - if (MT == 457) - { - inDecay.seekg(lineStart); - break; - } - } + if (MT == 457) + { + inDecay.seekg(lineStart); + } decayKza = new int[MAXEAFDCYMODES]; memCheck(decayKza,"EAFLib::getDecayInfo(...): decayKza"); @@ -447,17 +460,22 @@ int EAFLib::getDecayData() float pBranch, aBranch, dIsoFlag; - do - { - /* scan for first 457 card */ - inDecay.getline(buffer,MAXLINELENGTH); - /* extract the MT number of this line */ - int len = (int)strlen(buffer); - if (len < 75) { MT = 0; continue; } - char mtField[4] = {0}; - strncpy(mtField, buffer + 72, 3); - MT = atoi(mtField); - } while (MT != 457 && !inDecay.eof()); + MT = -1; + + /* prime loop with first MT value */ + if (inDecay.getline(buffer, MAXLINELENGTH)) + { + MT = parse_MT(buffer); + } + + /* scan for first 457 card */ + while (MT != 457 && !inDecay.eof()) + { + if (!inDecay.getline(buffer, MAXLINELENGTH)) + break; + + MT = parse_MT(buffer); + } if (inDecay.eof()) { @@ -569,15 +587,18 @@ int EAFLib::getDecayData() numSpec = getGammaData(); /* skip to end of this file section */ - do - { - /* scan for last 457 card */ - inDecay.getline(buffer,MAXLINELENGTH); - /* extract the MT number of this line */ - buffer[75] = '\0'; - MT = atoi(buffer+72); - } while (MT == 457 && !inDecay.eof()); + if (inDecay.getline(buffer, MAXLINELENGTH)) + { + MT = parse_MT(buffer); + } + while (MT == 457 && !inDecay.eof()) + { + if (!inDecay.getline(buffer, MAXLINELENGTH)) + break; + + MT = parse_MT(buffer); + } debug(6,"Returning %d decay branches for %d.",nDRxns,zak); diff --git a/src/DataLib/EAFLib.h b/src/DataLib/EAFLib.h index 528277f28..74136e0bc 100644 --- a/src/DataLib/EAFLib.h +++ b/src/DataLib/EAFLib.h @@ -120,6 +120,7 @@ class EAFLib : public ASCIILib void readDiscreteGammas(int, int, float, char*); void readContGammas(int, float, char*); int getGammaData(); + int parse_MT(const char* ); /* Interface from ASCIILib */ void getTransInfo(); @@ -127,7 +128,6 @@ class EAFLib : public ASCIILib int getTransData(); int getDecayData(); - public: /* Service */ From e135396672012d30a1fd8fe8100b25ecfc5bc06c Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Wed, 27 May 2026 17:12:31 -0500 Subject: [PATCH 06/13] Moving away from try/except approach to UKDD fixes --- tools/ALARAJOYWrapper/reaction_data.py | 165 +++++++------------------ 1 file changed, 48 insertions(+), 117 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 7c11e2676..70622df71 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -260,104 +260,27 @@ def process_mt_data(mt_dict): return mt_dict -def resolve_ukdd_inconsistencies(decay_parser, decay_file): - """ - Check formatting of a single-nuclide UKDD decay file. Expected formatting - inconsistencies resolvable by removing blank lines and/or inserting - missing TEND records. - - Arguments: - decay_parser (endf_parserpy.interpreter.endf_parser.EndfParserPy): - EndfParserPy parser object. - decay_file (pathlib._local.PosixPath): Path to a single-nuclide UKDD - decay file. - - Returns: - decay_data (dict): Dictionary containing all parsed decay data from a - single-nuclide UKDD decay file. - """ - - decay_MF = 8 - while True: - try: - return decay_parser.parsefile(decay_file)#[decay_MF][DECAY_MT] - - except BlankLineError as B: - blank_line = int(str(B).split()[1]) - with open(decay_file, 'r') as f: - lines = f.readlines() - del lines[blank_line] - - with open(decay_file, 'w') as f: - f.writelines(lines) - - except UnexpectedEndOfInputError: - with open(decay_file, 'a') as f: - f.write('\n' + TEND_RECORD) - - # NEA Gitlab distribution of UKDD-20 contains a binary reference file - # unreadable by the EndfParserPy. Empty dictionary will be returned - # and file will be passed over. - except UnicodeDecodeError as U: - warnings.warn( - f'Invalid file {decay_file} causing "{U}" error. Skipping.' - ) - return dict() - -def append_to_compiled_lib( - decay_file, compiled_file, decay_lib_type, last_file=False -): +def append_to_compiled_lib(lines, compiled_file): """ Append the contents of a single- or multi-nuclide decay file to a compiled file for the whole library. Arguments: - decay_file (pathlib._local.PosixPath): Path to a single-nuclide UKDD - or EAF decay file. + lines (list of str): List of parsed lines from a decay file. compiled_file (pathlib._local.PosixPath): Path to the compiled decay library file. - decay_lib_type (str): Tag for one of the two decay library types that - can be processed by ALARAJOYWrapper; either 'ukdd' or 'eaf'. - last_file (bool, optional): Boolean flag for to mark the final nuclide - file in the decay library. Only needed for UKDD decay libraries. - (Defaults to False) Returns: None """ - with open(decay_file, 'r') as f: - lines = f.readlines() + # Ensure final line includes a newline signifier + if not lines[-1].endswith('\n'): + lines[-1] += '\n' with open(compiled_file, 'a') as f: f.writelines(lines) -def ensure_line_length_compliance(compiled_file): - """ - Split lines that may have been joined together without a newline separator - during file compilation. - - Arguments: - compiled_file (pathlib._local.PosixPath): Path to the compiled decay - library file. - - Returns: - lines (list of str): List of all lines in the compiled decay library, - each with a maximum of 81 characters by ENDF/B formatting - conventions. - """ - - max_line_len = 81 - max_line_idx = max_line_len - 1 - - with open(compiled_file, 'r') as f: - lines = f.readlines() - for i, line in enumerate(lines): - if len(line) > max_line_len: - lines[i] = f'{line[:max_line_idx]}\n{line[max_line_idx:]}' - - return lines - def compile_decay_lib(decay_dir, decay_lib_type, dir): """ Iteratively compile the data from individual-nuclide decay files into a @@ -379,50 +302,58 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): compiled_file = dir / f'{decay_dir}_compiled' compiled_file.unlink(missing_ok=True) - + ukdd_options = ['ukdd', 'ukaeadd', 'decay_2020', 'decay_2012'] if decay_lib_type.lower() in ukdd_options: decay_lib_type = 'ukdd' - all_nucs = dict() - for ukdd_file in decay_dir.iterdir(): - if '.DS_Store' in str(ukdd_file): - ukdd_file.unlink() - continue + ukdd_nucs = dict() + for decay_file in decay_dir.iterdir(): + if '.DS_Store' in str(decay_file): + decay_file.unlink() + continue + + with open(decay_file, 'r') as f: + lines = f.readlines() + + # Remove blank lines + updated_lines = [line for line in lines if line.strip()] + + # Include missing TEND Records + if TEND_RECORD.strip().rstrip('0').strip() not in updated_lines[-1]: + tend_line = TEND_RECORD + if not updated_lines[-1].endswith('\n'): + tend_line = '\n' + tend_line + updated_lines.append(tend_line) + + # Overwrite file if changes were made + if lines != updated_lines: + with open(decay_file, 'w') as f: + f.writelines(updated_lines) + + # Save individual nuclide KZAs from UKDD data for ascending KZA + # compilation + if decay_lib_type == 'ukdd': decay_parser = EndfParserPy() - parsed_file = resolve_ukdd_inconsistencies(decay_parser, ukdd_file) - if parsed_file: - decay_data = parsed_file[8][457] - decay_data['filepath'] = ukdd_file - kza = int(decay_data['ZA']) * 10 + int(decay_data['LISO']) - all_nucs[kza] = decay_data - - for i, kza in enumerate(sorted(all_nucs)): - last_file = False - if i == len(all_nucs) - 1: - last_file = True - - append_to_compiled_lib( - all_nucs[kza]['filepath'], - compiled_file, - decay_lib_type, - last_file - ) + decay_data = decay_parser.parsefile(decay_file)[8][457] + decay_data['filepath'] = decay_file + kza = int(decay_data['ZA']) * 10 + int(decay_data['LISO']) + ukdd_nucs[kza] = decay_data + + elif 'eaf' in decay_lib_type.lower(): + append_to_compiled_lib(updated_lines, compiled_file) - elif 'eaf' in decay_lib_type.lower(): - for file in decay_dir.iterdir(): - append_to_compiled_lib( - file, compiled_file, decay_lib_type + else: + raise ValueError( + 'Invalid library type. Must be either an EAF or UKDD release.' ) - else: - raise ValueError( - 'Invalid library type. Must be either an EAF or UKDD release.' - ) + if decay_lib_type == 'ukdd': + for kza in sorted(ukdd_nucs): + with open(ukdd_nucs[kza]['filepath'], 'r') as f: + lines = f.readlines() - lines = ensure_line_length_compliance(compiled_file) - with open(compiled_file, 'w') as f: - f.writelines(lines) + append_to_compiled_lib(lines, compiled_file) print( f'Compiled {decay_lib_type.upper()} decay libary to {compiled_file}.' From 528563513406a7b39abc1d68ff6aeb6e45342ad0 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Thu, 28 May 2026 08:44:31 -0500 Subject: [PATCH 07/13] get_MT_from_line() now stable --- tools/ALARAJOYWrapper/reaction_data.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 70622df71..b3015e6dd 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -435,19 +435,7 @@ def find_nucs_from_decay_lib(compiled_decay_lib): line = f.readline() while line: - - try: - MT = get_MT_from_line(line) - - # Certain file headers may not be full 81 character lines - # containing an MT value. Does not affect decay library parsing - # here or in ALARA. - except ValueError as V: - if str(V) == "invalid literal for int() with base 10: ''": - pass - else: - raise V - + MT = get_MT_from_line(line) if not _in_decay_block and MT == DECAY_MT: _in_decay_block = True From 0669e6a34cb0d8949021a43d0f8b844d6021a706 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Thu, 28 May 2026 08:58:34 -0500 Subject: [PATCH 08/13] Removing unnecessary imports --- tools/ALARAJOYWrapper/reaction_data.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index b3015e6dd..44b172945 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -2,11 +2,6 @@ from numpy import array import pandas as pd from endf_parserpy import EndfParserPy -from endf_parserpy.interpreter.endf_utils import ( - BlankLineError, UnexpectedEndOfInputError, UnexpectedControlRecordError -) -import warnings -from pathlib import Path # Define a dictionary containing all of the pathways for neutron and proton # changes in a nucleus following neutron activation From e27480b0058b89f20858ef79688b99bd1e9a8f40 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Thu, 28 May 2026 09:23:06 -0500 Subject: [PATCH 09/13] Removing endf-parserpy dependency --- tools/ALARAJOYWrapper/README.md | 1 - tools/ALARAJOYWrapper/reaction_data.py | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/ALARAJOYWrapper/README.md b/tools/ALARAJOYWrapper/README.md index d1053712f..8d0806e74 100644 --- a/tools/ALARAJOYWrapper/README.md +++ b/tools/ALARAJOYWrapper/README.md @@ -21,7 +21,6 @@ This preprocessor uses [NJOY 2016](https://github.com/njoy/NJOY2016) Nuclear Dat * [Pandas](https://pandas.pydata.org/docs/getting_started/install.html) - Domain-specific packages * [ENDFtk](https://github.com/njoy/ENDFtk) - * [endf-parserpy](https://endf-parserpy.readthedocs.io/en/latest/index.html) * [NJOY 2016](https://github.com/njoy/NJOY2016) (at Git hash `1dbce787`) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 44b172945..091dea24e 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -1,7 +1,6 @@ import csv from numpy import array import pandas as pd -from endf_parserpy import EndfParserPy # Define a dictionary containing all of the pathways for neutron and proton # changes in a nucleus following neutron activation @@ -22,6 +21,7 @@ 'kza' : [10010, 10020, 10030, 20030, 20040], 'total_mt' : range(203, 207 + 1) }) +DECAY_MF = 8 DECAY_MT = 457 TEND_RECORD = ' ' * 68 + '-1 0 0 0' @@ -295,6 +295,8 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): library file. """ + from tendl_processing import create_endf_file_obj + compiled_file = dir / f'{decay_dir}_compiled' compiled_file.unlink(missing_ok=True) @@ -329,11 +331,10 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): # Save individual nuclide KZAs from UKDD data for ascending KZA # compilation if decay_lib_type == 'ukdd': - decay_parser = EndfParserPy() - decay_data = decay_parser.parsefile(decay_file)[8][457] - decay_data['filepath'] = decay_file - kza = int(decay_data['ZA']) * 10 + int(decay_data['LISO']) - ukdd_nucs[kza] = decay_data + endf_file_obj, _ = create_endf_file_obj(decay_file, DECAY_MF) + decay_data = endf_file_obj.section(DECAY_MT).parse() + kza = decay_data.ZA * 10 + decay_data.LISO + ukdd_nucs[kza] = decay_file elif 'eaf' in decay_lib_type.lower(): append_to_compiled_lib(updated_lines, compiled_file) @@ -345,7 +346,7 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): if decay_lib_type == 'ukdd': for kza in sorted(ukdd_nucs): - with open(ukdd_nucs[kza]['filepath'], 'r') as f: + with open(ukdd_nucs[kza], 'r') as f: lines = f.readlines() append_to_compiled_lib(lines, compiled_file) From 30675ea5def97e0baec6d7e64a3f882af82c49d3 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Thu, 28 May 2026 09:33:21 -0500 Subject: [PATCH 10/13] Streamlining KZA definition --- tools/ALARAJOYWrapper/reaction_data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 091dea24e..ed833bcec 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -333,8 +333,7 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): if decay_lib_type == 'ukdd': endf_file_obj, _ = create_endf_file_obj(decay_file, DECAY_MF) decay_data = endf_file_obj.section(DECAY_MT).parse() - kza = decay_data.ZA * 10 + decay_data.LISO - ukdd_nucs[kza] = decay_file + ukdd_nucs[decay_data.ZA * 10 + decay_data.LISO] = decay_file elif 'eaf' in decay_lib_type.lower(): append_to_compiled_lib(updated_lines, compiled_file) From 30a07e9266b4fbc9cd696a5caee2e77651f8dd41 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 29 May 2026 10:14:13 -0500 Subject: [PATCH 11/13] Incorporating calculate_KZA_from_ENDF() functionality from tendl_processing --- tools/ALARAJOYWrapper/reaction_data.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index ed833bcec..11c2b1e77 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -295,7 +295,7 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): library file. """ - from tendl_processing import create_endf_file_obj + from tendl_processing import calculate_KZA_from_ENDF compiled_file = dir / f'{decay_dir}_compiled' compiled_file.unlink(missing_ok=True) @@ -331,9 +331,8 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): # Save individual nuclide KZAs from UKDD data for ascending KZA # compilation if decay_lib_type == 'ukdd': - endf_file_obj, _ = create_endf_file_obj(decay_file, DECAY_MF) - decay_data = endf_file_obj.section(DECAY_MT).parse() - ukdd_nucs[decay_data.ZA * 10 + decay_data.LISO] = decay_file + kza = calculate_KZA_from_ENDF(decay_file, DECAY_MF, DECAY_MT) + ukdd_nucs[kza] = decay_file elif 'eaf' in decay_lib_type.lower(): append_to_compiled_lib(updated_lines, compiled_file) From 7861bfc03c50ab45248c74d5f699b187956a3e18 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 29 May 2026 10:31:02 -0500 Subject: [PATCH 12/13] Moving newline check/fix to be with the rest of the fixes. --- tools/ALARAJOYWrapper/reaction_data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 11c2b1e77..96cc5767b 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -269,10 +269,6 @@ def append_to_compiled_lib(lines, compiled_file): None """ - # Ensure final line includes a newline signifier - if not lines[-1].endswith('\n'): - lines[-1] += '\n' - with open(compiled_file, 'a') as f: f.writelines(lines) @@ -323,6 +319,10 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): tend_line = '\n' + tend_line updated_lines.append(tend_line) + # Ensure file ends with a newline + if not lines[-1].endswith('\n'): + lines[-1] += '\n' + # Overwrite file if changes were made if lines != updated_lines: with open(decay_file, 'w') as f: From 4b25607be96aa1481c9d15b49f29047a5d3d1756 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 29 May 2026 11:21:00 -0500 Subject: [PATCH 13/13] Separating out formatting checks/fixes from decay file compilation. --- tools/ALARAJOYWrapper/preprocess_fendl3.py | 10 +++ tools/ALARAJOYWrapper/reaction_data.py | 87 +++++++++++----------- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/tools/ALARAJOYWrapper/preprocess_fendl3.py b/tools/ALARAJOYWrapper/preprocess_fendl3.py index 8c67ee22e..08d866f90 100644 --- a/tools/ALARAJOYWrapper/preprocess_fendl3.py +++ b/tools/ALARAJOYWrapper/preprocess_fendl3.py @@ -331,8 +331,18 @@ def main(): mt_dict = rxd.process_mt_data(rxd.load_mt_table(dir / 'mt_table.csv')) decay_path, decay_lib_type = args().decay_lib + ukdd_options = ['ukdd', 'ukaeadd', 'decay_2020', 'decay_2012'] + if decay_lib_type.lower() in ukdd_options: + decay_lib_type = 'ukdd' + + if decay_lib_type.lower() not in ['ukdd', 'eaf']: + raise ValueError( + 'Invalid library type. Must be either an EAF or UKDD release.' + ) + decay_path = Path(decay_path) if decay_path.is_dir(): + rxd.resolve_decay_file_formatting_issues(decay_path, decay_lib_type) decay_path = rxd.compile_decay_lib(decay_path, decay_lib_type, dir) all_nucs = rxd.find_nucs_from_decay_lib(decay_path) diff --git a/tools/ALARAJOYWrapper/reaction_data.py b/tools/ALARAJOYWrapper/reaction_data.py index 96cc5767b..3d78fea9f 100644 --- a/tools/ALARAJOYWrapper/reaction_data.py +++ b/tools/ALARAJOYWrapper/reaction_data.py @@ -255,52 +255,31 @@ def process_mt_data(mt_dict): return mt_dict -def append_to_compiled_lib(lines, compiled_file): +def resolve_decay_file_formatting_issues(decay_dir, decay_lib_type): """ - Append the contents of a single- or multi-nuclide decay file to a compiled - file for the whole library. - - Arguments: - lines (list of str): List of parsed lines from a decay file. - compiled_file (pathlib._local.PosixPath): Path to the compiled decay - library file. - - Returns: - None - """ - - with open(compiled_file, 'a') as f: - f.writelines(lines) - -def compile_decay_lib(decay_dir, decay_lib_type, dir): - """ - Iteratively compile the data from individual-nuclide decay files into a - single file in ascending order of KZA. Can either be an EAF or UKDD - decay library. + Check a decay file to identify and resolve formatting inconsistencies, + which include blank lines, missing TEND records, and files not ending + in newlines. For single-nuclide decay files contained in UKDD decay + data distributions, rename files by their KZA so that they can be + later compiled by ascending KZA value, as expected by EAFLib for decay + library processing. Arguments: decay_dir (pathlib._local.PosixPath): Filepath to the directory exclusively containing decay data files for either an EAF or UKDD decay library. decay_lib_type (str): Identifier tag for either EAF or UKDD data. - dir (pathlib._local.PosixPath): Path to the current working directory - (CWD) from which the command was called. Returns: - compiled_file (pathlib._local.PosixPath): Path to the compiled decay - library file. + None """ from tendl_processing import calculate_KZA_from_ENDF - compiled_file = dir / f'{decay_dir}_compiled' - compiled_file.unlink(missing_ok=True) - ukdd_options = ['ukdd', 'ukaeadd', 'decay_2020', 'decay_2012'] if decay_lib_type.lower() in ukdd_options: decay_lib_type = 'ukdd' - ukdd_nucs = dict() for decay_file in decay_dir.iterdir(): if '.DS_Store' in str(decay_file): decay_file.unlink() @@ -320,38 +299,58 @@ def compile_decay_lib(decay_dir, decay_lib_type, dir): updated_lines.append(tend_line) # Ensure file ends with a newline - if not lines[-1].endswith('\n'): - lines[-1] += '\n' + if not updated_lines[-1].endswith('\n'): + updated_lines[-1] += '\n' # Overwrite file if changes were made if lines != updated_lines: with open(decay_file, 'w') as f: f.writelines(updated_lines) - # Save individual nuclide KZAs from UKDD data for ascending KZA - # compilation + # Rename decay file to the KZA value so that a sorted decay directory + # iteration will go in order of ascending KZA if decay_lib_type == 'ukdd': kza = calculate_KZA_from_ENDF(decay_file, DECAY_MF, DECAY_MT) - ukdd_nucs[kza] = decay_file + decay_file.rename(decay_dir / str(kza)) + +def compile_decay_lib(decay_dir, decay_lib_type, dir): + """ + Iteratively compile the data from individual-nuclide decay files into a + single file in ascending order of KZA. Can either be an EAF or UKDD + decay library. + + Arguments: + decay_dir (pathlib._local.PosixPath): Filepath to the directory + exclusively containing decay data files for either an EAF or UKDD + decay library. + decay_lib_type (str): Identifier tag for either EAF or UKDD data. + dir (pathlib._local.PosixPath): Path to the current working directory + (CWD) from which the command was called. - elif 'eaf' in decay_lib_type.lower(): - append_to_compiled_lib(updated_lines, compiled_file) + Returns: + compiled_file (pathlib._local.PosixPath): Path to the compiled decay + library file. + """ - else: - raise ValueError( - 'Invalid library type. Must be either an EAF or UKDD release.' - ) + compiled_file = dir / f'{decay_dir}_compiled' + compiled_file.unlink(missing_ok=True) + sorted_filepaths = sorted(decay_dir.iterdir()) if decay_lib_type == 'ukdd': - for kza in sorted(ukdd_nucs): - with open(ukdd_nucs[kza], 'r') as f: - lines = f.readlines() + sorted_KZAs = sorted([int(p.stem) for p in sorted_filepaths]) + sorted_filepaths = [decay_dir / str(kza) for kza in sorted_KZAs] + + for decay_file in sorted_filepaths: + with open(decay_file, 'r') as f: + lines = f.readlines() - append_to_compiled_lib(lines, compiled_file) + with open(compiled_file, 'a') as f: + f.writelines(lines) print( f'Compiled {decay_lib_type.upper()} decay libary to {compiled_file}.' ) + return compiled_file def standardize_float(num_str):