From d78310476ec44d52b252008208772c4899102b3d Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Tue, 8 Aug 2023 13:48:01 +0200
Subject: [PATCH] Results: Add results model, view scheme and mage results
 reading.

---
 src/Model/Saved.py           |   2 +-
 src/Solver/ASolver.py        |  14 ++++
 src/Solver/Mage.py           | 139 +++++++++++++++++++++++++++++++++++
 src/View/RunSolver/Window.py |  15 +++-
 src/View/ui/SolverLog.ui     |   6 ++
 5 files changed, 174 insertions(+), 2 deletions(-)

diff --git a/src/Model/Saved.py b/src/Model/Saved.py
index a1b91fd5..60013cde 100644
--- a/src/Model/Saved.py
+++ b/src/Model/Saved.py
@@ -33,5 +33,5 @@ class SavedStatus(object):
         self._saved = True
 
     def modified(self):
-        logger.debug("model status set as modified")
+        # logger.debug("model status set as modified")
         self._saved = False
diff --git a/src/Solver/ASolver.py b/src/Solver/ASolver.py
index 300bb36c..64cc01cb 100644
--- a/src/Solver/ASolver.py
+++ b/src/Solver/ASolver.py
@@ -19,6 +19,8 @@
 import os
 import logging
 
+from tools import timer
+
 try:
     from signal import SIGTERM, SIGSTOP, SIGCONT
     _signal = True
@@ -29,6 +31,9 @@ from enum import Enum
 
 from Model.Except import NotImplementedMethodeError
 
+from Model.Results.Results import Results
+from Model.Results.River.River import River, Reach, Profile
+
 logger = logging.getLogger()
 
 class STATUS(Enum):
