From 38f0db55b336bbe5776a645e76b8ee5eacb1079a Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:11:42 -0700 Subject: [PATCH 1/6] Refactor thermal processing functions Refactor thermal processing functions and improve handling of multiple thermal paths. --- utils.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/utils.py b/utils.py index 2830205..a611752 100644 --- a/utils.py +++ b/utils.py @@ -32,12 +32,8 @@ def process_neutron(path, output_dir, libver, temperatures=None): data.export_to_hdf5(h5_file, 'w', libver=libver) -def process_thermal(path_neutron, path_thermal, output_dir, libver): - """Process ENDF thermal scattering sublibrary file into HDF5 and write into a - specified output directory.""" - print(f'Converting: {path_thermal}') - - # Check if divide_incoherent_elastic should be set +def _thermal_from_njoy(path_neutron, path_thermal, **kwargs): + """Process one thermal scattering evaluation with NJOY.""" divide_incoherent_elastic = False with warnings.catch_warnings(action='error', category=UserWarning): try: @@ -48,13 +44,63 @@ def process_thermal(path_neutron, path_thermal, output_dir, libver): try: with warnings.catch_warnings(action='ignore', category=UserWarning): - data = openmc.data.ThermalScattering.from_njoy( - path_neutron, path_thermal, - divide_incoherent_elastic=divide_incoherent_elastic + return openmc.data.ThermalScattering.from_njoy( + path_neutron, + path_thermal, + divide_incoherent_elastic=divide_incoherent_elastic, + **kwargs, ) except Exception as e: print(path_neutron, path_thermal, e) raise + + +def process_thermal(path_neutron, path_thermal, output_dir, libver, + name=None, table_name=None, zaid=None, nuclide=None): + """Process ENDF thermal scattering sublibrary file into HDF5 and write into a + specified output directory.""" + + if isinstance(path_thermal, (str, Path)): + paths_thermal = [path_thermal] + else: + paths_thermal = path_thermal + + data = None + + for thermal_path in paths_thermal: + print(f'Converting: {thermal_path}') + + kwargs = {} + if table_name is not None: + kwargs = {'table_name': table_name, 'zaids': [zaid], 'nmix': 1} + new_data = _thermal_from_njoy(path_neutron, thermal_path, **kwargs) + + if name is not None: + new_data.name = name + new_data.nuclides = [nuclide] + + if data is None: + data = new_data + continue + + overlap = set(data.temperatures) & set(new_data.temperatures) + if overlap: + raise ValueError( + f'Duplicate temperatures: {sorted(overlap)}' + ) + + data.kTs.extend(new_data.kTs) + + for reaction_name in ('elastic', 'inelastic'): + reaction = getattr(data, reaction_name) + new_reaction = getattr(new_data, reaction_name) + + if new_reaction is not None and new_reaction.xs is not None: + reaction.xs.update(new_reaction.xs) + reaction.distribution.update(new_reaction.distribution) + + data.kTs.sort() + h5_file = output_dir / f'{data.name}.h5' print(f'Writing {h5_file} ...') data.export_to_hdf5(h5_file, 'w', libver=libver) From 46061ff353c854114850e69901f2f2da032a965b Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:19:25 -0700 Subject: [PATCH 2/6] Implement directory-based JEFF-4.0 evaluations Added directory-based JEFF-4.0 thermal evaluations and updated processing logic for specific materials. --- generate_jeff.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/generate_jeff.py b/generate_jeff.py index 143c488..ccac407 100755 --- a/generate_jeff.py +++ b/generate_jeff.py @@ -15,6 +15,71 @@ import openmc.data from utils import download, extract, process_neutron, process_thermal +# Directory-based JEFF-4.0 thermal evaluations. +DIRECTORY_TSL = { + 'PuO2': ('O16', 'Pu238', 'Pu239', 'Pu240', 'Pu241', 'Pu242'), + 'ThO2': ('O16', 'Th232'), + 'UO2': ('O16', 'U238'), + 'Zy4': ( + 'Sn112', 'Sn114', 'Sn115', 'Sn116', 'Sn117', + 'Sn118', 'Sn119', 'Sn120', 'Sn122', 'Sn124', + 'Zr90', 'Zr91', 'Zr92', 'Zr94', 'Zr96', + ), +} + + +def directory_tsl_args(neutron_dir, thermal_dir, output_dir, libver): + """Yield arguments for directory-based JEFF-4.0 TSL evaluations.""" + for material, nuclides in DIRECTORY_TSL.items(): + material_dir = thermal_dir / f'tsl_{material}' + + for nuclide in nuclides: + symbol = nuclide.rstrip('0123456789') + Z, A, _ = openmc.data.zam(nuclide) + + path_neutron = ( + neutron_dir / f'n_{Z}-{symbol}-{A:03d}g.jeff' + ) + paths_thermal = sorted( + material_dir.glob( + f'[0-9]*K/' + f'tsl_{nuclide}_{material}_[0-9]*K.jeff' + ), + key=lambda path: int(path.parent.name[:-1]), + ) + + if not paths_thermal: + raise FileNotFoundError( + f'No TSL evaluations found for ' + f'{nuclide} in {material}' + ) + + if nuclide == 'O16': + table_name = f'o{material.lower()}' + elif nuclide == 'U238' and material == 'UO2': + table_name = 'uuo2' + else: + table_name = ( + f'{nuclide.lower()}{material.lower()}'[:6] + ) + + isotope_specific = ( + material == 'Zy4'or (material == 'PuO2' and symbol == 'Pu') + ) + name_part = nuclide if isotope_specific else symbol + name = f'c_{name_part}_in_{material}' + + yield ( + path_neutron, + paths_thermal, + output_dir, + libver, + name, + table_name, + 1000*Z + A, + nuclide, + ) + class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): @@ -359,7 +424,15 @@ def sort_key(path): args.destination / particle, args.libver) r = pool.apply_async(process_thermal, func_args) results.append(r) - + # special treament for directory organized tsl (PuO2, UO2, Zy4) + for func_args in directory_tsl_args( + neutron_dir, + thermal_dir, + args.destination / particle, + args.libver): + r = pool.apply_async(process_thermal, func_args) + results.append(r) + for r in results: r.wait() From e5bdf6e29987d0529ce61f4e7d4fd1e0191ddd8a Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:36:13 -0700 Subject: [PATCH 3/6] touch-ups --- generate_jeff.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/generate_jeff.py b/generate_jeff.py index ccac407..fc1907b 100755 --- a/generate_jeff.py +++ b/generate_jeff.py @@ -64,7 +64,7 @@ def directory_tsl_args(neutron_dir, thermal_dir, output_dir, libver): ) isotope_specific = ( - material == 'Zy4'or (material == 'PuO2' and symbol == 'Pu') + material == 'Zy4' or (material == 'PuO2' and symbol == 'Pu') ) name_part = nuclide if isotope_specific else symbol name = f'c_{name_part}_in_{material}' @@ -424,7 +424,7 @@ def sort_key(path): args.destination / particle, args.libver) r = pool.apply_async(process_thermal, func_args) results.append(r) - # special treament for directory organized tsl (PuO2, UO2, Zy4) + # special treatment for directory organized tsl (PuO2, ThO2, UO2, Zy4) for func_args in directory_tsl_args( neutron_dir, thermal_dir, @@ -432,7 +432,7 @@ def sort_key(path): args.libver): r = pool.apply_async(process_thermal, func_args) results.append(r) - + for r in results: r.wait() From 86fe8f834ba8258387628737012bdeefe5032dd5 Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:37:02 -0700 Subject: [PATCH 4/6] Amend JEFF directory structure for Zy4 Burnup evaluations --- generate_jeff.py | 66 +++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/generate_jeff.py b/generate_jeff.py index fc1907b..8f77615 100755 --- a/generate_jeff.py +++ b/generate_jeff.py @@ -33,26 +33,37 @@ def directory_tsl_args(neutron_dir, thermal_dir, output_dir, libver): for material, nuclides in DIRECTORY_TSL.items(): material_dir = thermal_dir / f'tsl_{material}' + burnup_dirs = [] + if material == 'Zy4': + burnup_dirs = sorted( + (material_dir / 'Burnup').glob('[0-9]*GWdt'), + key=lambda path: int(path.name[:-4]), + ) + if not burnup_dirs: + raise FileNotFoundError('No Zy4 burnup evaluations found') + for nuclide in nuclides: symbol = nuclide.rstrip('0123456789') Z, A, _ = openmc.data.zam(nuclide) - path_neutron = ( neutron_dir / f'n_{Z}-{symbol}-{A:03d}g.jeff' ) - paths_thermal = sorted( - material_dir.glob( - f'[0-9]*K/' - f'tsl_{nuclide}_{material}_[0-9]*K.jeff' - ), - key=lambda path: int(path.parent.name[:-1]), - ) - if not paths_thermal: - raise FileNotFoundError( - f'No TSL evaluations found for ' - f'{nuclide} in {material}' - ) + evaluations = [ + ('', sorted( + material_dir.glob( + f'[0-9]*K/' + f'tsl_{nuclide}_{material}_[0-9]*K.jeff' + ), + key=lambda path: int(path.parent.name[:-1]), + )) + ] + evaluations.extend( + (f'_{path.name}', [ + path / f'tsl_{nuclide}_{material}.jeff' + ]) + for path in burnup_dirs + ) if nuclide == 'O16': table_name = f'o{material.lower()}' @@ -69,16 +80,25 @@ def directory_tsl_args(neutron_dir, thermal_dir, output_dir, libver): name_part = nuclide if isotope_specific else symbol name = f'c_{name_part}_in_{material}' - yield ( - path_neutron, - paths_thermal, - output_dir, - libver, - name, - table_name, - 1000*Z + A, - nuclide, - ) + for suffix, paths_thermal in evaluations: + missing = [p for p in paths_thermal if not p.is_file()] + if not paths_thermal or missing: + raise FileNotFoundError( + f'No TSL evaluations found for ' + f'{nuclide} in {material}{suffix}' + ) + + yield ( + path_neutron, + paths_thermal, + output_dir, + libver, + name + suffix, + table_name, + 1000*Z + A, + nuclide, + material != 'Zy4', + ) class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, From 3695d9424f284a4e3c7585ff74eb6036a258ba7b Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:42:59 -0700 Subject: [PATCH 5/6] Handle JEFF-4.0 Zy4 mixed-elastic TSL data Use NJOY ACE elastic data for Zy4 evaluations to avoid inconsistent Bragg-edge and structure-factor array lengths in the ENDF data. --- utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/utils.py b/utils.py index a611752..432c457 100644 --- a/utils.py +++ b/utils.py @@ -56,7 +56,8 @@ def _thermal_from_njoy(path_neutron, path_thermal, **kwargs): def process_thermal(path_neutron, path_thermal, output_dir, libver, - name=None, table_name=None, zaid=None, nuclide=None): + name=None, table_name=None, zaid=None, nuclide=None, + use_endf_data=True): """Process ENDF thermal scattering sublibrary file into HDF5 and write into a specified output directory.""" @@ -69,10 +70,11 @@ def process_thermal(path_neutron, path_thermal, output_dir, libver, for thermal_path in paths_thermal: print(f'Converting: {thermal_path}') - - kwargs = {} + # Needed for Zy4 mixed-elastic handling. + kwargs = {'use_endf_data': use_endf_data} if table_name is not None: - kwargs = {'table_name': table_name, 'zaids': [zaid], 'nmix': 1} + kwargs.update(table_name=table_name, zaids=[zaid], nmix=1) + new_data = _thermal_from_njoy(path_neutron, thermal_path, **kwargs) if name is not None: From a895fa0647687201e03d96ed8a789589112e9218 Mon Sep 17 00:00:00 2001 From: Perry <100789850+yrrepy@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:50:36 -0700 Subject: [PATCH 6/6] touch-ups --- generate_jeff.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/generate_jeff.py b/generate_jeff.py index 8f77615..3754864 100755 --- a/generate_jeff.py +++ b/generate_jeff.py @@ -19,13 +19,9 @@ DIRECTORY_TSL = { 'PuO2': ('O16', 'Pu238', 'Pu239', 'Pu240', 'Pu241', 'Pu242'), 'ThO2': ('O16', 'Th232'), - 'UO2': ('O16', 'U238'), - 'Zy4': ( - 'Sn112', 'Sn114', 'Sn115', 'Sn116', 'Sn117', - 'Sn118', 'Sn119', 'Sn120', 'Sn122', 'Sn124', - 'Zr90', 'Zr91', 'Zr92', 'Zr94', 'Zr96', - ), -} + 'UO2': ('O16', 'U238'), + 'Zy4': ('Sn112', 'Sn114', 'Sn115', 'Sn116', 'Sn117', 'Sn118', 'Sn119', 'Sn120', 'Sn122', 'Sn124', + 'Zr90', 'Zr91', 'Zr92', 'Zr94', 'Zr96')} def directory_tsl_args(neutron_dir, thermal_dir, output_dir, libver):