From 42b4464256ae46a6a4860c1675443edbe541ca93 Mon Sep 17 00:00:00 2001
From: DAVIDE LAGHI <davide.laghi01@gmail.com>
Date: Mon, 13 May 2024 14:46:46 +0200
Subject: [PATCH 1/4] fixed issue #287

---
 jade/output.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/jade/output.py b/jade/output.py
index 86a3fd7e..c9894406 100644
--- a/jade/output.py
+++ b/jade/output.py
@@ -329,8 +329,14 @@ def single_postprocess(self):
                     except KeyError:
                         # this means that the column is only one and we have
                         # two distinct DFs for values and errors
+                        # depending on pandas version, these may be series or
+                        # directly arrays     
                         values = vals_df["Value"]
                         error = err_df["Error"]
+                        if isinstance(values, pd.Series) or isinstance(values, pd.DataFrame):
+                            values = values.values
+                        if isinstance(error, pd.Series) or isinstance(error, pd.DataFrame):
+                            error = error.values
 
                     lib_name = self.session.conf.get_lib_name(self.lib)
                     lib = {"x": x, "y": values, "err": error, "ylabel": lib_name}

From 28787ba61e838e6deca7de8e54c5eb295d77fba1 Mon Sep 17 00:00:00 2001
From: DAVIDE LAGHI <davide.laghi01@gmail.com>
Date: Wed, 22 May 2024 19:02:14 +0200
Subject: [PATCH 2/4] fixes issue #289

---
 jade/expoutput.py | 181 ++++++++++++++++++++++------------------------
 1 file changed, 85 insertions(+), 96 deletions(-)

diff --git a/jade/expoutput.py b/jade/expoutput.py
index 653e5f59..072f1c10 100644
--- a/jade/expoutput.py
+++ b/jade/expoutput.py
@@ -168,16 +168,61 @@ def build_atlas(self):
         # Remove tmp images
         shutil.rmtree(tmp_path)
 
-    def _extract_outputs(self):
+    def _extract_single_output(
+        self, results_path: os.PathLike, folder: str, lib: str
+    ) -> tuple[pd.DataFrame, str]:
+        mfile, ofile = self._get_output_files(results_path)
+        # Parse output
+        output = MCNPoutput(mfile, ofile)
+
+        # need to extract the input in case of multi
+        if self.multiplerun:
+            pieces = folder.split("_")
+            input = pieces[-1]
+            if input not in self.inputs:
+                self.inputs.append(input)
+            self.outputs[input, lib] = output
+            # Get the meaningful results
+            self.results[input, lib] = self._processMCNPdata(output)
+        else:
+            # just treat it as a special case of multiple run
+            self.outputs[self.testname, lib] = output
+            # Get the meaningful results
+            self.results[self.testname, lib] = self._processMCNPdata(output)
+            input = self.testname
+
+        return output.tallydata, input
+
+    def _extract_outputs(self) -> None:
         """
-        Extract, organize and store the results coming from the MCNP runs
+        Extract, organize and store the results coming from the different codes
+        runs
+
         Returns
         -------
         None.
         """
-        outputs = {}
-        results = {}
-        inputs = []
+        self.outputs = {}
+        self.results = {}
+
+        # Each output object is processing only one code at the time at the moment
+        if self.mcnp:
+            code_tag = "mcnp"
+        if self.openmc:
+            print("Experimental comparison not implemented for OpenMC")
+            return
+        if self.serpent:
+            print("Experimental comparison not implemented for Serpent")
+            return
+        if self.d1s:
+            code_tag = "d1s"
+
+        # only multiple runs have multiple inputs
+        if self.multiplerun:
+            self.inputs = [self.testname]
+        else:
+            self.inputs = []
+
         # Iterate on the different libraries results except 'Exp'
         for lib, test_path in self.test_path.items():
             if lib != EXP_TAG:
@@ -185,72 +230,22 @@ def _extract_outputs(self):
                     # Results are organized by folder and lib
                     code_raw_data = {}
                     for folder in os.listdir(test_path):