@@ -164,6 +169,15 @@ class AbstractSolver(object):
         """
         raise NotImplementedMethodeError(self, self.log_file)
 
+    ###########
+    # RESULTS #
+    ###########
+
+    @timer
+    def results(self, study, repertory, qlog = None):
+        results = Results(study = study)
+        return results
+
     #######
     # Run #
     #######
diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py
index 0d43d2ef..907f5ff3 100644
--- a/src/Solver/Mage.py
+++ b/src/Solver/Mage.py
@@ -17,12 +17,19 @@
 # -*- coding: utf-8 -*-
 
 import os
+import logging
+import numpy as np
 
 from tools import timer
 
 from Solver.ASolver import AbstractSolver
 from Checker.Mage import MageNetworkGraphChecker
 
+from Model.Results.Results import Results
+from Model.Results.River.River import River, Reach, Profile
+
+logger = logging.getLogger()
+
 def mage_file_open(filepath, mode):
     f = open(filepath, mode)
 
@@ -337,6 +344,21 @@ class Mage(AbstractSolver):
 
         return True
 
+    ###########
+    # RESULTS #
+    ###########
+
+    def read_bin(self, study, repertory, results, qlog = None):
+        return
+
+    @timer
+    def results(self, study, repertory, qlog = None):
+        results = Results(study = study)
+
+        self.read_bin(study, repertory, results, qlog)
+
+        return results
+
 ##########
 # MAGE 7 #
 ##########
@@ -450,3 +472,120 @@ class Mage8(Mage):
         self._export_REP(study, repertory, files, qlog)
 
         return True
+
+    ###########
+    # RESULTS #
+    ###########
+
+    def read_bin(self, study, repertory, results, qlog = None):
+        with mage_file_open(os.path.join(repertory, f"0.BIN"), "r") as f:
+            newline = lambda: np.fromfile(f, dtype=np.int32, count=1)
+            endline = lambda: np.fromfile(f, dtype=np.int32, count=1)
+
+            read_int = lambda size: np.fromfile(f, dtype=np.int32, count=size)
+            read_float = lambda size: np.fromfile(f, dtype=np.float32, count=size)
+            read_float64 = lambda size: 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]
+
+            logger.debug(f"read_bin: nb_reach = {nb_reach}")
+            logger.debug(f"read_bin: nb_profile = {nb_profile}")
+            logger.debug(f"read_bin: mage_version = {mage_version}")
+
+            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)
+
+            results.set("solver_version", f"Mage8 ({mage_version})")
+            results.set("nb_reach", f"{nb_reach}")
+            results.set("nb_profile", f"{nb_profile}")
+
+            endline()
+
+            # Reach information (2nd line)
+            newline()
+
+            reachs = []
+            iprofiles = {}
+            reach_offset = {}
+
+            ip_to_r = lambda i: iprofiles[
+                next(
+                    filter(
+                        lambda k: k[0] <= i <= k[1],
+                        iprofiles
+                    )
+                )
+            ]
+            ip_to_ri = lambda r, i: i - reach_offset[r]
+
+            data = read_int(2*nb_reach)
+
+            for i in range(nb_reach):
+                # Add results reach to reach list
+                r = results.river.add(i)
+                reachs.append(r)
+
+                # ID of first and last reach profiles
+                i1 = data[2*i] - 1
+                i2 = data[2*i+1] - 1
+
+                # Add profile id correspondance to reach
+                key = (i1, i2)
+                iprofiles[key] = r
+
+                # Profile ID offset
+                reach_offset[r] = i1
+
+            logger.debug(f"read_bin: iprofiles = {iprofiles}")
+
+            endline()
+
+            # X (3rd line)
+            newline()
+            _ = read_float(nb_profile)
+            endline()
+
+            # Z and Y (4th line)
+            newline()
+            _ = read_float(3*nb_profile)
+            endline()
+
+            # Data
+            newline()
+
+            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)
+
+                logger.debug(f"read_bin: timestamp = {timestamp} sec")
+                for i, d in enumerate(data):
+                    # Get reach corresponding to profile ID
+                    reach = ip_to_r(i)
+                    # Get profile id in reach
+                    ri = ip_to_ri(reach, i)
+
+                    # Set data for profile RI
+                    reach.set(ri, timestamp, key, d)
+
+                endline()
+                end = newline().size <= 0
+
+            logger.info(reachs[0].profiles[0]._data)
diff --git a/src/View/RunSolver/Window.py b/src/View/RunSolver/Window.py
index 4f915950..b03a4603 100644
--- a/src/View/RunSolver/Window.py
+++ b/src/View/RunSolver/Window.py
@@ -16,8 +16,9 @@
 
 # -*- coding: utf-8 -*-
 
-import tempfile
 import os
+import logging
+import tempfile
 
 from queue import Queue
 from tools import trace, timer
@@ -53,6 +54,8 @@ except:
 
 _translate = QCoreApplication.translate
 
+logger = logging.getLogger()
+
 class SelectSolverWindow(ASubWindow, ListedSubWindow):
     def __init__(self, title="Select solver",
                  study=None, config=None,
@@ -110,6 +113,8 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow):
         self._config = config
         self._solver = solver
 
+        self._results = None
+
         super(SolverLogWindow, self).__init__(
             name=self._title, ui="SolverLog", parent=parent
         )
@@ -169,6 +174,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow):
         self.find(QAction, "action_pause").triggered.connect(self.pause)
         self.find(QAction, "action_stop").triggered.connect(self.stop)
         self.find(QAction, "action_log_file").triggered.connect(self.log_file)
+        self.find(QAction, "action_results").triggered.connect(self.results)
 
         self._alarm.timeout.connect(self.update)
 
@@ -193,6 +199,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow):
             self.find(QAction, "action_start").setEnabled(True)
             self.find(QAction, "action_pause").setEnabled(False)
             self.find(QAction, "action_stop").setEnabled(False)
+            self.find(QAction, "action_results").setEnabled(True)
             if self._solver.log_file() != "":
                 self.find(QAction, "action_log_file").setEnabled(True)
 
@@ -221,6 +228,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow):
             self.find(QAction, "action_pause").setEnabled(False)
         self.find(QAction, "action_stop").setEnabled(True)
         self.find(QAction, "action_log_file").setEnabled(False)
+        self.find(QAction, "action_results").setEnabled(False)
 
     def pause(self):
         self._log(" *** Pause", color="blue")
@@ -229,6 +237,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow):
         self.find(QAction, "action_start").setEnabled(True)
         self.find(QAction, "action_pause").setEnabled(False)
         self.find(QAction, "action_stop").setEnabled(True)
+        self.find(QAction, "action_results").setEnabled(False)
 
     def stop(self):
         self._log(" *** Stop", color="blue")
@@ -237,9 +246,13 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow):
         self.find(QAction, "action_start").setEnabled(True)
         self.find(QAction, "action_pause").setEnabled(False)
         self.find(QAction, "action_stop").setEnabled(False)
+        self.find(QAction, "action_results").setEnabled(True)
         if self._solver.log_file() != "":
             self.find(QAction, "action_log_file").setEnabled(True)
 
+    def results(self):
+        self._results = self._solver.results(self._study, self._workdir, qlog = self._output)
+
     def log_file(self):
         file_name = os.path.join(self._workdir, self._solver.log_file())
         log = SolverLogFileWindow(
diff --git a/src/View/ui/SolverLog.ui b/src/View/ui/SolverLog.ui
index bbbc3edb..c94c57db 100644
--- a/src/View/ui/SolverLog.ui
+++ b/src/View/ui/SolverLog.ui
@@ -63,6 +63,7 @@
    <addaction name="action_pause"/>
    <addaction name="action_stop"/>
    <addaction name="action_log_file"/>
+   <addaction name="action_results"/>
   </widget>
   <action name="action_stop">
    <property name="icon">
@@ -100,6 +101,11 @@
     <string>LogFile</string>
    </property>
   </action>
+  <action name="action_results">
+   <property name="text">
+    <string>results</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections/>
-- 
GitLab