From 51b3987a4a43d1d5b7248f515a81e06cfd6dc2c5 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 21 Aug 2026 15:27:35 +0200 Subject: [PATCH] Mage: Ensemble: Add bin reading method. --- src/Solver/Mage.py | 139 +++++++++++++++++++++++++++++++++++ src/View/RunSolver/Window.py | 12 ++- 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py index b31c48c6..d2fd7a08 100644 --- a/src/Solver/Mage.py +++ b/src/Solver/Mage.py @@ -1513,6 +1513,145 @@ class Mage8(Mage): return results + @timer + def read_ensemble_bin(self, study, fname, tables, run_number, qlog=None): + logger.info(f"read_bin: Start reading '{fname}' ...") + start = time.time() + + with mage_file_open(fname, "r") as f: + def newline(): return np.fromfile(f, dtype=np.int32, count=1) + def endline(): return np.fromfile(f, dtype=np.int32, count=1) + + def read_int(size): return np.fromfile( + f, dtype=np.int32, count=size) + + def read_float(size): return np.fromfile( + f, dtype=np.float32, count=size) + + def read_float64(size): return np.fromfile( + f, dtype=np.float64, count=size) + + # Meta data (1st line) + newline() + data = read_int(3) + + nb_reach = data[0] + nb_profile = data[1] + mage_version = data[2] + + if mage_version <= 80: + msg = ( + "Read BIN files: " + + f"Possible incompatible mage version '{mage_version}', " + + "please check your solver configuration..." + ) + logger.warning(msg) + + if qlog is not None: + qlog.put("[WARNING] " + msg) + + endline() + + # Reach information (2nd line) + newline() + _ = read_int(2*nb_reach) + endline() + + # X (3rd line) + newline() + _ = read_float(nb_profile) + endline() + + # Z and Y (4th line) + newline() + _ = read_float(3*nb_profile) + endline() + + # Data + newline() + + tmp_table = {} + end = False + while not end: + n = read_int(1)[0] + timestamp = read_float64(1)[0] + key = bytearray( + np.fromfile( + f, dtype=np.byte, count=1 + ) + ).decode() + data = read_float(n) + + if key not in tmp_table: + tmp_table[key] = [] + + tmp_table[key].append(data) + + endline() + end = newline().size <= 0 + + table = {} + for k in tmp_table: + table[k] = np.array(tmp_table[k]) + + + self.read_ensemble_bin_agregate_data( + table, tables, run_number + ) + + logger.info(f"reading time: '{time.time() - start}'") + + def read_ensemble_bin_agregate_data(self, tables, res, run_number): + z = tables["Z"] + q = tables["Q"] + + if "z_mean" not in res: + res["z_mean"] = z + res["q_mean"] = q + + res["z_min"] = z + res["z_max"] = z + + res["q_min"] = q + res["q_max"] = q + + # FIXME: Keep all results ? + res["traces"] = [(run_number, tables)] + else: + # Get mean results for Z and Q + res["z_mean"] = (res["z_mean"] + z) / 2 + res["q_mean"] = (res["q_mean"] + q) / 2 + + # Z and Q min max values + res["z_min"] = np.minimum(z, res["z_min"]) + res["z_max"] = np.maximum(z, res["z_max"]) + res["q_min"] = np.minimum(q, res["q_min"]) + res["q_max"] = np.maximum(q, res["q_max"]) + + # FIXME: Keep all results ? + res["traces"].append((run_number, tables)) + + @timer + def results_ensemble(self, study, repertory, + tables, run_number, + qlog=None, name=None): + self._study = study + if name is None: + name = study.name.replace(" ", "_") + + fname = os.path.join(repertory, f"{name}.BIN".replace(" ", "_")) + if not os.path.isfile(fname): + logger.info(f"Result file {name}.BIN does not exist") + return None + try: + self.read_ensemble_bin(study, fname, tables, run_number, qlog) + except Exception as e: + logger.error(f"Failed to read results") + logger_exception(e) + return None + + return True + class MageFake7(Mage8): _type = "mage_fake7" diff --git a/src/View/RunSolver/Window.py b/src/View/RunSolver/Window.py index 6488aed1..ac9345cd 100644 --- a/src/View/RunSolver/Window.py +++ b/src/View/RunSolver/Window.py @@ -474,6 +474,8 @@ class SolverLogEnsWindow(SolverLogWindow): self._samples[ens] = ens.get_sample(self._run_number) + self._results_tables = {} + super(SolverLogEnsWindow, self).__init__( solver=solver, study=study, @@ -503,7 +505,8 @@ class SolverLogEnsWindow(SolverLogWindow): ) self.statusbar.showMessage( - "Done" if int_code == 0 else "Failed", + ("Done" if int_code == 0 else "Failed") + + f"#{self._last_run}", 3000 ) @@ -515,6 +518,13 @@ class SolverLogEnsWindow(SolverLogWindow): def next(self): self._process = self.new_process(self._parent) + self._solver.results_ensemble( + self._study, self._workdir, + self._results_tables, + self._last_run, + qlog=self._output, + ) + self._log(f" *** Run #{self._last_run}", color="blue") self._solver.export_ensemble_variation( self._study, self._workdir, qlog=self._output,