From 1207b651b62c6782c27a4da49f120102a32ccf0b Mon Sep 17 00:00:00 2001 From: Davide Laghi Date: Mon, 10 Feb 2025 00:06:02 +0100 Subject: [PATCH] restored sphereSDDR --- docs/source/dev/insertbenchmarks.rst | 20 +++ src/jade/config/atlas_config.py | 14 ++ src/jade/post/atlas.py | 2 +- src/jade/post/atlas_processor.py | 11 +- src/jade/post/excel_processor.py | 13 +- src/jade/post/plotter.py | 83 ++++++++++ .../benchmarks_pp/atlas/Sphere.yaml | 4 +- .../benchmarks_pp/atlas/SphereSDDR.yaml | 151 ++++++++++++++++++ .../benchmarks_pp/excel/SphereSDDR.yaml | 4 +- tests/post/test_atlas_processor.py | 11 ++ 10 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 src/jade/resources/default_cfg/benchmarks_pp/atlas/SphereSDDR.yaml diff --git a/docs/source/dev/insertbenchmarks.rst b/docs/source/dev/insertbenchmarks.rst index e8a22a85..eeada00b 100644 --- a/docs/source/dev/insertbenchmarks.rst +++ b/docs/source/dev/insertbenchmarks.rst @@ -121,6 +121,9 @@ The currently supported modifiers are: * *by*: the name of the column to group by. * *action*: the aggregation function to be applied. The currently supported aggregations are 'sum', 'mean', 'max', 'min'. +* ``delete_cols``: deletes columns from the tally. The keyarg to provide is *cols* which expects a list + of column names to be deleted. + More than one modifiers can be applied in series to a single tally. If your benchmark requires a new modifier, please refer to :ref:`add_tally_mod`. @@ -191,6 +194,13 @@ The optional configurations that can be included in a *table* are: in orange if it is greater than 10 and in yellow if it is greater than 5 and green otherwise. * ``change_col_names``: a dictionary that specifies the new names for the columns. The keys are the original column names and the values are the new names. This will be applied as a last operation before dumping the df. +* ``subsets``: it is used to select only certain results. It is a list of dictionary. One dictionary + needs to be provided for each *result* for which only a subset needs to be selected. The keys + of each dictionary are: + + * *result*: the name of the *result* for which the subset is selected. + * *values*: a dictionary that will be used to select the subset. Keys are the colum names and items are + the values that will be used to select the subset in that specific column. An example of a *table* configuration is shown below: @@ -257,6 +267,16 @@ Optional configuration options are: names and the values are the arguments values. The list of plot_args parameters available in each plot are described in the plot gallery. * ``rectangles``: TODO +* ``subsets``: it is used to select only certain results. It is a list of dictionary. One dictionary + needs to be provided for each *result* for which only a subset needs to be selected. The keys + of each dictionary are: + + * *result*: the name of the *result* for which the subset is selected. + * *values*: a dictionary that will be used to select the subset. Keys are the colum names and items are + the values that will be used to select the subset in that specific column. + +* ``select_runs``: This option is effective only one ``expand_runs`` is set to True. This option allows + to specify a regex pattern (in string format). Only the cases/runs that match the pattern will be plotted. Implement new functionalities diff --git a/src/jade/config/atlas_config.py b/src/jade/config/atlas_config.py index a12c3ff9..a9b38303 100644 --- a/src/jade/config/atlas_config.py +++ b/src/jade/config/atlas_config.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re from dataclasses import dataclass from enum import Enum from pathlib import Path @@ -96,6 +97,11 @@ class PlotConfig: recs : list[tuple[str, str, float, float]], optional Rectangles to be added to the plot. Each tuple contains the label, the color, the xmin position and the xmax position of the rectangle. + subsets : list[dict], optional + List of dictionaries with the column and values to be used as a subset of a + specific result. Each dictionary should have the keys "result", "column" and "values". + select_runs : re.Pattern, optional + Regular expression to select only the runs that match the pattern. """ name: str @@ -112,9 +118,14 @@ class PlotConfig: additional_labels: dict[str, list[tuple[str, float]]] | None = None v_lines: dict[str, list[float]] | None = None recs: list[tuple[str, str, float, float]] | None = None + subsets: list[dict] | None = None + select_runs: re.Pattern | None = None @classmethod def from_dict(cls, dictionary: dict, name: str) -> PlotConfig: + select_runs = dictionary.get("select_runs", None) + if select_runs is not None: + select_runs = re.compile(select_runs) return cls( name=name, results=dictionary["results"], @@ -129,6 +140,8 @@ def from_dict(cls, dictionary: dict, name: str) -> PlotConfig: additional_labels=dictionary.get("additional_labels", None), v_lines=dictionary.get("v_lines", None), recs=dictionary.get("recs", None), + subsets=dictionary.get("subsets", None), + select_runs=select_runs, ) def __post_init__(self): @@ -178,6 +191,7 @@ class PlotType(Enum): BINNED = "binned" RATIO = "ratio" CE = "ce" + DOSE_CONTRIBUTION = "dose contribution" # EXP = "exp points" # EXP_GROUP = "exp points group" # CE_EXP_GROUP = "exp points group CE" diff --git a/src/jade/post/atlas.py b/src/jade/post/atlas.py index ffd55e55..3ff4cc03 100644 --- a/src/jade/post/atlas.py +++ b/src/jade/post/atlas.py @@ -52,7 +52,7 @@ def insert_img(self, figure: Figure, width=Inches(7.5)): """ # Convert the figure to an in-memory binary stream img_stream = io.BytesIO() - figure.savefig(img_stream, format="png", dpi=200) + figure.savefig(img_stream, format="png", dpi=200, bbox_inches="tight") img_stream.seek(0) self.doc.add_picture(img_stream, width=width) diff --git a/src/jade/post/atlas_processor.py b/src/jade/post/atlas_processor.py index 2a509945..884c5524 100644 --- a/src/jade/post/atlas_processor.py +++ b/src/jade/post/atlas_processor.py @@ -65,10 +65,19 @@ def process(self) -> None: logging.info("Parsing reference data") raw_folder = Path(self.raw_root, codelib, self.cfg.benchmark) - df = ExcelProcessor._get_table_df(plot_cfg.results, raw_folder) + df = ExcelProcessor._get_table_df( + plot_cfg.results, raw_folder, subsets=plot_cfg.subsets + ) if plot_cfg.expand_runs: df = df.reset_index() for run in df["Case"].unique(): + # skip the cases that are not in select_runs + if ( + plot_cfg.select_runs + and plot_cfg.select_runs.search(run) is None + ): + continue + run_df = df[df["Case"] == run] if run in cases: cases[run].append((codelib_pretty, run_df)) diff --git a/src/jade/post/excel_processor.py b/src/jade/post/excel_processor.py index 0dc85739..6f111c6d 100644 --- a/src/jade/post/excel_processor.py +++ b/src/jade/post/excel_processor.py @@ -137,14 +137,11 @@ def _get_concat_df_results( df = pd.read_csv(Path(folder, file)) # check here if only a subset of the dataframe is needed if subset: - try: - df = ( - df.set_index(subset["column"]) - .loc[subset["values"]] - .reset_index() - ) - except KeyError: - pass # accept that in some tallies the column may not be present + for value, items in subset["values"].items(): + try: + df = df.set_index(value).loc[items].reset_index() + except KeyError: + pass # accept that in some tallies the column may not be present df["Case"] = run_name dfs.append(df) if len(dfs) == 0: diff --git a/src/jade/post/plotter.py b/src/jade/post/plotter.py index 963f42c6..aba4ee30 100644 --- a/src/jade/post/plotter.py +++ b/src/jade/post/plotter.py @@ -5,6 +5,7 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +from f4enix.input.libmanager import LibManager from matplotlib.axes import Axes from matplotlib.figure import Figure from matplotlib.lines import Line2D @@ -14,6 +15,7 @@ from jade.config.atlas_config import PlotConfig, PlotType +LM = LibManager() # Color-blind saver palette COLORS = [ "#377eb8", @@ -62,6 +64,8 @@ def plot(self) -> tuple[Figure, Axes | list[Axes]]: for ax in axes: self._add_labels(ax) + fig.tight_layout() # Adjust padding + return fig, axes @abstractmethod @@ -392,6 +396,16 @@ def _get_figure(self) -> tuple[Figure, list[Axes]]: elinewidth=0.5, color=COLORS[idx], ) + # add a label if needed (only one per group) + if subcases and idx == 0: + ax1.text( + x[-1], + y[-1], + subcases[1][i], + fontsize=8, + verticalalignment="center", + horizontalalignment="right", + ) # Error Plot if plot_error: @@ -555,6 +569,73 @@ def _get_figure(self) -> tuple[Figure, list[Axes]]: return fig, axes +class DoseContributionPlot(Plot): + def _get_figure(self) -> tuple[Figure, Axes]: + nrows = len(self.data) + gridspec_kw = {"hspace": 0.25} + fig, axes = plt.subplots( + nrows=nrows, ncols=1, gridspec_kw=gridspec_kw, sharex=True + ) + + indices = [] + for idx, (codelib, df) in enumerate(self.data): + df.set_index("User", inplace=True) + indices.extend(df.index.to_list()) + + # build a common index of isotopes so that the colors are the same for all + # subplots + isotopes = list(set(indices)) + isotopes.sort() + + for idx, isotope in enumerate(isotopes): + if int(isotope) == 0: + continue + for i, (codelib, df) in enumerate(self.data): + if i == 0: + _, label = LM.get_zaidname(str(abs(isotope))) + else: + label = None + + tot_dose = df.groupby("Time", sort=False).sum()["Value"] + try: + y = df.loc[isotope].set_index("Time")["Value"] / tot_dose * 100 + times = y.index + except KeyError: + continue # not all libs have the same pathways + + axes[i].plot( + times, + y, + color=COLORS[idx], + marker=MARKERS[idx], + label=label, + linewidth=0.5, + ) + + for i, ax in enumerate(axes): + # --- Plot details --- + # ax details + ax.set_title(codelib) + ax.set_ylabel("SDDR [%]") + ax.tick_params(which="major", width=1.00, length=5) + ax.tick_params(axis="y", which="minor", width=0.75, length=2.50) + # Grid + ax.grid("True", axis="y", which="major", linewidth=0.50) + ax.grid("True", axis="y", which="minor", linewidth=0.20) + + # Adjust legend to have multiple columns + handles, labels = axes[0].get_legend_handles_labels() + ncol = len(labels) // 20 + 1 + axes[0].legend(handles, labels, bbox_to_anchor=(1, 1), ncol=ncol) + # plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") + for label in axes[-1].get_xticklabels(): + label.set_rotation(45) + label.set_ha("right") + label.set_rotation_mode("anchor") + + return fig, axes + + class PlotFactory: @staticmethod def create_plot( @@ -566,6 +647,8 @@ def create_plot( return RatioPlot(plot_config, data) elif plot_config.plot_type == PlotType.CE: return CEPlot(plot_config, data) + elif plot_config.plot_type == PlotType.DOSE_CONTRIBUTION: + return DoseContributionPlot(plot_config, data) else: raise NotImplementedError( f"Plot type {plot_config.plot_type} not implemented" diff --git a/src/jade/resources/default_cfg/benchmarks_pp/atlas/Sphere.yaml b/src/jade/resources/default_cfg/benchmarks_pp/atlas/Sphere.yaml index 44f7d48e..39d1748a 100644 --- a/src/jade/resources/default_cfg/benchmarks_pp/atlas/Sphere.yaml +++ b/src/jade/resources/default_cfg/benchmarks_pp/atlas/Sphere.yaml @@ -4,7 +4,7 @@ Neutron Leakage flux: plot_type: binned title: Neutron leakage flux by unit lethargy x_label: Energy [MeV] - y_labels: '[n/cm^2/s/u]' + y_labels: '[n/cm^2/n_s/u]' x: Energy y: Value expand_runs: true @@ -18,7 +18,7 @@ Photon Leakage flux: plot_type: binned title: Photon leakage flux by unit energy x_label: Energy [MeV] - y_labels: '[n/cm^2/s/MeV]' + y_labels: '[p/cm^2/n_s/MeV]' x: Energy y: Value expand_runs: true diff --git a/src/jade/resources/default_cfg/benchmarks_pp/atlas/SphereSDDR.yaml b/src/jade/resources/default_cfg/benchmarks_pp/atlas/SphereSDDR.yaml new file mode 100644 index 00000000..79fe70e2 --- /dev/null +++ b/src/jade/resources/default_cfg/benchmarks_pp/atlas/SphereSDDR.yaml @@ -0,0 +1,151 @@ +Decay photon leakage flux (Isotopes): + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + Time: [0s] + User: [0] + select_runs: SphereSDDR_\d+_[A-Za-z]+-\d+_ +# +Decay photon leakage flux (Materials) cooling time 0s: + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + User: [0] + Time: [0s] + select_runs: SphereSDDR_M\d+ +# +Decay photon leakage flux (Materials) cooling time 2.7h: + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + User: [0] + Time: [2.7h] + select_runs: SphereSDDR_M\d+ +# +Decay photon leakage flux (Materials) cooling time 24h: + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + User: [0] + Time: [24h] + select_runs: SphereSDDR_M\d+ +# +Decay photon leakage flux (Materials) cooling time 11.6d: + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + User: [0] + Time: [11.6d] + select_runs: SphereSDDR_M\d+ +# +Decay photon leakage flux (Materials) cooling time 30d: + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + User: [0] + Time: [30d] + select_runs: SphereSDDR_M\d+ +# +Decay photon leakage flux (Materials) cooling time 10y: + results: + - Leakage photon flux 24 groups + plot_type: binned + title: Photon leakage flux by unit energy + x_label: Energy [MeV] + y_labels: '[p/cm^2/n_s/MeV]' + x: Energy + y: Value + expand_runs: true + plot_args: + show_error: true + show_CE: true + subsets: + - result: Leakage photon flux 24 groups + values: + User: [0] + Time: [10y] + select_runs: SphereSDDR_M\d+ +# +Parent isotope contribution to SDDR (Materials): + results: + - SDDR + plot_type: dose contribution + title: Parent dose to SDDR + x_label: Time after shutdown + y_labels: dummy + x: dummy # It is a very specific plot which knows which columns to use + y: dummy # It is a very specific plot which knows which columns to use + expand_runs: true + select_runs: SphereSDDR_M\d+ \ No newline at end of file diff --git a/src/jade/resources/default_cfg/benchmarks_pp/excel/SphereSDDR.yaml b/src/jade/resources/default_cfg/benchmarks_pp/excel/SphereSDDR.yaml index ee415788..1d7b5552 100644 --- a/src/jade/resources/default_cfg/benchmarks_pp/excel/SphereSDDR.yaml +++ b/src/jade/resources/default_cfg/benchmarks_pp/excel/SphereSDDR.yaml @@ -13,5 +13,5 @@ comparison %: conditional_formatting: {"red": 20, "orange": 10, "yellow": 5} subsets: - result: SDDR - column: User - values: [0] + values: + User: [0] diff --git a/tests/post/test_atlas_processor.py b/tests/post/test_atlas_processor.py index f34ce924..cb31941b 100644 --- a/tests/post/test_atlas_processor.py +++ b/tests/post/test_atlas_processor.py @@ -72,3 +72,14 @@ def test_TiaraBS(self, tmpdir): codelibs = [("exp", "exp"), ("mcnp", "FENDL 3.2c")] processor = AtlasProcessor(ROOT_RAW, tmpdir, cfg, codelibs, word_template_path) processor.process() + + def test_SphereSDDR(self, tmpdir): + with as_file( + files(default_cfg).joinpath("benchmarks_pp/atlas/SphereSDDR.yaml") + ) as file: + cfg = ConfigAtlasProcessor.from_yaml(file) + + word_template_path = files(resources).joinpath("atlas_template.docx") + codelibs = [("d1s", "lib 1"), ("d1s", "lib 2")] + processor = AtlasProcessor(ROOT_RAW, tmpdir, cfg, codelibs, word_template_path) + processor.process()