-                        # FIX MCNP HARD CODED PATH HERE
-                        if self.mcnp:
-                            results_path = os.path.join(test_path, folder, "mcnp")
-                            pieces = folder.split("_")
-                            # Get zaid
-                            input = pieces[-1]
-                            mfile, ofile = self._get_output_files(results_path)
-                            # Parse output
-                            output = MCNPoutput(mfile, ofile)
-                            outputs[input, lib] = output
-                            code_raw_data[input, lib] = output.tallydata
-                            # self.raw_data[input, lib] = output.tallydata
-
-                            # Get the meaningful results
-                            results[input, lib] = self._processMCNPdata(output)
-                            if input not in inputs:
-                                inputs.append(input)
-                        if self.openmc:
-                            print(
-                                "Experimental comparison not implemented \
-                                for OpenMC"
-                            )
-                            break
-                        if self.serpent:
-                            print(
-                                "Experimental comparison not implemented \
-                                for Serpent"
-                            )
-                            break
-                        if self.d1s:
-                            results_path = os.path.join(test_path, folder, "d1s")
-                            pieces = folder.split("_")
-                            # Get zaid
-                            input = pieces[-1]
-                            mfile, ofile = self._get_output_files(results_path)
-                            # Parse output
-                            output = MCNPoutput(mfile, ofile)
-                            outputs[input, lib] = output
-                            code_raw_data[input, lib] = output.tallydata
-                            # self.raw_data[input, lib] = output.tallydata
-
-                            # Get the meaningful results
-                            results[input, lib] = self._processMCNPdata(output)
-                            if input not in inputs:
-                                inputs.append(input)
-                    if self.mcnp:
-                        self.raw_data["mcnp"].update(code_raw_data)
-                    if self.d1s:
-                        self.raw_data["d1s"].update(code_raw_data)
+                        results_path = os.path.join(test_path, folder, code_tag)
+                        tallydata, input = self._extract_single_output(
+                            results_path, folder, lib
+                        )
+                        code_raw_data[input, lib] = tallydata
+
                 # Results are organized just by lib
                 else:
-                    mfile, ofile = self._get_output_files(test_path)
-                    # Parse output
-                    output = MCNPoutput(mfile, ofile)
-                    outputs[self.testname, lib] = output
-                    # Adjourn raw Data
-                    self.raw_data[self.testname, lib] = output.tallydata
-                    # Get the meaningful results
-                    results[self.testname, lib] = self._processMCNPdata(output)
-
-        self.outputs = outputs
-        self.results = results
-        if inputs:
-            self.inputs = inputs
-        else:
-            self.inputs = [self.testname]
+                    results_path = os.path.join(test_path, code_tag)
+                    tallydata, input = self._extract_single_output(
+                        results_path, None, lib
+                    )
+                    code_raw_data = {(self.testname, lib): tallydata}
+
+                # Adjourn raw Data
+                self.raw_data[code_tag].update(code_raw_data)
 
     def _read_exp_results(self):
         """
@@ -483,9 +478,7 @@ def _pp_excel_comparison(self):
         Responsible for producing excel outputs
         """
         # Dump the global C/E table
-        ex_outpath = os.path.join(
-            self.excel_path, self.testname + "_CE_tables.xlsx"
-        )
+        ex_outpath = os.path.join(self.excel_path, self.testname + "_CE_tables.xlsx")
         # Create a Pandas Excel writer using XlsxWriter as the engine.
         with pd.ExcelWriter(ex_outpath, engine="xlsxwriter") as writer:
             # --- build and dump the C/E table ---
@@ -673,7 +666,7 @@ def _read_exp_file(self, filepath):
         filepath : str
             string containing the path to the experimental file to be read
             for comparison
-        
+
         """
         return pd.read_csv(filepath, sep=";")
 
@@ -684,7 +677,7 @@ def _build_atlas(self, tmp_path, atlas):
         """
         Fill the atlas with the customized plots. Creation and saving of the
         atlas are handled elsewhere.
-                
+
         Parameters
         ----------
         tmp_path : str
@@ -753,7 +746,7 @@ def _get_tally_info(self, tally):
             tallynum (int): Tally number of the tally being plotted
             particle (str): Type of quantity being plotted on the X axis
             quant + unit (str): Unit of quantity being plotted on the X axis
-            
+
         """
         tallynum = tally.tallyNumber
         particle = tally.particleList[np.where(tally.tallyParticles == 1)[0][0]]
@@ -786,7 +779,7 @@ def _define_title(self, input, quantity_CE):
 
     def _dump_ce_table(self):
         """
-        Generates the C/E table and dumps them as an .xlsx file 
+        Generates the C/E table and dumps them as an .xlsx file
         """
         final_table = pd.concat(self.tables)
         skipcol_global = 0
@@ -998,7 +991,7 @@ def _parse_data_df(self, data, output, x_axis, tallynum):
         x_axis : str
             X axis title
         tallynum : int
-            Tally number, used to determine behaviour for protons and 
+            Tally number, used to determine behaviour for protons and
             neutrons
 
         Returns
@@ -1172,7 +1165,7 @@ def _exp_comp_case_check(self, indexes):
 
     def _get_conv_df(self, df):
         """
-        Adds extra columns to the dataframe containing the maximum and 
+        Adds extra columns to the dataframe containing the maximum and
         average errors of the tallies
 
         Parameters
@@ -1184,9 +1177,9 @@ def _get_conv_df(self, df):
         Returns
         -------
         conv_df: Dataframe
-            Same as previous dataframe, but with two extra columns containing 
+            Same as previous dataframe, but with two extra columns containing
             maximum and average errors
-            
+
         """
         conv_df = pd.DataFrame()
         for library in df.index.unique(level="Library").tolist():
@@ -1203,7 +1196,7 @@ class TiaraFCOutput(TiaraOutput):
 
     def _pp_excel_comparison(self):
         """
