From 98994b8c0555789333c7d0d6dc4ad75e300c3e7d Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 24 Jul 2026 15:15:25 -0500 Subject: [PATCH 1/7] Adding new ratio plotting functionality --- tools/ALARAJOYWrapper/xs_plotting.py | 304 +++++++++++++++++++++++++-- 1 file changed, 281 insertions(+), 23 deletions(-) diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index b9c007ae..3eaf55a3 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -5,6 +5,7 @@ import njoy_tools as njt import reaction_data as rxd import matplotlib.pyplot as plt +from matplotlib.patches import StepPatch from pathlib import Path def flagged_num_to_int(num): @@ -125,15 +126,29 @@ def extract_groupwise_data_from_DSV(dsv_list, KZA, MT): cross-sections were processed. emitted (str): Particle(s) emitted from the nuclear reaction corresponding to the MT number provided. + reference_group (str): Group name of the reference group against which + to divide other group structures' cross-sections. Will be an empty + string if none of the provided DSV paths in `dsv_list` contain a + '(reference)' tag in the string (i.e. example.dsv (reference)). """ groupwise_dict = {} emitted = '' + reference_path = '' + reference_group = '' + for dsv in dsv_list: + if 'reference' in dsv.lower(): + reference_path = dsv.split('reference')[0].strip('(').strip() + dsv = reference_path + with open(dsv, 'r') as f: dsv_lines = f.readlines() group_name = dsv_lines[0].split()[-1] + if dsv == reference_path: + reference_group = group_name + _, energy_bounds = njt.load_external_group_struct(group_name) for line in dsv_lines[1:-1]: @@ -141,17 +156,18 @@ def extract_groupwise_data_from_DSV(dsv_list, KZA, MT): dsv_pKZA, dsv_dKZA, dsv_MT, emitted = rxn[:4] emitted = ensure_emission_specificity(emitted, dsv_dKZA) - if KZA == dsv_pKZA and MT == flagged_num_to_int(dsv_MT)[0]: + if KZA == dsv_pKZA and MT == flagged_num_to_int(dsv_MT): groupwise_dict[group_name] = { 'xs' : np.array(rxn[4:]).astype(float), 'energies' : energy_bounds } break - return groupwise_dict, ensure_emission_specificity(emitted, dsv_dKZA) + return groupwise_dict, emitted, reference_group def set_plot_save_path( - element, A, emitted, tendl_dir, group_names, img_ext='png' + element, A, emitted, tendl_dir, group_names, + img_ext='png', ratio_plotting=False ): """ For a given reaction's cross-section plot produced by @@ -178,6 +194,9 @@ def set_plot_save_path( img_ext (str, optional): Option to set the image filetype for the plot to be saved, limited to Matplotlib filetypes: png, ps, pdf, svg. (Defaults to 'png') + ratio_plotting (bool, optional): Option to specify the path for + plotting the ratio series between different group structures. + (Defaults to False) Returns: save_path (pathlib._local.PosixPath): Filepath for a given reaction @@ -190,6 +209,13 @@ def set_plot_save_path( filepath: CWD/tendl2017_plots/Fe/Fe56/Fe56_(n,p)_VITAMIN-J-175.png + + Likewise, the same nuclide/reaction pair plotting the cross- + section ratios between VITAMIN-J-175 and CCFE-709 groupwise + data could produce this filepath: + + CWD/tendl2017_plots/Fe/Fe56/ratio_plots/Fe56_(n,p)_ + VITAMIN-J-175_CCFE-709_ratios.png """ if isinstance(group_names, str): @@ -197,20 +223,59 @@ def set_plot_save_path( nuc = f'{element}{A}' nuc_dir = Path(f'{tendl_dir}_plots') / element / nuc + if ratio_plotting: + nuc_dir /= 'ratio_plots' nuc_dir.mkdir(parents=True, exist_ok=True) - return nuc_dir / f'{nuc}_(n,{emitted})_{"_".join(group_names)}.{img_ext}' + stem = str(nuc_dir / f'{nuc}_(n,{emitted})_{"_".join(group_names)}') + if ratio_plotting: + stem += '_ratios' + + return Path(stem).with_suffix(f'.{img_ext}') + +def set_plot_parameters(ax, title, ratio_plotting=False): + """ + Apply standard plotting parameters for either of the two types of plots + producable by `xs_plotting`: `plot_single_nuc_rxn_xs()` or + `plot_relative_group_xs()`. + + Arguments: + ax (matplotlib.axes._axes.Axes): Matplotlib Axes object of the plot + being constructed. + title (str): Plot title. + ratio_plotting (bool, optional): Option to produce a ratio series plot + comparing different group structures' cross-sections with + `plot_relative_group_xs()`. + (Defaults to False) + + Returns: + ax (matplotlib.axes._axes.Axes): Updated Matplotlib Axes object of the + plot being constructed. + """ + + ylabel = 'Cross-Section [b]' + if ratio_plotting: + ylabel = 'Ratio of Cross-Sections' + + ax.set_xscale('log') + ax.set_yscale('log') + ax.set_xlabel('Energy [eV]') + ax.set_ylabel(ylabel) + ax.set_title(title) + ax.grid() + ax.legend() + + return ax def plot_single_nuc_rxn_xs( ax, element, A, emitted, continuous_dict={}, groupwise_dict={} ): """ - Create and save a plot for a singular nuclide/reaction's cross-sections - vs. energy. Can be used to plot continuous TENDL data (not processed - by ALARAJOY), alongside an arbitrary number of groupwise cross- - sections according to the group structure in which they were - converted. Groupwise and continuous data can be plotted individually - if only one type is provided. + Create a plot for a singular nuclide/reaction's cross-sections vs. energy. + Can be used to plot continuous TENDL data (not processed by ALARAJOY), + alongside an arbitrary number of groupwise cross-sections according to + the group structure in which they were converted. Groupwise and + continuous data can be plotted individually if only one type is provided. Arguments: ax (matplotlib.axes._axes.Axes): Matplotlib Axes object of the plot @@ -245,9 +310,6 @@ def plot_single_nuc_rxn_xs( } (Defaults to {}) - img_ext (str, optional): Option to set the image filetype for the plot - to be saved, limited to Matplotlib filetypes: png, ps, pdf, svg. - (Defaults to 'png') Returns: ax (matplotlib.axes._axes.Axes): Updated Matplotlib Axes object of the @@ -272,15 +334,196 @@ def plot_single_nuc_rxn_xs( title += ', '.join([g for g in groupwise_dict]) + ' (Groupwise)' - ax.set_xscale('log') - ax.set_yscale('log') - ax.set_xlabel('Energy [eV]') - ax.set_ylabel('Cross-Section [b]') - ax.set_title(title) - ax.grid() - ax.legend() + return set_plot_parameters(ax, title) - return ax +def collect_all_stair_colors(ax): + """ + From a Matplotlib Axes object, determine the series colors of all + matplotlib.patches.StepPatch subplots, namely the `stair` plots for + groupwise data (as opposed to `plot` plots for continuous data). + Organize the RGBA color data into a dictionary keyed by each group + structure, with values of the tuple of 0-1 RGBA values definining the + color/transparency of each series. + + Arguments: + ax (matplotlib.axes._axes.Axes): Matplotlib Axes object of an already- + created plot with series corresponding to different group + structures' cross-section data. + + Returns: + color_dict (dict): Dictionary keyed by each group structure, with + values of the tuple of 0-1 RGBA values defining the color/ + transparency of each series. + """ + + handles, labels = ax.get_legend_handles_labels() + color_dict = dict() + for handle, label in zip(handles, labels): + if isinstance(handle, StepPatch): + color_dict[label] = handle.get_edgecolor() + + return color_dict + +def compute_groupwise_xs_ratios(groupwise_dict, reference_group=''): + """ + Calculate the ratios of groupwise cross-sections for each group in + `groupwise_dict` and a single reference series. For each group + pairing, adjust the energies and cross-sections to be on a merged + grid that accounts for potentially distinct bin boundaries between + energy groups. + + Arguments: + groupwise_dict (dict): Nested dictionary keyed at the highest level by + the name of the group structure according to which an array of + cross-sections were processed. + reference_group (str, optional): Group name of the reference group + structure against which to divide other group structures' cross- + sections. If not supplied, then the first group name key in + `groupwise_dict` will be set by default as the reference group. + (Defaults to '') + + Returns: + ratio_dict (dict): Nested dictionary keyed at the highest level by + the name of the group structure according to which an array of + cross-sections were processed. Similar to `groupwise_dict`, but + without a designated key for the reference group name, as all + other group names are implicitly refering to that group's cross- + section data divided by the reference group's (according to the + re-computed bins to appropriately match cross-sections and + energies between the two group structures). + reference_group (str): Group name of the reference group structure. + Will be the same as `reference_group` from Arguments if one is + supplied, otherwise, will be the group name of the first key in + `groupwise_dict`. + """ + + def _re_bin_xs(bin_edges, xs, midpoints): + """ + Adjust an array of groupwise cross-sections to fit a new set of energy + bin edges. + + Arguments: + bin_edges (numpy.ndarray): Iterable of ascending energy bounds + according to which to set the new cross-section binning. + xs (numpy.ndarray): Iterable of energy dependent cross-sections. + midpoint (numpy.ndarray): Midpoint energies at which to look up + the containing bin's cross-section. + + Returns: + re_binned_xs (numpy.ndarray): Cross-sections corresponding to each + energy group midpoint. + """ + + idx = np.searchsorted(bin_edges, midpoints, side='right') - 1 + return xs[np.clip(idx, 0, len(xs) - 1)] + + reference_group = reference_group or next(iter(groupwise_dict)) + ref_xs, ref_energies = groupwise_dict[reference_group].values() + + ratio_dict = {} + for group_name, group_data in groupwise_dict.items(): + if group_name == reference_group: + continue + + group_xs, group_energies = group_data.values() + + low = max(group_energies[0], ref_energies[0]) + high = min(group_energies[-1], ref_energies[-1]) + union = np.union1d(group_energies, ref_energies) + merged_energies = union[(union >= low) & (union <= high)] + midpoints = np.sqrt(merged_energies[:-1] * merged_energies[1:]) + + binned_group = _re_bin_xs(group_energies, group_xs[::-1], midpoints) + binned_ref = _re_bin_xs(ref_energies, ref_xs[::-1], midpoints) + + ratio_dict[group_name] = { + 'ratio_xs' : np.divide( + binned_group, binned_ref, + out=np.full_like(binned_group, np.nan, dtype=float), + where=binned_ref != 0 + ), + 'energies' : merged_energies + } + + return ratio_dict, reference_group + +def plot_relative_group_xs( + ax, element, A, emitted, groupwise_dict, color_dict, reference_group='' +): + """ + Create a plot of the ratio series of groupwise cross-sections relative to + a reference group structure's energy-dependent cross-sections. + + Arguments: + ax (matplotlib.axes._axes.Axes): Matplotlib Axes object of the plot + being constructed. + element (str): Symbol of the element to which the nuclide being + plotted belongs. + A (str or int): Mass number for selected isonuclide. + If the target is a metastable isomer, "m" or "n" is written after + the mass number, corresponding to the first or second metastable + states. + emitted (str): Particle(s) emitted from a nuclear reaction. + continuous_dict (dict, optional): Dictionary containing an individual + nuclide's continous TENDL cross-sections and energies for a given + reaction. Formatted as: + {'xs' : continuous_xs, 'energies' : continous_energies} + + (Defaults to {}) + groupwise_dict (dict): Nested dictionary keyed at the highest level by + the name of the group structure according to which an array of + cross-sections were processed. The form of this data structure is + as follows: + { + 'group_name_1' : { + 'xs' : groupwise_xs, + 'energies' : energy_group_bounds + }, + ... + 'group_name_n' : { + 'xs' : groupwise_xs, + 'energies' : energy_group_bounds + }, + } + color_dict (dict): Dictionary keyed by each group structure, with + values of the tuple of 0-1 RGBA values defining the color/ + transparency of each series. + reference_group (str, optional): Group name of the reference group + structure against which to divide other group structures' cross- + sections. If not supplied, then the first group name key in + `groupwise_dict` will be set by default as the reference group. + (Defaults to '') + + Returns: + ax (matplotlib.axes._axes.Axes): Updated Matplotlib Axes object of the + plot being constructed. + """ + + if len(groupwise_dict) < 2: + raise ValueError( + 'At least two group structures are required to compute a ' \ + 'relative cross-section.' + ) + + ratio_dict, reference_group = compute_groupwise_xs_ratios( + groupwise_dict, reference_group + ) + + for group_name, group_data in ratio_dict.items(): + ax.stairs( + group_data['ratio_xs'], group_data['energies'], + label=f'{group_name} / {reference_group}', + color=np.mean( + [color_dict[group_name], color_dict[reference_group]], axis=0 + ) + ) + + title = ( + f'Relative Cross-Section for $^{{{A}}}${element}(n,{emitted}):\n' + f'Reference Group = {reference_group}' + ) + + return set_plot_parameters(ax, title=title, ratio_plotting=True) def check_all_tag(param): """ @@ -348,7 +591,7 @@ def find_all_mass_nums(tendl_dir, element): mass_nums.add(nuc_match.group(1)) return mass_nums - + def main(): # Only load in yaml module when executing xs_plotting.py as a script, @@ -360,6 +603,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--yaml', '-y') + parser.add_argument('--ratio_plotting', '-r', action='store_true') args = parser.parse_args() with open(args.yaml, 'r') as f: @@ -414,7 +658,7 @@ def main(): tendl_dir / f'{element}{A}.tendl', flagged_num_to_int(MT) ) - groupwise_dict, emitted = extract_groupwise_data_from_DSV( + groupwise_dict, emitted, reference_group = extract_groupwise_data_from_DSV( dsv_list, KZA, MT ) @@ -428,6 +672,20 @@ def main(): ) plt.savefig(plot_path) + if args.ratio_plotting: + ratio_fig, ratio_ax = plt.subplots(figsize=(10,6)) + color_dict = collect_all_stair_colors(ax) + plot_relative_group_xs( + ratio_ax, element, A, emitted, groupwise_dict, + color_dict, reference_group + ) + ratio_plot_path = set_plot_save_path( + element, A, emitted, tendl_dir, + groupwise_dict.keys(), + ratio_plotting=args.ratio_plotting + ) + plt.savefig(ratio_plot_path) + print( f'Cross-section plots saved to {plot_path.parents[2]}/, ' \ 'organized by element, nuclide, reaction.' From 0cd58fe7bdfc4a677647f24d0dd53b8793e08a46 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 24 Jul 2026 15:36:29 -0500 Subject: [PATCH 2/7] Fixing line length issue --- tools/ALARAJOYWrapper/xs_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index 3eaf55a3..425c1f38 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -658,8 +658,8 @@ def main(): tendl_dir / f'{element}{A}.tendl', flagged_num_to_int(MT) ) - groupwise_dict, emitted, reference_group = extract_groupwise_data_from_DSV( - dsv_list, KZA, MT + groupwise_dict, emitted, reference_group = ( + extract_groupwise_data_from_DSV(dsv_list, KZA, MT) ) if groupwise_dict: From b1b29e2151cd4451c3b5c00092313caa6544b9f2 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 24 Jul 2026 16:10:04 -0500 Subject: [PATCH 3/7] Ensuring x-axis matches limits between both types of plots --- tools/ALARAJOYWrapper/xs_plotting.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index 425c1f38..521cce62 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -415,7 +415,13 @@ def _re_bin_xs(bin_edges, xs, midpoints): """ idx = np.searchsorted(bin_edges, midpoints, side='right') - 1 - return xs[np.clip(idx, 0, len(xs) - 1)] + re_binned_xs = xs[np.clip(idx, 0, len(xs) - 1)] + + re_binned_xs[ + (midpoints < bin_edges[0]) | (midpoints >= bin_edges[-1]) + ] = 0.0 + + return re_binned_xs reference_group = reference_group or next(iter(groupwise_dict)) ref_xs, ref_energies = groupwise_dict[reference_group].values() @@ -426,11 +432,7 @@ def _re_bin_xs(bin_edges, xs, midpoints): continue group_xs, group_energies = group_data.values() - - low = max(group_energies[0], ref_energies[0]) - high = min(group_energies[-1], ref_energies[-1]) - union = np.union1d(group_energies, ref_energies) - merged_energies = union[(union >= low) & (union <= high)] + merged_energies = np.union1d(group_energies, ref_energies) midpoints = np.sqrt(merged_energies[:-1] * merged_energies[1:]) binned_group = _re_bin_xs(group_energies, group_xs[::-1], midpoints) @@ -448,7 +450,8 @@ def _re_bin_xs(bin_edges, xs, midpoints): return ratio_dict, reference_group def plot_relative_group_xs( - ax, element, A, emitted, groupwise_dict, color_dict, reference_group='' + ax, element, A, emitted, groupwise_dict, color_dict, + reference_group='', x_limits=(None, None) ): """ Create a plot of the ratio series of groupwise cross-sections relative to @@ -493,6 +496,9 @@ def plot_relative_group_xs( sections. If not supplied, then the first group name key in `groupwise_dict` will be set by default as the reference group. (Defaults to '') + xlimits (tuple, optional): Option to specify the x-axis limits for + the plot. + (Defaults to (None, None)) Returns: ax (matplotlib.axes._axes.Axes): Updated Matplotlib Axes object of the @@ -518,6 +524,7 @@ def plot_relative_group_xs( ) ) + ax.set_xlim(x_limits) title = ( f'Relative Cross-Section for $^{{{A}}}${element}(n,{emitted}):\n' f'Reference Group = {reference_group}' @@ -663,7 +670,7 @@ def main(): ) if groupwise_dict: - plot_single_nuc_rxn_xs( + ax = plot_single_nuc_rxn_xs( ax, element, A, emitted, continuous_dict, groupwise_dict ) @@ -677,7 +684,7 @@ def main(): color_dict = collect_all_stair_colors(ax) plot_relative_group_xs( ratio_ax, element, A, emitted, groupwise_dict, - color_dict, reference_group + color_dict, reference_group, ax.get_xlim() ) ratio_plot_path = set_plot_save_path( element, A, emitted, tendl_dir, From c8bbbb3dbef9d3c9d6e3c440ce9a118e94f2adcc Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Tue, 4 Aug 2026 09:17:14 -0500 Subject: [PATCH 4/7] Responding to requested changes. --- tools/ALARAJOYWrapper/xs_plotting.py | 103 ++++++++------------------- 1 file changed, 28 insertions(+), 75 deletions(-) diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index 521cce62..951d6f89 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -134,20 +134,12 @@ def extract_groupwise_data_from_DSV(dsv_list, KZA, MT): groupwise_dict = {} emitted = '' - reference_path = '' - reference_group = '' for dsv in dsv_list: - if 'reference' in dsv.lower(): - reference_path = dsv.split('reference')[0].strip('(').strip() - dsv = reference_path - with open(dsv, 'r') as f: dsv_lines = f.readlines() group_name = dsv_lines[0].split()[-1] - if dsv == reference_path: - reference_group = group_name _, energy_bounds = njt.load_external_group_struct(group_name) @@ -163,7 +155,7 @@ def extract_groupwise_data_from_DSV(dsv_list, KZA, MT): } break - return groupwise_dict, emitted, reference_group + return groupwise_dict, emitted def set_plot_save_path( element, A, emitted, tendl_dir, group_names, @@ -364,23 +356,17 @@ def collect_all_stair_colors(ax): return color_dict -def compute_groupwise_xs_ratios(groupwise_dict, reference_group=''): +def compute_groupwise_xs_ratios(groupwise_dict): """ Calculate the ratios of groupwise cross-sections for each group in - `groupwise_dict` and a single reference series. For each group - pairing, adjust the energies and cross-sections to be on a merged - grid that accounts for potentially distinct bin boundaries between - energy groups. + `groupwise_dict` that shares a group structure as a single reference + series. `ValueError` is raised if no other groupwise data matches the + group structure of the reference group structure. Arguments: groupwise_dict (dict): Nested dictionary keyed at the highest level by the name of the group structure according to which an array of cross-sections were processed. - reference_group (str, optional): Group name of the reference group - structure against which to divide other group structures' cross- - sections. If not supplied, then the first group name key in - `groupwise_dict` will be set by default as the reference group. - (Defaults to '') Returns: ratio_dict (dict): Nested dictionary keyed at the highest level by @@ -388,42 +374,16 @@ def compute_groupwise_xs_ratios(groupwise_dict, reference_group=''): cross-sections were processed. Similar to `groupwise_dict`, but without a designated key for the reference group name, as all other group names are implicitly refering to that group's cross- - section data divided by the reference group's (according to the - re-computed bins to appropriately match cross-sections and - energies between the two group structures). + section data divided by the reference group's. All data must be of + the same group structure. reference_group (str): Group name of the reference group structure. Will be the same as `reference_group` from Arguments if one is supplied, otherwise, will be the group name of the first key in `groupwise_dict`. """ - def _re_bin_xs(bin_edges, xs, midpoints): - """ - Adjust an array of groupwise cross-sections to fit a new set of energy - bin edges. - - Arguments: - bin_edges (numpy.ndarray): Iterable of ascending energy bounds - according to which to set the new cross-section binning. - xs (numpy.ndarray): Iterable of energy dependent cross-sections. - midpoint (numpy.ndarray): Midpoint energies at which to look up - the containing bin's cross-section. - - Returns: - re_binned_xs (numpy.ndarray): Cross-sections corresponding to each - energy group midpoint. - """ - - idx = np.searchsorted(bin_edges, midpoints, side='right') - 1 - re_binned_xs = xs[np.clip(idx, 0, len(xs) - 1)] - - re_binned_xs[ - (midpoints < bin_edges[0]) | (midpoints >= bin_edges[-1]) - ] = 0.0 - - return re_binned_xs - - reference_group = reference_group or next(iter(groupwise_dict)) + reference_group = next(iter(groupwise_dict)) + print(reference_group) ref_xs, ref_energies = groupwise_dict[reference_group].values() ratio_dict = {} @@ -432,26 +392,26 @@ def _re_bin_xs(bin_edges, xs, midpoints): continue group_xs, group_energies = group_data.values() - merged_energies = np.union1d(group_energies, ref_energies) - midpoints = np.sqrt(merged_energies[:-1] * merged_energies[1:]) - - binned_group = _re_bin_xs(group_energies, group_xs[::-1], midpoints) - binned_ref = _re_bin_xs(ref_energies, ref_xs[::-1], midpoints) - - ratio_dict[group_name] = { - 'ratio_xs' : np.divide( - binned_group, binned_ref, - out=np.full_like(binned_group, np.nan, dtype=float), - where=binned_ref != 0 - ), - 'energies' : merged_energies - } + if len(group_energies) == len(ref_energies) and group_energies == ref_energies: + ratio_dict[group_name] = { + 'ratio_xs' : np.divide( + group_xs, ref_xs, + out=np.full_like(group_energies, np.nan, dtype=float), + where=ref_xs != 0 + )[::-1], + 'energies' : ref_energies + } + + if not ratio_dict: + raise ValueError( + 'No groupwise cross-sections provided with the same group ' \ + f'structure as the reference structure "{reference_group}".' + ) return ratio_dict, reference_group def plot_relative_group_xs( - ax, element, A, emitted, groupwise_dict, color_dict, - reference_group='', x_limits=(None, None) + ax, element, A, emitted, groupwise_dict, color_dict, x_limits=(None, None) ): """ Create a plot of the ratio series of groupwise cross-sections relative to @@ -491,11 +451,6 @@ def plot_relative_group_xs( color_dict (dict): Dictionary keyed by each group structure, with values of the tuple of 0-1 RGBA values defining the color/ transparency of each series. - reference_group (str, optional): Group name of the reference group - structure against which to divide other group structures' cross- - sections. If not supplied, then the first group name key in - `groupwise_dict` will be set by default as the reference group. - (Defaults to '') xlimits (tuple, optional): Option to specify the x-axis limits for the plot. (Defaults to (None, None)) @@ -511,9 +466,7 @@ def plot_relative_group_xs( 'relative cross-section.' ) - ratio_dict, reference_group = compute_groupwise_xs_ratios( - groupwise_dict, reference_group - ) + ratio_dict, reference_group = compute_groupwise_xs_ratios(groupwise_dict) for group_name, group_data in ratio_dict.items(): ax.stairs( @@ -665,7 +618,7 @@ def main(): tendl_dir / f'{element}{A}.tendl', flagged_num_to_int(MT) ) - groupwise_dict, emitted, reference_group = ( + groupwise_dict, emitted = ( extract_groupwise_data_from_DSV(dsv_list, KZA, MT) ) @@ -684,7 +637,7 @@ def main(): color_dict = collect_all_stair_colors(ax) plot_relative_group_xs( ratio_ax, element, A, emitted, groupwise_dict, - color_dict, reference_group, ax.get_xlim() + color_dict, ax.get_xlim() ) ratio_plot_path = set_plot_save_path( element, A, emitted, tendl_dir, From 0362e6d3c68e7d5fe069e39b8b8afed277ec9342 Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Tue, 4 Aug 2026 11:12:21 -0500 Subject: [PATCH 5/7] Ratio plotting for same group structure only --- tools/ALARAJOYWrapper/README.md | 6 ++++-- tools/ALARAJOYWrapper/xs_plotting.py | 18 +++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tools/ALARAJOYWrapper/README.md b/tools/ALARAJOYWrapper/README.md index ca2c080c..5db430c9 100644 --- a/tools/ALARAJOYWrapper/README.md +++ b/tools/ALARAJOYWrapper/README.md @@ -99,7 +99,9 @@ Either as an optional inclusion within the main ALARAJOY groupwise processing pi When the optional `-p` argument is invoked when executing `preprocess_fendl3.py`, all reactions for all nuclides written out to `cumulative_gendf_data.dsv` will be produced and saved according to the above directory structure. The highest level directory will be the same name as the `-f` TENDL data directory, with an additional "`_plots`" tag (e.g. `tendl2017/` → `tendl2017_plots/`). -To run `xs_plotting.py` as a standalone script, an input `.yaml` file must be supplied to specify the nuclides and reactions to be plotted. Additionally, the groupwise and continuous data sources to comparatively plot can be specified in this input file. These, however, are not required and will default to `cumulative_gendf_data.dsv` and `tendl2017/` respectively. The format of this input is shown below, as well as in `example_xs_plotting_input.yaml`, which can be used as a basis to supply custom plotting parameters according to their needs. Reactions are specified by their MT number, whose reference can be found at https://www.oecd-nea.org/dbdata/data/manual-endf/endf102_MT.pdf. +To run `xs_plotting.py` as a standalone script, an input `.yaml` file must be supplied to specify the nuclides and reactions to be plotted. Additionally, the groupwise and continuous data sources to comparatively plot can be specified in this input file. These, however, are not required and will default to `cumulative_gendf_data.dsv` and `tendl2017/` respectively. The format of this input is shown below, as well as in `example_xs_plotting_input.yaml`, which can be used as a basis to supply custom plotting parameters according to their needs. Reactions are specified by their MT number, whose reference can be found at https://www.oecd-nea.org/dbdata/data/manual-endf/endf102_MT.pdf. + +Relative cross-section plots can also be produced between datasets that have been produced with the same group-structure (i.e. ALARAJOY CCFE-709 cross-sections against FISPACT-II PREPRO/GROUPIE CCFE-709 cross-sections processed into an ALARAJOY DSV). To do so, when listing the DSV paths in the input YAML, put the reference data set to be divided against first. When calling `xs_plotting.py`, include the `-r` flag. ``` # Option to specify arbitrary number of groupwise data sources @@ -154,7 +156,7 @@ Using parameter `all` for any category of element, mass number, or reaction will The cross-section plotting script is run as follows: ``` -python xs_plotting.py -y /path/to/input_file.yaml +python xs_plotting.py -y /path/to/input_file.yaml -r ``` ## Application of Processed Data to ALARA Data Conversion Methods diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index 951d6f89..c5141058 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -139,10 +139,12 @@ def extract_groupwise_data_from_DSV(dsv_list, KZA, MT): with open(dsv, 'r') as f: dsv_lines = f.readlines() - group_name = dsv_lines[0].split()[-1] - + group_name, processing_code = dsv_lines[0].split()[-2:] _, energy_bounds = njt.load_external_group_struct(group_name) + if processing_code != 'NJOY': + group_name += f' ({processing_code})' + for line in dsv_lines[1:-1]: rxn = line.split() dsv_pKZA, dsv_dKZA, dsv_MT, emitted = rxn[:4] @@ -249,8 +251,10 @@ def set_plot_parameters(ax, title, ratio_plotting=False): if ratio_plotting: ylabel = 'Ratio of Cross-Sections' + if np.log10(np.ptp(ax.get_ylim())) > 1: + ax.set_yscale('log') + ax.set_xscale('log') - ax.set_yscale('log') ax.set_xlabel('Energy [eV]') ax.set_ylabel(ylabel) ax.set_title(title) @@ -383,7 +387,6 @@ def compute_groupwise_xs_ratios(groupwise_dict): """ reference_group = next(iter(groupwise_dict)) - print(reference_group) ref_xs, ref_energies = groupwise_dict[reference_group].values() ratio_dict = {} @@ -392,12 +395,12 @@ def compute_groupwise_xs_ratios(groupwise_dict): continue group_xs, group_energies = group_data.values() - if len(group_energies) == len(ref_energies) and group_energies == ref_energies: + if np.allclose(ref_energies, group_energies, rtol=1e-5): ratio_dict[group_name] = { 'ratio_xs' : np.divide( group_xs, ref_xs, - out=np.full_like(group_energies, np.nan, dtype=float), - where=ref_xs != 0 + out=np.full_like(ref_xs, np.nan, dtype=float), + where=(ref_xs != 0) )[::-1], 'energies' : ref_energies } @@ -471,6 +474,7 @@ def plot_relative_group_xs( for group_name, group_data in ratio_dict.items(): ax.stairs( group_data['ratio_xs'], group_data['energies'], + baseline=None, label=f'{group_name} / {reference_group}', color=np.mean( [color_dict[group_name], color_dict[reference_group]], axis=0 From ed6bf1ab3faf6408397dff1e50a4ca56d58df5db Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Fri, 21 Aug 2026 09:17:17 -0500 Subject: [PATCH 6/7] Updating to handle weight function specifications --- tools/ALARAJOYWrapper/xs_plotting.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index c5141058..b7d6aa8b 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -139,11 +139,16 @@ def extract_groupwise_data_from_DSV(dsv_list, KZA, MT): with open(dsv, 'r') as f: dsv_lines = f.readlines() - group_name, processing_code = dsv_lines[0].split()[-2:] + group_name, processing_code, weight_function = ( + dsv_lines[0].split()[1:] + ) + _, energy_bounds = njt.load_external_group_struct(group_name) + group_name += ' (' if processing_code != 'NJOY': - group_name += f' ({processing_code})' + group_name += f'{processing_code}, ' + group_name += f'{weight_function} weight function)' for line in dsv_lines[1:-1]: rxn = line.split() @@ -215,6 +220,8 @@ def set_plot_save_path( if isinstance(group_names, str): group_names = [group_names] + group_names = [g.replace(' ', '_').replace('/','') for g in group_names] + nuc = f'{element}{A}' nuc_dir = Path(f'{tendl_dir}_plots') / element / nuc if ratio_plotting: From 3effc417d26f6e2b0b81ee54333d77a4dce66a4a Mon Sep 17 00:00:00 2001 From: Eitan Shai Weinstein Date: Tue, 8 Sep 2026 08:56:30 -0500 Subject: [PATCH 7/7] Clarifying error message. --- tools/ALARAJOYWrapper/xs_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ALARAJOYWrapper/xs_plotting.py b/tools/ALARAJOYWrapper/xs_plotting.py index b7d6aa8b..e327092b 100644 --- a/tools/ALARAJOYWrapper/xs_plotting.py +++ b/tools/ALARAJOYWrapper/xs_plotting.py @@ -472,8 +472,8 @@ def plot_relative_group_xs( if len(groupwise_dict) < 2: raise ValueError( - 'At least two group structures are required to compute a ' \ - 'relative cross-section.' + 'At least two groupwise DSV files with equivalent group' \ + 'structures are required to compute a relative cross-section.' ) ratio_dict, reference_group = compute_groupwise_xs_ratios(groupwise_dict)