diff --git a/src/Model/HydraulicStructures/Basic/HydraulicStructures.py b/src/Model/HydraulicStructures/Basic/HydraulicStructures.py new file mode 100644 index 0000000000000000000000000000000000000000..691cf60c96afbdc9747ec1e8f4baab05517e40e4 --- /dev/null +++ b/src/Model/HydraulicStructures/Basic/HydraulicStructures.py @@ -0,0 +1,186 @@ +# HydraulicStructures.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +import logging + +from tools import trace, timer + +from Model.Tools.PamhyrDB import SQLSubModel +from Model.Except import NotImplementedMethodeError + +from Model.HydraulicStructures.Basic.Value import ( + BHSValue +) + +logger = logging.getLogger() + +class BasicHS(SQLSubModel): + _sub_classes = [ + BHSValue, + ] + _id_cnt = 0 + + def __init__(self, id: int = -1, name: str = "", + status=None): + super(BasicHS, self).__init__() + + self._status = status + + if id == -1: + self.id = BasicHS._id_cnt + else: + self.id = id + + self._name = name + self._type = "" + self._enabled = True + self._data = [] + + BasicHS._id_cnt = max(BasicHS._id_cnt + 1, self.id) + + @classmethod + def _db_create(cls, execute): + execute(""" + CREATE TABLE hydraulic_structures_basic( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + type TEXT NOT NULL, + enabled BOOLEAN NOT NULL, + hs INTEGER, + FOREIGN KEY(hs) REFERENCES hydraulic_structures(id) + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version): + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 6: + cls._db_create(execute) + + return True + + @classmethod + def _get_ctor_from_type(cls, t): + from Model.HydraulicStructure.Basic.Types import ( + NotDefined, + ) + + res = NotDefined + return res + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + + table = execute( + "SELECT id, name, type, enabled, hs " + + "FROM hydraulic_structures " + ) + + for row in table: + bhs_id = row[0] + name = row[1] + type = row[2] + enabled = (row[3] == 1) + hs_id = row[4] + + ctor = cls._get_ctor_from_type(type) + bhs = ctor( + id=bhs_id, + name=name, + status=data['status'] + ) + + bhs.enabled = enabled + + data['bhs_id'] = bhs_id + bhs._data = BasicHSValue._db_load( + execute, data + ) + + new.append(bhs) + + return new + + def _db_save(self, execute, data=None): + execute( + "DELETE FROM hydraulic_structures_basic " + + f"WHERE id = {self.id}" + ) + hs_id = data['hs_id'] + + sql = ( + "INSERT INTO " + + "hydraulic_structures_basic(id, name, type, enabled, hs) " + + "VALUES (" + + f"{self.id}, " + + f"'{self._db_format(self._name)}', " + + f"'{self._db_format(self._type)}', " + + f"{self._db_format(self.enabled)}, " + + f"{hs_id} " + + ")" + ) + execute(sql) + + data['bhs_id'] = self.id + execute( + "DELETE FROM hydraulic_structures_basic_value "+ + f"WHERE bhs = {bhs_id}" + ) + + for values in self._data: + values._db_save(execute, data) + + return True + + def __len__(self): + return len(self._data) + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + self._status.modified() + + @property + def type(self): + return self._type + + @type.setter + def type(self, type): + self._type = type + self._status.modified() + + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, enabled): + self._enabled = enabled + self._status.modified() + + @property + def lst(self): + return self._data.copy() diff --git a/src/Model/HydraulicStructures/Basic/Types.py b/src/Model/HydraulicStructures/Basic/Types.py new file mode 100644 index 0000000000000000000000000000000000000000..d82998adf9d9df9147ef0c74114011a2074c2cbc --- /dev/null +++ b/src/Model/HydraulicStructures/Basic/Types.py @@ -0,0 +1,37 @@ +# Types.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +from Model.Except import NotImplementedMethodeError + +from Model.HydraulicStructures.Basic.HydraulicStructures import ( + BasicHS +) +from Model.HydraulicStructures.Basic.Value import ( + BHSValue +) + +class NotDefined(BasicHS): + def __init__(self, id: int = -1, name: str = "", status=None): + super(NotDefined, self).__init__(id=id, name=name, status=status) + + self._type = "ND" + self._data = [ + BHSValue("foo", float, 0.0), + BHSValue("bar", float, 42.0), + BHSValue("baz", int, 13), + ] diff --git a/src/Model/HydraulicStructures/Basic/Value.py b/src/Model/HydraulicStructures/Basic/Value.py new file mode 100644 index 0000000000000000000000000000000000000000..dccd41e545b5bfa3616cf0100a0a2aa44b4c8953 --- /dev/null +++ b/src/Model/HydraulicStructures/Basic/Value.py @@ -0,0 +1,144 @@ +# Value.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +from Model.Tools.PamhyrDB import SQLSubModel + +class BHSValue(SQLSubModel): + _sub_classes = [] + _id_cnt = 0 + + def __init__(self, name: str = "", type = float, value = 0.0, + status=None): + super(BHSValue, self).__init__() + + self._status = status + + self._name = name + self._type = type + self._value = type(value) + + @classmethod + def _db_create(cls, execute): + execute(""" + CREATE TABLE hydraulic_structures_basic_value( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + type TEXT NOT NULL, + value TEXT NOT NULL, + bhs INTEGER, + FOREIGN KEY(bhs) REFERENCES hydraulic_structures_basic(id) + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version): + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 6: + cls._db_create(execute) + + return True + + @classmethod + def _str_to_type(cls, type): + res = str + + if type == "float": + res = float + elif type == "int": + res = int + elif type == "bool": + res = bool + + return res + + @classmethod + def _type_to_str(cls, type): + res = "str" + + if type == float: + res = "float" + elif type == int: + res = "int" + elif type == bool: + res = "bool" + + return res + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + bhs_id = data["bhs_id"] + + table = execute( + "SELECT name, type, value " + + "FROM hydraulic_structures_basic_value " + + f"WHERE bhs = '{bhs_id}'" + ) + + for row in table: + name = row[0] + type = cls._str_to_type(row[1]) + value = row[2] + + val = cls( + name=name, + type=type, + value=value, + status=data['status'] + ) + + new.append(val) + + return new + + def _db_save(self, execute, data=None): + bhs_id = data["bhs_id"] + + sql = ( + "INSERT INTO " + + "hydraulic_structures_basic_value(name, type, value, bhs) " + + "VALUES (" + + f"'{self._db_format(self._name)}', " + + f"'{self._db_format(self._type_to_str(self._type))}', "+ + f"'{self._db_format(self._value)}', " + + f"{bhs_id}" + + ")" + ) + execute(sql) + + return True + + @property + def name(self): + return self._name + + @property + def type(self): + return self._type + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + self._value = self._type(value) + self._status.modified() diff --git a/src/Model/HydraulicStructures/HydraulicStructures.py b/src/Model/HydraulicStructures/HydraulicStructures.py new file mode 100644 index 0000000000000000000000000000000000000000..ca55e05c349ebea06abd4866ec21af6c7d0a8ea5 --- /dev/null +++ b/src/Model/HydraulicStructures/HydraulicStructures.py @@ -0,0 +1,261 @@ +# HydraulicStructures.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +import logging + +from tools import trace, timer, old_pamhyr_date_to_timestamp + +from Model.Tools.PamhyrDB import SQLSubModel +from Model.Except import NotImplementedMethodeError + +from Model.HydraulicStructures.Basic.HydraulicStructures import BasicHS + +logger = logging.getLogger() + +class HydraulicStructure(SQLSubModel): + _sub_classes = [ + BasicHS, + ] + _id_cnt = 0 + + def __init__(self, id: int = -1, name: str = "", + status=None): + super(HydraulicStructure, self).__init__() + + self._status = status + + if id == -1: + self.id = HydraulicStructure._id_cnt + else: + self.id = id + + self._name = name + self._input_kp = None + self._output_kp = None + self._input_reach = None + self._output_reach = None + self._enabled = True + self._data = [] + + HydraulicStructure._id_cnt = max(HydraulicStructure._id_cnt + 1, self.id) + + @classmethod + def _db_create(cls, execute): + execute(""" + CREATE TABLE hydraulic_structures( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + enabled BOOLEAN NOT NULL, + input_kp INTEGER, + output_kp INTEGER, + input_reach INTEGER, + output_reach INTEGER, + FOREIGN KEY(input_reach) REFERENCES river_reach(id), + FOREIGN KEY(output_reach) REFERENCES river_reach(id) + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version): + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 6: + cls._db_create(execute) + + return True + + @classmethod + def _db_load(cls, execute, data=None): + new = [] + + table = execute( + "SELECT id, name, enabled, " + + "input_kp, output_kp, " + + "input_reach, output_reach " + + "FROM hydraulic_structures " + ) + + for row in table: + it = iter(row) + + hs_id = next(it) + name = next(it) + enabled = (next(it) == 1) + input_kp = next(it) + output_kp = next(it) + input_reach_id = next(it) + output_reach_id = next(it) + + hs = cls( + id=hs_id, + name=name, + status=data['status'] + ) + + hs.enabled = enabled + hs.input_kp = input_kp + hs.output_kp = output_kp + + hs.input_reach, hs.output_reach = reduce( + lambda acc, n: ( + n if n.id == input_reach_id else acc[0], + n if n.id == output_reach_id else acc[1] + ), + data["reachs"], + [None, None] + ) + + data['hs_id'] = hs_id + hs._data = BasicHS._db_load(execute, data) + + new.append(hs) + + return new + + def _db_save(self, execute, data=None): + execute(f"DELETE FROM hydraulic_structures WHERE id = {self.id}") + + input_reach_id = -1 + if self._input_reach is not None: + input_reach_id = self._input_reach.id + + output_reach_id = -1 + if self._output_reach is not None: + output_reach_id = self._output_reach.id + + sql = ( + "INSERT INTO " + + "hydraulic_structures(" + + " id, name, enabled, input_kp, output_kp, " + + " input_reach, output_reach" + + ") " + + "VALUES (" + + f"{self.id}, '{self._db_format(self._name)}', " + + f"{self._db_format(self.enabled)}, " + + f"{self.input_kp}, {self.input_kp}, " + + f"{input_reach_id}, {output_reach_id}" + + ")" + ) + execute(sql) + + data['hs_id'] = self.id + for basic in self._data: + basic._db_save(execute, data) + + return True + + def __len__(self): + return len(self._data) + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + self._status.modified() + + @property + def input_kp(self): + return self._input_kp + + @input_kp.setter + def input_kp(self, input_kp): + self._input_kp = input_kp + self._status.modified() + + @property + def output_kp(self): + return self._output_kp + + @output_kp.setter + def output_kp(self, output_kp): + self._output_kp = output_kp + self._status.modified() + + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, enabled): + self._enabled = enabled + self._status.modified() + + @property + def input_reach(self): + return self._input_reach + + @input_reach.setter + def input_reach(self, input_reach): + self._input_reach = input_reach + self._status.modified() + + @property + def output_reach(self): + return self._output_reach + + @output_reach.setter + def output_reach(self, output_reach): + self._output_reach = output_reach + self._status.modified() + + @property + def basic_structures(self): + return self._data.copy() + + def add(self, index: int): + value = BasicHS(status=self._status) + self._data.insert(index, value) + self._status.modified() + return value + + def insert(self, index: int, value: BasicHS): + self._data.insert(index, value) + self._status.modified() + + def delete_i(self, indexes): + self._data = list( + map( + lambda e: e[1], + filter( + lambda e: e[0] not in indexes, + enumerate(self.data) + ) + ) + ) + self._status.modified() + + def delete(self, els): + self._data = list( + filter( + lambda e: e not in els, + self.data + ) + ) + self._status.modified() + + def sort(self, _reverse=False, key=None): + if key is None: + self._data.sort(reverse=_reverse) + else: + self._data.sort(reverse=_reverse, key=key) + self._status.modified() diff --git a/src/Model/HydraulicStructures/HydraulicStructuresList.py b/src/Model/HydraulicStructures/HydraulicStructuresList.py new file mode 100644 index 0000000000000000000000000000000000000000..0e1039fda7b4d96d28bd5b2388cdd97ee1c43f7c --- /dev/null +++ b/src/Model/HydraulicStructures/HydraulicStructuresList.py @@ -0,0 +1,77 @@ +# HydraulicStructuresList.py -- Pamhyr +# Copyright (C) 2023 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# -*- coding: utf-8 -*- + +from copy import copy +from tools import trace, timer + +from Model.Tools.PamhyrList import PamhyrModelList +from Model.HydraulicStructures.HydraulicStructures import HydraulicStructure + +class HydraulicStructureList(PamhyrModelList): + _sub_classes = [ + HydraulicStructure, + ] + + @classmethod + def _db_load(cls, execute, data=None): + new = cls(status=data['status']) + + if data is None: + data = {} + + new._lst = HydraulicStructure._db_load( + execute, data + ) + + return new + + def _db_save(self, execute, data=None): + execute("DELETE FROM hydraulic_structures") + + if data is None: + data = {} + + for hs in self._lst: + hs._db_save(execute, data=data) + + return True + + def new(self, lst, index): + n = HydraulicStructure(status=self._status) + self._lst.insert(index, n) + self._status.modified() + return n + + def __copy__(self): + new = HydraulicStructureList() + + for lst in self._tabs: + new.tabs[lst] = self._tabs[lst].copy() + + return new + + def __deepcopy__(self): + new = HydraulicStructureList() + + for lst in self._tabs: + new.tabs[lst] = self._tabs[lst].deepcopy() + + return new + + def copy(self): + return copy(self) diff --git a/src/Model/River.py b/src/Model/River.py index 84836625f83f2d7e69031a744d07b3a6aa5803e6..a4dd8fa54318371139a4d8f9cef965cd819902f9 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -37,6 +37,9 @@ from Model.Friction.FrictionList import FrictionList from Model.SolverParameters.SolverParametersList import SolverParametersList from Model.SedimentLayer.SedimentLayerList import SedimentLayerList from Model.Reservoir.ReservoirList import ReservoirList +from Model.HydraulicStructures.HydraulicStructuresList import ( + HydraulicStructureList, +) from Solver.Solvers import solver_type_list @@ -219,6 +222,7 @@ class River(Graph, SQLSubModel): SolverParametersList, SedimentLayerList, ReservoirList, + HydraulicStructureList, ] def __init__(self, status=None): @@ -237,6 +241,7 @@ class River(Graph, SQLSubModel): self._parameters = {} self._sediment_layers = SedimentLayerList(status=self._status) self._reservoir = ReservoirList(status=self._status) + self._hydraulic_structures = HydraulicStructureList(status=self._status) @classmethod def _db_create(cls, execute): @@ -303,6 +308,12 @@ class River(Graph, SQLSubModel): data ) + # Hydraulic Structures + new._hydraulic_structures = HydraulicStructureList._db_load( + execute, + data + ) + # Parameters new._parameters = SolverParametersList._db_load( execute, @@ -319,6 +330,7 @@ class River(Graph, SQLSubModel): objs.append(self._sediment_layers) objs.append(self._stricklers) objs.append(self._reservoir) + objs.append(self._hydraulic_structures) for solver in self._parameters: objs.append(self._parameters[solver]) @@ -363,6 +375,10 @@ class River(Graph, SQLSubModel): def reservoir(self): return self._reservoir + @property + def hydraulics_structures(self): + return self._hydraulics_structures + @property def parameters(self): return self._parameters diff --git a/src/Model/Study.py b/src/Model/Study.py index b163088b8d93312297d38c7ae469f800233269df..d29910bba1564282de30dc8d2eeb1a086aa18e37 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -41,7 +41,7 @@ class Study(SQLModel): def __init__(self, filename=None, init_new=True): # Metadata - self._version = "0.0.5" + self._version = "0.0.6" self.creation_date = datetime.now() self.last_modification_date = datetime.now() self.last_save_date = datetime.now()