-        Builds dataframe from computational output comparable to experimental 
+        Builds dataframe from computational output comparable to experimental
         data and generates the excel comparison
         """
 
@@ -1247,9 +1240,7 @@ def _pp_excel_comparison(self):
         self._exp_comp_case_check(indexes=indexes)
         self.case_tree_df.sort_values(indexes, axis=0, inplace=True)
         # Build ExcelWriter object
-        filepath = os.path.join(
-            self.excel_path, "Tiara_Fission_Cells_CE_tables.xlsx"
-        )
+        filepath = os.path.join(self.excel_path, "Tiara_Fission_Cells_CE_tables.xlsx")
         with pd.ExcelWriter(filepath, engine="xlsxwriter") as writer:
 
             # Create 1 worksheet for each energy/material combination
@@ -1391,7 +1382,7 @@ def _build_atlas(self, tmp_path, atlas):
         """
         Fill the atlas with the customized plots. Creation and saving of the
         atlas are handled elsewhere.
-                
+
         Parameters
         ----------
         tmp_path : str
@@ -1549,9 +1540,7 @@ def _pp_excel_comparison(self):
         indexes = ["Library", "Shield Material", "Energy", "Shield Thickness"]
         self._exp_comp_case_check(indexes=indexes)
         # Create ExcelWriter object
-        filepath = os.path.join(
-            self.excel_path, "Tiara_Bonner_Spheres_CE_tables.xlsx"
-        )
+        filepath = os.path.join(self.excel_path, "Tiara_Bonner_Spheres_CE_tables.xlsx")
         with pd.ExcelWriter(filepath, engine="xlsxwriter") as writer:
             # Loop over shield material/energy combinations
             mat_list = self.case_tree_df.index.unique(level="Shield Material").tolist()
@@ -1662,7 +1651,7 @@ def _build_atlas(self, tmp_path, atlas):
         """
         Fill the atlas with the customized plots. Creation and saving of the
         atlas are handled elsewhere.
-                
+
         Parameters
         ----------
         tmp_path : str
@@ -1822,7 +1811,7 @@ def _build_atlas(self, tmp_path, atlas):
         """
         Fill the atlas with the customized plots. Creation and saving of the
         atlas are handled elsewhere.
-                
+
         Parameters
         ----------
         tmp_path : str
@@ -1922,7 +1911,7 @@ def _build_atlas(self, tmp_path, atlas):
         """
         Fill the atlas with the customized plots. Creation and saving of the
         atlas are handled elsewhere.
-                
+
         Parameters
         ----------
         tmp_path : str
@@ -1944,14 +1933,14 @@ def _build_atlas(self, tmp_path, atlas):
         return atlas
 
     def _plot_tally_group(self, group, tmp_path, atlas):
-        """ 
-        Plots tallies for a given group of outputs and add to Atlas object 
+        """
+        Plots tallies for a given group of outputs and add to Atlas object
 
         Parameters
         ----------
         group : list
-            list of groups in the experimental benchmark object, outputs are 
-            grouped by material, several tallies for each material/group 
+            list of groups in the experimental benchmark object, outputs are
+            grouped by material, several tallies for each material/group
         tmp_path : str
             path to temporary atlas plot folder
         atlas : JADE Atlas
@@ -1960,7 +1949,7 @@ def _plot_tally_group(self, group, tmp_path, atlas):
         Returns
         -------
         atlas : JADE Atlas
-            adjusted Atlas object 
+            adjusted Atlas object
         """
         # Extract 'Tally' and 'Input' values for the current 'Group'
         group_data = self.groups.xs(group, level="Group", drop_level=False)

From 778638b40f5fd13215510f38a378968b060be9b8 Mon Sep 17 00:00:00 2001
From: DAVIDE LAGHI <davide.laghi01@gmail.com>
Date: Thu, 23 May 2024 09:03:27 +0200
Subject: [PATCH 3/4] fix multirun inputs

---
 jade/expoutput.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/jade/expoutput.py b/jade/expoutput.py
index 072f1c10..38dccfb7 100644
--- a/jade/expoutput.py
+++ b/jade/expoutput.py
@@ -219,9 +219,9 @@ def _extract_outputs(self) -> None:
 
         # only multiple runs have multiple inputs
         if self.multiplerun:
-            self.inputs = [self.testname]
-        else:
             self.inputs = []
+        else:
+            self.inputs = [self.testname]
 
         # Iterate on the different libraries results except 'Exp'
         for lib, test_path in self.test_path.items():

From 55989e80f69f22dc5b2c4289a98dc552652f4cae Mon Sep 17 00:00:00 2001
From: DAVIDE LAGHI <davide.laghi01@gmail.com>
Date: Thu, 23 May 2024 09:12:32 +0200
Subject: [PATCH 4/4] solve typing error

---
 jade/expoutput.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/jade/expoutput.py b/jade/expoutput.py
index 38dccfb7..59f69d4e 100644
--- a/jade/expoutput.py
+++ b/jade/expoutput.py
@@ -20,6 +20,7 @@
 # You should have received a copy of the GNU General Public License
 # along with JADE.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import annotations
 import math
 import os
 import re