diff --git a/src/Model/HydraulicStructures/Basic/HydraulicStructures.py b/src/Model/HydraulicStructures/Basic/HydraulicStructures.py
new file mode 100644
index 00000000..8a779a1c
--- /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 .
+
+# -*- 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._enable = 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,
+ enable 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, enable, hs " +
+ "FROM hydraulic_structures "
+ )
+
+ for row in table:
+ bhs_id = row[0]
+ name = row[1]
+ type = row[2]
+ enable = (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.enable = enable
+
+ 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, enable, hs) " +
+ "VALUES (" +
+ f"{self.id}, " +
+ f"'{self._db_format(self._name)}', " +
+ f"'{self._db_format(self._type)}', " +
+ f"{self._db_format(self.enable)}, " +
+ 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 enable(self):
+ return self._enable
+
+ @enable.setter
+ def enable(self, enable):
+ self._enable = enable
+ 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 00000000..d82998ad
--- /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 .
+
+# -*- 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 00000000..dccd41e5
--- /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 .
+
+# -*- 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 00000000..6d0fa55c
--- /dev/null
+++ b/src/Model/HydraulicStructures/HydraulicStructures.py
@@ -0,0 +1,270 @@
+# 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 .
+
+# -*- 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._enable = 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,
+ enable 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, enable, " +
+ "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)
+ enable = (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.enable = enable
+ hs.input_kp = input_kp
+ hs.output_kp = output_kp
+
+ hs.input_reach = None
+ if input_reach_id != -1:
+ hs.input_reach = next(
+ filter(
+ lambda n: n.id == input_reach_id,
+ data["reachs"]
+ )
+ )
+
+ hs.output_reach = None
+ if output_reach_id != -1:
+ hs.output_reach = next(
+ filter(
+ lambda n: n.id == output_reach_id,
+ data["reachs"]
+ )
+ )
+
+ 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:
+ reach_id = self._input_reach.id
+
+ output_reach_id = -1
+ if self._output_reach is not None:
+ reach_id = self._output_reach.id
+
+ sql = (
+ "INSERT INTO " +
+ "hydraulic_structures(" +
+ " id, name, enable, input_kp, output_kp, " +
+ " input_reach, output_reach" +
+ ") " +
+ "VALUES (" +
+ f"{self.id}, '{self._db_format(self._name)}', " +
+ f"{self._db_format(self.enable)}, " +
+ 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 enable(self):
+ return self._enable
+
+ @enable.setter
+ def enable(self, enable):
+ self._enable = enable
+ 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()
+ 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 00000000..22c1ed01
--- /dev/null
+++ b/src/Model/HydraulicStructures/HydraulicStructuresList.py
@@ -0,0 +1,79 @@
+# 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 .
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.Except import NotImplementedMethodeError
+
+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 = NotDefined(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 84836625..a4dd8fa5 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 b163088b..d29910bb 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()