From 3a3c4d9d73ab77fd367140dda9e4d6339ffe6b21 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 22 Sep 2025 17:20:31 +0200 Subject: [PATCH] Results: Prepare result save. --- src/Model/Results/Results.py | 127 ++++++++++++++++++++++- src/Model/Results/River/River.py | 168 ++++++++++++++++++++++++++++++- 2 files changed, 288 insertions(+), 7 deletions(-) diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py index f44c9c20..dd04dfb6 100644 --- a/src/Model/Results/Results.py +++ b/src/Model/Results/Results.py @@ -14,7 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import struct import logging +import itertools import numpy as np from copy import deepcopy @@ -28,12 +30,10 @@ logger = logging.getLogger() class Results(SQLSubModel): - _SQL_TABLE = "solver_results" - - def __init__(self, study=None, solver=None, + def __init__(self, id=-1, study=None, solver=None, repertory="", name="0"): super(Results, self).__init__( - status=study.status, + id=id, status=study.status, owner_scenario=study.status.scenario.id ) @@ -78,3 +78,122 @@ class Results(SQLSubModel): self._repertory, qlog=None, ) + + def timestamps_to_struct(self): + ts = self._meta_data["timestamps"] + sf = ">" + ''.join(itertools.repeat("d", len(ts))) + + return struct.pack(sf, ts) + + @classmethod + def _db_create(cls, execute, ext=""): + execute(f""" + CREATE TABLE results{ext} ( + {cls.create_db_add_pamhyr_id()}, + solver TEXT NOT NULL, + study_revision INTEGER NOT NULL, + creation_data DATE NOT NULL, + nb_timestamps INTEGER NOT NULL, + timestamps BLOB NOT NULL, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + PRIMARY KEY(pamhyr_id, scenario) + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + major, minor, release = version.strip().split(".") + + if major == "0" and int(minor) <= 2: + cls._db_create(execute) + + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + + study = data['study'] + status = data['status'] + scenario = data["scenario"] + loaded = data['loaded_pid'] + + values = execute( + "SELECT pamhyr_id, solver_name, solver_type, " + + "study_revision, creation_data, nb_timestamps, timestamps, " + + "scenario " + + "FROM results " + + f"WHERE scenario = {scenario.id} " + + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " + + "ORDER BY ind ASC" + ) + + for v in values: + it = iter(v) + + pid = next(it) + solver = next(it) + revision = next(it) + creation_date = next(it) + nb_timestamps = next(it) + timestamps_bytes = next(it) + owner_scenario = next(it) + + new_results = cls( + id=pid, status=status, + owner_scenario=owner_scenario + ) + new_results.set("solver_name", solver_name) + new_results.set("solver_type", solver_type) + new_results.set("study_revision", revision) + new_results.set("creation_date", creation_date) + + sf = ">" + ''.join(itertools.repeat("d", len(nb_timestamps))) + ts = struct.unpack(sf, timestamp_bytes) + new_results.set("timestamps", ts) + + new_results._river = River._db_load(execute, data) + + loaded.add(pid) + new.append(new_results) + + return new + + def _db_save(self, execute, data=None): + execute( + "DELETED FROM results " + + f"WHERE scenario = {self._owner_scenario}" + ) + execute( + "DELETED FROM results_data " + + f"WHERE scenario = {self._owner_scenario}" + ) + + pid = self._pamhyr_id + if self._solver is None: + solver_name = self.get("solver_name") + solver_type = self.get("solver_type") + else: + solver_name = self._solver._name + solver_type = self._solver._type + + ts = self.get("timestamps") + sf = ">" + ''.join(itertools.repeat("d", len(ts))) + + execute( + "INSERT INTO " + + "results (pamhyr_id, solver_name, solver_type, " + + "study_revision, creation_data, nb_timestamps, timestamps, " + + "scenario) VALUES (?, ?, ?, ?, ?, ?, ?)", + self._pamhyr_id, solver_name, solver_type, + self._owner_scenario.revision, self.get("creation_data"), + len(ts), struct.pack(sf, ts), self._owner_scenario + ) + + data["result"] = self._pamhyr_id + self._river._db_save(execute, data) + + return True diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py index d068ec1b..7f89a7af 100644 --- a/src/Model/Results/River/River.py +++ b/src/Model/Results/River/River.py @@ -14,15 +14,25 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import struct import logging +import itertools +from functools import reduce from datetime import datetime +from Model.Tools.PamhyrDB import SQLSubModel + logger = logging.getLogger() -class Profile(object): +class Profile(SQLSubModel): def __init__(self, profile, study): + super(Profile, self).__init__( + id=-1, status=study.status, + owner_scenario=study.status.scenario.id + ) + self._study = study self._profile = profile # Source profile in the study self._data = {} # Dict of dict {: {: , ...}, ...} @@ -65,9 +75,122 @@ class Profile(object): def has_sediment(self): return any(map(lambda ts: "sl" in self._data[ts], self._data)) + @classmethod + def _db_create(cls, execute, ext=""): + execute(f""" + CREATE TABLE results_data{ext}( + {cls.create_db_add_pamhyr_id()}, + result INTEGER NOT NULL, + key TEXT NOT NULL, + reach INTEGER NOT NULL, + section INTEGER NOT NULL, + len_data INTEGER NOT NULL, + data BLOB NOT NULL, + {Scenario.create_db_add_scenario()}, + {Scenario.create_db_add_scenario_fk()}, + FOREIGN KEY(result) REFERENCES results(pamhyr_id), + FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id), + FOREIGN KEY(section) + REFERENCES geometry_profileXYZ(pamhyr_id), + PRIMARY KEY(pamhyr_id, result, key, scenario) + ) + """) -class Reach(object): + if ext == "_tmp": + return True + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + major, minor, release = version.strip().split(".") + + if major == "0" and int(minor) <= 2: + cls._db_create(execute) + + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + + study = data['study'] + status = data['status'] + scenario = data["scenario"] + loaded = data['loaded_pid'] + timestamps = data['timestamps'] + + values = execute( + "SELECT pamhyr_id, result, key, " + + "reach, section, len_data, data, scenario " + + "FROM results_data " + + f"WHERE scenario = {scenario.id} " + + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})" + ) + + for v in values: + it = iter(v) + + pid = next(it) + result = next(it) + key = next(it) + reach = next(it) + section = next(it) + len_data = next(it) + data = next(it) + owner_scenario = next(it) + + new_data = cls( + id=pid, status=status, + owner_scenario=owner_scenario + ) + + sf = ">" + ''.join(itertools.repeat("f", len_data)) + values = struct.unpack(sf, data) + + for timestamp, value in zip(timestamps, values): + new_data.set(timestamp, key, value) + + loaded.add(pid) + new.append(new_data) + + return new + + def get_keys(self): + return reduce( + lambda acc, ts: acc.union(d[ts].keys()) + ) + + def _db_save(self, execute, data=None): + pid = self._pamhyr_id + result = data["result"] + + for key in self.get_keys(): + data = self.get_key(key) + + sf = ">" + ''.join(itertools.repeat("f", len(data))) + data_bytes = struct.pack(sf, data) + + execute( + "INSERT INTO " + + "results_data (pamhyr_id, result, , " + + "study_revision, key, len_data, data, " + + "scenario) VALUES (?, ?, ?, ?, ?, ?, ?)", + pid, result, self._owner_scenario.revision, + key, len(data), data_bytes, + self._owner_scenario + ) + + return True + + +class Reach(SQLSubModel): def __init__(self, reach, study): + super(Reach, self).__init__( + id=-1, status=study.status, + owner_scenario=study.status.scenario.id + ) + self._study = study self._reach = reach # Source reach in the study self._profiles = list( @@ -101,9 +224,31 @@ class Reach(object): def has_sediment(self): return any(map(lambda profile: profile.has_sediment(), self._profiles)) + @classmethod + def _db_create(cls, execute, ext=""): + return cls._create_submodel(execute) -class River(object): + @classmethod + def _db_update(cls, execute, version, data=None): + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + return cls._db_load(execute, data) + + def _db_save(self, execute, data=None): + for profile in self._profiles: + data["profile"] = profile.geometry.pamhyr_id + profile._db_save(execute, data) + + +class River(SQLSubModel): def __init__(self, study): + super(River, self).__init__( + id=-1, status=study.status, + owner_scenario=study.status.scenario.id + ) + self._study = study # Dict with timestamps as key @@ -137,3 +282,20 @@ class River(object): self._reachs ) ) + + @classmethod + def _db_create(cls, execute, ext=""): + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + return cls._db_load(execute, data) + + def _db_save(self, execute, data=None): + for reach in self._reachs: + data["reach"] = reach.geometry.pamhyr_id + reach._db_save(execute, data)