Compare commits

..
Author SHA1 Message Date
Theophile Terraz 18a3d51d75 add adists with rubar3 + rubar3 in release 2026-02-23 10:46:05 +01:00
Theophile Terraz 118a39bd5c debug Rubar3 (from scenario branch) 2026-02-09 14:24:09 +01:00
Theophile Terraz 2009e8223b debug adists view 2026-01-30 11:32:06 +01:00
Theophile Terraz bdaa38ea5c Merge branch 'master' of gitlab.com:pamhyr/pamhyr2 2026-01-13 08:58:46 +01:00
Lionel Pénard 713732b1dd Fix doc links in README 2026-01-12 11:03:56 +01:00
Theophile Terraz f053539cdd debug 2025-12-19 11:29:02 +01:00
Theophile Terraz fc6916bb3e lot of debug 2025-12-12 14:18:09 +01:00
Theophile Terraz 97c26cb4fb debug HS 2025-12-12 11:33:13 +01:00
Theophile Terraz 10c98bb96e release for Jerome 2025-12-09 14:25:38 +01:00
Theophile Terraz 7c2d9891d0 debug IC 2025-12-05 16:51:39 +01:00
Theophile Terraz 3c3ac1f1df pep8 2025-12-05 10:19:43 +01:00
Theophile Terraz 80af278a0f add label in custom export results 2025-12-05 10:18:38 +01:00
Theophile Terraz f254eaee25 debug adists 2025-12-02 09:31:07 +01:00
Theophile Terraz 5adb0bdee5 debug GL in resluts 2025-11-28 15:31:07 +01:00
Theophile Terraz 64c99d46d4 debug named points in meshed profiles 2025-11-28 14:12:06 +01:00
Theophile Terraz 0f0defb36f Revert "debug ci"
This reverts commit 1e1ee67d4b.
2025-11-27 16:10:00 +01:00
Theophile Terraz 1e1ee67d4b debug ci 2025-11-27 16:03:09 +01:00
Theophile Terraz 5bc514c565 pep8 2025-11-26 11:58:58 +01:00
Theophile Terraz 1da3461dfe debug meshing 2025-11-26 11:16:19 +01:00
Theophile Terraz dd5eecb1e2 pep8 2025-11-24 15:18:28 +01:00
368 changed files with 4436 additions and 16307 deletions

No files matched your search

+3 -9
View File
@@ -41,14 +41,8 @@ __old__
*-venv
mage8/
adists/
rubarbe/
linux/
windows/
install/
mage8
adists
### END CUSTOM ###
@@ -630,7 +624,7 @@ TSWLatexianTemp*
# auto folder when using emacs and auctex
./auto/*
# *.el
*.el
# expex forward references with \gathertags
*-tags.tex
+5 -45
View File
@@ -1,5 +1,5 @@
# .gitlab-ci.yml -- Pamhyr gitlab-ci
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -24,13 +24,6 @@ stages:
- antivirus
- release
#########################
# PYINSTALLER VARIABLES #
#########################
variables:
HIDDEN_IMPORTS: "--hiddenimport rasterio --collect-submodules rasterio"
#############
# DOWNLOADS #
#############
@@ -138,6 +131,7 @@ dl-rubar-windows:
- cd rubar-windows
- curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_windows.tgz
- tar xf rubar.tgz
artifacts:
paths:
- rubar-windows/rubarbe.exe
@@ -332,7 +326,7 @@ build-linux:
- pip3 install -r ../requirements.txt
- pip3 install -U -r ../requirements.txt
# Run Pyinstaller
- pyinstaller -y $HIDDEN_IMPORTS --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
- pyinstaller -y --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
# Create directory
- mkdir -p pamhyr
- mkdir -p pamhyr/_internal
@@ -440,7 +434,7 @@ build-windows:
- python -m pip install -U -r ..\requirements.txt
# Run Pyinstaller
# - pyinstaller --noconsole -y ..\src\pamhyr.py
- pyinstaller -i ../src/View/ui/ressources/icon.ico $HIDDEN_IMPORTS --hide-console hide-early -y ..\src\pamhyr.py
- pyinstaller -i ../src/View/ui/ressources/icon.ico --hide-console hide-early -y ..\src\pamhyr.py
# Create directory
- mkdir pamhyr
- dir
@@ -619,40 +613,6 @@ pkg-check-clamav:
# RELEASE #
###########
shadow-release:
stage: release
tags:
- release
- linux
needs:
- job: linux-package-tar
artifacts: true
- job: windows-package-zip
artifacts: true
- job: windows-package-exe
artifacts: true
# - job: build-users-doc
# artifacts: true
- job: build-developers-doc
artifacts: true
- job: pkg-hash
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH != 'master' && $CI_COMMIT_TAG
artifacts:
paths:
- linux/pamhyr-gnulinux.tar.xz
- windows/pamhyr-windows.zip
- windows/pamhyr-windows.exe
- linux/pamhyr-gnulinux.tar.xz.sha256
- windows/pamhyr-windows.zip.sha256
- windows/pamhyr-windows.exe.sha256
- doc/dev/documentation.pdf
# - doc/users/documentation.pdf
script:
- cd packages
tag-release:
stage: release
tags:
@@ -672,7 +632,7 @@ tag-release:
- job: pkg-hash
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == 'master' && $CI_COMMIT_TAG
- if: $CI_COMMIT_TAG
artifacts:
paths:
- linux/pamhyr-gnulinux.tar.xz
+1 -1
View File
@@ -1,3 +1,3 @@
Sylvain COULIBALY, INRAE, 2020 - 2022
Théophile TERRAZ, INRAE, 2022 - 2024
Pierre-Antoine ROUBY, INRAE, 2023 - 2025
Pierre-Antoine ROUBY, INRAE, 2023 - 2024
+8 -9
View File
@@ -3,7 +3,7 @@
Pamhyr is a free and open source graphical user interface for 1D hydro-sedimentary
modelling of rivers.
![logo](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/raw/master/src/View/ui/ressources/Pamhyr2_logo.png)
![logo](https://gitlab.com/pamhyr/pamhyr2/-/raw/master/src/View/ui/ressources/Pamhyr2_logo.png)
## Features
@@ -18,12 +18,11 @@ modelling of rivers.
+ Define river sedimentary layers
+ Save the study in a unique file
+ Run Mage 8 on a study and visualize results
+ Create and edit study alternative scenario tree
Let see the
[documentation](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home)
([:fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-fr),
[:gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-en))
[documentation](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home)
([:fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-fr),
[:gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-en))
for more details.
## Install
@@ -31,11 +30,11 @@ for more details.
### GNU/Linux
See documentation:
- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20GNULinux)
- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20GNULinux)
- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20GNULinux)
- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20GNULinux)
### Windows
See documentation:
- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20Windows)
- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20Windows)
- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20Windows)
- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20Windows)
+15 -20
View File
@@ -1,23 +1,18 @@
#! /bin/sh
FILES="documentation.org scenario.org"
echo " PDF documentation.org"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-latex-export-to-pdf
for file in $FILES
do
echo " PDF $file"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./$file \
-f org-latex-export-to-pdf \
echo " HTML $file"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(require 'ox-html)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./$file \
-f org-html-export-to-html
done
echo " HTML documentation.org"
emacs -nw --batch \
--eval "(require 'org)" \
--eval "(require 'ox-latex)" \
--eval "(require 'ox-html)" \
--eval "(load-file \"../tools/setup.el\")" \
--file ./documentation.org \
-f org-html-export-to-html
-661
View File
@@ -1,661 +0,0 @@
# senario.org -- Pamhyr developers documentation
# Copyright (C) 2023-2024 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 -*-
#+STARTUP: indent show2levels
#+INCLUDE: ../tools/macro.org
#+INCLUDE: ../tools/latex.org
#+TITLE: Scenario implementation in Pamhyr2: \textbf{Preparatory work}
#+SUBTITLE: Version: {{{version}}}
#+AUTHOR: {{{INRAE}}}
#+OPTIONS: toc:t
#+LANGUAGE: UKenglish
#+latex: \newpage
* COMMENT tools
#+begin_src emacs-lisp :export none
(add-to-list 'org-structure-template-alist
'("p" "#+begin_src python :python python3 :results output\n\n#+end_src"))
#+end_src
#+RESULTS:
#+begin_example
((p #+begin_src python :python python3 :results output
,#+end_src) (d . #+name: graph-XXX
,#+header: :results drawer
,#+header: :exports results
,#+header: :post attr_wrap(width="12cm", data=*this*, name="graph-XXX", caption="Graph XXX", float="t")
,#+begin_src dot :file "images/graph-XXX.png" :cache no
digraph {
bgcolor="transparent";
node[colorscheme=set19,shape=box,style="filled",fillcolor=9];
}
,#+end_src) (p . src python :python python3 :results output :noweb yes
src) (t . EXPORT latex
\begin{table}
\centering
\resizebox{0.6\linewidth}{!}{
}
%\vspace{0.5pt}
%\caption{Number of tests by errors types}
\end{table}
,#+E) (B . src shell :session *shell* :results output :exports both) (a . export ascii) (c . center) (C . comment) (e . example) (E . export) (h . export html) (l . export latex) (q . quote) (s . src) (v . verse))
#+end_example
#+name: dbg-execute
#+begin_src python :python python3 :results output :noweb yes
def execute(db, query):
print(query)
return db.execute(query)
#+end_src
#+RESULTS: dbg-execute
#+name: execute
#+begin_src python :python python3 :results output :noweb yes
def execute(db, query):
return db.execute(query)
#+end_src
#+RESULTS: execute
* Principe
** Scenario
A scenario is defined by a name, a description and a parent
scenario. If a scenario as no parent, parent value is =None=. So, in
Pamhyr2 the list of scenario must by a tree with a root defined by a
default scenarios with no parent.
#+begin_src sql
CREATE TABLE scenario(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, -- Name of the scenario
description TEXT NOT NULL, -- Rich text description
parent_id INTEGER REFERENCES scenario(id) -- Recursive references
-- for parent scenario
)
#+end_src
** Data
Each data is associated with one and only one scenario. This senario
is defined by the =id=. So, each data structure who can saved in DB
must add the following lines in table définition:
#+begin_src sql
scenario_id INTEGER NOT NULL,
FOREIGN KEY(scenario_id) REFERENCES scenario(id)
#+end_src
To avoid, at least, memory usage exploitation, by default, a scenario
must saved only the modified data from its parent. This contrains
require to wrap any data modification to update the data scenario id
to current scenario id in source code. In addition, we need to modify
the data loading méthode to take in considération this recursive
dependencies.
#+CAPTION: Recursive loading method
#+begin_src python :python python3 :results output :noweb yes
def rec_load(cls, db, current_scenario):
# Default case, end of recursion
if current_scenario is None:
return [] # Default value
table = db.execute(f"<MY SELECT> WHERE scenario_id = {current_scenario}")
# If no data for this scenario, recursion
if len(table) == 0:
parent_id = db.get_parent_id(current_scenario)
return cls.rec_load(db, parent_id)
# Otherelse, parse data and return...
#+end_src
With this method, modify a data in parent scenario, modify this data
in child scenario too. Unless is value has been previously modified in
the child scenario. Deny tree node modification, except for leaf.
This method work, but it dont allow to delete data un child scenarios,
because if we dont found data for this scenario we search for parent
scenario. So, we need a condition to stop recursion in case of data
deletion in child scenario. To stop the recurtion, we can use a dummy
data flag. By default, this flags is =false=, but if this flags is
=true= the data is ignored at loading.
So, we can use this flags for each data structure who can saved in DB:
#+begin_src sql
is_dummy BOOLEAN, -- True if this data as been deleted
#+end_src
Modify the loading method:
#+CAPTION: Recursive loading method with possible dummy data
#+begin_src python :python python3 :results output :noweb yes
def rec_load_2(cls, db, current_scenario):
# Default case, end of recursion
if current_scenario is None:
return [] # Default value
table = db.execute(f"<MY SELECT> WHERE scenario_id = {current_scenario}")
# If no data for this scenario, recursion
if len(table) == 0:
parent_id = db.get_parent_id(current_scenario)
return cls.rec_load(db, parent_id)
for data in table:
if is_dummy(data):
continue
# Otherelse, parse data ...
# return ...
#+end_src
#+RESULTS:
And add a data reduction method:
#+caption: Data reduction method for data subtree
#+begin_src python :python python3 :results output :noweb yes
def reduce_change(self):
# Get the greater scenario
scenar = reduce(
lambda acc, x: max(acc, x._scenario),
self._values,
self._scenario
)
# Apply change on this subtree
self._scenario = scenar
if is_dummy(self): # There are no more subtree
self.values = []
else: # Update data subtree scenario id
for value in self._values:
value.set_scenario(self._scenario)
# Dummy data is no more necessary in this subtree
self._values = list(
filter(
lambda v: not is_dummy(v),
self._values
)
)
# Keep data id unique for all subtree
self.get_new_db_id()
#+end_src
#+RESULTS:
#+latex: \newpage
* Prototype
** Scenario
#+name: scenario
#+begin_src python :python python3 :results output :noweb yes
class Scenario():
def __init__(self, id, name, parent):
self._id = id
self._name = name
self._parent = parent
@classmethod
def create_db(cls, db):
execute(
db, """
CREATE TABLE scenario(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
parent_id INTEGER REFERENCES scenario(id)
)
"""
)
@classmethod
def load_db(cls, db):
cur = execute(
db, f"SELECT * FROM scenario"
)
table = cur.fetchall()
new = []
for values in table:
new.append(
cls(
values[0], values[1], values[2],
)
)
return new
def save(self, db):
sid = 'NULL' if self._parent is None else self._parent
execute(
db,
"INSERT INTO scenario (id, name, parent_id) " +
f"VALUES ({self._id}, '{self._name}', {sid})"
)
def __repr__(self):
return f"Scenario {{ {self._id}, {self._name}, parent {self._parent} }}"
def __str__(self):
return f"{self._name}({self._id})"
#+end_src
#+RESULTS: scenario
** Data
#+name: data-b
#+begin_src python :python python3 :results output :noweb yes
class B():
def __init__(self, value, scenario):
self._value = value
self._scenario = scenario
self._dummy = False
@classmethod
def create_db(cls, db):
execute(db, """
CREATE TABLE b(
id INTEGER PRIMARY KEY AUTOINCREMENT,
dummy_data BOOLEAN,
x INTEGER,
a_id INTEGER NOT NULL,
scenario_id INTEGER NOT NULL,
FOREIGN KEY(a_id) REFERENCES a(id),
FOREIGN KEY(scenario_id) REFERENCES scenario(id)
)
""")
def save(self, db, a_id):
execute(
db,
"INSERT INTO b (x, dummy_data, a_id, scenario_id) " +
f"VALUES ({self._value}, {self._dummy}, {a_id}, {self._scenario})"
)
@classmethod
def load_db(cls, db, a_id, senar):
if senar is None:
return []
cur = execute(
db,
"SELECT x, scenario_id, dummy_data FROM b " +
f"WHERE scenario_id = {senar} AND a_id = {a_id}"
)
table = cur.fetchall()
if len(table) == 0:
parent = execute(
db,
f"SELECT parent_id FROM scenario WHERE id = {senar}"
).fetchone()
return cls.load_db(db, parent[0])
new = []
for values in table:
if values[2]: # Is dummy
continue
new.append(cls(values[0], values[1]))
return new
def set_value(self, value, scenario):
self._value = value
self._scenario = scenario
def set_dummy(self, scenario):
self._scenario = scenario
self._dummy = True
def __repr__(self):
if self._dummy:
return ""
return f"{self._value}"
def __str__(self):
if self._dummy:
return ""
return f"{self._value}"
#+end_src
#+RESULTS: data-b
#+name: data-a
#+begin_src python :python python3 :results output :noweb yes
class A():
def __init__(self, id, values, scenario):
self._id = id
self._values = values
self._scenario = scenario
self._dummy = False
@classmethod
def create_db(cls, db):
execute(db, """
CREATE TABLE a(
id INTEGER PRIMARY KEY,
dummy_data BOOLEAN,
scenario_id INTEGER,
FOREIGN KEY(scenario_id) REFERENCES scenario(id)
)
""")
def save(self, db):
self.reduce_change()
execute(db, f"DELETE FROM a WHERE scenario_id = {self._scenario}")
execute(
db,
"INSERT INTO a (id, dummy_data, scenario_id) " +
f"VALUES ({self._id}, {self._dummy}, {self._scenario})"
)
execute(
db,
"DELETE FROM b " +
f"WHERE scenario_id = {self._scenario} " +
f"AND a_id = {self._id}"
)
for value in self._values:
value.save(db, self._id)
def reduce_change(self):
diff_scenar = reduce(
lambda acc, x: max(acc, x._scenario),
self._values,
self._scenario
)
print(f" ~> Reduce: {self._scenario} to {diff_scenar}")
self._scenario = diff_scenar
# Reduce new scenario value on each value
for value in self._values:
value._scenario = self._scenario
# Delete useless dummy data
self._values = list(
filter(
lambda v: not v._dummy,
self._values
)
)
# HACK: keep id unique
self._id = self._id * 100 + diff_scenar
@classmethod
def load_db(cls, db, senar):
if senar is None:
return []
cur = execute(
db,
f"SELECT id, scenario_id, dummy_data FROM a WHERE scenario_id = {senar}"
)
table = cur.fetchall()
if len(table) == 0:
parent = execute(
db,
f"SELECT parent_id FROM scenario WHERE id = {senar}"
).fetchone()
return cls.load_db(db, parent[0])
new = []
for values in table:
if values[2]:
continue
bb = B.load_db(db, values[0], senar)
new.append(cls(values[0], bb, values[1]))
return new
def set_dummy(self, scenario):
self._scenario = scenario
self._dummy = True
def add(self, b):
self._values.append(b)
def delete(self, index, scenario):
self._values[index].set_dummy(scenario)
self._scenario = scenario
def __repr__(self):
if self._dummy:
return "{ }"
return f"{{ {self._values}, ({self._scenario}) }}"
def __str__(self):
if self._dummy:
return "{ }"
return f"{{ {list(map(str, self._values))}, ({self._scenario}) }}"
#+end_src
#+RESULTS: data-a
** Full prototype
#+begin_src python :python python3 :results output :exports both :noweb yes :cache yes
from functools import reduce
def execute(db, query):
# print(query)
return db.execute(query)
def get_tree(db):
from treelib import Node, Tree
tree = Tree()
for senar in db.execute("SELECT * FROM scenario").fetchall():
if senar[1] is None:
tree.create_node(
str(Scenario(senar[0], senar[1], senar[2])) +
" : " + str(A.load_db(db, senar[0])[0]),
senar[0]
)
else:
tree.create_node(
str(Scenario(senar[0], senar[1], senar[2])) +
" : " + str(A.load_db(db, senar[0])[0]),
senar[0],
parent=senar[2]
)
return tree
### --- Scenario ---
<<scenario>>
### --- class B ---
<<data-b>>
### --- class A ---
<<data-a>>
### --- Script ---
import sqlite3
import pprint
pp = pprint.PrettyPrinter(width=79, compact=True, depth=6)
with sqlite3.connect(":memory:") as db:
cur = db.cursor()
Scenario.create_db(cur)
A.create_db(cur)
B.create_db(cur)
print("--- Default scenario (s0) ---")
# One scenario
s0 = Scenario(0, "s0", None)
a0 = A(
1,
[
B(0, s0._id),
B(1, s0._id),
B(2, s0._id),
],
s0._id
)
s0.save(cur)
a0.save(cur)
print("--- New scenario (s1) -- copy without modification ---")
# New scenario
s1 = Scenario(1, "s1", s0._id)
s1.save(cur)
print("--- New scenario (s2) -- data modification ---")
# New scenario 2 with data
s2 = Scenario(2, "s2", s0._id)
s2.save(cur)
a2 = a0
bb = list(map(
lambda b: b.set_value(b._value + 1, s2._id),
a2._values
))
a2.save(cur)
print("--- New scenario (s3) -- delete data ---")
# New scenario 3 with data
s3 = Scenario(3, "s3", s0._id)
s3.save(cur)
a3 = A.load_db(cur, s3._id)[0]
a3._values[0].set_dummy(s3._id)
a3.save(cur)
print("--- New scenario (s4) -- new data ---")
s4 = Scenario(4, "s4", s3._id)
s4.save(cur)
a4 = A.load_db(cur, s3._id)[0]
a4.add(B(666, s4._id))
a4.save(cur)
print("--- Scenario tree ---")
tree = get_tree(db)
print(tree)
print("--- Modify s0 data ---")
aa = A.load_db(db, s0._id)[0]
bb = list(map(
lambda b: b.set_value(b._value * 100 - 1, s0._id),
aa._values
))
aa.save(db)
print("--- Scenario tree ---")
tree = get_tree(db)
print(tree)
print("--- DB ---")
ss = db.execute("SELECT * FROM scenario").fetchall()
print(f"+ scenario ({len(ss)}):")
pp.pprint(ss)
aa = db.execute("SELECT * FROM a").fetchall()
print(f"+ a ({len(aa)}):")
pp.pprint(aa)
bb = db.execute("SELECT * FROM b").fetchall()
print(f"+ b ({len(bb)}):")
pp.pprint(bb)
#+end_src
#+RESULTS[96c84f23be557425ae2822ef7f174175ef69f9b9]:
#+begin_example
--- Default scenario (s0) ---
~> Reduce: 0 to 0
--- New scenario (s1) -- copy without modification ---
--- New scenario (s2) -- data modification ---
~> Reduce: 0 to 2
--- New scenario (s3) -- delete data ---
~> Reduce: 0 to 3
--- New scenario (s4) -- new data ---
~> Reduce: 3 to 4
--- Scenario tree ---
s0(0) : { ['0', '1', '2'], (0) }
├── s1(1) : { ['0', '1', '2'], (0) }
├── s2(2) : { ['1', '2', '3'], (2) }
└── s3(3) : { ['1', '2'], (3) }
└── s4(4) : { ['1', '2', '666'], (4) }
--- Modify s0 data ---
~> Reduce: 0 to 0
--- Scenario tree ---
s0(0) : { ['-1', '99', '199'], (0) }
├── s1(1) : { ['-1', '99', '199'], (0) }
├── s2(2) : { ['1', '2', '3'], (2) }
└── s3(3) : { ['1', '2'], (3) }
└── s4(4) : { ['1', '2', '666'], (4) }
--- DB ---
+ scenario (5):
[(0, 's0', None), (1, 's1', 0), (2, 's2', 0), (3, 's3', 0), (4, 's4', 3)]
+ a (4):
[(10000, 0, 0), (10002, 0, 2), (10003, 0, 3), (1000304, 0, 4)]
+ b (14):
[(1, 0, 0, 100, 0), (2, 0, 1, 100, 0), (3, 0, 2, 100, 0), (4, 0, 1, 10002, 2),
(5, 0, 2, 10002, 2), (6, 0, 3, 10002, 2), (7, 0, 1, 10003, 3),
(8, 0, 2, 10003, 3), (9, 0, 1, 1000304, 4), (10, 0, 2, 1000304, 4),
(11, 0, 666, 1000304, 4), (12, 0, -1, 10000, 0), (13, 0, 99, 10000, 0),
(14, 0, 199, 10000, 0)]
#+end_example
View File
Whitespace-only changes.
-525
View File
@@ -1,525 +0,0 @@
# presentation.org -- 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 -*-
#+STARTUP: indent hideblocks content beamer
#+TITLE: \textbf{Pamhyr2}
#+SUBTITLE: Scenarios managements
# #+AUTHOR: Pierre-Antoine Rouby
#+LATEX_HEADER: \author[Pierre-Antoine Rouby]{\textbf{Pierre-Antoine Rouby}}
#+EMAIL: pierre-antoine.rouby@inrae.fr
#+INSTITUTE: INRAE Lyon-Villerbanne, teams River Hydraulics
#+LATEX_HEADER: \institute{INRAE Lyon-Villerbanne, River Hydraulics team}
#+DATE: 9th November
#+KEYWORDS: g ui, hydraulics, sedimentary, hydro-sedimentary, 1D modelling, freesoftware, foss
#+OPTIONS: H:2 toc:t num:t author:nil
#+LATEX_CLASS: beamer
#+LATEX_CLASS_OPTIONS: [slideopt,A4,showboxes,svgnames,aspectratio=169]
# #+LATEX_CLASS_OPTIONS: [presentation]
#+BEAMER_THEME: default
#+COLUMNS: %45ITEM %10BEAMER_ENV(Env) %10BEAMER_ACT(Act) %4BEAMER_COL(Col)
#+LANGUAGE: fr
#+LATEX_HEADER: \usepackage[frenchb]{babel}
#+BEGIN_SRC emacs-lisp :results silent :exports none
(setq org-latex-pdf-process (list
"latexmk -pdflatex='lualatex -shell-escape -interaction nonstopmode' -pdf -f %f"))
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
(setq org-src-fontify-natively t)
(org-add-link-type
"cite" 'ebib
(lambda (path desc format)
(cond
((eq format 'html)
(format "(<cite>%s</cite>)" path))
((eq format 'text)
(format "[%s]" path))
((eq format 'latex)
(if (or (not desc) (equal 0 (search "cite:" desc)))
(format "\\cite{%s}" path)
(format "\\cite[%s][%s]{%s}"
(cadr (split-string desc ";"))
(car (split-string desc ";")) path))))))
#+END_SRC
# Wrapper
#+NAME: attr_wrap
#+HEADER: :var width="\\textwidth"
#+HEADER: :var caption=""
#+HEADER: :var smallcaption=""
#+HEADER: :var name=""
#+HEADER: :var data=""
#+HEADER: :var float="nil"
#+BEGIN_SRC sh :results output :exports none
echo "#+CAPTION[$smallcaption]: $caption"
echo "#+NAME: $name"
echo "#+ATTR_LATEX: :width $width :float $float"
echo "$data"
#+END_SRC
#+LATEX_HEADER: \setbeamertemplate{footline}[frame number]
#+LATEX_HEADER: \setbeamertemplate{headline}{}
#+LATEX_HEADER: \usepackage{multirow}
#+LATEX_HEADER: \usepackage{fontawesome5}
#+LATEX_HEADER: \usepackage{tcolorbox}
#+LATEX_HEADER: \usepackage{tikz}
#+LATEX_HEADER: \logo{\includegraphics[width=.1\textwidth]{../../../src/View/ui/ressources/Logo-INRAE.png}}
#+LATEX_HEADER: \newcommand{\R}[1]{\rotatebox[origin=c]{90}{#1}}
# Beamer colors
#+LATEX_HEADER: \setbeamersize{text margin left=0.5cm,text margin right=0.5cm}
#+LATEX_HEADER: \setbeamerfont{alerted text}{series=\bfseries}
#+LATEX_HEADER: \setbeamerfont{example text}{series=\bfseries}
#+LATEX_HEADER: \definecolor{inrae_1}{RGB}{102,193,191}
#+LATEX_HEADER: \definecolor{inrae_2}{RGB}{0,150,152}
#+LATEX_HEADER: \definecolor{inrae_3}{RGB}{39,86,98}
#+LATEX_HEADER: \setbeamercolor{alerted text}{fg=inrae_1}
#+LATEX_HEADER: \setbeamercolor{structure}{fg=inrae_2}
#+LATEX_HEADER: \setbeamercolor{normal text}{fg=inrae_3}
#+LATEX_HEADER: \setbeamercolor*{author}{fg=inrae_3}
#+LATEX_HEADER: \usepackage[absolute,showboxes,overlay]{textpos}
#+LATEX_HEADER: \TPshowboxesfalse
#+LATEX_HEADER: \textblockorigin{5mm}{0mm}
# Biblio
#+LATEX_HEADER: \usepackage{natbib}
# #+LATEX_HEADER: \bibliographystyle{apalike}
#+LATEX_HEADER: \setbeamertemplate{bibliography item}{}
#+LATEX_HEADER: \renewcommand\bibfont{\scriptsize}
#+LATEX_HEADER: \setbeamertemplate{frametitle continuation}[from second]
#+LATEX_HEADER: \setbeamercolor*{bibliography entry title}{fg=inrae_3}
#+LATEX_HEADER: \setbeamercolor*{bibliography entry author}{fg=inrae_3}
#+LATEX_HEADER: \setbeamercolor*{bibliography entry location}{fg=inrae_3}
#+LATEX_HEADER: \usepackage{bbding}
#+LATEX_HEADER: \usepackage{pdfpages}
# Renew title page
#+LATEX_HEADER: \defbeamertemplate*{title page}{customized}[1][]
#+LATEX_HEADER: {
# #+LATEX_HEADER: {
# #+LATEX_HEADER: \usebeamerfont{date}\usebeamercolor[fg]{date}
# #+LATEX_HEADER: \tikz[overlay,remember picture]
# #+LATEX_HEADER: \node[xshift=-1cm,yshift=-1cm,text width=10cm] at (current page.north east) {
# #+LATEX_HEADER: 7$^{th}$ International Conference\\
# #+LATEX_HEADER: 8TH-10TH November\\
# #+LATEX_HEADER: EDF Lab - CHATOU, FRANCE
# #+LATEX_HEADER: }
# #+LATEX_HEADER: }
#+LATEX_HEADER: {
#+LATEX_HEADER: \usebeamerfont{title}\inserttitle\par
#+LATEX_HEADER: \usebeamerfont{title}\usebeamercolor[fg]{title}
#+LATEX_HEADER: }
#+LATEX_HEADER: \bigskip
#+LATEX_HEADER: \begin{center}
#+LATEX_HEADER: \usebeamerfont{subtitle}\usebeamercolor[fg]{subtitle}
#+LATEX_HEADER: {
#+LATEX_HEADER: \LARGE\insertsubtitle\par
#+LATEX_HEADER: }
#+LATEX_HEADER: \bigskip
#+LATEX_HEADER: \usebeamerfont{author}\usebeamercolor[fg]{author}\insertauthor\par
#+LATEX_HEADER: \bigskip
#+LATEX_HEADER: \usebeamerfont{institute}\usebeamercolor[fg]{institute}\insertinstitute\par
#+LATEX_HEADER: \bigskip
#+LATEX_HEADER: \usebeamerfont{date}\usebeamercolor[fg]{date}\insertdate\par
#+LATEX_HEADER: \usebeamercolor[fg]{titlegraphic}\inserttitlegraphic
#+LATEX_HEADER: \end{center}
#+LATEX_HEADER: }
# #+LATEX_HEADER: \newcommand{\tred}[1]{\textcolor{rouge_inrae}{#1}}
#+TODO: TODO WIP TOREVIEW | DONE
#+MACRO: pamhyr \textsc{PAMHyR}
#+MACRO: pamhyr2 \textsc{Pamhyr2}
#+MACRO: pause \pause
#+MACRO: framebreak \framebreak
#+MACRO: no
# #+MACRO: OK \checkmark
#+MACRO: OK \faIcon{check}
#+MACRO: bf \textbf{$1}
# #+latex: \frame{\tocpage}
* Problématique
** Problématique
*** Utilisateur
- Définire une version modifier de l'étude (changement dans les
données)
- Comparé des résultats entre une variante et l'étude
{{{pause}}}
**** COMMENT Développeur
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
- Un unique fichier
- Limité la duplication de donnée à sauvegarder
- Garder une bonne cohérence entre données et résultats
{{{pause}}}
*** Scénarios
:PROPERTIES:
:BEAMER_COL: 1
:BEAMER_ENV: definition
:END:
Un scénario est une variate d'une étude.
** Problématique -- Avancer
*** Utilisateur
- Définire une version modifier d'un senarios
- Comparé des résultats entre plusieurs senarios
{{{pause}}}
**** COMMENT Développeur
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
- Un unique fichier
- Limité la duplication de donnée à sauvegarder
- Garder une bonne cohérence entre données et résultats
{{{pause}}}
*** Scénarios
:PROPERTIES:
:BEAMER_COL: 1
:BEAMER_ENV: definition
:END:
Un scénario peut être l'étude ou une variate d'un autre scénario.
** Problématique -- Encore plus avancer
*** Utilisateur
- Définire un ensemble fini de variante correspondant a un ensemble de
valeur pour un(des) paramètre(s) et executer un solveur sur
l'ensemble des variantes
- Pouvoir comparé des metrics sur un ensemble de résultats trop grand
pour une comparaisont à la mains
{{{pause}}}
**** COMMENT Développeur
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
- Un unique fichier
- Limité la duplication de donnée à sauvegarder
- Garder une bonne cohérence entre données et résultats
{{{pause}}}
*** Scénarios
:PROPERTIES:
:BEAMER_COL: 1
:BEAMER_ENV: definition
:END:
Un scénario peut être l'étude, une variate d'un scénarios ou un
ensemble fini de variantes d'un scénarios.
** Problématique -- Encore plus plus avancer
*** Utilisateur
- Définire un ensemble fini de variante correspondant a un ensemble de
valeur pour un(des) paramètre(s) et faire un nombre fini de tirage
aléatoire dans ces paramètre pour executer un solveur ces paramètres
- Pouvoir comparé des metrics sur un ensemble de résultats trop grand
pour une comparaisont à la mains
{{{pause}}}
*** Scénarios
:PROPERTIES:
:BEAMER_COL: 1
:BEAMER_ENV: definition
:END:
Un scénario peut être l'étude, une variate d'un scénarios ou un
ensemble fini ou un tirage aléatoire dans un ensemble de variantes
d'un scénarios.
** Problématique -- Développeur
*** Développeur
- Permetre a l'utilisateur de *garder une bonne cohérence entre
données et résultats* de façon claire
- Permetre a l'utilisateur de *géré les scénarios* de façon claire
(représentation graphique)
- Garder *un unique fichier* (=.pamhyr=)
- *Limité la duplication* de donnée à sauvegarder
- Permetre à l'utilisateur de garder les *résultats pertinant* sans
exploser la mémoire de l'ordinateur
* Proposition
** Proposition -- Concept
*** {{{no}}}
\vspace{-1.2cm}
**** Deux type de scénarios
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
- Scénarios simple
- Scénarios d'éxecution d'ensemble
{{{pause}}}
**** Scénarios simple
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
Est composer:
- d'un identifiant unique
- d'un nom (définie par l'utilisateur)
- d'une description (définie par l'utilisateur)
- d'un parent (définie par l'utilisateur)
{{{pause}}}
\vspace{-0.5cm}
*** Scénarios d'ensembles
**** {{{no}}}
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
\vspace{-1cm}
Est composer:
- d'un identifiant unique
- d'un nom (définie par l'utilisateur)
- d'une description (définie par l'utilisateur)
- d'un parent (définie par l'utilisateur)
- d'un ou plusieurs ensemble de valeur
**** {{{no}}}
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
\vspace{-0.5cm}
Un ensemble de valeurs est definie par:
- une valeurs de départ
- une valeurs de fin
- un pas de valeur
- un élément d'origine (élément définie dans l'étude)
- une fonctions (modification de l'élément à l'aide d'une valeur de
l'ensemble)
** Proposition -- Limitation
*** Scénarios simple
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
Si le scénarios est le *parent d'un autre scénarios*, il ne *peut plus
être modifier* sans risquer des comportement difficilement
comprehensible pour l'utilisateur.
{{{pause}}}
*** Scénarios d'ensembles
:PROPERTIES:
:BEAMER_COL: 0.5
:BEAMER_ENV: block
:END:
- Un scénarios d'ensemble *ne peut pas être parent* d'un autre
scénarios
- Le nombre d'exécution peut très rapidement grossir
** Proposition -- Affichage
#+name: graph-scenarios
#+header: :results drawer
#+header: :exports results
#+header: :post attr_wrap(width="7cm", data=*this*, name="graph-scenarios", caption="Exemple of scenarios graph", float="t")
#+begin_src dot :file "images/graph-scenarios.png" :cache no
digraph {
bgcolor="transparent";
node[colorscheme=pastel19,shape=ellipse,style="filled",fillcolor=9];
b[fillcolor=2];
aa[shape=box,fillcolor=2];
aa2[label="aa'", shape=box, fillcolor=2];
ad[shape=box, fillcolor=2];
re[shape=box, fillcolor=2];
rf[shape=box, fillcolor=2];
default -> a;
default -> b;
default -> c;
a -> aa;
a -> aa2;
a -> f -> rf;
c -> d -> ad;
c -> e -> re;
}
#+end_src
#+RESULTS[80ed2484b9cc71143f800ff23c10846fd04697c3]: graph-scenarios
:results:
#+CAPTION[]: Exemple of scenarios graph
#+NAME: graph-scenarios
#+ATTR_LATEX: :width 7cm :float t
[[file:images/graph-scenarios.png]]
:end:
* Solution technique
** Technique -- scénarios
#+begin_src sql
CREATE TABLE scenario(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, -- Name of the scenario
description TEXT NOT NULL, -- Rich text description
parent_id INTEGER REFERENCES scenario(id) -- Recursive references
-- for parent scenario
)
#+end_src
** Technique -- Données -- Enregistrement
#+begin_src sql
-- Unique ID (Must be unused, to delete)
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
-- New ID (not unique)
pamhyr_id INTEGER NOT NULL,
-- (Optional) Flag for set there is no data in this scenario and
-- ignore parent scenario
dummy BOOLEAN NOT NULL,
-- Corresponding scenario
scenario_id INTEGER NOT NULL,
PRIMARY KEY(pamhyr_id, scenario_id),
FOREIGN KEY(scenario_id) REFERENCES scenario(id),
#+end_src
** Technique -- Données -- Chargement
#+begin_src python :python python3 :results output :noweb yes
def load(cls, db, current_scenario):
# Default case, end of recursion
if current_scenario is None:
return [] # Default value
table = db.execute(f"<MY SELECT> WHERE scenario_id = {current_scenario}")
# If no data for this scenario, recursion
if len(table) == 0:
parent_id = db.get_parent_id(current_scenario)
return cls.load(db, parent_id)
# Otherelse, parse data and return...
#+end_src
** Technique -- Données -- Modification
#+begin_src python :python python3 :results output :noweb yes
def set_modified(self, current_scenario):
self.scenario = current_scenario
#+end_src
#+begin_src python :python python3 :results output :noweb yes
def delete(self, current_scenario):
self.deleted = True
self.set_modified(current_scenario)
#+end_src
** Technique -- Exécution d'ensemble
#+begin_src sql
CREATE TABLE range_set(
id INTEGER PRIMARY KEY AUTOINCREMENT,
start REAL NOT NULL, -- Range start
end REAL NOT NULL, -- Range end
step REAL NOT NULL, -- Range step
generator_name TEXT NOT NULL, -- Generator function name (ex:
-- 'minor_strickler')
original_data_id INTEGER, -- (optional) Ref to data who apply
-- this range (ex: a strickler id)
scenario_id INTEGER NOT NULL,
FOREIGN KEY(scenario_id) REFERENCES scenario(id)
)
#+end_src
* Sauvegarde des résultats
** Sauvegarde des résultats
*** Scénarios simple
Sauvegarde d'un seul résultats complet par scénarios (et par solver).
*** Scénarios d'ensemble
Sauvegarde de metrics récupéré sur un ensemble de résultats (pas de
sauvegarde de tous les résultats) ou certain résultats particuliers.
* Question & remarque
** Question & remarque
- Solution satisfaisante ?
- Cas d'usage intéressent non couvert ?
- Contre exemple ?
- Meilleur solution ?
Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

-3
View File
@@ -1,3 +0,0 @@
#!/usr/bin/env bash
guix shell --manifest=manifest.scm -- python3 ./src/pamhyr.py $@
-23
View File
@@ -1,23 +0,0 @@
;; manifest.scm -- Pamhyr2 Guix manifest
;; Copyright (C) 2025 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/>.
(specifications->manifest
(list "python" "python-platformdirs" "python-lxml"
"python-pyqt@5" "python-pyqt5-sip"
"python-pyqtgraph" "python-qscintilla"
"python-numpy@1" "python-shapely" "python-pyshp"
"python-matplotlib" "python-colorama"
"python-rasterio"))
+1 -1
View File
@@ -1,5 +1,5 @@
# Adists.py -- Pamhyr study checkers
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+1 -1
View File
@@ -1,5 +1,5 @@
# Checker.py -- Pamhyr abstract checker class
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+1 -1
View File
@@ -1,5 +1,5 @@
# Mage.py -- Pamhyr MAGE checkers
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+1 -1
View File
@@ -1,5 +1,5 @@
# Study.py -- Pamhyr study checkers
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+1 -1
View File
@@ -1,5 +1,5 @@
# AMeshingTool.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+75 -119
View File
@@ -17,7 +17,7 @@
# -*- coding: utf-8 -*-
import logging
from numpy import sign, interp
from numpy import sign
from copy import deepcopy
from tools import logger_color_red, logger_color_reset, logger_exception
@@ -40,32 +40,35 @@ class InternalMeshing(AMeshingTool):
step: float = 50,
limites=[-1, -1],
linear: bool = True):
if reach is None or len(reach.profiles) == 0:
return reach
# pre process
if limites[0] > limites[1]:
lim = limites[1]
limites[1] = limites[0]
limites[0] = lim
elif limites[0] == limites[1]:
return reach
profiles = self.get_profiles(reach, limites)
if profiles is None:
return []
self.st_to_m(reach, limites)
self.interpolate_transversal_step(reach,
limites,
step)
self.purge_aligned_points(reach)
for p in profiles:
if len(p) < 1:
return []
return reach
guide_list = self.get_guide_list(reach, profiles)
if guide_list is None:
return []
def st_to_m(self, reach, limites):
# we never modify original profiles, we work on the copies
m_profiles = self.st_to_m(profiles, guide_list)
new_profiles = self.interpolate_transversal_step(m_profiles, step)
gl = reach.compute_guidelines()
profiles = reach.profiles[limites[0]:limites[1]+1]
for new_profiles2 in new_profiles:
for p in new_profiles2:
p.hard_purge_aligned_points()
return new_profiles
def st_to_m(self, profiles, guide_list):
# we make sure that the lines are in the left-to-right order
guide_list = [
x.name
for x in profiles[0].named_points()
if x.name in gl[0]
]
guide_list = ["un"] + guide_list + ["np"]
max_values = [0] * (len(guide_list) - 1)
@@ -85,51 +88,54 @@ class InternalMeshing(AMeshingTool):
profiles[isect+1],
guide_list[i],
guide_list[i+1])
for isect in reversed(range(0, max_values_index[i])):
self.compl_sect(profiles[isect+1],
profiles[isect],
guide_list[i],
guide_list[i+1])
return profiles
def interpolate_transversal_step(self,
profiles,
reach,
limites,
step):
# calcul number of intermediate profiles
np = []
for i in range(len(profiles)-1):
np.append(int((profiles[i+1].rk -
profiles[i].rk) / step) - 1)
for i in range(limites[0], limites[1]):
np.append(int((reach.profiles[i+1].rk -
reach.profiles[i].rk) / step) - 1)
if np[-1] < 0:
np[-1] = 0
new_profiles = []
for i in range(len(profiles)-1):
new_profiles2 = []
d = [] # ratios
p = [] # new profiles
ptr = int(limites[0])
for i in range(limites[1] - limites[0]):
d = 1.0/float(np[i]+1)
ptr0 = ptr
for j in range(np[i]):
p = profiles[i].copy()
p = ProfileXYZ(reach=reach, status=reach._status)
# RATIO entre les deux sections initiales
dj = float(j+1)*d
pt1 = profiles[i].points
pt2 = profiles[i+1].points
for k in range(len(pt1)):
p.points[k].x = pt1[k].x + \
dj*(pt2[k].x -
pt1[k].x)
p.points[k].y = pt1[k].y + \
dj*(pt2[k].y -
pt1[k].y)
p.points[k].z = pt1[k].z + \
dj*(pt2[k].z -
pt1[k].z)
p.rk = profiles[i].rk + \
dj*(profiles[i+1].rk -
profiles[i].rk)
ptr += 1 # next profile, original
for k in range(len(reach.profiles[ptr0].points)):
p.add()
p.points[k].name = reach.profiles[ptr0].points[k].name
p.points[k].x = reach.profiles[ptr0].points[k].x + \
dj*(reach.profiles[ptr].points[k].x -
reach.profiles[ptr0].points[k].x)
p.points[k].y = reach.profiles[ptr0].points[k].y + \
dj*(reach.profiles[ptr].points[k].y -
reach.profiles[ptr0].points[k].y)
p.points[k].z = reach.profiles[ptr0].points[k].z + \
dj*(reach.profiles[ptr].points[k].z -
reach.profiles[ptr0].points[k].z)
p.rk = reach.profiles[ptr0].rk + \
dj*(reach.profiles[ptr].rk -
reach.profiles[ptr0].rk)
p.name = f'interpol{p.rk}'
new_profiles2.append(p)
new_profiles.append(new_profiles2)
return new_profiles
reach.insert_profile(ptr, p)
ptr += 1 # next profile, original
def compl_sect(self, sect1, sect2, tag1, tag2):
# sect1: reference section
@@ -149,7 +155,8 @@ class InternalMeshing(AMeshingTool):
len2 = end2 - start2
if len1 < len2:
self.compl_sect(sect2, sect1, tag1, tag2)
self.compl_sect(sect2, sect1, start2, start1,
len2, len1, tag1, tag2)
return
elif len1 == len2:
return
@@ -168,6 +175,10 @@ class InternalMeshing(AMeshingTool):
for i in range(len2):
ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1))
beta.append(ltot2)
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
for i in range(len1 - len2):
beta.append(1.0)
beta.append(0.0) # beta[-1]
if len2 < 1 or ltot2 < 0.0001:
# unique point (copy len1 times)
# we are at an edge of the profile
@@ -179,10 +190,6 @@ class InternalMeshing(AMeshingTool):
sect2.add_npoints(len1-len2)
len2 = len1
else: # regular case
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
for i in range(len1 - len2):
beta.append(1.0)
beta.append(0.0) # beta[-1]
# loop on the points to insert
for k in range(len1-len2):
trouve = False
@@ -243,11 +250,11 @@ class InternalMeshing(AMeshingTool):
sect2.point(start2+len2).name = ''
if tag1 != "un":
sect2.point(start2).name = tag1
if tag1 != "np":
if tag2 != "np":
sect2.point(start2+len2).name = tag2
sect2.modified()
def update_rk(self, reach, begin_rk, end_rk,
def update_rk(self, reach,
step: float = 50,
limites=[-1, -1],
origin=0,
@@ -258,40 +265,29 @@ class InternalMeshing(AMeshingTool):
origin_value=0.0,
orientation=0):
if reach is None or len(reach.profiles) == 0:
return []
return reach
nprof = reach.number_profiles
if origin >= nprof or origin < 0:
logger.warning(
f"Origin outside reach"
)
return reach.get_rk()
return reach
# orientation is the orintation of the bief:
# 1: up -> downstream
# 2: down -> upstream
# else: keep current orientation
old_orientation = sign(reach.profiles[-1].rk - reach.profiles[0].rk)
if orientation == 1:
sgn = 1.0
elif orientation == 2:
sgn = -1.0
else:
sgn = old_orientation
if abs(sgn) < 0.5:
sgn = 1.0
sgn = sign(reach.profiles[-1].rk - reach.profiles[0].rk)
if abs(sgn) < 0.5:
sgn = 1.0
new_rk = [0.0]*nprof
new_rk[origin] = origin_value
frictions = reach._parent.frictions
old_begin_rk = list(map(lambda x: x * old_orientation,
begin_rk))
old_end_rk = list(map(lambda x: x * old_orientation,
end_rk))
old_rk = list(map(lambda x: x * old_orientation,
reach.get_rk()))
reach.profiles[origin].rk = origin_value
if origin < nprof - 1:
for i in range(origin+1, nprof):
@@ -302,7 +298,8 @@ class InternalMeshing(AMeshingTool):
d2 = reach.profiles[i-1].named_point(
directrices[1]
).dist_2d(reach.profiles[i].named_point(directrices[1]))
new_rk[i] = new_rk[i-1] + (sgn * (d1 + d2) / 2)
reach.profiles[i].rk = reach.profiles[i-1].rk \
+ (sgn * (d1 + d2) / 2)
if origin > 0:
for i in reversed(range(0, origin)):
# 2D
@@ -312,50 +309,9 @@ class InternalMeshing(AMeshingTool):
d2 = reach.profiles[i+1].named_point(
directrices[1]
).dist_2d(reach.profiles[i].named_point(directrices[1]))
new_rk[i] = new_rk[i+1] - (sgn * (d1 + d2) / 2)
reach.profiles[i].rk = reach.profiles[i+1].rk \
- (sgn * (d1 + d2) / 2)
new_begin_rk = interp(old_begin_rk, old_rk, new_rk)
new_end_rk = interp(old_end_rk, old_rk, new_rk)
if abs(sgn - old_orientation) > 0.1: # orientation changed
return new_rk, new_end_rk, new_begin_rk
else:
return new_rk, new_begin_rk, new_end_rk
def get_guide_list(self, reach, profiles):
gl = reach.compute_guidelines()
# we make sure that the lines are in the left-to-right order
guide_list = [
[
x.name
for x in p.named_points()
if x.name in gl[0]
]
for p in profiles]
# we make sure the lines never cross
for gl in guide_list:
for i in range(len(guide_list[0])):
if gl[i] != guide_list[0][i]:
return None
return guide_list[0]
def get_profiles(self, reach, limites):
if reach is None or len(reach.profiles) == 0:
return None
if limites[0] > limites[1]:
lim = limites[1]
limites[1] = limites[0]
limites[0] = lim
elif limites[0] == limites[1]:
return None
profiles = [reach.profile(i).copy()
for i in range(limites[0], limites[1]+1)
]
return profiles
def purge_aligned_points(self, reach):
for p in reach.profiles:
p.purge_aligned_points()
+3 -7
View File
@@ -1,5 +1,5 @@
# Mage.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -344,9 +344,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
)
)
)
logger.info("Waiting ...")
proc.waitForFinished()
logger.info(f"Waiting ... Done (status : {proc.exitCode()})")
if proc.exitCode() != 0:
logger.error(
@@ -355,7 +353,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
f"{logger_color_reset()}"
)
outputs = proc.readAllStandardOutput()
logger.warning(outputs)
logger.debug(outputs)
errors = proc.readAllStandardError()
logger.error(
@@ -421,9 +419,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
)
)
)
logger.debug("Waiting ...")
proc.waitForFinished()
logger.debug(f"Waiting ... Done (status : {proc.exitCode()})")
if proc.exitCode() != 0:
logger.error(
@@ -432,7 +428,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
f"{logger_color_reset()}"
)
outputs = proc.readAllStandardOutput()
logger.warning(outputs)
logger.debug(outputs)
errors = proc.readAllStandardError()
logger.error(
+1 -1
View File
@@ -1,5 +1,5 @@
# __init__.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+2 -1
View File
@@ -1,5 +1,5 @@
# test_Model.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -21,6 +21,7 @@ import unittest
import warnings
import tempfile
from Model.Saved import SavedStatus
from Model.Study import Study
from Model.River import River
+34 -85
View File
@@ -1,5 +1,5 @@
# AddFile.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -22,25 +22,31 @@ from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
class AddFile(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1, enabled=True,
name="", path="", text="",
status=None, owner_scenario=-1):
super(AddFile, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(AddFile, self).__init__()
if id == -1:
self.id = AddFile._id_cnt
else:
self.id = id
self._status = status
self._enabled = enabled
self._name = f"File #{self._pamhyr_id}" if name == "" else name
self._name = f"File {self.id}" if name == "" else name
self._path = path
self._text = text
AddFile._id_cnt = max(self.id + 1, AddFile._id_cnt + 1)
def __getitem__(self, key):
value = None
@@ -65,7 +71,7 @@ class AddFile(SQLSubModel):
elif key == "text":
self._text = value
self.modified()
self._status.modified()
@property
def enabled(self):
@@ -74,7 +80,7 @@ class AddFile(SQLSubModel):
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
self._status.modified()
def is_enabled(self):
return self._enabled
@@ -86,7 +92,7 @@ class AddFile(SQLSubModel):
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def path(self):
@@ -95,7 +101,7 @@ class AddFile(SQLSubModel):
@path.setter
def path(self, path):
self._path = path
self.modified()
self._status.modified()
@property
def text(self):
@@ -104,80 +110,38 @@ class AddFile(SQLSubModel):
@text.setter
def text(self, text):
self._text = text
self.modified()
self._status.modified()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE additional_files{ext} (
{cls.create_db_add_pamhyr_id()},
def _db_create(cls, execute):
execute("""
CREATE TABLE additional_files(
id INTEGER NOT NULL PRIMARY KEY,
enabled BOOLEAN NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL,
path TEXT NOT NULL,
text TEXT NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario)
text TEXT NOT NULL
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
release = int(release)
if major == minor == "0":
if release < 8:
if int(release) < 8:
cls._db_create(execute)
return True
if 8 < release < 13:
cls._db_update_to_0_0_13(execute, data)
if major == "0" and minor == "1":
if release < 2:
execute(
"ALTER TABLE additional_files " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return True
@classmethod
def _db_update_to_0_0_13(cls, execute, data):
table = "additional_files"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
"INSERT INTO additional_files_tmp " +
"(pamhyr_id, enabled, name, path, text, scenario) " +
"SELECT pamhyr_id, enabled, name, path, text, scenario " +
"FROM additional_files"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, enabled, deleted, name, path, text, scenario " +
"FROM additional_files " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, enabled, name, path, text " +
"FROM additional_files"
)
for row in table:
@@ -185,45 +149,30 @@ class AddFile(SQLSubModel):
id = next(it)
enabled = (next(it) == 1)
deleted = (next(it) == 1)
name = next(it)
path = next(it)
text = next(it)
owner_scenario = next(it)
f = cls(
id=id, enabled=enabled, name=name, path=path, text=text,
status=data['status'], owner_scenario=owner_scenario
status=data['status']
)
if deleted:
f.set_as_deleted()
loaded.add(id)
new.append(f)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
sql = (
"INSERT INTO " +
"additional_files(pamhyr_id, enabled, deleted, " +
"name, path, text, scenario) " +
"additional_files(id, enabled, name, path, text) " +
"VALUES (" +
f"{self._pamhyr_id}, " +
f"{self._db_format(self._enabled)}, " +
f"{self._db_format(self.is_deleted())}, " +
f"{self.id}, {self._enabled}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._path)}', " +
f"'{self._db_format(self._text)}', " +
f"{self._status.scenario_id}" +
f"'{self._db_format(self._text)}'" +
")"
)
execute(sql)
return True
+3 -6
View File
@@ -1,5 +1,5 @@
# AddFileList.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -19,7 +19,7 @@
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.AdditionalFile.AddFile import AddFile
@@ -38,10 +38,7 @@ class AddFileList(PamhyrModelList):
ok = True
# Delete previous data
execute(
"DELETE FROM additional_files " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM additional_files")
for af in self._lst:
ok &= af._db_save(execute, data)
+97 -361
View File
@@ -1,5 +1,5 @@
# BoundaryCondition.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,193 +27,24 @@ from tools import (
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class Data(SQLSubModel):
_sub_classes = []
def __init__(self, data0, data1,
id: int = -1,
types=[float, float],
status=None,
owner_scenario=-1):
super(Data, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._types = types
self._data = [data0, data1]
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE boundary_condition_data{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
ind INTEGER NOT NULL,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
bc INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(bc) REFERENCES boundary_condition(pamhyr_id),
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) < 1:
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE boundary_condition_data " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "boundary_condition_data"
bcs = data['id2pid']['boundary_condition']
cls.update_db_add_pamhyr_id(
execute, table,
data=data
)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, data0, data1, bc, scenario) " +
"SELECT pamhyr_id, ind, data0, data1, bc, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_bc_pid(execute, table, bcs)
@classmethod
def _db_update_to_0_2_0_set_bc_pid(cls, execute, table, bcs):
els = execute(
f"SELECT pamhyr_id, bc FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
bc_id = next(it)
execute(
f"UPDATE {table} " +
f"SET bc = {bcs[bc_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
bc = data["bc"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
values = execute(
"SELECT pamhyr_id, deleted, ind, " +
"data0, data1, scenario " +
"FROM boundary_condition_data " +
f"WHERE bc = {bc._pamhyr_id} " +
f"AND 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)
deleted = (next(it) == 1)
ind = next(it)
data0 = bc._types[0](next(it))
data1 = bc._types[1](next(it))
owner_scenario = next(it)
nd = cls(
data0, data1,
id=pid,
types=bc._types,
status=data['status'],
owner_scenario=owner_scenario
)
if deleted:
nd.set_as_deleted()
loaded.add(pid)
new.append(nd)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
pid = self._pamhyr_id
ind = data["ind"]
data0 = self._db_format(str(self[0]))
data1 = self._db_format(str(self[1]))
bc = data["bc"]
execute(
"INSERT INTO " +
"boundary_condition_data(pamhyr_id, deleted, ind, " +
"data0, data1, bc, scenario) " +
f"VALUES (" +
f"{pid}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(data0)}', {self._db_format(data1)}, " +
f"{bc._pamhyr_id}, {self._status.scenario_id}" +
")"
)
return True
def __getitem__(self, key):
return self._types[key](self._data[key])
def __setitem__(self, key, value):
self._data[key] = self._types[key](value)
class BoundaryCondition(SQLSubModel):
_sub_classes = [Data]
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1,
name: str = "",
status=None,
owner_scenario=-1):
super(BoundaryCondition, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "",
status=None):
super(BoundaryCondition, self).__init__()
self._status = status
if id == -1:
self.id = BoundaryCondition._id_cnt
else:
self.id = id
self._name = name
self._type = ""
@@ -222,66 +53,38 @@ class BoundaryCondition(SQLSubModel):
self._header = []
self._types = [float, float]
BoundaryCondition._id_cnt = max(BoundaryCondition._id_cnt + 1,
self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE boundary_condition{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE boundary_condition(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
tab TEXT NOT NULL,
node INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(node) REFERENCES river_node(id)
)
""")
if ext == "_tmp":
return True
execute("""
CREATE TABLE boundary_condition_data(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ind INTEGER NOT NULL,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
bc INTEGER,
FOREIGN KEY(bc) REFERENCES boundary_condition(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
if major == minor == "0":
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE friction " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "boundary_condition"
nodes = data['id2pid']['river_node']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
# Copy table
execute(
f"INSERT INTO {table}_tmp " +
f"(pamhyr_id, name, type, tab, node, scenario) " +
"SELECT pamhyr_id, name, type, tab, node, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
def _db_update(cls, execute, version):
return True
@classmethod
def _get_ctor_from_type(cls, t):
@@ -308,117 +111,83 @@ class BoundaryCondition(SQLSubModel):
def _db_load(cls, execute, data=None):
new = []
tab = data["tab"]
nodes = data["nodes"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, type, node, scenario " +
"SELECT id, name, type, node " +
"FROM boundary_condition " +
f"WHERE tab = '{tab}' " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
f"WHERE tab = '{tab}'"
)
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
t = next(it)
node = next(it)
owner_scenario = next(it)
t = row[2]
ctor = cls._get_ctor_from_type(t)
bc = ctor(
id=pid, name=name,
status=data['status'],
owner_scenario=owner_scenario
id=row[0],
name=row[1],
status=data['status']
)
if deleted:
bc.set_as_deleted()
bc.node = None
if node != -1:
bc.node = next(filter(lambda n: n.id == node, nodes), None)
if row[3] != -1:
bc.node = next(filter(lambda n: n.id == row[3], data["nodes"]),
None)
data["bc"] = bc
bc._data = Data._db_load(execute, data=data)
values = execute(
"SELECT ind, data0, data1 FROM boundary_condition_data " +
f"WHERE bc = '{bc.id}'"
)
# Create dummy data list
for _ in values:
bc.add(0)
# Write data
for v in values:
ind = v[0]
data0 = bc._types[0](v[1])
data1 = bc._types[1](v[2])
# Replace data at pos ind
bc._data[ind] = (data0, data1)
loaded.add(pid)
new.append(bc)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
tab = data["tab"]
execute(
"DELETE FROM boundary_condition_data " +
f"WHERE bc = {self._pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM boundary_condition WHERE id = {self.id}")
execute(f"DELETE FROM boundary_condition_data WHERE bc = {self.id}")
node = -1
if self._node is not None:
node = self._node.id
execute(
sql = (
"INSERT INTO " +
"boundary_condition(" +
"pamhyr_id, deleted, name, type, tab, node, scenario" +
") " +
"boundary_condition(id, name, type, tab, node) " +
"VALUES (" +
f"{self._pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', '{tab}', {node}, " +
f"{self._status.scenario_id}" +
f"{self.id}, '{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', '{tab}', {node}" +
")"
)
data["bc"] = self
execute(sql)
ind = 0
for d in self._data:
data["ind"] = ind
d._db_save(execute, data)
data0 = self._db_format(str(d[0]))
data1 = self._db_format(str(d[1]))
sql = (
"INSERT INTO " +
"boundary_condition_data(ind, data0, data1, bc) " +
f"VALUES ({ind}, '{data0}', {data1}, {self.id})"
)
execute(sql)
ind += 1
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for el in self._data:
el._data_traversal(predicate, modifier, data)
def __len__(self):
return len(
list(
filter(
lambda el: not el.is_deleted(),
self._data
)
)
)
return len(self._data)
@classmethod
def compatibility(cls):
@@ -443,14 +212,14 @@ class BoundaryCondition(SQLSubModel):
@property
def name(self):
if self._name == "":
return f"B #{self.pamhyr_id}"
return f"B{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def bctype(self):
@@ -463,7 +232,7 @@ class BoundaryCondition(SQLSubModel):
@node.setter
def node(self, node):
self._node = node
self.modified()
self._status.modified()
def has_node(self):
return self._node is not None
@@ -474,12 +243,7 @@ class BoundaryCondition(SQLSubModel):
@property
def data(self):
return list(
filter(
lambda el: not el.is_deleted(),
self._data
)
)
return self._data.copy()
def get_type_column(self, column):
if 0 <= column < 2:
@@ -513,79 +277,51 @@ class BoundaryCondition(SQLSubModel):
new_0 = self._types[0](data[0].replace(",", "."))
new_1 = self._types[1](data[1].replace(",", "."))
return Data(
new_0, new_1,
status=self._status
)
return (new_0, new_1)
def add(self, index: int):
value = Data(
self._default_0, self._default_1,
status=self._status
)
value = (self._default_0, self._default_1)
self._data.insert(index, value)
self.modified()
self._status.modified()
return value
def insert(self, index: int, value):
if value in self._data:
value.set_as_not_deleted()
else:
self._data.insert(index, value)
self._data.insert(index, value)
self._status.modified()
self.modified()
def hard_delete_i(self, indexes):
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.modified()
def delete_i(self, indexes):
list(
map(
lambda e: e[1].set_as_deleted(),
filter(
lambda e: e[0] in indexes,
enumerate(self.data)
)
)
)
self.modified()
self._status.modified()
def delete(self, els):
for el in els:
el.set_as_deleted()
self.modified()
def undelete(self, els):
for el in els:
el.set_as_not_deleted()
self.modified()
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.modified()
self._status.modified()
def index(self, bc):
self._data.index(bc)
def get_i(self, index):
if len(self._data) == 0:
return None
return self._data[index]
return self.data[index]
def get_range(self, _range):
lst = []
@@ -594,10 +330,10 @@ class BoundaryCondition(SQLSubModel):
return lst
def _set_i_c_v(self, index, column, value):
v = self._data[index]
v = list(self._data[index])
v[column] = self._types[column](value)
self._data[index] = v
self.modified()
self._data[index] = tuple(v)
self._status.modified()
def set_i_0(self, index: int, value):
self._set_i_c_v(index, 0, value)
@@ -629,14 +365,14 @@ class BoundaryCondition(SQLSubModel):
next = index - 1
d = self._data
d[index], d[next] = d[next], d[index]
self.modified()
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
d = self._data
d[index], d[prev] = d[prev], d[index]
self.modified()
self._status.modified()
def reach(self, river):
r = []
@@ -1,5 +1,5 @@
# BoundaryConditionList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelListWithTab
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
@@ -41,6 +41,9 @@ class BoundaryConditionList(PamhyrModelListWithTab):
def _db_load(cls, execute, data=None):
new = cls(status=data['status'])
if data is None:
data = {}
for tab in new._tabs:
data["tab"] = tab
new._tabs[tab] = BoundaryCondition._db_load(
@@ -50,10 +53,10 @@ class BoundaryConditionList(PamhyrModelListWithTab):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM boundary_condition " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM boundary_condition")
if data is None:
data = {}
for tab in self._tabs:
data["tab"] = tab
@@ -1,5 +1,5 @@
# BoundaryConditionTypes.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,12 +22,8 @@ from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
class NotDefined(BoundaryCondition):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(NotDefined, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(NotDefined, self).__init__(id=id, name=name, status=status)
self._type = "ND"
self._header = ["x", "y"]
@@ -38,12 +34,9 @@ class NotDefined(BoundaryCondition):
class PonctualContribution(BoundaryCondition):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
def __init__(self, id: int = -1, name: str = "", status=None):
super(PonctualContribution, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
id=id, name=name, status=status)
self._type = "PC"
self._header = ["time", "discharge"]
@@ -55,12 +48,8 @@ class PonctualContribution(BoundaryCondition):
class TimeOverZ(BoundaryCondition):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(TimeOverZ, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(TimeOverZ, self).__init__(id=id, name=name, status=status)
self._type = "TZ"
self._header = ["time", "z"]
@@ -72,12 +61,9 @@ class TimeOverZ(BoundaryCondition):
class TimeOverDischarge(BoundaryCondition):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
def __init__(self, id: int = -1, name: str = "", status=None):
super(TimeOverDischarge, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
id=id, name=name, status=status)
self._type = "TD"
self._header = ["time", "discharge"]
@@ -89,12 +75,8 @@ class TimeOverDischarge(BoundaryCondition):
class ZOverDischarge(BoundaryCondition):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(ZOverDischarge, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(ZOverDischarge, self).__init__(id=id, name=name, status=status)
self._type = "ZD"
self._header = ["z", "discharge"]
@@ -110,12 +92,8 @@ class ZOverDischarge(BoundaryCondition):
class Solid(BoundaryCondition):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(Solid, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(Solid, self).__init__(id=id, name=name, status=status)
self.d50 = 0.002
self.sigma = 1
@@ -1,5 +1,5 @@
# BoundaryConditionsAdisTS.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,193 +27,25 @@ from tools import (
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class Data(SQLSubModel):
_sub_classes = []
def __init__(self,
data0, data1,
id: int = -1,
types=[float, float],
status=None,
owner_scenario=-1):
super(Data, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._types = types
self._data = [data0, data1]
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE boundary_condition_data_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
bca INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(bca) REFERENCES boundary_condition_adists(pamhyr_id)
)
""")
return True
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
if not created:
return cls._update_submodel(execute, version, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "boundary_condition_data_adists"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, data0, data1, bca, scenario) " +
"SELECT pamhyr_id, data0, data1, bc, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_bca_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_bca_pid(cls, execute, data):
pid_bca = data["id2pid"]["boundary_condition_adists"]
els = execute(
"SELECT pamhyr_id, bca " +
"FROM boundary_condition_data_adists"
)
for row in els:
it = iter(row)
pid = next(it)
bca_id = next(it)
if bca_id == -1:
continue
execute(
f"UPDATE boundary_condition_data_adists " +
f"SET bca = {pid_bca[bca_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
bca = data["bca"]
table = execute(
"SELECT pamhyr_id, deleted, data0, data1, scenario FROM " +
"boundary_condition_data_adists " +
f"WHERE bca = {bca.id} " +
f"AND scenario = {scenario.id}"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
data0 = next(it)
data1 = next(it)
owner_scenario = next(it)
bc = cls(
data0, data1,
id=pid,
status=status,
owner_scenario=owner_scenario
)
if deleted:
bc.set_as_deleted()
loaded.add(pid)
new.append(bc)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
bca = data["bca"]
data0 = self._db_format(self._data[0])
data1 = self._db_format(self._data[1])
execute(
"INSERT INTO " +
"boundary_condition_data_adists(" +
"pamhyr_id, data0, data1, bca, scenario) " +
f"VALUES ({self.id}, '{data0}', {data1}, {bca.id}, " +
f"{self._status.scenario_id})"
)
return True
def __getitem__(self, key):
return self._types[key](self._data[key])
def __setitem__(self, key, value):
self._data[key] = self._types[key](value)
class BoundaryConditionAdisTS(SQLSubModel):
_sub_classes = [Data]
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1,
pollutant: int = -1, status=None,
owner_scenario=-1):
super(BoundaryConditionAdisTS, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
pollutant: int = -1, status=None):
super(BoundaryConditionAdisTS, self).__init__()
self._status = status
if id == -1:
self.id = BoundaryConditionAdisTS._id_cnt
else:
self.id = id
self._type = ""
self._node = None
self._pollutant = pollutant
@@ -221,210 +53,117 @@ class BoundaryConditionAdisTS(SQLSubModel):
self._header = []
self._types = [self.time_convert, float]
BoundaryConditionAdisTS._id_cnt = max(
BoundaryConditionAdisTS._id_cnt + 1, self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE boundary_condition_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE boundary_condition_adists(
id INTEGER NOT NULL PRIMARY KEY,
pollutant INTEGER NOT NULL,
type TEXT NOT NULL,
node INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(pollutant) REFERENCES Pollutants(pamhyr_id),
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id)
FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
FOREIGN KEY(node) REFERENCES river_node(id)
)
""")
if ext != "":
return True
execute("""
CREATE TABLE boundary_condition_data_adists(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
bc INTEGER,
FOREIGN KEY(bc) REFERENCES boundary_condition_adists(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
if not created:
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "boundary_condition_adists"
nodes = data['id2pid']['river_node']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, pollutant, type, node, scenario) " +
"SELECT pamhyr_id, pollutant, type, node, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
cls._db_update_to_0_2_0_set_pollutants_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_pollutants_pid(cls, execute, data):
pid_pol = data["id2pid"]["Pollutants"]
els = execute(
f"SELECT pamhyr_id, pollutant " +
"FROM boundary_condition_adists"
)
for row in els:
it = iter(row)
pid = next(it)
pol_id = next(it)
if pol_id == -1:
continue
execute(
f"UPDATE boundary_condition_adists " +
f"SET pollutant = {pid_pol[pol_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
nodes = data['nodes']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, pollutant, type, node, scenario " +
"FROM boundary_condition_adists " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, pollutant, type, node " +
"FROM boundary_condition_adists"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
pollutant = next(it)
bc_type = next(it)
node = next(it)
owner_scenario = next(it)
bc = cls(
id=pid,
pollutant=pollutant,
status=status,
owner_scenario=owner_scenario
id=row[0],
pollutant=row[1],
status=data['status']
)
if deleted:
bc.set_as_deleted()
bc.type = bc_type
bc.type = row[2]
bc.node = None
if row[3] != -1:
tmp = next(
filter(
lambda n: n.id == node, nodes
),
None
)
tmp = next(filter(
lambda n: n.id == row[3], data["nodes"]),
None)
if tmp is not None:
bc.node = tmp.id
else:
bc.node = -1
data["bca"] = bc
bc._data = Data._db_load(execute, data=data)
values = execute(
"SELECT data0, data1 FROM " +
"boundary_condition_data_adists " +
f"WHERE bc = '{bc.id}'"
)
# Write data
for v in values:
data0 = bc._types[0](v[0])
data1 = bc._types[1](v[1])
# Replace data at pos ind
bc._data.append((data0, data1))
loaded.add(pid)
new.append(bc)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"DELETE FROM boundary_condition_adists " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
)
execute(
"DELETE FROM boundary_condition_data_adists " +
f"WHERE bca = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
)
execute(f"DELETE FROM boundary_condition_adists WHERE id = {self.id}")
execute(f"DELETE FROM boundary_condition_data_adists" +
f" WHERE bc = {self.id}")
node = -1
if self._node is not None:
node = self._node
execute(
sql = (
"INSERT INTO " +
"boundary_condition_adists(" +
"pamhyr_id, deleted, pollutant, type, " +
"node, scenario) " +
"boundary_condition_adists(id, pollutant, type, node) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"{self._pollutant}, " +
f"'{self._db_format(self._type)}', {node}, " +
f"{self._status.scenario_id}" +
f"{self.id}, {self._pollutant}, " +
f"'{self._db_format(self._type)}', {node}" +
")"
)
execute(sql)
data["bca"] = self
ind = 0
for d in self._data:
data["ind"] = ind
data0 = self._db_format(str(d[0]))
data1 = self._db_format(str(d[1]))
d._db_save(execute, data)
ind += 1
sql = (
"INSERT INTO " +
"boundary_condition_data_adists(data0, data1, bc) " +
f"VALUES ('{data0}', {data1}, {self.id})"
)
execute(sql)
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self._data)
@@ -1,5 +1,5 @@
# BoundaryConditionsAdisTSList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Except import NotImplementedMethodeError
from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS \
@@ -45,20 +45,14 @@ class BoundaryConditionsAdisTSList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM boundary_condition_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute(
"DELETE FROM boundary_condition_data_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM boundary_condition_adists")
execute("DELETE FROM boundary_condition_data_adists")
if data is None:
data = {}
for bca in self._lst:
bca._db_save(execute, data=data)
for bc in self._lst:
bc._db_save(execute, data=data)
return True
+62 -145
View File
@@ -1,5 +1,5 @@
# D90AdisTS.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -23,7 +23,6 @@ from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
from Model.D90AdisTS.D90AdisTSSpec import D90AdisTSSpec
@@ -34,162 +33,107 @@ class D90AdisTS(SQLSubModel):
_sub_classes = [
D90AdisTSSpec,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "default",
status=None, owner_scenario=-1):
super(D90AdisTS, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(D90AdisTS, self).__init__()
self._status = status
if id == -1:
self.id = D90AdisTS._id_cnt
else:
self.id = id
self._name = name
self._d90 = None
self._enabled = True
self._data = []
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE d90_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL,
d90 REAL NOT NULL,
enabled BOOLEAN NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()}
)
""")
D90AdisTS._id_cnt = max(
D90AdisTS._id_cnt + 1,
self.id + 1
)
if ext != "":
return True
@classmethod
def _db_create(cls, execute):
execute("""
CREATE TABLE d90_adists(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
d90 REAL NOT NULL,
enabled BOOLEAN NOT NULL
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if major == 0 and minor < 1:
if int(release) < 6:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
if not created:
return cls._update_submodel(execute, version, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "d90_adists"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, d90, enabled, scenario) " +
"SELECT pamhyr_id, name, d90, enabled, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, d90, enabled, scenario " +
"FROM d90_adists " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, name, d90, enabled " +
"FROM d90_adists"
)
if table is not None:
for row in table:
it = iter(row)
d90_id = row[0]
name = row[1]
d90 = row[2]
enabled = (row[3] == 1)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
value_d90 = next(it)
enabled = (next(it) == 1)
owner_scenario = next(it)
d90 = cls(
id=pid,
D90 = cls(
id=d90_id,
name=name,
status=status,
owner_scenario=owner_scenario
status=data['status']
)
if deleted:
d90.set_as_deleted()
d90.d90 = value_d90
d90.enabled = enabled
D90.d90 = d90
D90.enabled = enabled
data['d90_default_id'] = pid
d90._data = D90AdisTSSpec._db_load(execute, data)
data['d90_default_id'] = d90_id
D90._data = D90AdisTSSpec._db_load(execute, data)
loaded.add(pid)
new.append(d90)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new.append(D90)
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
f"DELETE FROM d90_adists " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
)
execute(f"DELETE FROM d90_adists WHERE id = {self.id}")
d90 = -1.
if self.d90 is not None:
d90 = self.d90
execute(
sql = (
"INSERT INTO " +
"d90_adists(" +
"pamhyr_id, deleted, " +
"name, d90, enabled, scenario" +
"id, name, d90, enabled" +
") " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"{d90}, {self._enabled}, {self._status.scenario_id}" +
f"{self.id}, '{self._db_format(self._name)}', " +
f"{d90}, {self._enabled}" +
")"
)
execute(sql)
data['d90_default_id'] = self.id
execute(
"DELETE FROM d90_adists_spec " +
f"WHERE d90_default = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
"DELETE FROM d90_spec " +
f"WHERE d90_default = {self.id}"
)
for d90_spec in self._data:
@@ -197,16 +141,6 @@ class D90AdisTS(SQLSubModel):
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self._data)
@@ -217,7 +151,7 @@ class D90AdisTS(SQLSubModel):
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def d90(self):
@@ -226,7 +160,7 @@ class D90AdisTS(SQLSubModel):
@d90.setter
def d90(self, d90):
self._d90 = d90
self.modified()
self._status.modified()
@property
def enabled(self):
@@ -235,45 +169,28 @@ class D90AdisTS(SQLSubModel):
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
self._status.modified()
def new(self, index):
n = D90AdisTSSpec(status=self._status)
self._data.insert(index, n)
self.modified()
self._status.modified()
return n
def delete(self, data):
list(
map(
lambda x: x.set_as_deleted(),
data
self._data = list(
filter(
lambda x: x not in data,
self._data
)
)
self.modified()
self._status.modified()
def delete_i(self, indexes):
list(
map(
lambda e: e[1].set_as_deleted(),
filter(
lambda e: e[0] in indexes,
enumerate(self._data)
)
)
)
self.modified()
for ind in indexes:
del self._data[ind]
self._status.modified()
def insert(self, index, data):
if data in self._data:
self.undelete([data])
else:
self._data.insert(index, data)
self.modified()
def undelete(self, lst):
for x in lst:
x.set_as_not_deleted()
self.modified()
self._data.insert(index, data)
self._status.modified()
+3 -6
View File
@@ -1,5 +1,5 @@
# D90AdisTSList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.D90AdisTS.D90AdisTS import D90AdisTS
@@ -42,10 +42,7 @@ class D90AdisTSList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM d90_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM d90_adists")
if data is None:
data = {}
+58 -128
View File
@@ -1,5 +1,5 @@
# D90AdisTSSpec.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,21 +22,25 @@ from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class D90AdisTSSpec(SQLSubModel):
_sub_classes = []
_sub_classes = [
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(D90AdisTSSpec, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(D90AdisTSSpec, self).__init__()
self._status = status
if id == -1:
self.id = D90AdisTSSpec._id_cnt
else:
self.id = id
self._name_section = name
self._reach = None
@@ -45,129 +49,62 @@ class D90AdisTSSpec(SQLSubModel):
self._d90 = None
self._enabled = True
D90AdisTSSpec._id_cnt = max(D90AdisTSSpec._id_cnt + 1, self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE d90_adists_spec{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
d90_default INTEGER NOT NULL,
name TEXT NOT NULL,
reach INTEGER NOT NULL,
start_rk REAL NOT NULL,
end_rk REAL NOT NULL,
d90 REAL NOT NULL,
enabled BOOLEAN NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(d90_default) REFERENCES d90_adists(pamhyr_id),
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id)
)
""")
def _db_create(cls, execute):
execute("""
CREATE TABLE d90_spec(
id INTEGER NOT NULL PRIMARY KEY,
d90_default INTEGER NOT NULL,
name TEXT NOT NULL,
reach INTEGER NOT NULL,
start_rk REAL NOT NULL,
end_rk REAL NOT NULL,
d90 REAL NOT NULL,
enabled BOOLEAN NOT NULL,
FOREIGN KEY(d90_default) REFERENCES d90_adists(id),
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
created = False
if major == 0 and minor < 1:
if int(release) < 6:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "d90_spec"
table_new = "d90_adists_spec"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table_new}_tmp " +
"(pamhyr_id, name, d90, enabled, scenario) " +
"SELECT pamhyr_id, name, d90, enabled, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}")
cls._db_update_to_0_2_0_set_d90_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_d90_pid(cls, execute, data):
pid_d90 = data["id2pid"]["d90_adists"]
els = execute(
f"SELECT pamhyr_id, d90 FROM d90_adists_spec"
)
for row in els:
it = iter(row)
pid = next(it)
d90_id = next(it)
if d90_id == -1:
continue
execute(
f"UPDATE d90_adists_spec" +
f"SET d90 = {pid_d90[d90_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, " +
f"name, reach, start_rk, end_rk, " +
"d90, enabled, scenario " +
"FROM d90_adists_spec " +
f"WHERE d90_default = {data['d90_default_id']} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, d90_default, name, reach, start_rk, end_rk, " +
"d90, enabled " +
"FROM d90_spec " +
f"WHERE d90_default = {data['d90_default_id']} "
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
reach = next(it)
start_rk = next(it)
end_rk = next(it)
d90 = next(it)
enabled = (next(it) == 1)
owner_scenario = next(it)
id = row[0]
name = row[2]
reach = row[3]
start_rk = row[4]
end_rk = row[5]
d90 = row[6]
enabled = (row[7] == 1)
new_spec = cls(
id=pid,
id=id,
name=name,
status=status,
owner_scenario=owner_scenario
status=data['status']
)
if deleted:
new_spec.is_deleted()
new_spec.reach = reach
new_spec.start_rk = start_rk
@@ -175,36 +112,29 @@ class D90AdisTSSpec(SQLSubModel):
new_spec.d90 = d90
new_spec.enabled = enabled
loaded.add(pid)
new.append(new_spec)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
d90_default = data['d90_default_id']
execute(
sql = (
"INSERT INTO " +
"d90_adists_spec(pamhyr_id, deleted, " +
"d90_default, name, reach, " +
"start_rk, end_rk, d90, enabled, scenario) " +
"d90_spec(id, d90_default, name, reach, " +
"start_rk, end_rk, d90, enabled) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}" +
f"{self.id}, " +
f"{d90_default}, " +
f"'{self._db_format(self._name_section)}', " +
f"{self._reach}, " +
f"{self._start_rk}, {self._end_rk}, " +
f"{self._d90}, {self._enabled}" +
f"{self._status.scenario_id}" +
f"{self._start_rk}, " +
f"{self._end_rk}, " +
f"{self._d90}, " +
f"{self._enabled}" +
")"
)
execute(sql)
return True
@@ -215,7 +145,7 @@ class D90AdisTSSpec(SQLSubModel):
@name.setter
def name(self, name):
self._name_section = name
self.modified()
self._status.modified()
@property
def reach(self):
@@ -224,7 +154,7 @@ class D90AdisTSSpec(SQLSubModel):
@reach.setter
def reach(self, reach):
self._reach = reach
self.modified()
self._status.modified()
@property
def start_rk(self):
@@ -233,7 +163,7 @@ class D90AdisTSSpec(SQLSubModel):
@start_rk.setter
def start_rk(self, start_rk):
self._start_rk = start_rk
self.modified()
self._status.modified()
@property
def end_rk(self):
@@ -242,7 +172,7 @@ class D90AdisTSSpec(SQLSubModel):
@end_rk.setter
def end_rk(self, end_rk):
self._end_rk = end_rk
self.modified()
self._status.modified()
@property
def d90(self):
@@ -251,7 +181,7 @@ class D90AdisTSSpec(SQLSubModel):
@d90.setter
def d90(self, d90):
self._d90 = d90
self.modified()
self._status.modified()
@property
def enabled(self):
@@ -260,4 +190,4 @@ class D90AdisTSSpec(SQLSubModel):
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
self._status.modified()
+70 -150
View File
@@ -1,5 +1,5 @@
# DIFAdisTS.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -23,7 +23,6 @@ from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
from Model.DIFAdisTS.DIFAdisTSSpec import DIFAdisTSSpec
@@ -34,16 +33,19 @@ class DIFAdisTS(SQLSubModel):
_sub_classes = [
DIFAdisTSSpec,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "default",
status=None, owner_scenario=-1):
super(DIFAdisTS, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(DIFAdisTS, self).__init__()
self._status = status
if id == -1:
self.id = DIFAdisTS._id_cnt
else:
self.id = id
self._name = name
self._method = None
self._dif = None
@@ -53,129 +55,76 @@ class DIFAdisTS(SQLSubModel):
self._types = ["iwasa", "fisher", "elder", "constante", "generique"]
self._data = []
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE dif_adists{ext}(
{cls.create_db_add_pamhyr_id()},
name TEXT NOT NULL,
method TEXT NOT NULL,
dif REAL NOT NULL,
b REAL,
c REAL,
enabled BOOLEAN NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()}
)
""")
DIFAdisTS._id_cnt = max(
DIFAdisTS._id_cnt + 1,
self.id + 1
)
if ext != "":
return True
@classmethod
def _db_create(cls, execute):
execute("""
CREATE TABLE dif_adists(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
method TEXT NOT NULL,
dif REAL NOT NULL,
b REAL,
c REAL,
enabled BOOLEAN NOT NULL
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if major == 0 and minor < 1:
if int(release) < 6:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
if not created:
return cls._update_submodel(execute, version, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "dif_adists"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, method, dif, b, c, enabled, scenario) " +
"SELECT pamhyr_id, name, method, dif, b, c, enabled, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, name, method, dif, b, c, enabled, scenario " +
"FROM dif_adists " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, name, method, dif, b, c, enabled " +
"FROM dif_adists"
)
if table is not None:
for row in table:
it = iter(row)
dif_id = row[0]
name = row[1]
method = row[2]
dif = row[3]
b = row[4]
c = row[5]
enabled = (row[6] == 1)
pid = next(it)
name = next(it)
method = next(it)
dif = next(it)
b = next(it)
c = next(it)
enabled = (next(it) == 1)
owner_scenario = next(it)
dif = cls(
id=pid,
DIF = cls(
id=dif_id,
name=name,
status=status,
owner_scenario=owner_scenario,
status=data['status']
)
dif.method = method
dif.dif = dif
dif.b = b
dif.c = c
dif.enabled = enabled
DIF.method = method
DIF.dif = dif
DIF.b = b
DIF.c = c
DIF.enabled = enabled
data['dif_default_id'] = pid
dif._data = DIFAdisTSSpec._db_load(execute, data)
data['dif_default_id'] = dif_id
DIF._data = DIFAdisTSSpec._db_load(execute, data)
loaded.add(pid)
new.append(dif)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new.append(DIF)
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"DELETE FROM dif_adists " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
)
execute(f"DELETE FROM dif_adists WHERE id = {self.id}")
method = ""
if self.method is not None:
@@ -196,13 +145,12 @@ class DIFAdisTS(SQLSubModel):
sql = (
"INSERT INTO " +
"dif_adists(" +
"pamhyr_id, name, method, dif, b, c, enabled, scenario" +
"id, name, method, dif, b, c, enabled" +
") " +
"VALUES (" +
f"{self.id}, '{self._db_format(self._name)}', " +
f"'{self._db_format(self._method)}', " +
f"{dif.id}, {b}, {c}, {self._enabled}, " +
f"{self._status.scenario_id}" +
f"{dif}, {b}, {c}, {self._enabled}" +
")"
)
@@ -210,9 +158,8 @@ class DIFAdisTS(SQLSubModel):
data['dif_default_id'] = self.id
execute(
"DELETE FROM dif_adists_spec " +
f"WHERE dif_default = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
"DELETE FROM dif_spec " +
f"WHERE dif_default = {self.id}"
)
for dif_spec in self._data:
@@ -220,16 +167,6 @@ class DIFAdisTS(SQLSubModel):
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self._data)
@@ -240,7 +177,7 @@ class DIFAdisTS(SQLSubModel):
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def method(self):
@@ -249,7 +186,7 @@ class DIFAdisTS(SQLSubModel):
@method.setter
def method(self, method):
self._method = method
self.modified()
self._status.modified()
@property
def types(self):
@@ -262,7 +199,7 @@ class DIFAdisTS(SQLSubModel):
@dif.setter
def dif(self, dif):
self._dif = dif
self.modified()
self._status.modified()
@property
def b(self):
@@ -271,7 +208,7 @@ class DIFAdisTS(SQLSubModel):
@b.setter
def b(self, b):
self._b = b
self.modified()
self._status.modified()
@property
def c(self):
@@ -280,7 +217,7 @@ class DIFAdisTS(SQLSubModel):
@c.setter
def c(self, c):
self._c = c
self.modified()
self._status.modified()
@property
def enabled(self):
@@ -289,45 +226,28 @@ class DIFAdisTS(SQLSubModel):
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
self._status.modified()
def new(self, index):
n = DIFAdisTSSpec(status=self._status)
self._data.insert(index, n)
self.modified()
self._status.modified()
return n
def delete(self, data):
list(
map(
lambda x: x.set_as_deleted(),
data
self._data = list(
filter(
lambda x: x not in data,
self._data
)
)
self.modified()
self._status.modified()
def delete_i(self, indexes):
list(
map(
lambda e: e[1].set_as_deleted(),
filter(
lambda e: e[0] in indexes,
enumerate(self._data)
)
)
)
self.modified()
for ind in indexes:
del self._data[ind]
self._status.modified()
def insert(self, index, data):
if data in self._data:
self.undelete([data])
else:
self._data.insert(index, data)
self.modified()
def undelete(self, lst):
for x in lst:
x.set_as_not_deleted()
self.modified()
self._data.insert(index, data)
self._status.modified()
+3 -6
View File
@@ -1,5 +1,5 @@
# DIFAdisTSList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
@@ -42,10 +42,7 @@ class DIFAdisTSList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM dif_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM dif_adists")
if data is None:
data = {}
+44 -125
View File
@@ -1,5 +1,5 @@
# DIFAdisTSSpec.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,7 +22,6 @@ from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
@@ -52,140 +51,66 @@ class DIFAdisTSSpec(SQLSubModel):
self._c = None
self._enabled = True
DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self.id)
DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE dif_adists_spec{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
dif_default INTEGER NOT NULL,
method TEXT NOT NULL,
reach INTEGER NOT NULL,
start_rk REAL NOT NULL,
end_rk REAL NOT NULL,
dif REAL NOT NULL,
b REAL,
c REAL,
enabled BOOLEAN NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(dif_default) REFERENCES dif_adists(id),
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
def _db_create(cls, execute):
execute("""
CREATE TABLE dif_spec(
id INTEGER NOT NULL PRIMARY KEY,
dif_default INTEGER NOT NULL,
method TEXT NOT NULL,
reach INTEGER NOT NULL,
start_rk REAL NOT NULL,
end_rk REAL NOT NULL,
dif REAL NOT NULL,
b REAL,
c REAL,
enabled BOOLEAN NOT NULL,
FOREIGN KEY(dif_default) REFERENCES dif_adists(id),
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
created = False
if major == 0 and minor < 1:
if int(release) < 6:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "dif_spec"
table_new = "dif_adists_spec"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table_new}_tmp " +
"(pamhyr_id, dif_default, " +
"method, reach, start_rk, end_rk, " +
"dif, b, c, scenario) " +
"SELECT pamhyr_id, dif_default, " +
"method, reach, start_rk, end_rk, " +
"dif, b, c, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table_new, reachs)
cls._db_update_to_0_2_0_set_dif_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_dif_pid(cls, execute, data):
pid_dif = data["id2pid"]["dif_adists"]
els = execute(
"SELECT pamhyr_id, dif_default FROM dif_adists_spec"
)
for row in els:
it = iter(row)
pid = next(it)
dif_id = next(it)
if dif_id == -1:
continue
execute(
f"UPDATE dif_adists_spec" +
f"SET dif_default = {pid_dif[dif_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, method, reach, start_rk, end_rk, " +
"dif, b, c, enabled, scenario " +
"FROM dif_adists_spec " +
f"WHERE dif_default = {data['dif_default_id']} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, dif_default, method, reach, start_rk, end_rk, " +
"dif, b, c, enabled " +
"FROM dif_spec " +
f"WHERE dif_default = {data['dif_default_id']} "
)
if table is not None:
for row in table:
it = iter(row)
id = next(it)
deleted = (next(it) == 1)
method = next(it)
reach = next(it)
start_rk = next(it)
end_rk = next(it)
dif = next(it)
b = next(it)
c = next(it)
enabled = (next(it) == 1)
owner_scenario = next(it)
id = row[0]
method = row[2]
reach = row[3]
start_rk = row[4]
end_rk = row[5]
dif = row[6]
b = row[7]
c = row[8]
enabled = (row[9] == 1)
new_spec = cls(
id=id,
method=method,
status=status,
owner_scenario=owner_scenario
status=data['status']
)
if deleted:
new_spec.set_as_deleted()
new_spec.reach = reach
new_spec.start_rk = start_rk
@@ -195,37 +120,31 @@ class DIFAdisTSSpec(SQLSubModel):
new_spec.c = c
new_spec.enabled = enabled
loaded.add(pid)
new.append(new_spec)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
dif_default = data['dif_default_id']
execute(
sql = (
"INSERT INTO " +
"dif_adists_spec(pamhyr_id, deleted, " +
"dif_default, method, reach, " +
"start_rk, end_rk, dif, b, c, enabled, scenario) " +
"dif_spec(id, dif_default, method, reach, " +
"start_rk, end_rk, dif, b, c, enabled) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}" +
f"{self.id}, " +
f"{dif_default}, " +
f"'{self._db_format(self._method)}', " +
f"{self._reach}, " +
f"{self._start_rk}, {self._end_rk}, " +
f"{self._dif}, {self._b}, {self._c}, " +
f"{self._start_rk}, " +
f"{self._end_rk}, " +
f"{self._dif}, " +
f"{self._b}, " +
f"{self._c}, " +
f"{self._enabled}" +
f"{self._status.scenario_id}" +
")"
)
execute(sql)
return True
+1 -1
View File
@@ -1,5 +1,5 @@
# Except.py -- Pamhyr model exceptions
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+87 -190
View File
@@ -1,5 +1,5 @@
# Friction.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -21,7 +21,6 @@ import logging
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
from numpy import interp
@@ -29,49 +28,49 @@ logger = logging.getLogger()
class Friction(SQLSubModel):
def __init__(self, id: int = -1,
reach=None,
status=None,
owner_scenario=-1):
super(Friction, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
_id_cnt = 0
self._reach = reach
def __init__(self, id: int = -1, name: str = "",
status=None):
super(Friction, self).__init__()
self._status = status
if id == -1:
self.id = Friction._id_cnt
else:
self.id = id
Friction._id_cnt = max(self.id + 1, Friction._id_cnt + 1)
self._name = name
self._edge = None
self._begin_rk = 0.0
self._end_rk = 0.0
self._begin_strickler = None
self._end_strickler = None
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE friction{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE friction(
id INTEGER NOT NULL PRIMARY KEY,
ind INTEGER NOT NULL,
begin_rk REAL NOT NULL,
end_rk REAL NOT NULL,
reach INTEGER NOT NULL,
begin_strickler INTEGER NOT NULL,
end_strickler INTEGER NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
FOREIGN KEY(begin_strickler) REFERENCES stricklers(pamhyr_id),
FOREIGN KEY(end_strickler) REFERENCES stricklers(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(reach) REFERENCES river_reach(id),
FOREIGN KEY(begin_strickler) REFERENCES stricklers(id),
FOREIGN KEY(end_strickler) REFERENCES stricklers(id)
)
""")
if ext != "":
return True
return cls._create_submodel(execute)
return True
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 11:
@@ -79,164 +78,49 @@ class Friction(SQLSubModel):
"RENAME COLUMN begin_kp TO begin_rk")
execute("ALTER TABLE friction RENAME COLUMN end_kp TO end_rk")
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE friction " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "friction"
reachs = data['id2pid']['river_reach']
stricklers = data['id2pid']['stricklers']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, begin_rk, end_rk, reach, " +
"begin_strickler, end_strickler, scenario) " +
"SELECT pamhyr_id, ind, begin_rk, end_rk, reach, " +
"begin_strickler, end_strickler, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table, reachs)
cls._db_update_to_0_2_0_set_stricklers_pid(execute, table, stricklers)
@classmethod
def _db_update_to_0_2_0_set_stricklers_pid(cls, execute, table,
stricklers):
els = execute(
f"SELECT pamhyr_id, begin_strickler, end_strickler FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
b_s_id = next(it)
e_s_id = next(it)
b_s = stricklers[b_s_id] if b_s_id != -1 else -1
e_s = stricklers[e_s_id] if e_s_id != -1 else -1
execute(
f"UPDATE {table} " +
f"SET begin_strickler = {b_s}, " +
f"end_strickler = {e_s} " +
f"WHERE pamhyr_id = {pid}"
)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
reach = data["reach"]
status = data["status"]
stricklers = data["stricklers"].stricklers
table = execute(
"SELECT pamhyr_id, deleted, begin_rk, end_rk, " +
"begin_strickler, end_strickler, scenario " +
"FROM friction " +
f"WHERE reach = {reach.pamhyr_id} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
"ORDER BY ind ASC"
"SELECT id, ind, begin_rk, end_rk, " +
"begin_strickler, end_strickler " +
f"FROM friction WHERE reach = {reach.id}"
)
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
begin_rk = next(it)
end_rk = next(it)
begin_strickler_pid = int(next(it))
end_strickler_pid = int(next(it))
owner_scenario = next(it)
id = row[0]
ind = row[1]
# Get stricklers
begin_strickler = None
if begin_strickler_pid != -1:
begin_strickler = next(
filter(
lambda s: s.id == begin_strickler_pid,
stricklers
)
)
if int(row[4]) == -1:
bs = None
else:
bs = next(filter(lambda s: s.id == row[4], stricklers))
end_strickler = None
if end_strickler_pid != -1:
end_strickler = next(
filter(
lambda s: s.id == begin_strickler_pid,
stricklers
)
)
if int(row[5]) == -1:
es = None
else:
es = next(filter(lambda s: s.id == row[5], stricklers))
new_friction = cls(
id=pid, status=data['status'],
owner_scenario=owner_scenario
)
if deleted:
f.set_as_deleted()
# Create friction
sec = cls(status=status)
sec.edge = reach
sec.begin_rk = row[2]
sec.end_rk = row[3]
sec.begin_strickler = bs
sec.end_strickler = es
new_friction.reach = reach
new_friction.begin_rk = begin_rk
new_friction.end_rk = end_rk
new_friction.begin_strickler = begin_strickler
new_friction.end_strickler = begin_strickler
loaded.add(pid)
new.append(new_friction)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new.append((ind, sec))
return new
def cloned_for(self, reach):
new = Friction(reach=reach, status=self._status)
new.reach = reach
new.begin_rk = self._begin_rk
new.end_rk = self._end_rk
new.begin_strickler = self._begin_strickler
new.end_strickler = self._begin_strickler
return new
def must_be_saved(self):
ssi = self._status.scenario_id
return (
self._begin_strickler._owner_scenario == ssi
# or self._end_strickler._owner_scenario == ssi
or super(Friction, self).must_be_saved()
)
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
ind = data["ind"]
b_s_id = -1
@@ -249,34 +133,47 @@ class Friction(SQLSubModel):
execute(
"INSERT INTO " +
"friction(pamhyr_id, deleted, ind, begin_rk, end_rk, " +
"reach, begin_strickler, end_strickler, scenario) " +
"friction(id, ind, begin_rk, end_rk, " +
"reach, begin_strickler, end_strickler) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"{ind}, {self._begin_rk}, {self._end_rk}, " +
f"{self._reach.id}, {b_s_id}, {e_s_id}, " +
f"{self._status.scenario_id}" +
f"{self.id}, {ind}, " +
f"{self._begin_rk}, {self._end_rk}, " +
f"{self._edge.id}, " +
f"{b_s_id}, {e_s_id}" +
")"
)
return True
@property
def reach(self):
return self._reach
def name(self):
return self._name
@reach.setter
def reach(self, reach):
self._reach = reach
if (reach is not None and
self._begin_rk == 0.0 and
self._end_rk == 0.0):
self._begin_rk = self._reach.reach.get_rk_min()
self._end_rk = self._reach.reach.get_rk_max()
@name.setter
def name(self, name):
self._name = name
self._status.modified()
def has_reach(self):
return self._reach is not None
@property
def edge(self):
return self._edge
@property
def reach(self):
return self._edge
@edge.setter
def edge(self, edge):
self._edge = edge
if (edge is not None and
self._begin_rk == 0.0 and
self._end_rk == 0.0):
self._begin_rk = self._edge.reach.get_rk_min()
self._end_rk = self._edge.reach.get_rk_max()
self._status.modified()
def has_edge(self):
return self._edge is not None
def has_coefficient(self):
return (
@@ -284,7 +181,7 @@ class Friction(SQLSubModel):
)
def is_full_defined(self):
return self.has_reach() and self.has_coefficient()
return self.has_edge() and self.has_coefficient()
@property
def begin_rk(self):
@@ -292,11 +189,11 @@ class Friction(SQLSubModel):
@begin_rk.setter
def begin_rk(self, begin_rk):
if self._reach is None:
if self._edge is None:
self._begin_rk = begin_rk
else:
_min = self._reach.reach.get_rk_min()
_max = self._reach.reach.get_rk_max()
_min = self._edge.reach.get_rk_min()
_max = self._edge.reach.get_rk_max()
if _min <= begin_rk <= _max:
self._begin_rk = begin_rk
@@ -309,11 +206,11 @@ class Friction(SQLSubModel):
@end_rk.setter
def end_rk(self, end_rk):
if self._reach is None:
if self._edge is None:
self._end_rk = end_rk
else:
_min = self._reach.reach.get_rk_min()
_max = self._reach.reach.get_rk_max()
_min = self._edge.reach.get_rk_min()
_max = self._edge.reach.get_rk_max()
if _min <= end_rk <= _max:
self._end_rk = end_rk
+18 -38
View File
@@ -1,5 +1,5 @@
# FrictionList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -20,10 +20,9 @@ import logging
from copy import copy
from tools import trace, timer
from functools import reduce
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Friction.Friction import Friction
@@ -40,30 +39,30 @@ class FrictionList(PamhyrModelList):
execute("ALTER TABLE `section` RENAME TO `friction`")
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
if version == "0.0.0":
logger.info(f"Update friction TABLE from {version}")
cls._db_update_0_0_1(execute, version)
return cls._update_submodel(execute, version, data)
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data['status'])
new._lst = Friction._db_load(
ilst = Friction._db_load(
execute, data
)
new._lst = list(map(lambda x: x[1], sorted(ilst)))
return new
def _db_save(self, execute, data=None):
frictions = self.lst
reach = data["reach"]
execute(
f"DELETE FROM friction WHERE reach = {reach.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM friction WHERE reach = {reach.id}")
ok = True
ind = 0
@@ -87,38 +86,19 @@ class FrictionList(PamhyrModelList):
)
)
@property
def lst(self):
if not isinstance(self._lst, list):
self._lst = self._get_frictions_list()
return self._lst
@property
def frictions(self):
return self.lst
def new(self, reach, index):
n = Friction(reach=reach, status=self._status)
def new(self, index):
n = Friction(status=self._status)
self._lst.insert(index, n)
self._status.modified()
return n
def _splited_for(self, reach):
new = FrictionList(status=self._status)
rk_min, rk_max = reduce(
lambda mm, p: (
min(mm[0], p.rk),
max(mm[1], p.rk)
),
reach.reach.profiles,
((999 ** 100), -(999 ** 100))
)
frictions = list(
filter(
lambda f: ((rk_min <= f.begin_rk <= rk_max) or
(rk_min <= f.end_rk <= rk_max)),
self._lst))
for ind, friction in enumerate(frictions):
n = friction.cloned_for(reach)
new._lst.insert(ind, n)
new._status.modified()
return new
-368
View File
@@ -1,368 +0,0 @@
# GeoTIFF.py -- Pamhyr
# Copyright (C) 2024-2025 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 os
import struct
import logging
from functools import reduce
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
try:
import rasterio
import rasterio.control
import rasterio.crs
import rasterio.sample
import rasterio.vrt
import rasterio._features
from rasterio.io import MemoryFile
_rasterio_loaded = True
except Exception as e:
print(f"Module 'rasterio' is not available: {e}")
_rasterio_loaded = False
logger = logging.getLogger()
class GeoTIFF(SQLSubModel):
_sub_classes = []
def __init__(self, id: int = -1, enabled=True,
name="", description="",
path="", coordinates=None,
status=None, owner_scenario=-1):
super(GeoTIFF, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._enabled = enabled
self._name = f"GeoTIFF #{self._pamhyr_id}" if name == "" else name
self._description = description
self._file_bytes = b''
if coordinates is None:
self._coordinates = {
"bottom": 0.0,
"top": 0.0,
"left": 0.0,
"right": 0.0,
}
else:
self._coordinates = coordinates
self._file_name = ""
if path != "":
self.read_file(path)
self._memfile = None
def __getitem__(self, key):
value = None
if key == "enabled":
value = self._enabled
elif key == "name":
value = self.name
elif key == "description":
value = self.description
elif key == "file_name":
value = self.file_name
elif key == "coordinates":
value = self.coordinates
elif key == "coordinates_bottom":
value = self.coordinates["bottom"]
elif key == "coordinates_top":
value = self.coordinates["top"]
elif key == "coordinates_left":
value = self.coordinates["left"]
elif key == "coordinates_right":
value = self.coordinates["right"]
elif key == "memfile":
value = self.memfile
return value
def __setitem__(self, key, value):
if key == "enabled":
self.enabled = value
elif key == "name":
self.name = value
elif key == "description":
self.description = value
elif key == "file_name":
if self._file_name != value:
self.read_file(value)
elif key == "coordinates":
self.coordinates = value
elif key == "coordinates_bottom" or key == "bottom":
self.coordinates["bottom"] = value
elif key == "coordinates_top" or key == "top":
self.coordinates["top"] = value
elif key == "coordinates_left" or key == "left":
self.coordinates["left"] = value
elif key == "coordinates_right" or key == "right":
self.coordinates["right"] = value
self.modified()
@property
def enabled(self):
return self._enabled
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
def is_enabled(self):
return self._enabled
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
@property
def description(self):
return self._description
@description.setter
def description(self, description):
self._description = description
self.modified()
@property
def file_name(self):
return self._file_name
@file_name.setter
def file_name(self, file_name):
self._file_name = file_name
self.modified()
@property
def coordinates(self):
return self._coordinates
@coordinates.setter
def coordinates(self, coordinates):
self._coordinates = coordinates
self.modified()
@property
def coord_bottom(self):
if self._coordinates is None:
return 0.0
return self._coordinates["bottom"]
@property
def coord_top(self):
if self._coordinates is None:
return 0.0
return self._coordinates["top"]
@property
def coord_left(self):
if self._coordinates is None:
return 0.0
return self._coordinates["left"]
@property
def coord_right(self):
if self._coordinates is None:
return 0.0
return self._coordinates["right"]
@property
def memfile(self):
if not _rasterio_loaded:
return None
if self._file_bytes == b'':
return None
if self._memfile is None:
self._memfile = MemoryFile()
self._memfile.write(self._file_bytes)
return self._memfile
def read_file(self, path):
logger.debug(f"Read GeoTIFF file at : '{path}'")
self._file_name = path
self._file_bytes = b''
self._memfile = None
nbytes = 0
with open(path, "rb") as f:
while True:
data = f.read(4096)
if not data:
break
nbytes += len(data)
self._file_bytes += data
logger.debug(f"Read GeoTIFF: {nbytes} bytes readed")
def write_file(self, path):
with open(path, "w+b") as f:
f.write(self._file_bytes)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE geotiff{ext} (
{cls.create_db_add_pamhyr_id()},
enabled BOOLEAN NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL,
description TEXT NOT NULL,
file_name TEXT NOT NULL,
file_bytes BLOB NOT NULL,
coordinates_bottom REAL NOT NULL,
coordinates_top REAL NOT NULL,
coordinates_left REAL NOT NULL,
coordinates_right REAL 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)
if major == "0" and int(minor) == 2:
if int(release) < 3:
cls._db_create(execute)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, enabled, deleted, " +
"name, description, file_name, file_bytes, " +
"coordinates_bottom, coordinates_top, " +
"coordinates_left, coordinates_right, " +
"scenario " +
"FROM geotiff " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
)
if table is not None:
for row in table:
it = iter(row)
id = next(it)
enabled = (next(it) == 1)
deleted = (next(it) == 1)
name = next(it)
description = next(it)
file_name = next(it)
file_bytes = next(it)
coordinates_bottom = next(it)
coordinates_top = next(it)
coordinates_left = next(it)
coordinates_right = next(it)
owner_scenario = next(it)
f = cls(
id=id, enabled=enabled, name=name,
description=description, coordinates={
"bottom": coordinates_bottom,
"top": coordinates_top,
"left": coordinates_left,
"right": coordinates_right,
},
status=data['status'],
owner_scenario=owner_scenario
)
if deleted:
f.set_as_deleted()
f._file_bytes = file_bytes
loaded.add(id)
new.append(f)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"INSERT INTO geotiff (" +
"pamhyr_id, enabled, deleted, " +
"name, description, file_name, file_bytes, " +
"coordinates_bottom, coordinates_top, " +
"coordinates_left, coordinates_right, " +
"scenario) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
self._pamhyr_id,
self._enabled,
self.is_deleted(),
self.name,
self.description,
self.file_name,
self._file_bytes,
self.coordinates['bottom'],
self.coordinates['top'],
self.coordinates['left'],
self.coordinates['right'],
self._status.scenario_id,
)
return True
-59
View File
@@ -1,59 +0,0 @@
# GeoTIFFList.py -- Pamhyr
# Copyright (C) 2024-2025 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 tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.GeoTIFF.GeoTIFF import GeoTIFF
class GeoTIFFList(PamhyrModelList):
_sub_classes = [GeoTIFF]
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data["status"])
new._lst = GeoTIFF._db_load(execute, data)
return new
def _db_save(self, execute, data=None):
ok = True
# Delete previous data
execute(
"DELETE FROM geotiff " +
f"WHERE scenario = {self._status.scenario_id}"
)
for gt in self._lst:
ok &= gt._db_save(execute, data)
return ok
@property
def files(self):
return self.lst
def new(self, index):
n = GeoTIFF(status=self._status)
self.insert(index, n)
self._status.modified()
return n
+8 -14
View File
@@ -1,5 +1,5 @@
# Point.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -16,18 +16,14 @@
# -*- coding: utf-8 -*-
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
class Point(SQLSubModel):
def __init__(self, id: int = -1, name: str = "",
profile=None, status=None,
owner_scenario=-1):
super(Point, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
class Point(object):
def __init__(self, name: str = "", profile=None, status=None):
super(Point, self).__init__()
self._status = status
self._name = name
self._profile = profile
@@ -47,8 +43,6 @@ class Point(SQLSubModel):
else:
self._sl = sl
self.modified()
@property
def name(self):
return self._name
@@ -56,9 +50,9 @@ class Point(SQLSubModel):
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
def is_named(self):
def point_is_named(self):
"""
Returns:
True if the point is named.
+1 -1
View File
@@ -1,5 +1,5 @@
# PointXY.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+50 -206
View File
@@ -1,5 +1,5 @@
# PointXYZ.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -16,40 +16,30 @@
# -*- coding: utf-8 -*-
import logging
from math import dist
import numpy as np
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
from Model.Geometry.Point import Point
logger = logging.getLogger()
class PointXYZ(Point):
class PointXYZ(Point, SQLSubModel):
_sub_classes = []
def __init__(self, id: int = -1,
x: float = 0.0, y: float = 0.0, z: float = 0.0,
name: str = "", profile=None,
status=None, owner_scenario=-1):
def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0,
name: str = "", profile=None, status=None):
super(PointXYZ, self).__init__(
id=id, name=name, profile=profile, status=status,
owner_scenario=owner_scenario
)
name=name, profile=profile, status=status)
self._x = float(x)
self._y = float(y)
self._z = float(z)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE geometry_pointXYZ{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE geometry_pointXYZ(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ind INTEGER NOT NULL,
name TEXT,
x INTEGER NOT NULL,
@@ -57,19 +47,17 @@ class PointXYZ(Point):
z INTEGER NOT NULL,
profile INTEGER NOT NULL,
sl INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(profile)
REFERENCES geometry_profileXYZ(pamhyr_id),
FOREIGN KEY(sl) REFERENCES sedimentary_layer(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(profile) REFERENCES geometry_profileXYZ(id),
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
cls._update_submodel(execute, version)
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
@@ -80,174 +68,63 @@ class PointXYZ(Point):
REFERENCES sedimentary_layer(id)
"""
)
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE geometry_pointXYZ " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "geometry_pointXYZ"
id2pid = data['id2pid']
profiles = id2pid['geometry_profileXYZ']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, name, x, y, z, profile, sl, scenario) " +
"SELECT pamhyr_id, ind, name, x, y, z, profile, sl, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_profile_pid(execute, table, profiles)
if 'sedimentary_layer' in id2pid:
sl = id2pid['sedimentary_layer']
cls._db_update_to_0_2_0_set_sl_pid(execute, table, sl)
@classmethod
def _db_update_to_0_2_0_set_profile_pid(cls, execute, table, profiles):
els = execute(
f"SELECT pamhyr_id, profile FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
profile_id = next(it)
if profile_id not in profiles:
logger.warning(
f"This point as no associated profile ({profile_id})"
)
continue
execute(
f"UPDATE {table} " +
f"SET profile = {profiles[profile_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_update_to_0_2_0_set_sl_pid(cls, execute, table, sl):
els = execute(
f"SELECT pamhyr_id, sl FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
sl_id = next(it)
if sl_id == -1:
continue
execute(
f"UPDATE {table} " +
f"SET sl = {sl[sl_id]} " +
f"WHERE pamhyr_id = {pid}"
)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data["status"]
profile = data["profile"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, ind, deleted, " +
"name, x, y, z, sl, scenario " +
"SELECT ind, name, x, y, z, sl " +
"FROM geometry_pointXYZ " +
f"WHERE profile = {profile.pamhyr_id} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
"ORDER BY ind ASC"
f"WHERE profile = {profile.id}"
)
# Fill points list with new point
for row in table:
it = iter(row)
ind = row[0]
name = row[1]
x = row[2]
y = row[3]
z = row[4]
sl = row[5]
pid = next(it)
ind = next(it)
deleted = (next(it) == 1)
name = next(it)
x = next(it)
y = next(it)
z = next(it)
sl = next(it)
owner_scenario = next(it)
point = cls(
id=pid,
new = cls(
name=name,
x=x, y=y, z=z,
profile=profile,
status=status,
owner_scenario=owner_scenario
status=status
)
if deleted:
point.set_as_deleted()
if sl == -1 or sl is None:
point._sl = None
new._sl = None
else:
point._sl = next(
new._sl = next(
filter(
lambda s: s.pamhyr_id == sl,
lambda s: s.id == sl,
data["sediment_layers_list"].sediment_layers
)
)
loaded.add(pid)
new.append((ind, point))
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
yield ind, new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
profile = data["profile"]
ind = data["ind"]
sl = self._sl.pamhyr_id if self._sl is not None else -1
sl = self._sl.id if self._sl is not None else -1
execute(
sql = (
"INSERT INTO " +
"geometry_pointXYZ(pamhyr_id, deleted, ind, name, " +
"x, y, z, profile, sl, scenario) " +
"geometry_pointXYZ(ind, name, x, y, z, profile, sl) " +
"VALUES (" +
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(self._name)}', " +
f"{self.x}, {self.y}, {self.z}, " +
f"{profile.pamhyr_id}, {sl}, " +
f"{self._status.scenario_id}" +
f"{profile.id}, {sl}" +
")"
)
execute(sql)
return True
@@ -273,44 +150,9 @@ class PointXYZ(Point):
return point
def copy(self):
new_p = PointXYZ(
id=-1,
name=self.name,
x=self.x, y=self.y, z=self.z,
profile=self._profile,
status=self._status
)
if self.is_deleted():
new_p.set_as_deleted()
new_p._sl = self.sl
new_p.modified()
return new_p
def cloned_for(self, new_profile):
new_p = PointXYZ(
id=-1,
name=self.name,
x=self.x, y=self.y, z=self.z,
profile=new_profile,
status=self._status
)
if self.is_deleted():
new_p.set_as_deleted()
new_p._sl = self.sl
new_p.modified()
return new_p
def __repr__(self):
return f"({self._x}, {self._y}, {self._z}, {self._name})"
def __gt__(self, other):
return self.y > other.y
@property
def x(self):
return self._x
@@ -318,7 +160,7 @@ class PointXYZ(Point):
@x.setter
def x(self, value):
self._x = float(value)
self.modified()
self._status.modified()
@property
def y(self):
@@ -327,7 +169,7 @@ class PointXYZ(Point):
@y.setter
def y(self, value):
self._y = float(value)
self.modified()
self._status.modified()
@property
def z(self):
@@ -336,10 +178,7 @@ class PointXYZ(Point):
@z.setter
def z(self, value):
self._z = float(value)
self.modified()
def get_coordinate(self):
return (self._x, self._y, self._z)
self._status.modified()
def is_nan(self):
"""
@@ -387,13 +226,8 @@ class PointXYZ(Point):
a = PointXYZ.distance(p1, p2)
b = PointXYZ.distance(p2, p3)
c = PointXYZ.distance(p3, p1)
s = float((a + b + c) / 2)
res = abs(
s * (s - a) * (s - b) * (s - c)
) ** 0.5
return res
s = (a + b + c) / 2
return (s*(s-a) * (s-b)*(s-c)) ** 0.5
def dist_from_seg(self, p1, p2):
a2 = pow(PointXYZ.distance(self, p1), 2)
@@ -406,3 +240,13 @@ class PointXYZ(Point):
d = np.sqrt(abs(a2 - pow((c2 - b2 + a2) / (2*np.sqrt(c2)), 2)))
return d
def copy(self):
p = PointXYZ(self.x,
self.y,
self.z,
self.name,
self._profile,
self._status)
p.sl = self.sl
return p
+56 -47
View File
@@ -1,5 +1,5 @@
# Profile.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -28,15 +28,23 @@ logger = logging.getLogger()
class Profile(object):
_id_cnt = 0
def __init__(self, id: int = -1, num: int = 0,
rk: float = 0.0, name: str = "",
code1: int = 0, code2: int = 0,
_type: str = "", reach=None,
status=None, owner_scenario=-1):
super(Profile, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(Profile, self).__init__()
self._status = status
if id == -1:
self.id = Profile._id_cnt
else:
self.id = id
Profile._id_cnt = max(self.id + 1, Profile._id_cnt + 1)
self._num = int(num)
self._code1 = int(code1)
@@ -58,19 +66,24 @@ class Profile(object):
return len(self.points)
def _get_points_list(self):
return list(self._points)
# Points list generator is type (int, Point) with the first
# element the index of the Point in list
return list(
map(
lambda p: p[1],
sorted(
self._points,
key=lambda p: p[0]
)
)
)
@property
def points(self):
if not isinstance(self._points, list):
self._points = self._get_points_list()
return list(
filter(
lambda p: not p.is_deleted(),
self._points
)
)
return self._points
def point(self, index):
return self.points[index]
@@ -109,7 +122,7 @@ class Profile(object):
@num.setter
def num(self, value: int):
self._num = int(value)
self.modified()
self._status.modified()
@property
def code1(self):
@@ -122,7 +135,7 @@ class Profile(object):
@code1.setter
def code1(self, value: int):
self._code1 = int(value)
self.modified()
self._status.modified()
@property
def code2(self):
@@ -135,7 +148,7 @@ class Profile(object):
@code2.setter
def code2(self, value: int):
self._code2 = int(value)
self.modified()
self._status.modified()
@property
def nb_points(self):
@@ -152,7 +165,7 @@ class Profile(object):
@rk.setter
def rk(self, value: float):
self._rk = float(value)
self.modified()
self._status.modified()
@property
def name(self):
@@ -165,7 +178,7 @@ class Profile(object):
@name.setter
def name(self, value: str):
self._name = value.strip()
self.modified()
self._status.modified()
@property
def sl(self):
@@ -178,7 +191,7 @@ class Profile(object):
@sl.setter
def sl(self, value):
self._sl = value
self.modified()
self._status.modified()
@property
def profile_type(self):
@@ -191,7 +204,7 @@ class Profile(object):
@profile_type.setter
def profile_type(self, value: str):
self._profile_type = value
self.modified()
self._status.modified()
def point(self, i: int):
if i < len(self.points):
@@ -206,7 +219,7 @@ class Profile(object):
The list of named point
"""
return [point for point in self.points
if point.is_named()]
if point.point_is_named()]
def insert_point(self, index: int, point: Point):
"""Insert point at index.
@@ -218,25 +231,21 @@ class Profile(object):
Returns:
Nothing.
"""
if point in self._points:
point.set_as_not_deleted()
else:
self._points.insert(index, point)
self.modified()
self.points.insert(index, point)
self._status.modified()
def delete_i(self, indexes: list):
list(
self._points = list(
map(
lambda e: e[1].set_as_deleted(),
lambda e: e[1],
filter(
lambda e: e[0] in indexes,
lambda e: e[0] not in indexes,
enumerate(self.points)
)
)
)
self.modified()
self._status.modified()
def delete_points(self, points):
"""Delete some elements in profile list
@@ -247,31 +256,31 @@ class Profile(object):
Returns:
Nothing.
"""
list(
map(
lambda p: p.set_as_deleted(),
points
self._points = list(
filter(
lambda p: p not in points,
self.points
)
)
self.modified()
self._status.modified()
# Move
def move_up_point(self, index: int):
if index < len(self._points):
if index < len(self.points):
next = index - 1
p = self._points
p = self.points
p[index], p[next] = p[next], p[index]
self.modified()
self._status.modified()
def move_down_point(self, index: int):
if index >= 0:
prev = index + 1
p = self._points
p = self.points
p[index], p[prev] = p[prev], p[index]
self.modified()
self._status.modified()
# Sort
@@ -284,32 +293,32 @@ class Profile(object):
def predicate(p): return p.z
self._points = sorted(
self._points,
self.points,
key=predicate,
reverse=is_reversed
)
self.modified()
self._status.modified()
@timer
def sort_with_indexes(self, indexes: list):
if len(self._points) != len(indexes):
if len(self.points) != len(indexes):
logger.critical("Indexes list do not correspond to point list")
self._points = list(
map(
lambda x: x[1],
sorted(
enumerate(self._points),
enumerate(self.points),
key=lambda x: indexes[x[0]]
)
)
)
self.modified()
self._status.modified()
@timer
def reverse(self):
self._points.reverse()
self.modified()
self._status.modified()
# Sediment Layers
+126 -340
View File
@@ -1,5 +1,5 @@
# ProfileXYZ.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -16,20 +16,17 @@
# -*- coding: utf-8 -*-
import queue
import random
import logging
import numpy as np
from typing import List
from functools import reduce
from dataclasses import dataclass
from tools import timer, flatten
from tools import timer
from shapely import geometry
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import ClipboardFormatError
from Model.Scenario import Scenario
from Model.Geometry.Profile import Profile
from Model.Geometry.PointXYZ import PointXYZ
from Model.Geometry.Vector_1d import Vector1d
@@ -51,14 +48,13 @@ class ProfileXYZ(Profile, SQLSubModel):
def __init__(self,
id: int = -1,
ind: int = -1,
name: str = "",
rk: float = 0.,
reach=None,
num=0,
nb_point: int = 0,
code1: int = 0, code2: int = 0,
status=None, owner_scenario=-1):
status=None):
"""ProfileXYZ constructor
Args:
@@ -73,16 +69,15 @@ class ProfileXYZ(Profile, SQLSubModel):
"""
super(ProfileXYZ, self).__init__(
id=id,
num=num,
name=name,
num=num, rk=rk,
rk=rk,
code1=code1, code2=code2,
_type="XYZ",
reach=reach,
status=status,
owner_scenario=owner_scenario
)
self._db_ind = ind
self.tab = Tabulation([], [], [])
self.tab_up_to_date = False
self.time_z = 0.0
@@ -92,11 +87,10 @@ class ProfileXYZ(Profile, SQLSubModel):
self.station_up_to_date = False
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE geometry_profileXYZ{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE geometry_profileXYZ(
id INTEGER NOT NULL PRIMARY KEY,
ind INTEGER NOT NULL,
name TEXT,
reach INTEGER NOT NULL,
@@ -105,21 +99,15 @@ class ProfileXYZ(Profile, SQLSubModel):
code1 INTEGER NOT NULL,
code2 INTEGER NOT NULL,
sl INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
FOREIGN KEY(sl) REFERENCES sedimentary_layer(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(reach) REFERENCES river_reach(id),
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
)
""")
if ext == "_tmp":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
rl = int(release)
@@ -140,215 +128,83 @@ class ProfileXYZ(Profile, SQLSubModel):
"""
)
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE geometry_profileXYZ " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "geometry_profileXYZ"
id2pid = data['id2pid']
reachs = id2pid['river_reach']
cls._db_update_cleanup_points(execute)
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, name, reach, rk, " +
"num, code1, code2, sl, scenario) " +
"SELECT pamhyr_id, ind, name, reach, rk, " +
"num, code1, code2, sl, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table, reachs)
if 'sedimentary_layer' in id2pid:
sl = id2pid['sedimentary_layer']
cls._db_update_to_0_2_0_set_sl_pid(execute, table, sl)
@classmethod
def _db_update_cleanup_points(cls, execute):
profiles = set(
map(
str,
flatten(
execute(
"SELECT id FROM geometry_profileXYZ"
)
)
)
)
execute(
"DELETE FROM geometry_pointXYZ " +
f"WHERE profile NOT IN ({', '.join(profiles)})"
)
@classmethod
def _db_update_to_0_2_0_set_sl_pid(cls, execute, table, sl):
els = execute(
f"SELECT pamhyr_id, sl FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
sl_id = next(it)
if sl_id == -1:
continue
execute(
f"UPDATE {table} " +
f"SET sl = {sl[sl_id]} " +
f"WHERE pamhyr_id = {pid}"
)
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
new = []
profiles = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
reach = data["reach"]
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, ind, deleted, name, rk, num, " +
"code1, code2, sl, scenario " +
"SELECT id, ind, name, rk, num, code1, code2, sl " +
"FROM geometry_profileXYZ " +
f"WHERE reach = {reach.id} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
"ORDER BY ind ASC"
f"WHERE reach = {reach.id}"
)
for row in table:
it = iter(row)
id = row[0]
ind = row[1]
name = row[2]
rk = row[3]
num = row[5]
code1 = row[5]
code2 = row[6]
sl = row[7]
pid = next(it)
ind = next(it)
deleted = (next(it) == 1)
name = next(it)
rk = next(it)
num = next(it)
code1 = next(it)
code2 = next(it)
sl = next(it)
owner_scenario = next(it)
profile = cls(
id=pid, ind=ind, num=num,
new = cls(
id=id, num=num,
name=name, rk=rk,
code1=code1, code2=code2,
reach=reach,
status=status,
owner_scenario=owner_scenario
status=status
)
if deleted:
profile.set_as_deleted()
if sl == -1 or sl is None:
profile._sl = None
new._sl = None
else:
profile._sl = next(
new._sl = next(
filter(
lambda s: s.id == sl,
data["sediment_layers_list"].sediment_layers
),
None
)
), None)
loaded_save = data['loaded_pid']
data['loaded_pid'] = set()
data["profile"] = profile
data["profile"] = new
new._points = PointXYZ._db_load(execute, data.copy())
profile._points = list(
map(
lambda ip: ip[1],
sorted(
PointXYZ._db_load(execute, data.copy()),
key=lambda ip: ip[0]
)
)
)
data['loaded_pid'] = loaded_save
loaded.add(pid)
new.append(profile)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
yield ind, new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
ok = True
ind = data["ind"]
sl = self._sl.pamhyr_id if self._sl is not None else -1
sl = self._sl.id if self._sl is not None else -1
execute(
sql = (
"INSERT OR REPLACE INTO " +
"geometry_profileXYZ(pamhyr_id, deleted, ind, name, reach, " +
"rk, num, code1, code2, sl, scenario) " +
"geometry_profileXYZ(id, ind, name, reach, " +
"rk, num, code1, code2, sl) " +
"VALUES (" +
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(self._name)}', " +
f"{self.reach.pamhyr_id}, {self.rk}, {self.num}, " +
f"{self.code1}, {self.code1}, {sl}, {self._status.scenario_id}" +
f"{self.id}, {ind}, '{self._db_format(self._name)}', " +
f"{self.reach.id}, {self.rk}, {self.num}, " +
f"{self.code1}, {self.code1}, {sl}" +
")"
)
execute(sql)
points = self.points
data["profile"] = self
execute(
"DELETE FROM geometry_pointXYZ " +
f"WHERE profile = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM geometry_pointXYZ WHERE profile = {self.id}")
ind = 0
for point in self._points:
for point in points:
data["ind"] = ind
ok &= point._db_save(execute, data)
ind += 1
return ok
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for p in self._points:
p._data_traversal(predicate, modifier, data)
@classmethod
def from_data(cls, header, data):
profile = None
@@ -381,47 +237,6 @@ class ProfileXYZ(Profile, SQLSubModel):
return profile
def copy(self):
new_p = ProfileXYZ(
id=-1,
name=self.name,
rk=self.rk,
reach=self.reach,
status=self._status
)
if self.is_deleted():
new_p.set_as_deleted()
new_p._sl = self.sl
for point in self._points:
p = point.copy()
print(p)
new_p._points.append(p)
new_p.modified()
return new_p
def cloned_for(self, new_reach):
new_p = ProfileXYZ(
id=-1,
name=self.name,
rk=self.rk,
reach=new_reach,
status=self._status
)
if self.is_deleted():
new_p.set_as_deleted()
new_p._sl = self.sl
for point in self._points:
p = point.cloned_for(new_p)
new_p._points.append(p)
new_p.modified()
return new_p
def point_from_data(self, header, data):
def float_format(s: str):
return float(
@@ -462,12 +277,6 @@ class ProfileXYZ(Profile, SQLSubModel):
return name
def modified(self):
super(ProfileXYZ, self).modified()
self.tab_up_to_date = False
self.station_up_to_date = False
def x(self):
return [point.x for point in self.points]
@@ -508,20 +317,11 @@ class ProfileXYZ(Profile, SQLSubModel):
Nothing.
"""
for point in list_points:
named_args = {
"x": point[0],
"y": point[1],
"z": point[2],
"name": point[3],
}
pt = PointXYZ(
id=-1, **named_args, profile=self,
status=self._status
)
self._points.append(pt)
self.modified()
pt = PointXYZ(*point, profile=self, status=self._status)
self.points.append(pt)
self._status.modified()
self.tab_up_to_date = False
self.station_up_to_date = False
def get_point_i(self, index: int) -> PointXYZ:
"""Get point at index.
@@ -580,13 +380,11 @@ class ProfileXYZ(Profile, SQLSubModel):
Returns:
Nothing.
"""
point_xyz = PointXYZ(
x=0., y=0., z=0., profile=self,
status=self._status
)
point_xyz = PointXYZ(0., 0., 0., profile=self, status=self._status)
self.points.append(point_xyz)
self.modified()
self._status.modified()
self.tab_up_to_date = False
self.station_up_to_date = False
def insert(self, index: int):
"""Insert a new point at index.
@@ -597,16 +395,11 @@ class ProfileXYZ(Profile, SQLSubModel):
Returns:
The new point.
"""
x, y, z = (0., 0., 0.)
if 0 < len(self._points) >= index:
x, y, z = self._points[index - 1].get_coordinate()
point = PointXYZ(x=x, y=y, z=z, profile=self, status=self._status)
self._points.insert(index, point)
self.modified()
point = PointXYZ(0., 0., 0., profile=self, status=self._status)
self.points.insert(index, point)
self._status.modified()
self.tab_up_to_date = False
self.station_up_to_date = False
return point
def filter_isnan(self, lst):
@@ -749,8 +542,8 @@ class ProfileXYZ(Profile, SQLSubModel):
if zz[i] >= z and zz[i+1] < z:
y = np.interp(
z,
[zz[i+1], zz[i]],
[station[i+1], station[i]]
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
line.append([y, z])
@@ -839,8 +632,11 @@ class ProfileXYZ(Profile, SQLSubModel):
for i in range(self.number_points-1):
if zz[i] > z and zz[i+1] <= z:
fact = (z - zz[i]) / (zz[i+1] - zz[i])
y = station[i] + fact * (station[i+1] - station[i])
y = np.interp(
z,
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
start.append(y)
end = []
@@ -849,8 +645,11 @@ class ProfileXYZ(Profile, SQLSubModel):
for i in reversed(range(self.number_points-1)):
if zz[i] <= z and zz[i+1] > z:
fact = (z - zz[i]) / (zz[i+1] - zz[i])
y = station[i] + fact * (station[i+1] - station[i])
y = np.interp(
z,
[zz[i], zz[i+1]],
[station[i], station[i+1]]
)
end.append(y)
if len(start) != len(end):
@@ -881,31 +680,33 @@ class ProfileXYZ(Profile, SQLSubModel):
# Interpolate points at river left side
if (i_left > 0):
if abs(self.point(i_left - 1).z - self.point(i_left).z) < 1e-20:
pt_left = self.point(i_left)
else:
fact = (z - self.point(i_left).z) / \
(self.point(i_left - 1).z - self.point(i_left).z)
x = self.point(i_left).x + fact * \
(self.point(i_left - 1).x - self.point(i_left).x)
y = self.point(i_left).y + fact * \
(self.point(i_left - 1).y - self.point(i_left).y)
pt_left = PointXYZ(x=x, y=y, z=z, name="wl_left")
x = np.interp(
z,
[self.point(i_left).z, self.point(i_left - 1).z],
[self.point(i_left).x, self.point(i_left - 1).x]
)
y = np.interp(
z,
[self.point(i_left).z, self.point(i_left - 1).z],
[self.point(i_left).y, self.point(i_left - 1).y]
)
pt_left = PointXYZ(x, y, z, name="wl_left")
else:
pt_left = self.point(0)
# Interpolate points at river right side
if (i_right < self.number_points - 1):
if abs(self.point(i_right + 1).z - self.point(i_right).z) < 1e-20:
pt_right = self.point(i_right)
else:
fact = (z - self.point(i_right).z) / \
(self.point(i_right + 1).z - self.point(i_right).z)
x = self.point(i_right).x + fact * \
(self.point(i_right + 1).x - self.point(i_right).x)
y = self.point(i_right).y + fact * \
(self.point(i_right + 1).y - self.point(i_right).y)
pt_right = PointXYZ(x=x, y=y, z=z, name="wl_right")
x = np.interp(
z,
[self.point(i_right).z, self.point(i_right + 1).z],
[self.point(i_right).x, self.point(i_right + 1).x]
)
y = np.interp(
z,
[self.point(i_right).z, self.point(i_right + 1).z],
[self.point(i_right).y, self.point(i_right + 1).y]
)
pt_right = PointXYZ(x, y, z, name="wl_right")
else:
pt_right = self.point(self.number_points - 1)
@@ -953,13 +754,13 @@ class ProfileXYZ(Profile, SQLSubModel):
last_point_not_nan = self._last_point_not_nan(points)
for index, point in enumerate(points):
if point.is_named():
if point.point_is_named():
index_first_named_point = index
first_named_point = point
break
for point in reversed(points):
if point.is_named():
if point.point_is_named():
last_named_point = point
break
@@ -1030,56 +831,54 @@ class ProfileXYZ(Profile, SQLSubModel):
"""
Remove points to keep at most np_purge points.
"""
if self.nb_points <= np_purge:
if (self.nb_points <= np_purge):
return
nb_named = 0 # we consider the first and last point as named
area = []
nb_named = 2 # we consider the first and last point as named
area = [0.0]
for i, p in enumerate(self.points):
if p.is_named() or i == 0 or i == self.nb_points - 1:
area.append((9999999.999, p))
for i in range(1, self.nb_points-1):
if self.point(i).point_is_named():
area.append(9999999.999)
nb_named += 1
else:
area.append((
area.append(
PointXYZ.areatriangle3d(
self.point(i-1),
p,
self.point(i+1)
),
p
))
self.point(i),
self.point(i+1))
)
area.append(0.0)
while self.nb_points > max(np_purge, nb_named):
ind, (min_area, p) = min(
enumerate(area),
key=lambda t: t[1][0]
)
to_rm = np.argmin(area[1:self.nb_points - 1]) + 1
p.set_as_deleted()
area.pop(ind)
self.delete_i([to_rm])
area.pop(to_rm)
for i in [ind-1, ind]:
p = self.point(i)
for i in [to_rm-1, to_rm]:
if (i == 0):
continue
if p.is_named() or i == 0 or i == self.nb_points - 1:
area[i] = (9999999.999, p)
if (i == self.nb_points - 1):
continue
if self.point(i).point_is_named():
area[i] = 9999999.999
else:
area[i] = (
PointXYZ.areatriangle3d(
self.point(i-1), p, self.point(i+1)
),
p
area[i] = PointXYZ.areatriangle3d(
self.point(i-1),
self.point(i),
self.point(i+1)
)
def shift(self, x, y, z):
for p in self._points:
for p in self.points:
p.x = p.x + x
p.y = p.y + y
p.z = p.z + z
self.modified()
def modified(self):
self.tab_up_to_date = False
self.station_up_to_date = False
@@ -1104,7 +903,9 @@ class ProfileXYZ(Profile, SQLSubModel):
p = ProfileXYZ(name=self.name,
rk=self.rk,
reach=self.reach,
status=self._status)
nb_point=0,
code1=0, code2=0,
status=self.reach._status)
for i, k in enumerate(self.points):
p.insert_point(i, k.copy())
@@ -1122,18 +923,3 @@ class ProfileXYZ(Profile, SQLSubModel):
align = True
self.delete_i([i])
break
def hard_purge_aligned_points(self):
align = True
points = self.points
while (align):
align = False
for i in range(1, len(points) - 1):
d = points[i].dist_from_seg(points[i-1],
points[i+1])
if d < 0.00001 and points[i].name == "":
align = True
points.pop(i)
break
self._points = points
self.modified()
+83 -120
View File
@@ -1,5 +1,5 @@
# Reach.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -45,6 +45,7 @@ class Reach(SQLSubModel):
]
def __init__(self, status=None, parent=None):
self.id = parent.id if parent is not None else 0
self._status = status
self._parent = parent
self._profiles: List[Profile] = []
@@ -52,59 +53,41 @@ class Reach(SQLSubModel):
self._guidelines_is_valid = False
self._guidelines = {}
@property
def pamhyr_id(self):
return self._parent.pamhyr_id
@property
def _pamhyr_id(self):
return self._parent.pamhyr_id
@classmethod
def _db_create(cls, execute):
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data["status"], parent=data["reach"])
new._profiles = sorted(
ProfileXYZ._db_load(
execute,
data=data.copy()
),
key=lambda p: p._db_ind
new._profiles = ProfileXYZ._db_load(
execute,
data=data.copy()
)
return new
def _db_save(self, execute, data=None):
ok = True
profiles = self.profiles
execute(
"DELETE FROM geometry_profileXYZ " +
f"WHERE reach = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
# Delete old data
execute(f"DELETE FROM geometry_profileXYZ WHERE reach = {self.id}")
if data is None:
data = {}
ind = 0
for profile in self._profiles:
for profile in profiles:
data["ind"] = ind
ok &= profile._db_save(execute, data)
profile._db_save(execute, data)
ind += 1
return ok
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
for p in self._profiles:
p._data_traversal(predicate, modifier, data)
return True
def profile(self, i):
"""Returns profile at index i
@@ -128,18 +111,31 @@ class Reach(SQLSubModel):
return self._parent.name
def _get_profiles_list(self):
# Profiles list generator is type (int, Point) with the first
# element the index of the Point in list
logger.debug(f"Load profiles from reach {self.name}")
return list(self._profiles)
return list(
map(
lambda p: p[1],
sorted(
self._profiles,
key=lambda p: p[0]
)
)
)
def __len__(self):
return len(self.profiles)
if not isinstance(self._profiles, list):
self._profiles = self._get_profiles_list()
return len(self._profiles)
@property
def profiles(self):
if not isinstance(self._profiles, list):
self._profiles = self._get_profiles_list()
return list(filter(lambda p: not p.is_deleted(), self._profiles))
return self._profiles
@profiles.setter
def profiles(self, profiles):
@@ -167,6 +163,15 @@ class Reach(SQLSubModel):
"""
return self.profiles
def _update_profile_numbers(self):
"""Update profiles index
Returns:
Nothing.
"""
for ind, profile in enumerate(self.get_geometry()):
profile.num = ind + 1
def insert(self, index: int):
"""Insert new profile in list
@@ -178,9 +183,10 @@ class Reach(SQLSubModel):
"""
profile = ProfileXYZ(reach=self, status=self._status)
self._profiles.insert(index, profile)
self.profiles.insert(index, profile)
self._update_profile_numbers()
self.modified()
self._status.modified()
return profile
@@ -193,14 +199,9 @@ class Reach(SQLSubModel):
Returns:
Nothing.
"""
if profile in self._profiles:
self.undelete([profile])
profile.num = index
else:
profile.num = index
self._profiles.insert(index, profile)
self.modified()
self.profiles.insert(index, profile)
self._update_profile_numbers()
self._status.modified()
def delete(self, indexes):
"""Delete some elements in profile list
@@ -211,36 +212,24 @@ class Reach(SQLSubModel):
Returns:
Nothing.
"""
list(
profiles = set(
map(
lambda p: p[1].set_as_deleted(),
lambda e: e[1],
filter(
lambda e: e[0] in indexes,
enumerate(self._profiles)
enumerate(self.profiles)
)
)
)
self.modified()
def hard_delete(self, indexes):
"""Delete some elements in profile list
Args:
indexes: The list of index to delete
Returns:
Nothing.
"""
self._profiles = list(
map(
lambda p: p[1],
filter(
lambda e: e[0] not in indexes,
enumerate(self._profiles)
)
filter(
lambda p: p not in profiles,
self.profiles
)
)
self.modified()
self._update_profile_numbers()
self._status.modified()
def delete_profiles(self, profiles):
"""Delete some elements in profile list
@@ -251,41 +240,35 @@ class Reach(SQLSubModel):
Returns:
Nothing.
"""
list(
map(
lambda p: p.set_as_deleted(),
profiles
self._profiles = list(
filter(
lambda p: p not in profiles,
self.profiles
)
)
self.modified()
def undelete(self, lst):
for el in lst:
el.set_as_not_deleted()
self.modified()
self._update_profile_numbers()
self._status.modified()
def purge(self):
for el in self._profiles:
el.set_as_deleted()
self.modified()
self._profiles = []
self._update_profile_numbers()
self._status.modified()
def move_up_profile(self, index: int):
if index < len(self._profiles):
if index < len(self.profiles):
next = index - 1
p = self._profiles
p = self.profiles
p[index], p[next] = p[next], p[index]
self.modified()
self._status.modified()
def move_down_profile(self, index: int):
if index >= 0:
prev = index + 1
p = self._profiles
p = self.profiles
p[index], p[prev] = p[prev], p[index]
self.modified()
self._status.modified()
def get_x(self):
return list(
@@ -513,7 +496,7 @@ class Reach(SQLSubModel):
self._compute_guidelines_cache(guide_set, named_points,
complete, incomplete)
self.modified()
self._status.modified()
return (complete, incomplete)
def _map_guidelines_points(self, func, full=False):
@@ -554,28 +537,28 @@ class Reach(SQLSubModel):
@timer
def sort(self, is_reversed: bool = False):
self._profiles = sorted(
self._profiles,
self.profiles = sorted(
self.profiles,
key=lambda profile: profile.rk,
reverse=is_reversed
)
self.modified()
self._status.modified()
@timer
def sort_with_indexes(self, indexes: list):
if len(self._profiles) != len(indexes):
if len(self.profiles) != len(indexes):
logger.critical("Indexes list do not correspond to profile list")
self._profiles = list(
self.profiles = list(
map(
lambda x: x[1],
sorted(
enumerate(self._profiles),
enumerate(self.profiles),
key=lambda x: indexes[x[0]]
)
)
)
self.modified()
self._status.modified()
# Import/Export
@@ -631,7 +614,8 @@ class Reach(SQLSubModel):
profiles.append(prof)
self._profiles = profiles + self._profiles
self.profiles = profiles + self.profiles
self._update_profile_numbers()
self._recompute_rk()
return profiles
@@ -649,8 +633,6 @@ class Reach(SQLSubModel):
"""
imported_profiles = []
logger.info(f"import reach from {file_path_name}")
try:
list_profile, list_header = self.read_file_st(str(file_path_name))
profile_header = ["num", "code1", "code2",
@@ -663,15 +645,12 @@ class Reach(SQLSubModel):
d[profile_header[i]] = data
prof = ProfileXYZ(
id=-1,
**d,
reach=self,
status=self._status
**d, reach=self, status=self._status
)
prof.import_points(profile)
imported_profiles.append(prof)
self.modified()
self._status.modified()
except FileNotFoundError as e:
logger.error(e)
exception_message_box(e)
@@ -679,8 +658,9 @@ class Reach(SQLSubModel):
logger.error(e)
e.alert()
finally:
self._profiles = self._profiles + imported_profiles
self.profiles = imported_profiles + self.profiles
self._update_profile_numbers()
return imported_profiles
@timer
@@ -914,20 +894,3 @@ class Reach(SQLSubModel):
profile.rk = previous.rk + dist
previous = profile
def _split(self, profile, reach1, reach2):
if profile is None:
return
nr1 = Reach(status=self._status, parent=reach1)
nr2 = Reach(status=self._status, parent=reach2)
ind = self.profiles.index(profile)
nr1_profiles = self.profiles[:ind+1]
nr2_profiles = self.profiles[ind:]
nr1._profiles = list(map(lambda p: p.cloned_for(nr1), nr1_profiles))
nr2._profiles = list(map(lambda p: p.cloned_for(nr2), nr2_profiles))
return nr1, nr2
+1 -1
View File
@@ -1,5 +1,5 @@
# Vector_1d.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -1,5 +1,5 @@
# HydraulicStructures.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -21,7 +21,6 @@ import logging
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
from Model.Except import NotImplementedMethodeError
from Model.HydraulicStructures.Basic.Value import (
@@ -35,97 +34,49 @@ class BasicHS(SQLSubModel):
_sub_classes = [
BHSValue,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(BasicHS, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
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 + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE hydraulic_structures_basic{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
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,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(hs) REFERENCES hydraulic_structures(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(hs) REFERENCES hydraulic_structures(id)
)
""")
if ext == "_tmp":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
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
else:
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE hydraulic_structures_basic " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "hydraulic_structures_basic"
hs = data['id2pid']['hydraulic_structures']
cls.update_db_add_pamhyr_id(execute, table, data)
cls._db_update_to_0_2_0_set_hs_pid(execute, table, hs)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, type, enabled, hs, scenario) " +
"SELECT pamhyr_id, name, type, enabled, hs, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_update_to_0_2_0_set_hs_pid(cls, execute, table, hs):
els = execute(
f"SELECT pamhyr_id, hs FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
hs_id = next(it)
execute(
f"UPDATE {table} " +
f"SET hs = {hs[hs_id]} " +
f"WHERE pamhyr_id = {pid}"
)
return True
@classmethod
def _get_ctor_from_type(cls, t):
@@ -143,85 +94,58 @@ class BasicHS(SQLSubModel):
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return []
table = execute(
"SELECT pamhyr_id, deleted, name, type, enabled, hs, scenario " +
"SELECT id, name, type, enabled, hs " +
"FROM hydraulic_structures_basic " +
f"WHERE hs = {data['hs_id']} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
f"WHERE hs = {data['hs_id']} "
)
for row in table:
it = iter(row)
bhs_pid = next(it)
deleted = (next(it) == 1)
name = next(it)
type = next(it)
enabled = (next(it) == 1)
hs_id = next(it)
owner_scenario = next(it)
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_pid, name=name,
status=status, owner_scenario=owner_scenario
id=bhs_id,
name=name,
status=data['status']
)
if deleted:
bhs.set_as_deleted()
bhs.enabled = enabled
data['bhs_id'] = bhs_pid
data['bhs_id'] = bhs_id
bhs._data = BHSValue._db_load(
execute, data
)
print(f"{bhs_pid} : {deleted}")
if deleted:
bhs.set_as_deleted()
new.append(bhs)
loaded.add(bhs_pid)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
hs_id = data['hs_id']
execute(
sql = (
"INSERT INTO " +
"hydraulic_structures_basic(pamhyr_id, deleted, " +
"name, type, enabled, hs, scenario) " +
"hydraulic_structures_basic(id, name, type, enabled, hs) " +
"VALUES (" +
f"{self.pamhyr_id}, " +
f"{self._db_format(self.is_deleted())}, " +
f"{self.id}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', " +
f"{self._db_format(self.enabled)}, " +
f"{hs_id}, " +
f"{self._status.scenario_id}"
f"{hs_id} " +
")"
)
execute(sql)
data['bhs_id'] = self.pamhyr_id
data['bhs_id'] = self.id
execute(
"DELETE FROM hydraulic_structures_basic_value " +
f"WHERE bhs = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
f"WHERE bhs = {self.id}"
)
for values in self._data:
@@ -229,30 +153,20 @@ class BasicHS(SQLSubModel):
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self._data)
@property
def name(self):
if self._name == "":
return f"{self._type} #{self.pamhyr_id}"
return f"{self._type}{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def type(self):
@@ -261,7 +175,7 @@ class BasicHS(SQLSubModel):
@type.setter
def type(self, type):
self._type = type
self.modified()
self._status.modified()
@property
def enabled(self):
@@ -270,7 +184,7 @@ class BasicHS(SQLSubModel):
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
self._status.modified()
@property
def parameters(self):
+74 -86
View File
@@ -1,5 +1,5 @@
# Types.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,11 +27,10 @@ from Model.HydraulicStructures.Basic.Value import (
class NotDefined(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(NotDefined, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "ND"
@@ -40,208 +39,197 @@ class NotDefined(BasicHS):
class DischargeWeir(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(DischargeWeir, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "S1"
self._data = [
BHSValue(-1, "width", float, 1.0, status=status),
BHSValue(-1, "elevation", float, 1.0, status=status),
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
BHSValue("width", float, 1.0, status=status),
BHSValue("elevation", float, 1.0, status=status),
BHSValue("discharge_coefficient", float, 0.4, status=status),
]
class TrapezoidalWeir(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(TrapezoidalWeir, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "S2"
self._data = [
BHSValue(-1, "width", float, 1.0, status=status),
BHSValue(-1, "elevation", float, 1.0, status=status),
BHSValue(-1, "loading_elevation", float, 9999.999, status=status),
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
BHSValue(-1, "half-angle_tangent", float, 0.0, status=status),
BHSValue("width", float, 1.0, status=status),
BHSValue("elevation", float, 1.0, status=status),
BHSValue("loading_elevation", float, 9999.999, status=status),
BHSValue("discharge_coefficient", float, 0.4, status=status),
BHSValue("half-angle_tangent", float, 0.0, status=status),
]
class TriangularWeir(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(TriangularWeir, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "S3"
self._data = [
BHSValue(-1, "elevation", float, 1.0, status=status),
BHSValue(-1, "loading_elevation", float, 9999.999, status=status),
BHSValue(-1, "discharge_coefficient", float, 0.31, status=status),
BHSValue(-1, "half-angle_tangent", float, 1.0, status=status),
BHSValue("elevation", float, 1.0, status=status),
BHSValue("loading_elevation", float, 9999.999, status=status),
BHSValue("discharge_coefficient", float, 0.31, status=status),
BHSValue("half-angle_tangent", float, 1.0, status=status),
]
class RectangularOrifice(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(RectangularOrifice, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "OR"
self._data = [
BHSValue(-1, "width", float, 0.0, status=status),
BHSValue(-1, "elevation", float, 0.0, status=status),
BHSValue(-1, "loading_elevation", float, 9999.999,
BHSValue("width", float, 0.0, status=status),
BHSValue("elevation", float, 0.0, status=status),
BHSValue("loading_elevation", float, 9999.999,
status=status),
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
BHSValue(-1, "maximal_loading_elevation", float, 9999.999,
BHSValue("discharge_coefficient", float, 0.4, status=status),
BHSValue("maximal_loading_elevation", float, 9999.999,
status=status),
]
class CircularOrifice(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(CircularOrifice, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "OC"
self._data = [
BHSValue(-1, "diameter", float, 0.0, status=status),
BHSValue(-1, "elevation", float, 0.0, status=status),
BHSValue(-1, "siltation_height", float, 0.0, status=status),
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
BHSValue("diameter", float, 0.0, status=status),
BHSValue("elevation", float, 0.0, status=status),
BHSValue("siltation_height", float, 0.0, status=status),
BHSValue("discharge_coefficient", float, 0.4, status=status),
]
class VaultedOrifice(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(VaultedOrifice, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "OV"
self._data = [
BHSValue(-1, "elevation", float, 1.0, status=status),
BHSValue(-1, "width", float, 1.0, status=status),
BHSValue(-1, "top_of_the_vault", float, 0.0, status=status),
BHSValue(-1, "bottom_of_the_vault", float, 0.0, status=status),
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
BHSValue("elevation", float, 1.0, status=status),
BHSValue("width", float, 1.0, status=status),
BHSValue("top_of_the_vault", float, 0.0, status=status),
BHSValue("bottom_of_the_vault", float, 0.0, status=status),
BHSValue("discharge_coefficient", float, 0.4, status=status),
]
class RectangularGate(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(RectangularGate, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "V1"
self._data = [
BHSValue(-1, "width", float, 1.0, status=status),
BHSValue(-1, "elevation", float, 0.0, status=status),
BHSValue(-1, "discharge coefficient", float, 0.4, status=status),
BHSValue(-1, "opening", float, 1.0, status=status),
BHSValue(-1, "maximal_opening", float, 1.0, status=status),
BHSValue("width", float, 1.0, status=status),
BHSValue("elevation", float, 0.0, status=status),
BHSValue("discharge coefficient", float, 0.4, status=status),
BHSValue("opening", float, 1.0, status=status),
BHSValue("maximal_opening", float, 1.0, status=status),
]
class SimplifiedRectangularGate(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(SimplifiedRectangularGate, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "V2"
self._data = [
BHSValue(-1, "width", float, 1.0, status=status),
BHSValue(-1, "elevation", float, 0.0, status=status),
BHSValue(-1, "discharge coefficient", float, 0.4, status=status),
BHSValue(-1, "opening", float, 1.0, status=status),
BHSValue(-1, "maximal_opening", float, 1.0, status=status),
BHSValue("width", float, 1.0, status=status),
BHSValue("elevation", float, 0.0, status=status),
BHSValue("discharge coefficient", float, 0.4, status=status),
BHSValue("opening", float, 1.0, status=status),
BHSValue("maximal_opening", float, 1.0, status=status),
]
class Borda(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(Borda, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "BO"
self._data = [
BHSValue(-1, "step_space", float, 0.1, status=status),
BHSValue(-1, "weir", float, 0.15, status=status),
BHSValue(-1, "coefficient", float, 0.4, status=status),
BHSValue("step_space", float, 0.1, status=status),
BHSValue("weir", float, 0.15, status=status),
BHSValue("coefficient", float, 0.4, status=status),
]
class CheckValve(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(CheckValve, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "CV"
self._data = [
BHSValue(-1, "width", float, 0.0, status=status),
BHSValue(-1, "elevation", float, 0.0, status=status),
BHSValue(-1, "loading_elevation", float, 9999.999,
BHSValue("width", float, 0.0, status=status),
BHSValue("elevation", float, 0.0, status=status),
BHSValue("loading_elevation", float, 9999.999,
status=status),
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
BHSValue(-1, "maximal_loading_elevation", float, 9999.999,
BHSValue("discharge_coefficient", float, 0.4, status=status),
BHSValue("maximal_loading_elevation", float, 9999.999,
status=status),
]
class UserDefined(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
status=None):
super(UserDefined, self).__init__(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
status=status
)
self._type = "UD"
self._data = [
BHSValue(-1, "parameter_1", float, 0.0, status=status),
BHSValue(-1, "parameter_2", float, 0.0, status=status),
BHSValue(-1, "parameter_3", float, 0.0, status=status),
BHSValue(-1, "parameter_4", float, 0.0, status=status),
BHSValue(-1, "parameter_5", float, 0.0, status=status),
BHSValue("parameter_1", float, 0.0, status=status),
BHSValue("parameter_2", float, 0.0, status=status),
BHSValue("parameter_3", float, 0.0, status=status),
BHSValue("parameter_4", float, 0.0, status=status),
BHSValue("parameter_5", float, 0.0, status=status),
]
+27 -92
View File
@@ -1,5 +1,5 @@
# Value.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -17,92 +17,45 @@
# -*- coding: utf-8 -*-
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
class BHSValue(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
type=float, value=0.0,
status=None, owner_scenario=-1):
super(BHSValue, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
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, ext=""):
execute(f"""
CREATE TABLE hydraulic_structures_basic_value{ext} (
{cls.create_db_add_pamhyr_id()},
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,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(bhs) REFERENCES hydraulic_structures_basic(id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(bhs) REFERENCES hydraulic_structures_basic(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
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
else:
cls._db_update_to_0_2_0(execute, data)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "hydraulic_structures_basic_value"
bhs = data['id2pid']['hydraulic_structures_basic']
cls.update_db_add_pamhyr_id(execute, table, data)
cls._db_update_to_0_2_0_set_bhs_pid(execute, table, bhs)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, type, value, bhs, scenario) " +
"SELECT pamhyr_id, name, type, value, bhs, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_update_to_0_2_0_set_bhs_pid(cls, execute, table, bhs):
els = execute(
f"SELECT pamhyr_id, bhs FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
bhs_id = next(it)
execute(
f"UPDATE {table} " +
f"SET bhs = {bhs[bhs_id]} " +
f"WHERE pamhyr_id = {pid}"
)
return True
@classmethod
def _str_to_type(cls, type):
@@ -133,62 +86,44 @@ class BHSValue(SQLSubModel):
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
bhs_id = data["bhs_id"]
if scenario is None:
return []
table = execute(
"SELECT pamhyr_id, name, type, value, scenario " +
"SELECT name, type, value " +
"FROM hydraulic_structures_basic_value " +
f"WHERE bhs = '{bhs_id}' " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
f"WHERE bhs = '{bhs_id}'"
)
for row in table:
it = iter(row)
pid = next(it)
name = next(it)
type = cls._str_to_type(next(it))
value = next(it)
owner_scenario = next(it)
name = row[0]
type = cls._str_to_type(row[1])
value = row[2]
val = cls(
id=pid, name=name,
type=type, value=value,
status=status, owner_scenario=owner_scenario
name=name,
type=type,
value=value,
status=data['status']
)
new.append(val)
loaded.add(pid)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
bhs_id = data["bhs_id"]
execute(
sql = (
"INSERT INTO " +
"hydraulic_structures_basic_value" +
"(pamhyr_id, name, type, value, bhs, scenario) " +
"hydraulic_structures_basic_value(name, type, value, bhs) " +
"VALUES (" +
f"{self._pamhyr_id}, " +
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}, " +
f"{self._status.scenario_id}"
f"{bhs_id}" +
")"
)
execute(sql)
return True
@@ -207,4 +142,4 @@ class BHSValue(SQLSubModel):
@value.setter
def value(self, value):
self._value = self._type(value)
self.modified()
self._status.modified()
@@ -1,5 +1,5 @@
# HydraulicStructures.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,7 +22,6 @@ from functools import reduce
from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
from Model.Except import NotImplementedMethodeError
from Model.HydraulicStructures.Basic.HydraulicStructures import BasicHS
@@ -37,289 +36,162 @@ class HydraulicStructure(SQLSubModel):
_sub_classes = [
BasicHS,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(HydraulicStructure, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
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_section = None
self._output_section = None
self._input_rk = None
self._output_rk = None
self._input_reach = None
self._output_reach = None
self._enabled = True
self._data = []
HydraulicStructure._id_cnt = max(
HydraulicStructure._id_cnt + 1,
self.id + 1
)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE hydraulic_structures{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
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_rk REAL NOT NULL,
output_rk REAL NOT NULL,
input_reach INTEGER,
output_reach INTEGER,
input_section INTEGER,
output_section INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(input_reach) REFERENCES river_reach(pamhyr_id),
FOREIGN KEY(output_reach) REFERENCES river_reach(pamhyr_id),
FOREIGN KEY(input_section)
REFERENCES geometry_profileXYZ(pamhyr_id),
FOREIGN KEY(output_section)
REFERENCES geometry_profileXYZ(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(input_reach) REFERENCES river_reach(id),
FOREIGN KEY(output_reach) REFERENCES river_reach(id)
)
""")
if ext == "_tmp":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
rl = int(release)
if major == minor == "0":
rl = int(release)
if rl < 6:
cls._db_create(execute)
return True
if rl < 11:
for v in ["input", "output"]:
execute(
f"""
ALTER TABLE hydraulic_structures
RENAME COLUMN {v}_kp TO {v}_rk
"""
)
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if rl < 1:
cls._db_update_to_0_1_1(execute, data)
if rl < 2:
execute(
"ALTER TABLE hydraulic_structures " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
"""
ALTER TABLE hydraulic_structures
RENAME COLUMN input_kp TO input_rk
"""
)
execute(
"""
ALTER TABLE hydraulic_structures
RENAME COLUMN output_kp TO output_rk
"""
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "hydraulic_structures"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
cls._db_update_to_0_2_0_set_reach_pid(execute, table, reachs)
cls._db_update_to_0_1_1(
execute, data,
origin_version="0.0.*"
)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, enabled, input_section, output_section, " +
"input_reach, output_reach, scenario) " +
"SELECT pamhyr_id, name, enabled, " +
"input_section, output_section, " +
"input_reach, output_reach, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_update_to_0_2_0_set_reach_pid(cls, execute, table, reachs):
els = execute(
f"SELECT pamhyr_id, input_reach, output_reach FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
in_reach_id = next(it)
out_reach_id = next(it)
if in_reach_id == -1:
return
if out_reach_id == -1:
out_reach_id = in_reach_id
execute(
f"UPDATE {table} " +
f"SET input_reach = {reachs[in_reach_id]}, " +
f"output_reach = {reachs[out_reach_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_update_to_0_1_1(cls, execute, data,
origin_version="0.1.0"):
for v in ["input", "output"]:
execute(
"ALTER TABLE hydraulic_structures " +
f"ADD COLUMN {v}_section INTEGER"
)
cls._db_update_to_0_1_1_assoc_section_from_rk(
execute, "hydraulic_structures",
reach_column=f"{v}_reach",
rk_column=f"{v}_rk",
section_column=f"{v}_section",
origin_version=origin_version
)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return []
table = execute(
"SELECT pamhyr_id, deleted, name, enabled, " +
"input_section, output_section, " +
"input_reach, output_reach, scenario " +
"FROM hydraulic_structures " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, name, enabled, " +
"input_rk, output_rk, " +
"input_reach, output_reach " +
"FROM hydraulic_structures "
)
for row in table:
new.append(cls._db_load_new(execute, data, row))
it = iter(row)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
hs_id = next(it)
name = next(it)
enabled = (next(it) == 1)
input_rk = next(it)
output_rk = 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_rk = input_rk if input_rk != -1 else None
hs.output_rk = output_rk if output_rk != -1 else None
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["edges"],
[None, None]
)
data['hs_id'] = hs_id
hs._data = BasicHS._db_load(execute, data)
new.append(hs)
return new
@classmethod
def _db_load_new(cls, execute, data, row):
it = iter(row)
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
hs_id = next(it)
deleted = (next(it) == 1)
name = next(it)
enabled = (next(it) == 1)
input_section_id = next(it)
input_section_id = (
-1 if input_section_id is None else input_section_id
)
output_section_id = next(it)
output_section_id = (
-1 if output_section_id is None else output_section_id
)
input_reach_id = next(it)
output_reach_id = next(it)
owner_scenario = next(it)
hs = cls(
id=hs_id, name=name, status=status,
owner_scenario=owner_scenario
)
if deleted:
hs.set_as_deleted()
hs.enabled = enabled
hs.input_reach, hs.output_reach = reduce(
lambda acc, n: (
n if n.pamhyr_id == input_reach_id else acc[0],
n if n.pamhyr_id == output_reach_id else acc[1]
),
data["edges"],
[None, None]
)
sections = []
if hs.input_reach is not None:
sections += hs.input_reach.reach.profiles
if hs.output_reach is not None:
sections += hs.output_reach.reach.profiles
hs.input_section, hs.output_section = reduce(
lambda acc, s: (
s if s.pamhyr_id == input_section_id else acc[0],
s if s.pamhyr_id == output_section_id else acc[1]
),
sections,
[None, None]
)
loaded.add(hs_id)
data['hs_id'] = hs_id
hs._data = BasicHS._db_load(execute, data)
return hs
def _db_save(self, execute, data=None):
execute(
"DELETE FROM hydraulic_structures " +
f"WHERE pamhyr_id = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
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.pamhyr_id
input_reach_id = self._input_reach.id
output_reach_id = -1
if self._output_reach is not None:
output_reach_id = self._output_reach.pamhyr_id
output_reach_id = self._output_reach.id
input_section = 'NULL'
if self.input_section is not None:
input_section = self.input_section.pamhyr_id
input_rk = -1
if self.input_rk is not None:
input_rk = self.input_rk
output_section = 'NULL'
if self.output_section is not None:
output_section = self.output_section.pamhyr_id
output_rk = -1
if self.output_rk is not None:
output_rk = self.output_rk
execute(
sql = (
"INSERT INTO " +
"hydraulic_structures(" +
" pamhyr_id, deleted, name, enabled, " +
" input_section, output_section, " +
" input_reach, output_reach, " +
" scenario" +
" id, name, enabled, input_rk, output_rk, " +
" input_reach, output_reach" +
") " +
"VALUES (" +
f"{self.pamhyr_id}, " +
f"{self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"{self.id}, '{self._db_format(self._name)}', " +
f"{self._db_format(self.enabled)}, " +
f"{input_section}, {output_section}, " +
f"{input_reach_id}, {output_reach_id}, " +
f"{self._status.scenario_id}"
f"{input_rk}, {output_rk}, " +
f"{input_reach_id}, {output_reach_id}" +
")"
)
execute(sql)
data['hs_id'] = self.pamhyr_id
data['hs_id'] = self.id
execute(
"DELETE FROM hydraulic_structures_basic " +
f"WHERE hs = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
f"WHERE hs = {self.id}"
)
for basic in self._data:
@@ -327,71 +199,41 @@ class HydraulicStructure(SQLSubModel):
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self.lst)
@property
def lst(self):
return list(
filter(
lambda bhs: not bhs.is_deleted(),
self._data,
)
)
return len(self._data)
@property
def name(self):
if self._name == "":
return f"HS #{self.pamhyr_id}"
return f"HS{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def input_rk(self):
if self._input_section is None:
return None
return self._input_rk
return self._input_section
@input_rk.setter
def input_rk(self, input_rk):
if input_rk is not None:
input_rk = float(input_rk)
self._input_rk = input_rk
self._status.modified()
@property
def output_rk(self):
if self._output_section is None:
return None
return self._output_rk
return self._output_section
@property
def input_section(self):
return self._input_section
@input_section.setter
def input_section(self, input_section):
self._input_section = input_section
self.modified()
@property
def output_section(self):
return self._output_section
@output_section.setter
def output_section(self, output_section):
self._output_section = output_section
self.modified()
@output_rk.setter
def output_rk(self, output_rk):
self._output_rk = output_rk
self._status.modified()
@property
def enabled(self):
@@ -400,7 +242,7 @@ class HydraulicStructure(SQLSubModel):
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self.modified()
self._status.modified()
@property
def input_reach(self):
@@ -409,7 +251,7 @@ class HydraulicStructure(SQLSubModel):
@input_reach.setter
def input_reach(self, input_reach):
self._input_reach = input_reach
self.modified()
self._status.modified()
@property
def output_reach(self):
@@ -418,34 +260,26 @@ class HydraulicStructure(SQLSubModel):
@output_reach.setter
def output_reach(self, output_reach):
self._output_reach = output_reach
self.modified()
self._status.modified()
@property
def basic_structures(self):
return self.lst.copy()
return self._data.copy()
def basic_structure(self, index: int):
if len(self._data) == 0:
return None
return self.lst[index]
return self._data[index]
def add(self, index: int):
value = NotDefined(status=self._status)
self._data.insert(index, value)
self.modified()
self._status.modified()
return value
def insert(self, index: int, value: BasicHS):
if value in self._data:
value.set_as_not_deleted()
else:
self._data.insert(index, value)
self._data.insert(index, value)
self._status.modified()
self.modified()
def hard_delete_i(self, indexes):
def delete_i(self, indexes):
self._data = list(
map(
lambda e: e[1],
@@ -455,30 +289,20 @@ class HydraulicStructure(SQLSubModel):
)
)
)
self.modified()
def delete_i(self, indexes):
list(
map(
lambda e: e[1].set_as_deleted(),
filter(
lambda e: e[0] in indexes,
enumerate(self.lst)
)
)
)
self.modified()
self._status.modified()
def delete(self, els):
for el in els:
el.set_as_deleted()
self.modified()
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.modified()
self._status.modified()
@@ -1,5 +1,5 @@
# HydraulicStructuresList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.HydraulicStructures.HydraulicStructures import HydraulicStructure
@@ -42,10 +42,10 @@ class HydraulicStructureList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM hydraulic_structures " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM hydraulic_structures")
if data is None:
data = {}
for hs in self._lst:
hs._db_save(execute, data=data)
@@ -53,12 +53,9 @@ class HydraulicStructureList(PamhyrModelList):
return True
def new(self, lst, index):
n = HydraulicStructure(
status=self._status,
owner_scenario=self._status.scenario_id
)
n = HydraulicStructure(status=self._status)
self._lst.insert(index, n)
self.modified()
self._status.modified()
return n
def __copy__(self):
+90 -233
View File
@@ -1,5 +1,5 @@
# InitialConditions.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -24,34 +24,32 @@ from functools import reduce
from numpy import interp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
logger = logging.getLogger()
class Data(SQLSubModel):
def __init__(self, id: int = -1,
name: str = "", comment: str = "",
reach=None, section=None,
discharge: float = 0.0,
def __init__(self, name: str = "",
comment: str = "", reach=None,
rk: float = 0.0, discharge: float = 0.0,
height: float = 0.0,
status=None,
owner_scenario=-1):
super(Data, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(Data, self).__init__()
self._status = status
self._reach = reach
self._name = name
self._comment = comment
self._section = section
self._rk = rk
self._discharge = discharge
self._speed = 0.0
self._elevation = 0.0
self._height = height
if self._section is not None:
if self._rk != 0.0:
self._update_from_rk()
if self._height != 0.0:
self._update_from_height()
@@ -59,29 +57,25 @@ class Data(SQLSubModel):
self._update_from_discharge()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE initial_conditions{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE initial_conditions(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ind INTEGER NOT NULL,
name TEXT NOT NULL,
comment TEXT NOT NULL,
reach INTEGER,
section INTEGER,
rk REAL NOT NULL,
discharge REAL NOT NULL,
height REAL NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 11:
@@ -89,138 +83,41 @@ class Data(SQLSubModel):
"ALTER TABLE initial_conditions RENAME COLUMN kp TO rk"
)
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 1:
cls._db_update_to_0_1_1(execute, data)
if int(release) < 2:
execute(
"ALTER TABLE initial_conditions " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "initial_conditions"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
cls._db_update_to_0_2_0_set_reach_pid(execute, table, reachs)
cls._db_update_to_0_1_1(
execute, data,
origin_version="0.0.*"
)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, name, comment, reach, section, " +
"discharge, height, scenario) " +
"SELECT pamhyr_id, ind, name, comment, reach, section, " +
"discharge, height, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_update_to_0_1_1(cls, execute, data,
origin_version="0.1.0"):
execute(
"ALTER TABLE initial_conditions " +
f"ADD COLUMN section INTEGER"
)
cls._db_update_to_0_1_1_assoc_section_from_rk(
execute, "initial_conditions",
origin_version=origin_version
)
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
new = []
reach = data["reach"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
id = reach.pamhyr_id
id = data["reach"].id
table = execute(
"SELECT pamhyr_id, deleted, ind, name, comment, " +
"section, discharge, height, scenario " +
"SELECT ind, name, comment, rk, discharge, height " +
"FROM initial_conditions " +
f"WHERE reach = {id} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
"ORDER BY ind ASC"
f"WHERE reach = {id}"
)
ok = True
new = []
for _ in table:
new.append(None)
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
ind = next(it)
name = next(it)
comment = next(it)
section_id = next(it)
discharge = next(it)
height = next(it)
owner_scenario = next(it)
section = reduce(
lambda acc, s: (
s if s.pamhyr_id == section_id else acc
),
reach.reach.profiles,
None
)
if section is None:
logger.debug(
f"Section ID {section_id} do not exist in reach " +
f"{reach.name} ({reach.pamhyr_id})"
)
ok = False
continue
ind = row[0]
name = row[1]
comment = row[2]
rk = row[3]
discharge = row[4]
height = row[5]
d = cls(
id=pid,
reach=data["reach"],
status=data["status"],
name=name,
comment=comment,
section=section,
rk=rk,
discharge=discharge,
height=height,
owner_scenario=owner_scenario
)
if deleted:
d.set_as_deleted()
loaded.add(pid)
new.append(d)
if not ok:
logger.info(
"Please regenerate/defined the inital conditions " +
f"for reach {reach.name} ({reach.pamhyr_id})"
)
# Recution only if no data found
if len(new) == 0:
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new[ind] = d
return new
@@ -229,14 +126,13 @@ class Data(SQLSubModel):
execute(
"INSERT INTO " +
"initial_conditions(pamhyr_id, deleted, ind, name, comment, " +
"section, discharge, height, reach, scenario) " +
"initial_conditions(ind, name, comment, rk, " +
"discharge, height, reach) " +
"VALUES (" +
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(self.name)}', " +
f"'{self._db_format(self._comment)}', " +
f"{self._section.id}, {self._discharge}, {self._height}, " +
f"{self._reach.id}, {self._status.scenario_id}" +
f"{self._rk}, {self._discharge}, {self._height}, " +
f"{self._reach.id}" +
")"
)
@@ -246,7 +142,7 @@ class Data(SQLSubModel):
new = Data(
name=self.name,
comment=self._comment,
section=self._section,
rk=self._rk,
discharge=self._discharge,
height=self._height,
reach=self._reach,
@@ -265,8 +161,8 @@ class Data(SQLSubModel):
val = self._name
elif key == "comment":
val = self._comment
elif key == "rk" or key == "section":
val = self._section
elif key == "rk":
val = self._rk
elif key == "speed":
val = self._speed
elif key == "discharge":
@@ -279,9 +175,9 @@ class Data(SQLSubModel):
return val
def _update_get_min(self):
profile = self._section
if profile is not None:
min = profile.z_min()
profile = self._reach.reach.get_profiles_from_rk(self._rk)
if len(profile) > 0:
min = profile[0].z_min()
else:
min = 0.0
@@ -308,8 +204,8 @@ class Data(SQLSubModel):
self._name = str(value)
elif key == "comment":
self._comment = str(value)
elif key == "rk" or key == "section":
self._section = value
elif key == "rk":
self._rk = float(value)
self._update_from_rk()
elif key == "speed":
# Not supposed to be modified
@@ -324,7 +220,7 @@ class Data(SQLSubModel):
self._height = float(value)
self._update_from_height()
self.modified()
self._status.modified()
class InitialConditions(SQLSubModel):
@@ -345,8 +241,8 @@ class InitialConditions(SQLSubModel):
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
@@ -374,18 +270,8 @@ class InitialConditions(SQLSubModel):
return ok
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self.data)
return len(self._data)
def lst(self):
return self._data
@@ -397,69 +283,49 @@ class InitialConditions(SQLSubModel):
@reach.setter
def reach(self, new):
self._reach = reach
self.modified()
self._status.modified()
@property
def data(self):
return list(
filter(
lambda el: not el.is_deleted(),
self._data
)
)
return self._data.copy()
@data.setter
def data(self, data):
self._data = data
def get(self, index):
return self.data[index]
return self._data[index]
def set(self, index, data):
self._data.insert(index, data)
self.modified()
self._status.modified()
def new(self, index):
n = Data(reach=self._reach, status=self._status)
self._data.insert(index, n)
self.modified()
self._status.modified()
def new_from_data(self, rk, discharge, elevation):
n = Data(reach=self._reach, status=self._status)
section = reduce(
lambda acc, s: (
s if s.rk == rk else acc
),
self._reach.reach.profiles,
None
)
n['section'] = section
n['rk'] = rk
n['discharge'] = discharge
n['elevation'] = elevation
return n
def insert(self, index, data):
if data in self._data:
data.set_as_not_deleted()
else:
self._data.insert(index, data)
self.modified()
self._data.insert(index, data)
self._status.modified()
def delete(self, data):
list(
map(
lambda x: x.set_as_deleted(),
filter(
lambda x: x in data,
self._data
)
self._data = list(
filter(
lambda x: x not in data,
self._data
)
)
self.modified()
self._status.modified()
def delete_i(self, indexes):
data = list(
@@ -475,23 +341,18 @@ class InitialConditions(SQLSubModel):
def sort(self, reverse=False, key=None):
self._data.sort(reverse=reverse, key=key)
self.modified()
self._status.modified()
def _data_get(self, key):
return list(
map(
lambda d: d[key],
self.data
self._data
)
)
def get_rk(self):
return list(
map(
lambda d: d["rk"].rk,
self.data
)
)
return self._data_get("rk")
def get_elevation(self):
return self._data_get("elevation")
@@ -499,13 +360,12 @@ class InitialConditions(SQLSubModel):
def get_discharge(self):
return self._data_get("discharge")
def clear_data(self):
for data in self._data:
data.set_as_deleted()
def generate_growing_constant_depth(self, height: float,
compute_discharge: bool):
profiles = self._reach.reach.profiles.copy()
profiles.reverse()
previous_elevation = -99999.99
data_discharge = {}
@@ -515,14 +375,12 @@ class InitialConditions(SQLSubModel):
data_discharge[profile.rk] = 0.0
else:
for data in self._data:
data_discharge[data["rk"].rk] = data["discharge"]
data_discharge[data["rk"]] = data["discharge"]
incline = self._reach.reach.get_incline_median_mean()
logger.debug(f"incline = {incline}")
self.clear_data()
for profile in reversed(profiles):
self._data = []
for profile in profiles:
width = profile.wet_width(profile.z_min() + height)
area = profile.wet_area(profile.z_min() + height)
radius = profile.wet_radius(profile.z_min() + height)
@@ -563,17 +421,21 @@ class InitialConditions(SQLSubModel):
logger.debug(f" discharge = {discharge}")
new = Data(reach=self._reach, status=self._status)
new["rk"] = profile
new["rk"] = profile.rk
new["discharge"] = discharge
new["elevation"] = elevation
previous_elevation = elevation
self._data.append(new)
self._data.reverse()
def generate_discharge(self, discharge: float, compute_height: bool):
profiles = self._reach.reach.profiles.copy()
if profiles is None:
return None
profiles.reverse()
previous_elevation = -99999.99
@@ -584,26 +446,20 @@ class InitialConditions(SQLSubModel):
data_height[profile.rk] = 0.0
else:
for data in self._data:
data_height[data["rk"].rk] = data["height"]
print(data_height)
data_height[data["rk"]] = data["height"]
incline = self._reach.reach.get_incline_median_mean()
logger.debug(f"incline = {incline}")
self.clear_data()
for profile in reversed(profiles):
self._data = []
for profile in profiles:
width = profile.width_approximation()
frictions = self._reach.frictions.frictions
strickler = None
if frictions is not None:
if len(frictions) >= 1:
for f in frictions:
if f.contains_rk(profile.rk):
strickler = f.get_friction(profile.rk)[0]
if strickler is None:
strickler = 25.0
@@ -618,9 +474,8 @@ class InitialConditions(SQLSubModel):
else:
height = (
discharge
/ ((width * 0.8)
* strickler
* (abs(incline) ** (0.5)))
/
((width * 0.8) * strickler * (abs(incline) ** (0.5)))
) ** (0.6)
elevation = max(
@@ -634,13 +489,15 @@ class InitialConditions(SQLSubModel):
logger.debug(f" height = {height}")
new = Data(reach=self._reach, status=self._status)
new["section"] = profile
new["rk"] = profile.rk
new["discharge"] = discharge
new["elevation"] = elevation
previous_elevation = elevation
self._data.append(new)
self._data.reverse()
def generate_height(self,
elevation1: float,
elevation2: float,
@@ -656,9 +513,9 @@ class InitialConditions(SQLSubModel):
data_discharge[profile.rk] = 0.0
else:
for data in self._data:
data_discharge[data["rk"].rk] = data["discharge"]
data_discharge[data["rk"]] = data["discharge"]
self.clear_data()
self._data = []
for profile in profiles:
if not compute_discharge:
@@ -672,7 +529,7 @@ class InitialConditions(SQLSubModel):
[upstream_rk, downstream_rk],
[elevation1, elevation2])
new = Data(reach=self._reach, status=self._status)
new["rk"] = profile
new["rk"] = profile.rk
new["discharge"] = d
new["elevation"] = elevation
self._data.append(new)
@@ -1,5 +1,5 @@
# InitialConditionsDict.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -64,8 +64,7 @@ class InitialConditionsDict(PamhyrModelDict):
execute(
"DELETE FROM initial_conditions " +
f"WHERE reach = '{reach.id}' " +
f"AND scenario = {self._status.scenario_id}"
f"WHERE reach = '{reach.id}'"
)
ok &= self._dict[reach]._db_save(execute, data)
@@ -1,5 +1,5 @@
# InitialConditionsAdisTS.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -23,7 +23,6 @@ from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
from Model.InitialConditionsAdisTS.InitialConditionsAdisTSSpec \
import ICAdisTSSpec
@@ -35,17 +34,19 @@ class InitialConditionsAdisTS(SQLSubModel):
_sub_classes = [
ICAdisTSSpec,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "default",
pollutant: int = -1, status=None,
owner_scenario=-1):
super(InitialConditionsAdisTS, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
pollutant: int = -1, status=None):
super(InitialConditionsAdisTS, self).__init__()
self._status = status
if id == -1:
self.id = InitialConditionsAdisTS._id_cnt
else:
self.id = id
self._name = name
self._pollutant = pollutant
self._concentration = None
@@ -55,159 +56,81 @@ class InitialConditionsAdisTS(SQLSubModel):
self._enabled = True
self._data = []
InitialConditionsAdisTS._id_cnt = max(
InitialConditionsAdisTS._id_cnt + 1,
self.id + 1
)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE initial_conditions_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
pollutant INTEGER NOT NULL,
name TEXT NOT NULL,
concentration REAL NOT NULL,
eg REAL NOT NULL,
em REAL NOT NULL,
ed REAL NOT NULL,
enabled BOOLEAN NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(pollutant) REFERENCES Pollutants(pamhyr_id)
)
""")
def _db_create(cls, execute):
execute("""
CREATE TABLE initial_conditions_adists(
id INTEGER NOT NULL PRIMARY KEY,
pollutant INTEGER NOT NULL,
name TEXT NOT NULL,
concentration REAL NOT NULL,
eg REAL NOT NULL,
em REAL NOT NULL,
ed REAL NOT NULL,
enabled BOOLEAN NOT NULL,
FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if major == 0 and minor < 1:
if int(release) < 6:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "initial_conditions_adists"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, pollutant, name, concentration, " +
"eg, em, ed, enabled, scenario) " +
"SELECT pamhyr_id, pollutant, name, concentration, " +
"eg, em, ed, enabled, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_pollutants_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_pollutants_pid(cls, execute, data):
pid_pol = data["id2pid"]["Pollutants"]
els = execute(
f"SELECT pamhyr_id, pollutant " +
"FROM initial_conditions_adists"
)
for row in els:
it = iter(row)
pid = next(it)
pol_id = next(it)
if pol_id == -1:
continue
execute(
f"UPDATE initial_conditions_adists " +
f"SET pollutant = {pid_pol[pol_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, " +
"pollutant, name, concentration, eg, em, ed, " +
"enabled, scenario " +
"FROM initial_conditions_adists " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, pollutant, name, concentration, eg, em, ed, " +
"enabled " +
"FROM initial_conditions_adists"
)
if table is not None:
for row in table:
it = iter(row)
IC_id = row[0]
pollutant = row[1]
name = row[2]
concentration = row[3]
eg = row[4]
em = row[5]
ed = row[6]
enabled = (row[7] == 1)
pid = next(it)
deleted = (next(it) == 1)
pollutant = next(it)
name = next(it)
concentration = next(it)
eg = next(it)
em = next(it)
ed = next(it)
enabled = (next(it) == 1)
owner_scenario = next(it)
ic = cls(
id=pid,
IC = cls(
id=IC_id,
name=name,
status=status,
owner_scenario=owner_scenario
status=data['status']
)
if deleted:
ic.set_as_deleted()
ic.pollutant = pollutant
ic.concentration = concentration
ic.eg = eg
ic.em = em
ic.ed = ed
ic.enabled = enabled
IC.pollutant = pollutant
IC.concentration = concentration
IC.eg = eg
IC.em = em
IC.ed = ed
IC.enabled = enabled
data['ic_default_id'] = pid
ic._data = ICAdisTSSpec._db_load(execute, data)
data['ic_default_id'] = IC_id
IC._data = ICAdisTSSpec._db_load(execute, data)
loaded.add(pid)
new.append(ic)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new.append(IC)
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"DELETE FROM initial_conditions_adists " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM initial_conditions_adists WHERE id = {self.id}")
pollutant = -1
if self.pollutant is not None:
@@ -232,14 +155,12 @@ class InitialConditionsAdisTS(SQLSubModel):
sql = (
"INSERT INTO " +
"initial_conditions_adists(" +
"pamhyr_id, deleted, pollutant, name, concentration, " +
"eg, em, ed, enabled, scenario" +
"id, pollutant, name, concentration, " +
"eg, em, ed, enabled" +
") " +
"VALUES (" +
f"{self.id}, {self.is_deleted()}, " +
f"{pollutant}, '{self._db_format(self._name)}', " +
f"{concentration}, {eg}, {em}, {ed}, {self._enabled}, " +
f"{self._status.scenario_id}" +
f"{self.id}, {pollutant}, '{self._db_format(self._name)}', " +
f"{concentration}, {eg}, {em}, {ed}, {self._enabled}" +
")"
)
@@ -247,9 +168,8 @@ class InitialConditionsAdisTS(SQLSubModel):
data['ic_default_id'] = self.id
execute(
"DELETE FROM initial_conditions_adists_spec " +
f"WHERE ic_default = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
"DELETE FROM initial_conditions_spec " +
f"WHERE ic_default = {self.id}"
)
for ic_spec in self._data:
@@ -1,5 +1,5 @@
# InitialConditionsAdisTSList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.InitialConditionsAdisTS.InitialConditionsAdisTS \
import InitialConditionsAdisTS
@@ -43,10 +43,7 @@ class InitialConditionsAdisTSList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM initial_conditions_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM initial_conditions_adists")
if data is None:
data = {}
@@ -1,5 +1,5 @@
# InitialConditionsAdisTSSpec.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,13 +22,14 @@ from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class ICAdisTSSpec(SQLSubModel):
_sub_classes = []
_sub_classes = [
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None):
@@ -36,6 +37,11 @@ class ICAdisTSSpec(SQLSubModel):
self._status = status
if id == -1:
self.id = ICAdisTSSpec._id_cnt
else:
self.id = id
self._name_section = name
self._reach = None
self._start_rk = None
@@ -47,142 +53,73 @@ class ICAdisTSSpec(SQLSubModel):
self._rate = None
self._enabled = True
ICAdisTSSpec._id_cnt = max(ICAdisTSSpec._id_cnt + 1, self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE initial_conditions_adists_spec{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
ic_default INTEGER NOT NULL,
name TEXT NOT NULL,
reach INTEGER NOT NULL,
start_rk REAL NOT NULL,
end_rk REAL NOT NULL,
concentration REAL NOT NULL,
eg REAL NOT NULL,
em REAL NOT NULL,
ed REAL NOT NULL,
rate REAL NOT NULL,
enabled BOOLEAN NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(ic_default)
REFERENCES initial_conditions_adists(pamhyr_id),
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id)
)
""")
def _db_create(cls, execute):
execute("""
CREATE TABLE initial_conditions_spec(
id INTEGER NOT NULL PRIMARY KEY,
ic_default INTEGER NOT NULL,
name TEXT NOT NULL,
reach INTEGER NOT NULL,
start_rk REAL NOT NULL,
end_rk REAL NOT NULL,
concentration REAL NOT NULL,
eg REAL NOT NULL,
em REAL NOT NULL,
ed REAL NOT NULL,
rate REAL NOT NULL,
enabled BOOLEAN NOT NULL,
FOREIGN KEY(ic_default) REFERENCES initial_conditions_adists(id),
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
created = False
if major == 0 and minor < 1:
if int(release) < 6:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "initial_conditions_spec"
table_new = "initial_conditions_adists_spec"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table_new}_tmp " +
"(pamhyr_id, ic_default, name, reach, start_rk, end_rk, " +
"concentration, eg, em, ed, rate, enabled, scenario) " +
"SELECT pamhyr_id, ic_default, name, reach, start_rk, end_rk, " +
"concentration, eg, em, ed, rate, enabled, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table_new, reachs)
cls._db_update_to_0_2_0_set_ica_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_ica_pid(cls, execute, data):
pid_ica = data["id2pid"]["initial_conditions_adist"]
els = execute(
"SELECT pamhyr_id, ic_default " +
"FROM initial_conditions_adists_spec"
)
for row in els:
it = iter(row)
pid = next(it)
ica_id = next(it)
if ica_id == -1:
continue
execute(
f"UPDATE initial_conditions_adists_spec " +
f"SET ic_default = {pid_ica[ica_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
reachs = data["edges"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, reach, start_rk, end_rk, " +
"concentration, eg, em, ed, rate, enabled, scenario " +
"FROM initial_conditions_adists_spec " +
f"WHERE ic_default = {data['ic_default_id']} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, ic_default, name, reach, start_rk, end_rk, " +
"concentration, eg, em, ed, rate, enabled " +
"FROM initial_conditions_spec " +
f"WHERE ic_default = {data['ic_default_id']} "
)
if table is not None:
for row in table:
it = iter(row)
id = row[0]
name = row[2]
reach = row[3]
start_rk = row[4]
end_rk = row[5]
concentration = row[6]
eg = row[7]
em = row[8]
ed = row[9]
rate = row[10]
enabled = (row[11] == 1)
id = next(it)
deleted = (next(it) == 1)
name = next(it)
reach = next(it)
start_rk = next(it)
end_rk = next(it)
concentration = next(it)
eg = next(it)
em = next(it)
ed = next(it)
rate = next(it)
enabled = (next(it) == 1)
owner_scenario = next(it)
# new_spec = [name, reach, start_rk, end_rk,
# concentration, eg, em, ed, rate, enabled]
new_spec = cls(
id=id, name=name,
status=status,
owner_scenario=owner_scenario
id=id,
name=name,
status=data['status']
)
if deleted:
new_spec.set_as_deleted()
new_spec.reach = reach
new_spec.start_rk = start_rk
@@ -194,37 +131,30 @@ class ICAdisTSSpec(SQLSubModel):
new_spec.rate = rate
new_spec.enabled = enabled
loaded.add(pid)
new.append(new_spec)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
ic_default = data['ic_default_id']
sql = (
"INSERT INTO " +
"initial_conditions_adists_spec(id, deleted, " +
"ic_default, name, reach, " +
"start_rk, end_rk, concentration, eg, em, ed, rate, " +
"enabled, scenario) " +
"initial_conditions_spec(id, ic_default, name, reach, " +
"start_rk, end_rk, concentration, eg, em, ed, rate, enabled) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"{self.id}, " +
f"{ic_default}, " +
f"'{self._db_format(self._name_section)}', " +
f"{self._reach}, " +
f"{self._start_rk}, {self._end_rk}, " +
f"{self._start_rk}, " +
f"{self._end_rk}, " +
f"{self._concentration}, " +
f"{self._eg}, {self._em}, {self._ed}, " +
f"{self._rate}, {self._enabled}, " +
f"{self._status.scenario_id}" +
f"{self._eg}, " +
f"{self._em}, " +
f"{self._ed}, " +
f"{self._rate}, " +
f"{self._enabled}" +
")"
)
execute(sql)
@@ -1,5 +1,5 @@
# LateralContribution.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,226 +27,66 @@ from tools import (
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class Data(SQLSubModel):
_sub_classes = []
def __init__(self,
data0, data1,
id: int = -1,
types=[float, float],
status=None,
owner_scenario=-1):
super(Data, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._types = types
self._data = [data0, data1]
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE lateral_contribution_data{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
ind INTEGER NOT NULL,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
lc INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(lc) REFERENCES lateral_contribution(pamhyr_id),
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 == minor == "0":
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE boundary_condition_data " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data=None):
table = "lateral_contribution_data"
lcs = data['id2pid']['lateral_contribution']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table} " +
"(pamhyr_id, ind, data0, data1, lc, scenario) " +
"SELECT pamhyr_id, ind, data0, data1, lc, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_lc_pid(execute, table, lcs)
@classmethod
def _db_update_to_0_2_0_set_lc_pid(cls, execute, table, lcs):
els = execute(
f"SELECT pamhyr_id, lc FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
lc_id = next(it)
execute(
f"UPDATE {table} " +
f"SET lc = {lcs[lc_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
lc = data["lc"]
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
values = execute(
"SELECT pamhyr_id, deleted, " +
"data0, data1, scenario " +
"FROM lateral_contribution_data " +
f"WHERE lc = {lc._pamhyr_id} " +
f"AND 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)
delete = next(it)
data0 = bc._types[0](next(it))
data1 = bc._types[1](next(it))
owner_scenario = next(it)
nd = cls(
data0, data1,
id=pid,
types=lc._types,
status=status,
owner_scenario=owner_scenario
)
if deleted:
nd.set_as_deleted()
loaded.add(pid)
new.append(nd)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
pid = self._pamhyr_id
ind = data["ind"]
data0 = self._db_format(str(self[0]))
data1 = self._db_format(str(self[1]))
lc = data["lc"]
execute(
"INSERT INTO " +
"lateral_contribution_data (pamhyr_id, deleted, ind, " +
"data0, data1, lc, scenario) " +
f"VALUES ({pid}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(data0)}', {self._db_format(data1)}, " +
f"{lc._pamhyr_id}, {self._status.scenario_id}" +
")"
)
return True
def __getitem__(self, key):
return self._types[key](self._data[key])
def __setitem__(self, key, value):
self._data[key] = self._types[key](value)
class LateralContribution(SQLSubModel):
_sub_classes = [Data]
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1,
name: str = "",
status=None, owner_scenario=-1):
super(LateralContribution, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(LateralContribution, self).__init__()
self._status = status
if id == -1:
self.id = LateralContribution._id_cnt
else:
self.id = id
self._name = name
self._type = ""
self._reach = None
self._begin_section = None
self._end_section = None
self._edge = None
self._begin_rk = 0.0
self._end_rk = 0.0
self._data = []
self._header = []
self._types = [float, float]
LateralContribution._id_cnt = max(
LateralContribution._id_cnt + 1, self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE lateral_contribution{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE lateral_contribution(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
tab TEXT NOT NULL,
reach INTEGER,
begin_section INTEGER,
end_section INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
FOREIGN KEY(begin_section)
REFERENCES geometry_profileXYZ(pamhyr_id),
FOREIGN KEY(end_section)
REFERENCES geometry_profileXYZ(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
edge INTEGER,
begin_rk REAL NOT NULL,
end_rk REAL NOT NULL,
FOREIGN KEY(edge) REFERENCES river_reach(id)
)
""")
if ext == "_tmp":
return True
execute("""
CREATE TABLE lateral_contribution_data(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ind INTEGER NOT NULL,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
lc INTEGER,
FOREIGN KEY(lc) REFERENCES lateral_contribution(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 11:
@@ -263,45 +103,7 @@ class LateralContribution(SQLSubModel):
"""
)
execute(
"""
ALTER TABLE lateral_contribution
RENAME COLUMN edge TO reach
"""
)
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE lateral_contribution " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "lateral_contribution"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table} " +
"(pamhyr_id, name, type, tab, reach, scenario) " +
"SELECT pamhyr_id, name, type, tab, reach, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table, reachs)
return True
@classmethod
def _get_ctor_from_type(cls, t):
@@ -322,142 +124,84 @@ class LateralContribution(SQLSubModel):
def _db_load(cls, execute, data=None):
new = []
tab = data["tab"]
status = data['status']
edges = data["edges"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, type, " +
"reach, begin_section, end_section, scenario " +
"FROM lateral_contribution " +
f"WHERE tab = '{tab}'" +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, name, type, edge, begin_rk, end_rk " +
f"FROM lateral_contribution WHERE tab = '{tab}'"
)
for row in table:
it = iter(row)
pid = next(it)
deleted = next(it)
name = next(it)
t = next(it)
reach = next(it)
b_section = next(it)
e_section = next(it)
owner_scenario = next(it)
t = row[2]
ctor = cls._get_ctor_from_type(t)
lc = ctor(
id=pid, name=name,
status=status,
owner_scenario=owner_scenario
id=row[0],
name=row[1],
status=data['status']
)
lc.reach = None
lc._begin_section = None
lc._end_section = None
lc.edge = None
if row[3] != -1:
lc.reach = next(
filter(
lambda e: e.id == reach,
edges
)
)
lc._begin_section = next(
filter(
lambda p: p.id == b_section,
lc.reach.reach.profiles
)
)
lc._end_section = next(
filter(
lambda p: p.id == e_section,
lc.reach.reach.profiles
)
)
lc.edge = next(filter(lambda e: e.id == row[3], data["edges"]))
lc._begin_rk = row[4]
lc._end_rk = row[5]
data["lc"] = lc
lc._data = Data._db_load(execute, data=data)
values = execute(
"SELECT ind, data0, data1 FROM lateral_contribution_data " +
f"WHERE lc = '{lc.id}'"
)
# Create dummy data list
for _ in values:
lc.add(0)
# Write data
for v in values:
ind = v[0]
data0 = lc._types[0](v[1])
data1 = lc._types[1](v[2])
# Replace data at pos ind
lc._data[ind] = (data0, data1)
loaded.add(pid)
new.append(lc)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
tab = data["tab"]
data["lc"] = self
execute(
"DELETE FROM lateral_contribution_data " +
f"WHERE lc = {self._pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM lateral_contribution WHERE id = {self.id}")
execute(f"DELETE FROM lateral_contribution_data WHERE lc = {self.id}")
reach = -1
begin_section = -1
end_section = -1
edge = -1
if self._edge is not None:
edge = self._edge.id
if self._reach is not None:
reach = self._reach._pamhyr_id
begin_section = self._begin_section._pamhyr_id
end_section = self._end_section._pamhyr_id
execute(
sql = (
"INSERT INTO " +
"lateral_contribution(" +
"pamhyr_id, deleted, name, type, tab, " +
"reach, begin_section, end_section, scenario) " +
"lateral_contribution(id, name, type, tab, " +
"edge, begin_rk, end_rk) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', '{tab}', {reach}, " +
f"{begin_section}, {end_section}, " +
f"{self._status.scenario_id}" +
f"{self.id}, '{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', '{tab}', {edge}, " +
f"{self._begin_rk}, {self._end_rk}" +
")"
)
execute(sql)
ind = 0
for d in self._data:
data["ind"] = ind
d._db_save(execute, data)
data0 = self._db_format(str(d[0]))
data1 = self._db_format(str(d[1]))
sql = (
"INSERT INTO " +
"lateral_contribution_data(ind, data0, data1, lc) " +
f"VALUES ({ind}, '{data0}', {data1}, {self.id})"
)
execute(sql)
ind += 1
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(
list(
filter(
lambda el: not el.is_deleted(),
self._data
)
)
)
return len(self._data)
@classmethod
def compatibility(cls):
@@ -482,66 +226,67 @@ class LateralContribution(SQLSubModel):
@property
def name(self):
if self._name == "":
return f"LC #{self.id}"
return f"L{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def lctype(self):
return self._type
@property
def reach(self):
return self._reach
def edge(self):
return self._edge
@reach.setter
def reach(self, reach):
self._reach = reach
if reach is not None:
self._begin_section = self._reach.reach.profiles[0]
self._end_section = self._reach.reach.profiles[-1]
@edge.setter
def edge(self, edge):
self._edge = edge
if edge is not None:
self._begin_rk = self._edge.reach.get_rk_min()
self._end_rk = self._edge.reach.get_rk_max()
self._status.modified()
self.modified()
def has_reach(self):
return self._reach is not None
def has_edge(self):
return self._edge is not None
@property
def begin_rk(self):
if self._begin_section is None:
return 0
return self._begin_rk
return self._begin_section.rk
@begin_rk.setter
def begin_rk(self, begin_rk):
if self._edge is None:
self._begin_rk = begin_rk
else:
_min = self._edge.reach.get_rk_min()
_max = self._edge.reach.get_rk_max()
@property
def begin_section(self):
return self._begin_section
if _min <= begin_rk <= _max:
self._begin_rk = begin_rk
@begin_section.setter
def begin_section(self, section):
self._begin_section = section
self.modified()
self._status.modified()
@property
def end_rk(self):
if self._end_section is None:
return 0
return self._end_rk
return self._end_section.rk
@end_rk.setter
def end_rk(self, end_rk):
if self._edge is None:
self._end_rk = end_rk
else:
_min = self._edge.reach.get_rk_min()
_max = self._edge.reach.get_rk_max()
@property
def end_section(self):
return self._end_section
if _min <= end_rk <= _max:
self._end_rk = end_rk
@end_section.setter
def end_section(self, section):
self._end_section = section
self.modified()
self._status.modified()
@property
def header(self):
@@ -549,12 +294,7 @@ class LateralContribution(SQLSubModel):
@property
def data(self):
return list(
filter(
lambda el: not el.is_deleted(),
self._data
)
)
return self._data.copy()
def get_type_column(self, column):
if 0 <= column < 2:
@@ -588,17 +328,17 @@ class LateralContribution(SQLSubModel):
new_0 = self._types[0](data[0].replace(",", "."))
new_1 = self._types[1](data[1].replace(",", "."))
return Data(new_0, new_1, status=self._status)
return (new_0, new_1)
def add(self, index: int):
value = Data(self._default_0, self._default_1, status=self._status)
value = (self._default_0, self._default_1)
self._data.insert(index, value)
self.modified()
self._status.modified()
return value
def insert(self, index: int, value):
self._data.insert(index, value)
self.modified()
self._status.modified()
def delete_i(self, indexes):
self._data = list(
@@ -610,7 +350,7 @@ class LateralContribution(SQLSubModel):
)
)
)
self.modified()
self._status.modified()
def delete(self, els):
self._data = list(
@@ -619,14 +359,14 @@ class LateralContribution(SQLSubModel):
self.data
)
)
self.modified()
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.modified()
self._status.modified()
def get_i(self, index):
return self.data[index]
@@ -638,10 +378,10 @@ class LateralContribution(SQLSubModel):
return lst
def _set_i_c_v(self, index, column, value):
v = self._data[index]
v = list(self._data[index])
v[column] = self._types[column](value)
self._data[index] = v
self.modified()
self._data[index] = tuple(v)
self._status.modified()
def set_i_0(self, index: int, value):
self._set_i_c_v(index, 0, value)
@@ -652,9 +392,9 @@ class LateralContribution(SQLSubModel):
@timer
def convert(self, cls):
new = cls(name=self.name, status=self._status)
new.reach = self.reach
new.begin_section = self.begin_section
new.end_section = self.end_section
new.edge = self.edge
new.begin_rk = self.begin_rk
new.end_rk = self.end_rk
for i, _ in enumerate(self.data):
new.add(i)
@@ -668,7 +408,7 @@ class LateralContribution(SQLSubModel):
except Exception as e:
logger.info(e)
self.modified()
self._status.modified()
return new
def move_up(self, index):
@@ -676,11 +416,11 @@ class LateralContribution(SQLSubModel):
next = index - 1
d = self._data
d[index], d[next] = d[next], d[index]
self.modified()
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
d = self._data
d[index], d[prev] = d[prev], d[index]
self.modified()
self._status.modified()
@@ -1,5 +1,5 @@
# LateralContributionList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelListWithTab
from Model.Tools.PamhyrList import PamhyrModelListWithTab
from Model.Except import NotImplementedMethodeError
from Model.LateralContribution.LateralContribution import LateralContribution
@@ -50,10 +50,7 @@ class LateralContributionList(PamhyrModelListWithTab):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM lateral_contribution " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM lateral_contribution")
if data is None:
data = {}
@@ -1,5 +1,5 @@
# LateralContributionTypes.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,12 +22,8 @@ from Model.LateralContribution.LateralContribution import LateralContribution
class NotDefined(LateralContribution):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(NotDefined, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(NotDefined, self).__init__(id=id, name=name, status=status)
self._type = "ND"
self._header = ["x", "y"]
@@ -38,12 +34,8 @@ class NotDefined(LateralContribution):
class LateralContrib(LateralContribution):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(LateralContrib, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(LateralContrib, self).__init__(id=id, name=name, status=status)
self._type = "LC"
self._header = ["time", "discharge"]
@@ -55,12 +47,8 @@ class LateralContrib(LateralContribution):
class Rain(LateralContribution):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(Rain, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(Rain, self).__init__(id=id, name=name, status=status)
self._type = "RA"
self._header = ["time", "discharge"]
@@ -72,12 +60,8 @@ class Rain(LateralContribution):
class Evaporation(LateralContribution):
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(Evaporation, self).__init__(
id=id, name=name, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(Evaporation, self).__init__(id=id, name=name, status=status)
self._type = "EV"
self._header = ["time", "discharge"]
@@ -1,5 +1,5 @@
# LateralContributionAdisTS.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,390 +27,137 @@ from tools import (
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class Data(SQLSubModel):
_sub_classes = []
def __init__(self,
data0, data1,
id: int = -1,
types=[float, float],
status=None,
owner_scenario=-1):
super(Data, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._types = types
self._data = [data0, data1]
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE lateral_contribution_data_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
lca INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(lca) REFERENCES lateral_contribution_adists(pamhyr_id)
)
""")
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "lateral_contribution_data_adists"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, data0, data1, lca, scenario) " +
"SELECT pamhyr_id, data0, data1, lc, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_lca_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_lca_pid(cls, execute, data):
pid_lca = data["id2pid"]["lateral_contribution_adists"]
els = execute(
"SELECT pamhyr_id, lca " +
"FROM lateral_contribution_data_adists"
)
for row in els:
it = iter(row)
pid = next(it)
lca_id = next(it)
if lca_id == -1:
continue
execute(
f"UPDATE lateral_contribution_data_adists " +
f"SET lca = {pid_lca[lca_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, data0, data1 " +
"FROM lateral_contribution_data_adists " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
f"AND lca = '{lca.id}'"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
data0 = next(it)
data1 = next(it)
owner_scenario = next(it)
data = cls(
data0, data1,
id=pid, status=status,
owner_scenario=owner_scenario
)
if deleted:
data.set_as_deleted()
loaded.add(pid)
new.append(data)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
lca = data["lca"]
execute(
"INSERT INTO " +
"lateral_contribution_adists(pamhyr_id, deleted," +
"data0, data1, lca, scenario) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"{self._data[0]}, {self._data[1]}, " +
f"{lca.id}, {self._status.scenario_id}" +
")"
)
return True
def __getitem__(self, key):
return self._types[key](self._data[key])
def __setitem__(self, key, value):
self._data[key] = self._types[key](value)
class LateralContributionAdisTS(SQLSubModel):
_sub_classes = [Data]
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1, pollutant: int = -1,
name: str = "", status=None,
owner_scenario=-1):
super(LateralContributionAdisTS, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
name: str = "", status=None):
super(LateralContributionAdisTS, self).__init__()
self._status = status
if id == -1:
self.id = LateralContributionAdisTS._id_cnt
else:
self.id = id
self._pollutant = pollutant
self._reach = None
self._edge = None
self._begin_rk = 0.0
self._end_rk = 0.0
self._data = []
self._header = ["time", "rate"]
self._types = [self.time_convert, float]
LateralContributionAdisTS._id_cnt = max(
LateralContributionAdisTS._id_cnt + 1, self.id)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE lateral_contribution_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE lateral_contribution_adists(
id INTEGER NOT NULL PRIMARY KEY,
pollutant INTEGER NOT NULL,
reach INTEGER NOT NULL,
edge INTEGER NOT NULL,
begin_rk REAL NOT NULL,
end_rk REAL NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(pollutant) REFERENCES Pollutants(pamhyr_id),
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id)
FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
FOREIGN KEY(edge) REFERENCES river_reach(id)
)
""")
if ext != "":
return True
execute("""
CREATE TABLE lateral_contribution_data_adists(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
data0 TEXT NOT NULL,
data1 TEXT NOT NULL,
lc INTEGER,
FOREIGN KEY(lc) REFERENCES lateral_contribution(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
if not created:
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "lateral_contribution_adists"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, pollutant, reach, begin_rk, end_rk, scenario) " +
"SELECT pamhyr_id, pollutant, edge, begin_rk, end_rk, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table, reachs)
cls._db_update_to_0_2_0_set_pollutants_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_pollutants_pid(cls, execute, data):
pid_pol = data["id2pid"]["Pollutants"]
els = execute(
f"SELECT pamhyr_id, pollutant " +
"FROM lateral_contribution_adists"
)
for row in els:
it = iter(row)
pid = next(it)
pol_id = next(it)
if pol_id == -1:
continue
execute(
f"UPDATE lateral_contribution_adists " +
f"SET pollutant = {pid_pol[pol_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, pollutant, reach, " +
"begin_rk, end_rk, scenario " +
"FROM lateral_contribution_adists " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, pollutant, edge, begin_rk, end_rk " +
"FROM lateral_contribution_adists"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
pollutant = next(it)
reach = next(it)
brk = next(it)
erk = next(it)
owner_scenario = next(it)
lca = cls(
id=pid,
pollutant=pollutant,
status=status,
owner_scenario=owner_scenario
lc = cls(
id=row[0],
pollutant=row[1],
status=data['status']
)
if deleted:
lca.set_as_deleted()
lca.reach = reach
lca.begin_rk = brk
lca.end_rk = erk
lc.edge = row[2]
lc.begin_rk = row[3]
lc.end_rk = row[4]
data["lca"] = lca
lca._data = Data._db_load(execute, data=data)
values = execute(
"SELECT data0," +
" data1 FROM lateral_contribution_data_adists " +
f"WHERE lc = '{lc.id}'"
)
loaded.add(pid)
new.append(lca)
# Write data
for v in values:
data0 = lc._types[0](v[0])
data1 = lc._types[1](v[1])
# Replace data at pos ind
lc._data.append((data0, data1))
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new.append(lc)
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
f"DELETE FROM lateral_contribution_adists " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(
f"DELETE FROM lateral_contribution_data_adists " +
f"WHERE lca = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM lateral_contribution_adists" +
f" WHERE id = {self.id}")
execute(f"DELETE FROM lateral_contribution_data_adists" +
f" WHERE lc = {self.id}")
execute(
sql = (
"INSERT INTO " +
"lateral_contribution_adists(id, deleted," +
"pollutant, reach, begin_rk, end_rk, scenario) " +
"lateral_contribution_adists(id, " +
"pollutant, edge, begin_rk, end_rk) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"{self._pollutant}, {self.reach}, " +
f"{self.id}, {self._pollutant}, {self.edge}, " +
f"{self._begin_rk}, {self._end_rk}" +
f"{self._status.scenario_id}" +
")"
)
execute(sql)
ind = 0
for d in self._data:
data["ind"] = ind
data0 = self._db_format(str(d[0]))
data1 = self._db_format(str(d[1]))
d._db_save(execute, data)
ind += 1
sql = (
"INSERT INTO " +
"lateral_contribution_data_adists(data0, data1, lc) " +
f"VALUES ('{data0}', {data1}, {self.id})"
)
execute(sql)
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(
list(
filter(
lambda el: not el.is_deleted(),
self._data
)
)
)
return len(self._data)
@classmethod
def time_convert(cls, data):
@@ -429,13 +176,13 @@ class LateralContributionAdisTS(SQLSubModel):
return int(data)
@property
def reach(self):
return self._reach
def edge(self):
return self._edge
@reach.setter
def reach(self, reach):
self._reach = reach
self.modified()
@edge.setter
def edge(self, edge):
self._edge = edge
self._status.modified()
@property
def header(self):
@@ -444,7 +191,7 @@ class LateralContributionAdisTS(SQLSubModel):
@header.setter
def header(self, header):
self._header = header
self.modified()
self._status.modified()
@property
def pollutant(self):
@@ -461,7 +208,7 @@ class LateralContributionAdisTS(SQLSubModel):
@begin_rk.setter
def begin_rk(self, begin_rk):
self._begin_rk = begin_rk
self.modified()
self._status.modified()
@property
def end_rk(self):
@@ -470,4 +217,4 @@ class LateralContributionAdisTS(SQLSubModel):
@end_rk.setter
def end_rk(self, end_rk):
self._end_rk = end_rk
self.modified()
self._status.modified()
@@ -1,5 +1,5 @@
# LateralContributionsAdisTSList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Except import NotImplementedMethodeError
from Model.LateralContributionsAdisTS.LateralContributionAdisTS \
@@ -45,14 +45,8 @@ class LateralContributionsAdisTSList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM lateral_contribution_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute(
"DELETE FROM lateral_contribution_data_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM lateral_contribution_adists")
execute("DELETE FROM lateral_condition_data_adists")
if data is None:
data = {}
+23 -16
View File
@@ -1,5 +1,5 @@
# Edge.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -16,18 +16,29 @@
# -*- coding: utf-8 -*-
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Node import Node
class Edge(SQLSubModel):
def __init__(self, id: int = -1, name: str = "",
class Edge(object):
_id_cnt = 0
def __init__(self, id: int, name: str,
node1: Node = None,
node2: Node = None,
**kwargs):
super(Edge, self).__init__(id=id, **kwargs)
status=None):
super(Edge, self).__init__()
self._status = status
if id == -1:
self.id = type(self)._id_cnt
else:
self.id = id
type(self)._id_cnt = max(self.id + 1, type(self)._id_cnt + 1)
self._name = name
self.node1 = node1
self.node2 = node2
@@ -39,7 +50,7 @@ class Edge(SQLSubModel):
if name == "name":
ret = self.name
elif name == "id":
ret = self.pamhyr_id
ret = self.id
elif name == "node1":
ret = self.node1
elif name == "node2":
@@ -53,7 +64,7 @@ class Edge(SQLSubModel):
if name == "name":
self._name = value
elif name == "id":
self.pamhyr_id = value
self.id = value
elif name == "node1":
self.node1 = value
elif name == "node2":
@@ -61,11 +72,7 @@ class Edge(SQLSubModel):
elif name == "enable":
self._enable = value
self.modified()
@property
def id(self):
return self.pamhyr_id
self._status.modified()
@property
def name(self):
@@ -79,14 +86,14 @@ class Edge(SQLSubModel):
def enable(self, enable=True):
self._enable = enable
self.modified()
self._status.modified()
def disable(self):
self._enable = False
self.modified()
self._status.modified()
def reverse(self):
tmp = self.node1
self.node1 = self.node2
self.node2 = tmp
self.modified()
self._status.modified()
+34 -40
View File
@@ -1,5 +1,5 @@
# Graph.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -18,15 +18,15 @@
from functools import reduce
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Node import Node
from Model.Network.Edge import Edge
class Graph(SQLSubModel):
def __init__(self, **kwargs):
super(Graph, self).__init__(**kwargs)
class Graph(object):
def __init__(self, status=None):
super(Graph, self).__init__()
self._status = status
self._node_ctor = Node
self._edge_ctor = Edge
@@ -38,13 +38,13 @@ class Graph(SQLSubModel):
return f"Graph {{nodes: {self._nodes}, edges: {self._edges}}}"
def nodes(self):
return list(filter(lambda n: not n.is_deleted(), self._nodes))
return self._nodes
def nodes_names(self):
return list(map(lambda n: n.name, self.nodes()))
return list(map(lambda n: n.name, self._nodes))
def edges(self):
return list(filter(lambda e: not e.is_deleted(), self._edges))
return self._edges
def enable_edges(self):
return list(
@@ -54,44 +54,44 @@ class Graph(SQLSubModel):
def _enable_edges(self):
"""Return a generator"""
return filter(
lambda e: e.is_enable() and not e.is_deleted(),
self.edges()
lambda e: e.is_enable(),
self._edges
)
def edges_names(self):
return list(map(lambda e: e.name, self.edges()))
return list(map(lambda e: e.name, self._edges))
def nodes_counts(self):
return len(self.nodes())
return len(self._nodes)
def edges_counts(self):
return len(self.edges())
return len(self._edges)
def enable_nodes_counts(self):
return reduce(
lambda acc, n: acc + 1 if self.is_enable_node(n) else acc,
self.nodes(),
self._nodes,
0
)
def enable_edges_counts(self):
return reduce(
lambda acc, e: acc + 1 if e.is_enable() else acc,
self.edges(),
self._edges,
0
)
def is_node_exists(self, node_name):
return reduce(
lambda acc, n: (acc or (n.name == node_name)),
self.nodes(),
self._nodes,
False
)
def is_edge_exists(self, edge_name):
return reduce(
lambda acc, e: (acc or (e.name == edge_name)),
self.edges(),
self._edges,
False
)
@@ -99,7 +99,7 @@ class Graph(SQLSubModel):
node = list(
filter(
lambda n: n.name == node_name,
self.nodes()
self._nodes
)
)
@@ -112,7 +112,7 @@ class Graph(SQLSubModel):
edge = list(
filter(
lambda e: e.name == edge_name,
self.edges()
self._edges
)
)
@@ -121,23 +121,18 @@ class Graph(SQLSubModel):
return edge[0]
def reach(self, reach_name: str):
return self.edge(reach_name)
def _create_node(self, x: float, y: float):
node = self._node_ctor(
id=-1, name="", x=x, y=y,
-1,
"",
x=x, y=y,
status=self._status
)
return node
def _add_node(self, node):
if node in self._nodes:
node.set_as_not_deleted()
else:
self._nodes.append(node)
self.modified()
self._nodes.append(node)
self._status.modified()
return node
def add_node(self, x: float = 0.0, y: float = 0.0):
@@ -148,9 +143,9 @@ class Graph(SQLSubModel):
return self._add_node(node)
def remove_node(self, node):
node.set_as_deleted()
self._nodes.remove(node)
self._remove_associated_edge(node)
self.modified()
self._status.modified()
def _remove_associated_edge(self, node: str):
edges = list(
@@ -169,7 +164,8 @@ class Graph(SQLSubModel):
def _create_edge(self, n1: Node, n2: Node):
edge = self._edge_ctor(
node1=n1, node2=n2,
-1,
"", n1, n2,
status=self._status
)
return edge
@@ -180,12 +176,10 @@ class Graph(SQLSubModel):
# e.node2 == edge.node2),
# self._edges)):
# return None
if edge in self._edges:
edge.set_as_not_deleted()
else:
self._edges.append(edge)
self.modified()
self._edges.append(edge)
self._status.modified()
return edge
def add_edge(self, n1: Node, n2: Node):
@@ -199,8 +193,8 @@ class Graph(SQLSubModel):
return self._create_edge(n1, n2)
def remove_edge(self, edge):
edge.set_as_deleted()
self.modified()
self._edges.remove(edge)
self._status.modified()
def is_upstream_node(self, node):
return reduce(
+20 -10
View File
@@ -1,5 +1,5 @@
# Node.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -16,15 +16,25 @@
# -*- coding: utf-8 -*-
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Network.Point import Point
class Node(SQLSubModel):
def __init__(self, id: int = -1, name: str = "",
class Node(object):
_id_cnt = 0
def __init__(self, id: int, name: str,
x: float = 0.0, y: float = 0.0,
**kwargs):
super(Node, self).__init__(id=id, **kwargs)
status=None):
super(Node, self).__init__()
self._status = status
if id == -1:
self.id = type(self)._id_cnt
else:
self.id = id
type(self)._id_cnt = max(self.id + 1, type(self)._id_cnt + 1)
self._name = name
self.pos = Point(x, y)
@@ -35,7 +45,7 @@ class Node(SQLSubModel):
if key == "name":
ret = self._name
elif key == "id":
ret = self.pamhyr_id
ret = self.id
elif key == "pos":
ret = f"({self.pos.x},{self.pos.y})"
@@ -45,9 +55,9 @@ class Node(SQLSubModel):
if key == "name":
self._name = value
elif key == "id":
self.pamhyr_id = value
self.id = value
self.modified()
self._status.modified()
@property
def name(self):
@@ -67,4 +77,4 @@ class Node(SQLSubModel):
def setPos(self, x, y):
self.pos.x = x
self.pos.y = y
self.modified()
self._status.modified()
+1 -1
View File
@@ -1,5 +1,5 @@
# Point.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+1 -1
View File
@@ -1,5 +1,5 @@
# __init__.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+3 -6
View File
@@ -1,5 +1,5 @@
# test_Network.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -20,7 +20,7 @@ import os
import unittest
import tempfile
from Model.Status import StudyStatus
from Model.Saved import SavedStatus
from Model.Network.Graph import Graph
from Model.Network.Edge import Edge
@@ -28,7 +28,7 @@ from Model.Network.Node import Node
def new_graph():
status = StudyStatus()
status = SavedStatus()
g = Graph(status=status)
return g
@@ -60,9 +60,6 @@ class GraphTestCase(unittest.TestCase):
e0 = g.add_edge(n0, n1)
self.assertEqual(g.nodes_counts(), 2)
self.assertEqual(g.edges_counts(), 1)
g.remove_edge(e0)
self.assertEqual(g.nodes_counts(), 2)
+44 -97
View File
@@ -1,5 +1,5 @@
# OutputRKAdists.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,27 +27,33 @@ from tools import (
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class OutputRKAdists(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1, reach=None,
rk=None, title: str = "", status=None,
owner_scenario=-1):
super(OutputRKAdists, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
rk=None, title: str = "", status=None):
super(OutputRKAdists, self).__init__()
self._status = status
if id == -1:
self.id = OutputRKAdists._id_cnt
else:
self.id = id
self._reach = reach
self._rk = rk
self._title = str(title)
self._enabled = True
OutputRKAdists._id_cnt = max(
OutputRKAdists._id_cnt + 1, self.id + 1)
@property
def reach(self):
return self._reach
@@ -76,130 +82,71 @@ class OutputRKAdists(SQLSubModel):
self._status.modified()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE output_rk_adists{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
reach INTEGER NOT NULL,
rk REAL NOT NULL,
title TEXT NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
def _db_create(cls, execute):
sql = (
"CREATE TABLE OutputRKAdists(" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"reach INTEGER NOT NULL, " +
"rk REAL NOT NULL, " +
"title TEXT NOT NULL, " +
"FOREIGN KEY(reach) REFERENCES river_reach(id)" +
")"
)
execute(sql)
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
def _db_update(cls, execute, version):
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "OutputRKAdists"
table_new = "output_rk_adists"
reachs = data['id2pid']['river_reach']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table_new}_tmp " +
"(pamhyr_id, reach, rk, title, scenario) " +
"SELECT pamhyr_id, reach, rk, title, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}")
cls._db_update_to_0_2_0_set_reach_pid(execute, table_new, reachs)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data['status']
scenario = data["scenario"]
loaded = data['loaded_pid']
# reach = data["reach"]
# profile = data["profile"]
if scenario is None:
return new
status = data["status"]
table = execute(
"SELECT pamhyr_id, deleted, reach, rk, title, scenario " +
f"FROM output_rk_adists"
"SELECT id, reach, rk, title " +
f"FROM OutputRKAdists"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
id_reach = next(it)
id_rk = next(it)
title = next(it)
owner_scenario = next(it)
id = row[0]
id_reach = row[1]
id_rk = row[2]
title = row[3]
new_output = cls(
id=pid, reach=id_reach,
id=id, reach=id_reach,
rk=id_rk, title=title,
status=status,
owner_scenario=owner_scenario,
status=status
)
if deleted:
new_output.set_as_deleted()
loaded.add(pid)
new.append(new_output)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"DELETE FROM output_rk_adists " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id} "
)
execute(f"DELETE FROM OutputRKAdists WHERE id = {self.id}")
execute(
sql = (
"INSERT INTO " +
"output_rk_adists(pamhyr_id, deleted, " +
"reach, rk, title, scenario) " +
"OutputRKAdists(id, reach, rk, title) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}" +
f"{self._reach}, {self._rk}, " +
f"'{self._db_format(self._title)}', " +
f"{self._status.scenario_id}" +
f"{self.id}, {self._reach}, {self._rk}, " +
f"'{self._db_format(self._title)}'" +
")"
)
execute(sql)
return True
@property
@@ -1,5 +1,5 @@
# OutputRKListAdists.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -19,7 +19,7 @@
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.OutputRKAdists.OutputRKAdists import OutputRKAdists
@@ -38,10 +38,7 @@ class OutputRKAdistsList(PamhyrModelList):
ok = True
# Delete previous data
execute(
"DELETE FROM output_rk_adists " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM OutputRKAdists")
for sl in self._lst:
ok &= sl._db_save(execute, data)
+71 -369
View File
@@ -1,5 +1,5 @@
# Pollutants.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -27,265 +27,24 @@ from tools import (
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
logger = logging.getLogger()
class PollutantCharacteristics(SQLSubModel):
_sub_classes = []
def __init__(self, id: int = -1, type=-1,
diametre=-1, rho=-1,
porosity=-1,
cdc_riv=-1, cdc_cas=-1,
apd=-1, ac=-1, bc=-1,
pollutant=None,
status=None,
owner_scenario=-1):
super(PollutantCharacteristics, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._status = status
self._type = type
self._diametre = diametre
self._rho = rho
self._porosity = porosity
self._cdc_riv = cdc_riv
self._cdc_cas = cdc_cas
self._apd = apd
self._ac = ac
self._bc = bc
self._pollutant = pollutant
# FIXME: This four next method is dirty work, just an hack to keep
# the View API working... Must be refactoring
def to_list(self):
return [
self._type,
self._diametre,
self._rho,
self._porosity,
self._cdc_riv,
self._cdc_cas,
self._apd,
self._ac,
self._bc,
]
def __len__(self):
return len(self.to_list())
def __getitem__(self, key):
return self.to_list()[key]
def __setitem__(self, key, value):
if key == 0:
self._type = value
elif key == 1:
self._diametre = value
elif key == 2:
self._rho = value
elif key == 3:
self._porosity = value
elif key == 4:
self._cdc_riv = value
elif key == 5:
self._cdc_cas = value
elif key == 6:
self._apd = value
elif key == 7:
self._ac = value
elif key == 8:
self._bc = value
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE pollutants_characteristics{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
type INTEGER NOT NULL,
diametre REAL NOT NULL,
rho REAL NOT NULL,
porosity REAL NOT NULL,
cdc_riv REAL NOT NULL,
cdc_cas REAL NOT NULL,
apd REAL NOT NULL,
ac REAL NOT NULL,
bc REAL NOT NULL,
pollutant INTEGER NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(pollutant) REFERENCES pollutants(pamhyr_id)
)
""")
return True
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "Pollutants_characteristics"
table_new = "pollutants_characteristics"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table_new}_tmp " +
"(pamhyr_id, type, diametre, rho, porosity, " +
"cdc_riv, cdc_cas, apd, ac, bc, pollutant, scenario) " +
"SELECT pamhyr_id, type, diametre, rho, porosity, " +
"cdc_riv, cdc_cas, apd, ac, bc, pollutant, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}")
cls._db_update_to_0_2_0_set_pollutants_pid(execute, data)
@classmethod
def _db_update_to_0_2_0_set_pollutants_pid(cls, execute, data):
pid_pol = data["id2pid"]["Pollutants"]
els = execute(
f"SELECT pamhyr_id, pollutant " +
"FROM pollutants_characteristics"
)
for row in els:
it = iter(row)
pid = next(it)
pol_id = next(it)
if pol_id == -1:
continue
execute(
f"UPDATE pollutants_characteristics " +
f"SET pollutant = {pid_pol[pol_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
pollutant = data['pollutant']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, type, diametre, rho, porosity, " +
"cdc_riv, cdc_cas, apd, ac, bc, scenario " +
"FROM pollutants_characteristics " +
f"WHERE pollutant = {pollutant.id} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
type = next(it)
diametre = next(it)
rho = next(it)
porosity = next(it)
cdc_riv = next(it)
cdc_cas = next(it)
apd = next(it)
ac = next(it)
bc = next(it)
owner_scenario = next(it)
pc = cls(
id=pid, type=type,
diametre=diametre,
rho=rho,
porosity=porosity,
cdc_riv=cdc_riv, cdc_cas=cdc_cas,
apd=apd, ac=ac, bc=bc,
pollutant=pollutant,
status=status,
owner_scenario=owner_scenario
)
if deleted:
pc.set_as_deleted()
loaded.add(pid)
new.append(pc)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"INSERT INTO " +
"pollutants_characteristics(pamhyr_id, deleted, " +
"type, diametre, rho, porosity, " +
"cdc_riv, cdc_cas, apd, ac, bc, " +
"pollutant, scenario) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"{self._type}, " +
f"{self._diametre}, {self._rho}, " +
f"{self._porosity}, " +
f"{self._cdc_riv}, {self._cdc_cas}, " +
f"{self._apd}, {self._ac}, {self._bc}, " +
f"{self._pollutant.id}, " +
f"{self._status.scenario_id}" +
")"
)
return True
class Pollutants(SQLSubModel):
_sub_classes = [PollutantCharacteristics]
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None, owner_scenario=-1):
super(Pollutants, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "", status=None):
super(Pollutants, self).__init__()
self._status = status
if id == -1:
self.id = Pollutants._id_cnt
else:
self.id = id
if name is None or name == "":
self.name = f"pol{self.id}"
else:
@@ -294,6 +53,9 @@ class Pollutants(SQLSubModel):
self._data = []
Pollutants._id_cnt = max(
Pollutants._id_cnt + 1, self.id + 1)
@property
def name(self):
return self._name
@@ -305,167 +67,106 @@ class Pollutants(SQLSubModel):
@property
def data(self):
return self._data
@data.setter
def data(self, data):
self._data = [PollutantCharacteristics(type=int(data[0]),
diametre=float(data[1]),
rho=float(data[2]),
porosity=float(data[3]),
cdc_riv=float(data[4]),
cdc_cas=float(data[5]),
apd=float(data[6]),
ac=float(data[7]),
bc=float(data[8]),
pollutant=self,
status=self._status
)
]
return self._data.copy()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE pollutants{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()}
def _db_create(cls, execute):
execute("""
CREATE TABLE Pollutants(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
)
""")
if ext != "":
return True
execute("""
CREATE TABLE Pollutants_characteristics(
id INTEGER NOT NULL PRIMARY KEY,
type INTEGER NOT NULL,
diametre REAL NOT NULL,
rho REAL NOT NULL,
porosity REAL NOT NULL,
cdc_riv REAL NOT NULL,
cdc_cas REAL NOT NULL,
apd REAL NOT NULL,
ac REAL NOT NULL,
bc REAL NOT NULL,
pollutant INTEGER NOT NULL,
FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
created = False
if major == "0" and int(minor) <= 1:
if int(release) < 7:
cls._db_create(execute)
created = True
if major == "0" and int(minor) < 2:
if not created:
cls._db_update_to_0_2_0(execute, data)
if not created:
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return True
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "Pollutants"
table_new = "pollutants"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table_new}_tmp " +
"(pamhyr_id, name, scenario) " +
"SELECT pamhyr_id, name, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table_new}_tmp RENAME TO {table_new}")
@classmethod
def _db_load(cls, execute, data=None):
new = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name FROM pollutants " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, name " +
f"FROM Pollutants"
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
id = row[0]
name = row[1]
new_pollutant = cls(
id=pid, name=name,
id=id, name=name,
status=status
)
if deleted:
new_pollutant.set_as_deleted()
data["pollutant"] = new_pollutant
new_pollutant._data = PollutantCharacteristics._db_load(
execute, data=data
new_data = []
table = execute(
"SELECT * " +
"FROM Pollutants_characteristics " +
f"WHERE pollutant = {id}"
)
loaded.add(pid)
new.append(new_pollutant)
if table is not None:
for t in table:
new_data = t[1:-1]
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new_pollutant._data.append(new_data)
new.append(new_pollutant)
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
ok = True
execute(f"DELETE FROM Pollutants WHERE id = {self.id}")
execute(f"DELETE FROM Pollutants_characteristics" +
f" WHERE pollutant = {self.id}")
execute(
"DELETE FROM pollutants " +
f"WHERE pamhyr_id = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(
"DELETE FROM pollutants_characteristics " +
f"WHERE pollutant = {self.id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(
sql = (
"INSERT INTO " +
"pollutants(pamhyr_id, deleted, name, scenario) " +
"Pollutants(id, name) " +
"VALUES (" +
f"{self.id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"{self._status.scenario_id}" +
f"{self.id}, " +
f"'{self._db_format(self._name)}'" +
")"
)
for d in self._data:
ok &= d._db_save(execute, data)
return ok
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
execute(sql)
for d in self._data:
d._data_traversal(predicate, modifier, data)
sql = (
"INSERT INTO " +
"Pollutants_characteristics(type, diametre, rho, porosity, " +
"cdc_riv, cdc_cas, apd, ac, bc, pollutant) " +
f"VALUES ({d[0]}, {d[1]}, {d[2]},{d[3]}, {d[4]}, "
f"{d[5]}, {d[6]}, {d[7]}, {d[8]}, {self.id})"
)
execute(sql)
return True
@property
def enabled(self):
@@ -480,6 +181,7 @@ class Pollutants(SQLSubModel):
return len(self._data) != 0
def new_from_data(self, data):
try:
new = [int(data[0])]
new += [float(d) for d in data[1:]]
+2 -5
View File
@@ -1,5 +1,5 @@
# PollutantsList.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -38,10 +38,7 @@ class PollutantsList(PamhyrModelList):
ok = True
# Delete previous data
execute(
"DELETE FROM Pollutants " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM Pollutants")
for sl in self._lst:
ok &= sl._db_save(execute, data)
+29 -78
View File
@@ -1,5 +1,5 @@
# REPLine.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -22,25 +22,31 @@ from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.Scenario import Scenario
class REPLine(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1, enabled=True,
name="", line="", solvers=set(),
status=None, owner_scenario=-1):
super(REPLine, self).__init__(
id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(REPLine, self).__init__()
if id == -1:
self.id = REPLine._id_cnt
else:
self.id = id
self._status = status
self._enabled = enabled
self._name = f"Line #{self._pamhyr_id}" if name == "" else name
self._name = f"Line{self.id}" if name == "" else name
self._line = line
self._solvers = solvers
REPLine._id_cnt = max(self.id + 1, REPLine._id_cnt + 1)
def __getitem__(self, key):
value = None
@@ -107,75 +113,35 @@ class REPLine(SQLSubModel):
self._status.modified()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE rep_lines{ext} (
{cls.create_db_add_pamhyr_id()},
def _db_create(cls, execute):
execute("""
CREATE TABLE rep_lines(
id INTEGER NOT NULL PRIMARY KEY,
enabled BOOLEAN NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL,
line TEXT NOT NULL,
solvers TEXT NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario)
solvers TEXT NOT NULL
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 9:
cls._db_create(execute)
return True
else:
cls._db_update_to_0_0_14(execute, data)
if major == "0" and int(minor) <= 1:
if int(release) < 2:
execute(
"ALTER TABLE rep_lines " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return True
@classmethod
def _db_update_to_0_0_14(cls, execute, data):
table = "rep_lines"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, enabled, name, line, solvers, scenario) " +
"SELECT pamhyr_id, enabled, name, line, solvers, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, enabled, deleted, name, line, " +
"solvers, scenario " +
"FROM rep_lines " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, enabled, name, line, solvers " +
"FROM rep_lines"
)
for row in table:
@@ -183,47 +149,32 @@ class REPLine(SQLSubModel):
id = next(it)
enabled = (next(it) == 1)
deleted = (next(it) == 1)
name = next(it)
line = next(it)
solvers = set(next(it).split(";;"))
owner_scenario = next(it)
f = cls(
id=id, enabled=enabled,
name=name, line=line, solvers=solvers,
status=data['status'], owner_scenario=owner_scenario
id=id, enabled=enabled, name=name, line=line,
solvers=solvers, status=data['status']
)
if deleted:
f.set_as_deleted()
loaded.add(id)
new.append(f)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
solvers = ";;".join(self._solvers)
execute(
sql = (
"INSERT INTO " +
"rep_lines(pamhyr_id, enabled, deleted, " +
"name, line, solvers, scenario) " +
"rep_lines(id, enabled, name, line, solvers) " +
"VALUES (" +
f"{self._pamhyr_id}, {self._enabled}, " +
f"{self._db_format(self.is_deleted())}, " +
f"{self.id}, {self._enabled}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._line)}', " +
f"'{self._db_format(solvers)}', " +
f"{self._status.scenario_id}" +
f"'{self._db_format(solvers)}'" +
")"
)
execute(sql)
return True
+3 -6
View File
@@ -1,5 +1,5 @@
# REPLineList.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -19,7 +19,7 @@
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.REPLine.REPLine import REPLine
@@ -38,10 +38,7 @@ class REPLineList(PamhyrModelList):
ok = True
# Delete previous data
execute(
"DELETE FROM rep_lines " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM rep_lines")
for af in self._lst:
ok &= af._db_save(execute, data)
+89 -347
View File
@@ -1,5 +1,5 @@
# Reservoir.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,386 +19,148 @@
import logging
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
logger = logging.getLogger()
class Data(SQLSubModel):
_sub_classes = []
def __init__(self, id: int = -1,
elevation: float = 0.,
surface: float = 0.,
status=None, owner_scenario=-1):
super(Data, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
self._data = [elevation, surface]
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE reservoir_data{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
ind INTEGER NOT NULL,
elevation REAL NOT NULL,
surface REAL NOT NULL,
reservoir INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reservoir) REFERENCES reservoir(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
)
""")
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 == minor == "0":
if int(release) > 5:
cls._db_update_to_0_2_0(execute, data)
if major == "0" and int(minor) <= 1:
if int(release) < 2:
execute(
"ALTER TABLE reservoir_data " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "reservoir_data"
id2pid = data['id2pid']
cls.update_db_add_pamhyr_id(
execute, table,
data=data
)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, elevation, surface, reservoir, scenario) " +
"SELECT pamhyr_id, ind, elevation, surface, reservoir, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
if 'reservoir' in id2pid:
reservoirs = id2pid['reservoir']
cls._db_update_to_0_2_0_set_reservoir_pid(
execute, table, reservoirs
)
@classmethod
def _db_update_to_0_2_0_set_reservoir_pid(cls, execute, table, reservoirs):
bcs = execute(
f"SELECT pamhyr_id, reservoir FROM {table}"
)
for row in bcs:
it = iter(row)
pid = next(it)
reservoir_id = next(it)
execute(
f"UPDATE {table} " +
f"SET reservoir = {reservoirs[reservoir_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
if scenario is None:
return new
loaded = data['loaded_pid']
reseroire = data["reservoir"]
values = execute(
"SELECT pamhyr_id, deleted, ind, " +
"elevation, surface, scenario " +
"FROM reservoir_data " +
f"WHERE reservoir = {reseroire.pamhyr_id} " +
f"AND 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)
deleted = (next(it) == 1)
ind = next(it)
elevation = next(it)
surface = next(it)
owner_scenario = next(it)
nd = cls(
id=pid, status=data['status'],
owner_scenario=owner_scenario
)
if deleted:
nd.set_as_deleted()
nd._data = [elevation, surface]
loaded.add(pid)
new.append(nd)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
pid = self._pamhyr_id
ind = data["ind"]
elevation = self._db_format(str(self[0]))
surface = self._db_format(str(self[1]))
reservoir = data["reservoir"]
execute(
"INSERT INTO reservoir_data " +
"(pamhyr_id, deleted, ind, " +
"elevation, surface, reservoir, scenario) " +
"VALUES (" +
f"{pid}, " +
f"{self._db_format(self.is_deleted())}, " +
f"{ind}, '{elevation}', {surface}, " +
f"{reservoir._pamhyr_id}, " +
f"{self._status.scenario_id}" +
")"
)
def __getitem__(self, key):
return self._data[key]
def __setitem__(self, key, value):
self._data[key] = float(value)
class Reservoir(SQLSubModel):
_sub_classes = [Data]
_sub_classes = []
_id_cnt = 0
def __init__(self, id: int = -1,
name: str = "",
status=None, owner_scenario=-1):
super(Reservoir, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
def __init__(self, id: int = -1, name: str = "",
status=None):
super(Reservoir, self).__init__()
self._status = status
if id == -1:
self.id = Reservoir._id_cnt
else:
self.id = id
self._name = name
self._node = None
self._data = []
Reservoir._id_cnt = max(Reservoir._id_cnt + 1, self.id + 1)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE reservoir{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE reservoir(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
node INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(node) REFERENCES river_node(id)
)
""")
if ext == "_tmp":
return True
execute("""
CREATE TABLE reservoir_data(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ind INTEGER NOT NULL,
elevation REAL NOT NULL,
surface REAL NOT NULL,
reservoir INTEGER,
FOREIGN KEY(reservoir) REFERENCES reservoir(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 5:
cls._db_create(execute)
return True
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE additional_files " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "reservoir"
nodes = data['id2pid']['river_node']
cls.update_db_add_pamhyr_id(execute, table, data=data)
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
Scenario.update_db_add_scenario(execute, table)
print(execute(f"SELECT * FROM {table}"))
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, node, scenario) " +
"SELECT pamhyr_id, name, node, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, node, scenario " +
"FROM reservoir " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, name, node " +
"FROM reservoir "
)
for row in table:
it = iter(row)
id = next(it)
deleted = (next(it) == 1)
name = next(it)
node_id = next(it)
owner_scenario = next(it)
new_reservoir = cls(
id, name,
status=data["status"],
owner_scenario=owner_scenario
)
if deleted:
new_reservoir.set_as_deleted()
id = row[0]
name = row[1]
node_id = row[2]
new_reservoir = cls(id, name, status=data["status"])
new_reservoir._node = None
if node_id != -1 and node_id is not None:
if node_id != -1:
new_reservoir._node = next(
filter(
lambda n: n.id == node_id, data["nodes"]
)
), None
)
data["reservoir"] = new_reservoir
new_reservoir._data = Data._db_load(
execute, data=data
new_data = []
table = execute(
"SELECT elevation, surface " +
"FROM reservoir_data " +
f"WHERE reservoir = {id} " +
"ORDER BY ind ASC"
)
for t in table:
new_data.append((t[0], t[1]))
new_reservoir._data = new_data
loaded.add(id)
new.append(new_reservoir)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
"DELETE FROM reservoir " +
f"WHERE pamhyr_id = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(
"DELETE FROM reservoir_data " +
f"WHERE reservoir = {self.pamhyr_id} " +
f"AND scenario = {self._status.scenario_id}"
)
execute(f"DELETE FROM reservoir WHERE id = {self.id}")
execute(f"DELETE FROM reservoir_data WHERE reservoir = {self.id}")
node_id = 'NULL'
node_id = -1
if self._node is not None:
node_id = self._node.pamhyr_id
node_id = self._node.id
sql = (
"INSERT INTO " +
"reservoir(pamhyr_id, deleted, name, node, scenario) " +
"reservoir(id, name, node) " +
"VALUES (" +
f"{self.pamhyr_id}, " +
f"{self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"{node_id}, " +
f"{self._status.scenario_id}" +
f"{self.id}, '{self._db_format(self._name)}', " +
f"{node_id}" +
")"
)
execute(sql)
data["reservoir"] = self
ind = 0
for d in self._data:
data["ind"] = ind
d._db_save(execute, data)
sql = (
"INSERT INTO " +
"reservoir_data(ind, elevation, surface, reservoir) " +
f"VALUES ({ind}, '{d[0]}', {d[1]}, {self.id})"
)
execute(sql)
ind += 1
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for d in self._data:
d._data_traversal(predicate, modifier, data)
def __len__(self):
return len(self._data)
@property
def name(self):
if self._name == "":
return f"Reservoir #{self.pamhyr_id}"
return f"R{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
self._status.modified()
@property
def node(self):
@@ -407,7 +169,7 @@ class Reservoir(SQLSubModel):
@node.setter
def node(self, node):
self._node = node
self.modified()
self._status.modified()
def has_node(self):
return self._node is not None
@@ -440,63 +202,42 @@ class Reservoir(SQLSubModel):
return (new_0, new_1)
def add(self, index: int):
data = Data(
elevation=self._default_elevation,
surface=self._default_surface,
status=self._status,
)
self._data.insert(index, data)
self.modified()
return data
value = (self._default_elevation, self._default_surface)
self._data.insert(index, value)
self._status.modified()
return value
def insert(self, index: int, value):
if type(value) is tuple or type(value) is list:
value = Data(
elevation=value[0],
surface=value[1]
)
self._data.insert(index, value)
self.modified()
self._status.modified()
def hard_delete_i(self, indexes):
def delete_i(self, indexes):
self._data = list(
map(
lambda e: e[1],
filter(
lambda e: e[0] not in indexes,
enumerate(self._data)
enumerate(self.data)
)
)
)
self.modified()
def delete_i(self, indexes):
list(
map(
lambda e: e[1].set_as_deleted(),
filter(
lambda e: e[0] in indexes,
enumerate(self.lst)
)
)
)
self.modified()
self._status.modified()
def delete(self, els):
for el in els:
el.set_as_deleted()
self.modified()
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, key=lambda d: d[0])
self._data.sort(reverse=_reverse)
else:
self._data.sort(reverse=_reverse, key=key)
self.modified()
self._status.modified()
def get_i(self, index):
return self.data[index]
@@ -505,12 +246,13 @@ class Reservoir(SQLSubModel):
lst = []
for r in _range:
lst.append(r)
return lst
def _set_i_c_v(self, index, column, value):
self._data[index][column] = value
self.modified()
v = list(self._data[index])
v[column] = value
self._data[index] = tuple(v)
self._status.modified()
def set_i_elevation(self, index: int, value):
self._set_i_c_v(index, 0, value)
@@ -523,11 +265,11 @@ class Reservoir(SQLSubModel):
next = index - 1
d = self._data
d[index], d[next] = d[next], d[index]
self.modified()
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
d = self._data
d[index], d[prev] = d[prev], d[index]
self.modified()
self._status.modified()
+4 -10
View File
@@ -1,5 +1,5 @@
# ReservoirList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,7 +19,7 @@
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Reservoir.Reservoir import Reservoir
@@ -39,14 +39,8 @@ class ReservoirList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM reservoir " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute(
"DELETE FROM reservoir_data " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM reservoir")
execute("DELETE FROM reservoir_data")
if data is None:
data = {}
+6 -316
View File
@@ -1,5 +1,5 @@
# Results.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -14,174 +14,32 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import struct
import logging
import itertools
import numpy as np
from copy import deepcopy
from datetime import datetime
from Model.Scenario import Scenario
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Results.River.River import River
logger = logging.getLogger()
class AdditionalData(SQLSubModel):
_sub_classes = []
def __init__(self, id=-1, study=None, data=None):
super(AdditionalData, self).__init__(
id=id, status=study.status,
owner_scenario=study.status.scenario.id
)
self._study = study
self._data = data
@property
def data(self):
return self._data
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE results_add_data{ext} (
{cls.create_db_add_pamhyr_id()},
result INTEGER NOT NULL,
type_x TEXT NOT NULL,
type_y TEXT NOT NULL,
legend TEXT NOT NULL,
unit TEXT NOT NULL,
data_len INTEGER NOT NULL,
x BLOB NOT NULL,
y BLOB NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
PRIMARY KEY(pamhyr_id, result, scenario)
)
""")
if ext != "":
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 and int(release) <= 1:
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"]
table = execute(
"SELECT pamhyr_id, type_x, type_y, " +
"legend, unit, data_len, x, y, " +
"scenario " +
"FROM results_add_data " +
f"WHERE scenario = {scenario.id}"
)
if table is None:
return new
for v in table:
it = iter(v)
pid = next(it)
type_x = next(it)
type_y = next(it)
legend = next(it)
unit = next(it)
data_len = next(it)
bx = next(it)
by = next(it)
owner_scenario = next(it)
data_format = ">" + ''.join(itertools.repeat("d", data_len))
x = struct.unpack(data_format, bx)
y = struct.unpack(data_format, by)
data = {
'type_x': type_x,
'type_y': type_y,
'legend': legend,
'unit': unit,
'x': x, 'y': y
}
new_data = cls(study=study)
new_data._data = data
new.append(new_data)
return new
def _db_save(self, execute, data=None):
if self._status.scenario.id != self._owner_scenario:
return
pid = self._pamhyr_id
data_len = len(self._data["x"])
data_format = ">" + ''.join(itertools.repeat("d", data_len))
bx = struct.pack(data_format, *self._data["x"])
by = struct.pack(data_format, *self._data["y"])
execute(
"INSERT INTO " +
"results_add_data (pamhyr_id, result, " +
"type_x, type_y, " +
"legend, unit, data_len, x, y, " +
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
self._pamhyr_id, data["result"],
self._data["type_x"], self._data["type_y"],
self._data["legend"], self._data["unit"],
data_len, bx, by, self._owner_scenario
)
return True
class Results(SQLSubModel):
_sub_classes = [River, AdditionalData]
def __init__(self, id=-1, study=None, solver=None,
class Results(object):
def __init__(self, study=None, solver=None,
repertory="", name="0"):
super(Results, self).__init__(
id=id, status=study.status,
owner_scenario=study.status.scenario.id
)
self._name = name
self._study = study
self._river = River(self._study)
self._solver = solver
self._repertory = repertory
self._name = name
self._river = River(self._study)
self._meta_data = {
# Keep results creation date
"creation_date": datetime.now(),
"study_revision": study.status.version,
"additional_data": [],
}
if solver is not None:
self.set("solver_type", solver._type)
@property
def date(self):
date = self._meta_data["creation_date"]
@@ -202,13 +60,6 @@ class Results(SQLSubModel):
def is_valid(self):
return ("timestamps" in self._meta_data)
@property
def solver_name(self):
if self._solver is None:
return self._meta_data["solver_name"]
return self._solver.name
def get(self, key):
return self._meta_data[key]
@@ -218,164 +69,3 @@ 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_name TEXT NOT NULL,
solver_type 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)
)
""")
if ext != "":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
create = False
if major == "0" and int(minor) < 2:
cls._db_create(execute)
create = True
if major == "0" and int(minor) == 2:
if int(release) < 1 and not create:
cls._db_create(execute)
create = True
return cls._update_submodel(execute, version, data)
@classmethod
def _db_load(cls, execute, data=None):
new = None
study = data['study']
status = data['status']
scenario = data["scenario"]
table = execute(
"SELECT pamhyr_id, solver_name, solver_type, " +
"study_revision, creation_data, nb_timestamps, timestamps, " +
"scenario " +
"FROM results " +
f"WHERE scenario = {scenario.id}"
)
if table is None:
yield
if len(table) > 1:
logger.warning("Multiple results for this scenario")
for v in table:
it = iter(v)
pid = next(it)
solver_name = next(it)
solver_type = next(it)
revision = next(it)
creation_date = next(it)
nb_timestamps = next(it)
timestamps_bytes = next(it)
owner_scenario = next(it)
new_results = cls(study=study)
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", nb_timestamps))
ts = struct.unpack(sf, timestamps_bytes)
new_results.set("timestamps", ts)
data["timestamps"] = sorted(ts)
new_results._river = River._db_load(execute, data)
new_results.set(
"additional_data",
AdditionalData._db_load(execute, data)
)
yield (solver_type, new_results)
def _db_save_clear(self, execute, solver_type, data=None):
old_pid = execute(
"SELECT pamhyr_id FROM results " +
f"WHERE scenario = {self._owner_scenario} " +
f"AND solver_type = '{solver_type}'"
)
if len(old_pid) != 0:
for pid in old_pid:
pid = pid[0]
execute(
"DELETE FROM results " +
f"WHERE scenario = {self._owner_scenario} " +
f"AND solver_type = '{solver_type}'"
)
execute(
"DELETE FROM results_data " +
f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}"
)
execute(
"DELETE FROM results_add_data " +
f"WHERE scenario = {self._owner_scenario} " +
f"AND result = {pid}"
)
def _db_save(self, execute, data=None):
if self._status.scenario.id != self._owner_scenario:
return
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
self._db_save_clear(execute, solver_type, data=data)
ts = sorted(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._status.version, self.get("creation_date"),
len(ts), struct.pack(sf, *ts), self._owner_scenario
)
data["result"] = self._pamhyr_id
self._river._db_save(execute, data)
for add_data in self.get("additional_data"):
add_data._db_save(execute, data)
return True
+1 -4
View File
@@ -1,5 +1,5 @@
# Results.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -50,9 +50,6 @@ class Results(object):
self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"]
if solver is not None:
self.set("solver_type", solver._type)
@property
def date(self):
date = self._meta_data["creation_date"]
+12 -267
View File
@@ -1,5 +1,5 @@
# River.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -14,27 +14,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import struct
import logging
import itertools
from tools import flatten
from functools import reduce
from datetime import datetime
from Model.Scenario import Scenario
from Model.Tools.PamhyrDB import SQLSubModel
logger = logging.getLogger()
class Profile(SQLSubModel):
class Profile(object):
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 {<ts>: {<key>: <value>, ...}, ...}
@@ -64,11 +52,9 @@ class Profile(SQLSubModel):
return self._data[timestamp]
def get_key(self, key):
res = list(
map(lambda ts: self._data[ts][key],
sorted(self._data.keys()))
return list(
map(lambda ts: self._data[ts][key], self._data)
)
return res
def get_ts_key(self, timestamp, key):
if timestamp in self._data:
@@ -79,196 +65,17 @@ class Profile(SQLSubModel):
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)
)
""")
return True
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
return cls._update_submodel(execute, version, data)
@classmethod
def _db_load(cls, execute, data=None):
new = {}
status = data['status']
study = data['study']
reach = data['reach']
profile = data['profile']
scenario = data["scenario"]
loaded = data['loaded_pid']
timestamps = data['timestamps']
values = execute(
"SELECT pamhyr_id, result, key, " +
"len_data, data, scenario " +
"FROM results_data " +
f"WHERE scenario = {scenario.id} " +
f"AND reach = {reach.pamhyr_id} " +
f"AND section = {profile.pamhyr_id}"
)
for v in values:
it = iter(v)
pid = next(it)
result = next(it)
key = next(it)
len_data = next(it)
data = next(it)
owner_scenario = next(it)
if profile not in new:
new_data = cls(profile, study)
new[profile] = new_data
else:
new_data = new[profile]
if key in ["Z", "Q", "V"]:
sf = ">" + ''.join(itertools.repeat("f", len_data))
len_values = len(values)
values = struct.unpack(sf, data)
elif key in ["sl"]:
sf = ">" + ''.join(itertools.repeat("f", len_data))
values = struct.unpack(sf, data)
values = cls._db_load_data_sl_format(
values, len_data, timestamps
)
for timestamp, value in zip(timestamps, values):
new_data.set(timestamp, key, value)
if key == "Z":
new_data.update_water_limits(timestamp, value)
return list(new.values())
@classmethod
def _db_load_data_sl_format(cls, values, len_data, timestamps):
tuple_size = 3
# HACK: Transforme list of value to list of n-tuple
# sediment results: (h, d50, sigma)
tmp_values = [[]]
for value in values:
t = tmp_values[-1]
if len(t) < tuple_size:
t.append(value)
else:
tmp_values.append([value])
tmp_values = list(map(tuple, tmp_values))
# HACK: Transforme list of n-tuple to list of n-list
# of n-tuple, where n is the number of layer.
nb_layer = len_data / len(timestamps) / tuple_size
values = [[]]
for value in tmp_values:
t = values[-1]
if len(t) < nb_layer:
t.append(value)
else:
values.append([value])
return list(map(lambda x: [x], values))
def update_water_limits(self, timestamp, z):
limits = self.geometry.get_water_limits(z)
self.set(
timestamp, "water_limits", limits
)
def get_keys(self):
return reduce(
lambda acc, ts: acc.union(self._data[ts].keys()),
self._data.keys(), set()
)
def _db_save(self, execute, data=None):
pid = self._pamhyr_id
result = data["result"]
keys = self.get_keys()
logger.debug(f"{keys}...")
for key in keys:
values = self.get_key(key)
if key in ["Z", "Q", "V"]:
values = list(map(float, values))
sf = ">" + ''.join(itertools.repeat("f", len(values)))
len_values = len(values)
elif key == "sl":
# HACK: Some dirty code to transforme list of list of
# tuple to list of values and ensure the values is
# float type...
values = flatten(
flatten(
map(lambda v: list(
map(lambda t: list(
map(float, t)), v[0])),
values)))
len_values = len(values)
sf = ">" + ''.join(itertools.repeat("f", len_values))
else:
continue
data_bytes = struct.pack(sf, *values)
execute(
"INSERT INTO " +
"results_data (pamhyr_id, result, " +
"reach, section, " +
"key, len_data, data, " +
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
pid, result,
data["reach"].pamhyr_id,
self._profile.pamhyr_id,
key, len_values, data_bytes,
self._owner_scenario
)
return True
class Reach(SQLSubModel):
_sub_classes = [Profile]
def __init__(self, reach, study, with_init=True):
super(Reach, self).__init__(
id=-1, status=study.status,
owner_scenario=study.status.scenario.id
)
class Reach(object):
def __init__(self, reach, study):
self._study = study
self._reach = reach # Source reach in the study
self._profiles = []
if with_init:
self._profiles = list(
map(
lambda p: Profile(p, self._study),
reach.profiles
)
self._profiles = list(
map(
lambda p: Profile(p, self._study),
reach.profiles
)
)
self._profile_mask = list(
map(
@@ -304,44 +111,9 @@ class Reach(SQLSubModel):
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)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
@classmethod
def _db_load(cls, execute, data=None):
reach = data["reach"]
new_reach = cls(
data["reach"], data["study"], with_init=False
)
for i, profile in enumerate(reach.profiles):
data["profile"] = profile
new_reach._profiles += Profile._db_load(execute, data)
return new_reach
def _db_save(self, execute, data=None):
logger.debug("Save reach...")
for profile in self._profiles:
data["profile"] = profile.geometry.pamhyr_id
profile._db_save(execute, data)
class River(SQLSubModel):
_sub_classes = [Reach]
class River(object):
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
@@ -375,30 +147,3 @@ class River(SQLSubModel):
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):
study = data["study"]
new_river = cls(study)
for reach in study.river.reachs():
data["reach"] = reach.reach
new_river._reachs.append(
Reach._db_load(execute, data)
)
return new_river
def _db_save(self, execute, data=None):
logger.debug("Save river...")
for reach in self._reachs:
data["reach"] = reach.geometry
reach._db_save(execute, data)
+1 -1
View File
@@ -1,5 +1,5 @@
# River.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+78 -429
View File
@@ -1,5 +1,5 @@
# River.py -- Pamhyr river model
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -16,13 +16,9 @@
# -*- coding: utf-8 -*-
import types
import logging
from tools import flatten, logger_exception
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
from Model.Network.Node import Node
from Model.Network.Edge import Edge
@@ -59,150 +55,64 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
import LateralContributionsAdisTSList
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
from Model.GeoTIFF.GeoTIFFList import GeoTIFFList
from Model.Results.Results import Results
logger = logging.getLogger()
class RiverNode(Node):
class RiverNode(Node, SQLSubModel):
_sub_classes = []
def __init__(self, id: int = -1, name: str = "",
x: float = 0.0, y: float = 0.0,
status=None, owner_scenario=-1):
def __init__(self, id: str, name: str,
x: float, y: float,
status=None):
super(RiverNode, self).__init__(
id=id, name=name, x=x, y=y,
status=status,
owner_scenario=owner_scenario
id, name,
x, y,
status=status
)
self._locker = None
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE river_node{ext}(
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE river_node(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
x REAL NOT NULL,
y REAL NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario)
y REAL NOT NULL
)
""")
if ext == "_tmp":
return True
return cls._create_submodel(execute)
cls._create_submodel(execute)
return True
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
if major == "0" and int(minor) < 2:
cls._db_update_to_0_2_0(execute, data=data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE river_node " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data=None):
table = "river_node"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
# Copy table
execute(
f"INSERT INTO {table}_tmp " +
f"(pamhyr_id, name, x, y, scenario) " +
"SELECT pamhyr_id, name, x, y, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
def _db_update(cls, execute, version):
return True
@classmethod
def _db_load(cls, execute, data=None):
nodes = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return nodes
table = execute(
"SELECT pamhyr_id, deleted, name, x, y, scenario " +
"FROM river_node " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
)
table = execute("SELECT id, name, x, y FROM river_node")
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
x = next(it)
y = next(it)
owner_scenario = next(it)
node = cls(
id=pid,
name=name, x=x, y=y,
status=data["status"],
owner_scenario=owner_scenario
)
if deleted:
node.set_as_deleted()
loaded.add(pid)
nodes.append(node)
data["scenario"] = scenario.parent
nodes += cls._db_load(execute, data)
data["scenario"] = scenario
# Update id counter
cls._id_cnt = max(cls._id_cnt, row[0])
# Create new node
nodes.append(cls(*row, status=data["status"]))
return nodes
def _db_save(self, execute, data=None):
execute(
"INSERT OR REPLACE INTO river_node(" +
"pamhyr_id, deleted, name, x, y, scenario" +
") " +
"VALUES (" +
f"{self._pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self.name)}', " +
f"{self.x}, {self.y}, {self._status.scenario_id}" +
sql = (
"INSERT OR REPLACE INTO river_node(id, name, x, y) VALUES (" +
f"{self.id}, '{self._db_format(self.name)}', " +
f"{self.x}, {self.y}" +
")"
)
execute(sql)
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
def is_deleted(self):
return self._deleted
@property
def locker(self):
return self._locker
@@ -212,204 +122,100 @@ class RiverNode(Node):
self._locker = locker
class RiverReach(Edge):
class RiverReach(Edge, SQLSubModel):
_sub_classes = [
Reach,
FrictionList,
]
def __init__(self, id: str = -1, name: str = "",
def __init__(self, id: str, name: str,
node1: RiverNode = None,
node2: RiverNode = None,
status=None, owner_scenario=-1):
status=None):
super(RiverReach, self).__init__(
id=id, name=name,
node1=node1, node2=node2,
status=status,
owner_scenario=owner_scenario
id, name,
node1, node2,
status=status
)
self._reach = Reach(status=self._status, parent=self)
self._frictions = FrictionList(status=self._status)
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE river_reach{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE river_reach(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
enabled BOOLEAN NOT NULL,
enable BOOLEAN NOT NULL,
node1 INTEGER,
node2 INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(node1) REFERENCES river_node(pamhyr_id),
FOREIGN KEY(node2) REFERENCES river_node(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(node1) REFERENCES river_node(id),
FOREIGN KEY(node2) REFERENCES river_node(id)
)
""")
if ext == "_tmp":
return True
return cls._create_submodel(execute)
cls._create_submodel(execute)
return True
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
if major == minor == "0":
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE river_reach " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data=None):
table = "river_reach"
nodes = data['id2pid']['river_node']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
# Copy table
execute(
f"INSERT INTO {table}_tmp " +
f"(pamhyr_id, name, enabled, node1, node2, scenario) " +
"SELECT pamhyr_id, name, enable, node1, node2, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
@classmethod
def _db_update_to_0_2_0_set_node_pid(cls, execute, table, nodes):
bcs = execute(
f"SELECT pamhyr_id, node1, node2 FROM {table}"
)
for row in bcs:
it = iter(row)
pid = next(it)
node1_id = next(it)
node2_id = next(it)
execute(
f"UPDATE {table} " +
f"SET node1 = {nodes[node1_id]}, " +
f"node2 = {nodes[node2_id]} " +
f"WHERE pamhyr_id = {pid}"
)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
reachs = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return reachs
if data is None:
data = {}
table = execute(
"SELECT pamhyr_id, deleted, name, enabled, " +
"node1, node2, scenario " +
"FROM river_reach " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, name, enable, node1, node2 FROM river_reach"
)
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
enabled = (next(it) == 1)
node1_pid = next(it)
node2_pid = next(it)
owner_scenario = next(it)
# Update id counter
cls._id_cnt = max(cls._id_cnt, row[0])
# Create new reach
id = row[0]
name = row[1]
enable = (row[2] == 1)
# Get nodes corresponding to db foreign key id
node1 = next(
filter(
lambda n: n.pamhyr_id == node1_pid, data["nodes"]
)
)
node2 = next(
filter(
lambda n: n.pamhyr_id == node2_pid, data["nodes"]
)
)
node1 = next(filter(lambda n: n.id == row[3], data["nodes"]))
node2 = next(filter(lambda n: n.id == row[4], data["nodes"]))
new = cls(
id=pid, name=name, node1=node1, node2=node2,
status=data["status"],
owner_scenario=owner_scenario
)
new.enable(enable=enabled)
if deleted:
nd.set_as_deleted()
new = cls(id, name, node1, node2, status=data["status"])
new.enable(enable=enable)
data["reach"] = new
new._reach = Reach._db_load(execute, data)
new._frictions = FrictionList._db_load(execute, data)
loaded.add(pid)
reachs.append(new)
data["scenario"] = scenario.parent
reachs += cls._db_load(execute, data)
data["scenario"] = scenario
return reachs
def _db_save(self, execute, data=None):
execute(
sql = (
"INSERT OR REPLACE INTO " +
"river_reach(" +
"pamhyr_id, deleted, name, enabled, " +
"node1, node2, scenario" +
") " +
"river_reach(id, name, enable, node1, node2) " +
"VALUES (" +
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"{self.id}, '{self._db_format(self._name)}', " +
f"{self._db_format(self.is_enable())},"
f"{self.node1.pamhyr_id}, {self.node2.pamhyr_id}, " +
f"{self._status.scenario_id}" +
f"{self.node1.id}, {self.node2.id}" +
")"
)
execute(sql)
if data is None:
data = {}
data["reach"] = self
objs = [self._reach, self._frictions]
return self._save_submodel(execute, objs, data)
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
self._reach._data_traversal(predicate, modifier, data)
self._frictions._data_traversal(predicate, modifier, data)
def is_deleted(self):
return self._deleted
@property
def reach(self):
return self._reach
@@ -418,46 +224,17 @@ class RiverReach(Edge):
def frictions(self):
return self._frictions
def _split(self, profile, new_node):
name = self.name
node1 = self.node1
node2 = self.node2
new_reach1 = RiverReach(
id=-1, name=name + "(part 1)",
node1=node1, node2=new_node,
status=self._status,
)
new_reach2 = RiverReach(
id=-1, name=name + "(part 2)",
node1=new_node, node2=node2,
status=self._status,
)
nr1, nr2 = self._reach._split(profile, new_reach1, new_reach2)
new_reach1._reach = nr1
new_reach1._frictions = self._frictions._splited_for(new_reach1)
new_reach2._reach = nr2
new_reach2._frictions = self._frictions._splited_for(new_reach2)
self.disable()
return new_reach1, new_reach2
class River(Graph):
class River(Graph, SQLSubModel):
_sub_classes = [
StricklersList,
SedimentLayerList,
RiverNode,
RiverReach,
BoundaryConditionList,
LateralContributionList,
InitialConditionsDict,
StricklersList,
SolverParametersList,
SedimentLayerList,
ReservoirList,
HydraulicStructureList,
AddFileList,
@@ -469,14 +246,10 @@ class River(Graph):
LateralContributionsAdisTSList,
D90AdisTSList,
DIFAdisTSList,
GeoTIFFList,
Results
]
def __init__(self, status=None):
super(River, self).__init__(
status=status
)
super(River, self).__init__(status=status)
# Replace Node and Edge ctor by custom ctor
self._node_ctor = RiverNode
@@ -507,22 +280,19 @@ class River(Graph):
self._D90AdisTS = D90AdisTSList(status=self._status)
self._DIFAdisTS = DIFAdisTSList(status=self._status)
self._geotiff = GeoTIFFList(status=self._status)
self._results = {}
@classmethod
def _db_create(cls, execute):
cls._create_submodel(execute)
return True
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
cls._update_submodel(execute, version)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data["status"])
new = cls(data["status"])
# Stricklers (Stricklers is load in first because it's needed
# for reachs)
@@ -530,84 +300,65 @@ class River(Graph):
execute, data
)
data["stricklers"] = new._stricklers
data['loaded_pid'] = set()
# Initial conditions
new._sediment_layers = SedimentLayerList._db_load(
execute, data
)
data["sediment_layers_list"] = new._sediment_layers
data['loaded_pid'] = set()
# Network
new._nodes = RiverNode._db_load(
execute, data
)
data["nodes"] = new.nodes()
data['loaded_pid'] = set()
new._edges = RiverReach._db_load(
execute, data
)
data["edges"] = new.edges()
data['loaded_pid'] = set()
# Boundary Condition
new._boundary_condition = BoundaryConditionList._db_load(
execute, data
)
data['loaded_pid'] = set()
# Lateral Contribution
new._lateral_contribution = LateralContributionList._db_load(
execute, data
)
data['loaded_pid'] = set()
# Initial conditions
new._initial_conditions = InitialConditionsDict._db_load(
execute, data
)
data['loaded_pid'] = set()
# Reservoir
new._reservoir = ReservoirList._db_load(
execute, data
)
data['loaded_pid'] = set()
# Hydraulic Structures
new._hydraulic_structures = HydraulicStructureList._db_load(
execute, data
)
data['loaded_pid'] = set()
# Parameters
new._parameters = SolverParametersList._db_load(
execute, data
)
data['loaded_pid'] = set()
# Additional Files
new._additional_files = AddFileList._db_load(
execute, data
)
new._rep_lines = REPLineList._db_load(execute, data)
data['loaded_pid'] = set()
new._Pollutants = PollutantsList._db_load(execute, data)
new._Output_rk_adists = OutputRKAdistsList._db_load(
execute, data
)
new._Pollutants = PollutantsList._db_load(execute, data)
new._InitialConditionsAdisTS = InitialConditionsAdisTSList._db_load(
execute, data)
@@ -621,16 +372,8 @@ class River(Graph):
new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
new._geotiff = GeoTIFFList._db_load(execute, data)
return new
def _db_load_results(self, execute, data=None):
results_lst = Results._db_load(execute, data)
for solv_type, results in results_lst:
self._results[solv_type] = results
def _db_save(self, execute, data=None):
self._db_save_delete_artefact(execute, data)
@@ -656,11 +399,6 @@ class River(Graph):
objs.append(self._D90AdisTS)
objs.append(self._DIFAdisTS)
objs.append(self._geotiff)
for solv_type in self.results:
objs.append(self.results[solv_type])
self._save_submodel(execute, objs, data)
return True
@@ -684,64 +422,15 @@ class River(Graph):
execute(
f"DELETE FROM {table} " +
"WHERE " +
f"scenario = {self._status.scenario_id} AND (" +
" OR ".join(
map(
lambda n: f"( pamhyr_id <> {n.pamhyr_id} )",
els
lambda n: f"( id <> {n.id} )", els
)
) + ")"
)
)
except Exception as e:
logger_exception(e)
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
if predicate(self, data):
modifier(self, data)
for obj in self._get_objs_list():
obj._data_traversal(predicate, modifier, data)
def _get_objs_list(self):
objs = (self._nodes + self._edges)
objs += [
self._boundary_condition,
self._initial_conditions,
self._lateral_contribution,
self._sediment_layers,
self._stricklers,
self._reservoir,
self._hydraulic_structures,
self._additional_files,
self._rep_lines,
self._Output_rk_adists,
self._Pollutants,
self._InitialConditionsAdisTS,
self._BoundaryConditionsAdisTS,
self._LateralContributionsAdisTS,
self._D90AdisTS, self._DIFAdisTS,
self._geotiff,
]
for solver in self._parameters:
objs.append(self._parameters[solver])
return objs
def init_default(self):
self.init_default_network()
self.init_default_sediment()
@@ -779,9 +468,6 @@ execution...
Last export at: @date."""
def reachs(self):
return self.edges()
@property
def boundary_condition(self):
return self._boundary_condition
@@ -827,10 +513,6 @@ Last export at: @date."""
def additional_files(self):
return self._additional_files
@property
def geotiff(self):
return self._geotiff
@property
def rep_lines(self):
return self._rep_lines
@@ -907,36 +589,3 @@ Last export at: @date."""
)
return has
@property
def results(self):
results = self._results
if isinstance(results, types.GeneratorType):
self._results = next(results)
return self._results
@results.setter
def results(self, results):
solv_type = results.get("solver_type")
self._results[solv_type] = results
def _split_reach(self, reach, profile):
node1 = reach.node1
node2 = reach.node2
x1, y1 = node1.x, node1.y
x2, y2 = node2.x, node2.y
center_x = (x1 + x2) / 2
center_y = (y1 + y2) / 2
new_node = self.add_node(center_x, center_y)
r1, r2 = reach._split(profile, new_node)
self._add_edge(r1)
self._add_edge(r2)
return r1, r2
+53
View File
@@ -0,0 +1,53 @@
# Saved.py -- Pamhyr model status class
# Copyright (C) 2023-2024 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
logger = logging.getLogger()
class SavedStatus(object):
def __init__(self, version=0):
super(SavedStatus, self).__init__()
self._version = version
self._saved = True
@property
def version(self):
return self._version
@version.setter
def version(self, version):
self._version = version
def is_saved(self):
return self._saved
def save(self):
logger.debug("model status set as saved")
self._saved = True
def modified(self):
if self._saved:
self._version += 1
logger.debug(
"STATUS: Model status set as modified " +
f"at version {self._version}"
)
self._saved = False
-282
View File
@@ -1,282 +0,0 @@
# Scenario.py -- Pamhyr
# Copyright (C) 2024-2025 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 os
from tools import logger_exception
from Model.Tools.PamhyrDB import SQLSubModel
class Scenario(SQLSubModel):
_id_cnt = 0
_sub_classes = []
def __init__(self,
id: int = -1,
name: str = "",
description: str = "",
x: int = 1000.0, y: int = 1000.0,
revision: int = 0,
parent=None):
super(Scenario, self).__init__()
self._set_id(id)
self._x, self._y = x, y
self._name = name
self._description = description
self._revision = revision
self._parent = parent
def _set_id(self, id):
if id == -1:
self._id = Scenario._id_cnt
else:
self._id = id
Scenario._id_cnt = max(
self._id + 1, Scenario._id_cnt + 1
)
@classmethod
def _db_create(cls, execute):
execute("""
CREATE TABLE scenario(
id INTEGER PRIMARY KEY,
x REAL NOT NULL DEFAULT 1000,
y REAL NOT NULL DEFAULT 1000,
name TEXT NOT NULL,
description TEXT NOT NULL,
revision INTEGER NOT NULL,
parent_id INTEGER REFERENCES scenario(id)
)
""")
cls._create_submodel(execute)
return True
@classmethod
def _db_add_default(cls, execute):
execute(
"INSERT OR REPLACE INTO " +
"scenario(id, x, y, name, description, revision, parent_id) " +
"VALUES (\n" +
" 0, 1000, 1000, 'default', 'Default scenario',\n" +
" 0, NULL\n" +
")"
)
@classmethod
def create_db_add_scenario(cls):
return "scenario INTEGER NOT NULL DEFAULT 0"
@classmethod
def create_db_add_scenario_fk(cls):
return "FOREIGN KEY(scenario) REFERENCES scenario(id)"
@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)
cls._db_add_default(execute)
return True
@classmethod
def update_db_add_scenario(cls, execute, table):
execute(
f"ALTER TABLE {table} " +
"ADD COLUMN scenario INTEGER NOT NULL DEFAULT 0"
)
# execute(
# f"ALTER TABLE {table} " +
# "ADD CONSTRAINT fk_scenario FOREIGN KEY (scenario) " +
# "REFERENCES scenario(id)"
# )
@classmethod
def _db_load(cls, execute, data=None):
scenarios = {}
table = execute(
"SELECT id, x, y, name, description, revision, parent_id " +
"FROM scenario " +
"ORDER BY id ASC"
)
for row in table:
it = iter(row)
id = next(it)
x, y = next(it), next(it)
name = next(it)
desc = next(it)
revi = next(it)
parent = next(it)
if parent is not None:
parent = scenarios[parent]
new = cls(
id=id, x=x, y=y,
name=name, description=desc,
revision=revi, parent=parent
)
scenarios[id] = new
return scenarios
def _db_save(self, execute, data=None):
if self.is_deleted():
return self.drop_all(execute)
parent = 'NULL'
if self.parent is not None:
parent = self.parent._id
execute(
"INSERT OR REPLACE INTO " +
"scenario(id, x, y, name, description, revision, parent_id) " +
"VALUES (" +
f"{self._id}, " +
f"{self.x}, {self.y}, " +
f"'{self._db_format(self.name)}', " +
f"'{self._db_format(self.description)}', " +
f"{self._revision}, " +
f"{parent}" +
")"
)
return True
def drop_all(self, execute):
execute(f"DELETE FROM scenario WHERE id = {self.id}")
tables = [
# Adists
"output_rk_adists",
"boundary_condition_adists", "boundary_condition_data_adists",
"lateral_contribution_adists", "lateral_contribution_data_adists",
"initial_conditions_adists",
"d90_adists", "dif_adists",
"pollutants", "pollutants_characteristics",
# Hydraulic
"additional_files",
"boundary_condition", "boundary_condition_data",
"lateral_contribution", "lateral_contribution_data",
"friction", "stricklers",
"hydraulic_structures",
"hydraulic_structures_basic", "hydraulic_structures_basic_value",
"initial_conditions",
"sedimentary_layer", "sedimentary_layer_layer",
"reservoir", "reservoir_data",
"rep_lines",
"solver_parameter",
"geometry_pointXYZ", "geometry_profileXYZ",
"river_reach", "river_node",
]
for table in tables:
execute(
f"DELETE FROM {table} " +
f"WHERE scenario = {self.id}"
)
return True
@property
def id(self):
return self._id
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def name(self):
if self._name == "":
return f"Child of '{self._parent.name}' ({self.id})"
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def description(self):
if self._description == "":
return f"Child of '{self._parent.name}' ({self.id})"
return self._description
@description.setter
def description(self, description):
self._description = description
@property
def revision(self):
return self._revision
@revision.setter
def revision(self, revision):
self._revision = revision
@property
def parent(self):
return self._parent
def set_pos(self, x, y):
self._x = x
self._y = y
def __setitem__(self, key, value):
if key == "name":
self.name = value
elif key == "description":
self.description = value
def __getitem__(self, key):
if key == "id":
return self.id
if key == "name":
return self.name
if key == "description":
return self.description
if key == "parent":
return self.parent
return None
def workdir(self):
srep = ""
if self.id != 0:
srep = os.path.join(
self.parent.workdir(),
"scenario_" + str(self.id)
)
return srep
-80
View File
@@ -1,80 +0,0 @@
# Scenarios.py -- Pamhyr
# Copyright (C) 2024-2025 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 tools import logger_exception
from Model.Tools.PamhyrDict import PamhyrModelDict
from Model.Scenario import Scenario
class Scenarios(PamhyrModelDict):
_sub_classes = [
Scenario,
]
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data["status"])
new._dict = Scenario._db_load(
execute,
data=data
)
return new
def _db_save(self, execute, data=None):
ok = True
if data is None:
data = {}
scenarios = self._dict
for sid in scenarios:
ok &= scenarios[sid]._db_save(execute, data)
return ok
def get(self, key):
if key in self._dict:
return self._dict[key]
return None
def new(self, parent):
new = Scenario(parent=parent)
self.set(new._id, new)
return new
def __len__(self):
return len(self.lst)
@property
def lst(self):
return list(
map(
lambda k: self.get(k),
self._dict
)
)
def delete(self, key):
if key not in self._dict:
return
self._dict[key].set_as_deleted()
self._status.modified()
+91 -278
View File
@@ -1,5 +1,5 @@
# SedimentLayer.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -21,26 +21,23 @@ from functools import reduce
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
from Model.Except import NotImplementedMethodeError
class Layer(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self,
id: int = -1, name: str = "",
type="",
height=0.0, d50=0.0, sigma=0.0,
critical_constraint=0.0,
sl=None, status=None,
owner_scenario=-1):
super(Layer, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
sl=None, status=None):
super(Layer, self).__init__()
self._status = status
self._sl = sl
self._name = name
self._type = type
@@ -49,17 +46,23 @@ class Layer(SQLSubModel):
self._sigma = sigma
self._critical_constraint = critical_constraint
if id == -1:
self.id = Layer._id_cnt
else:
self.id = id
Layer._id_cnt = max(self.id + 1, Layer._id_cnt + 1)
@property
def name(self):
if self._name == "":
return f"Layer #{self.pamhyr_id}"
return f"Layer{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
@property
def type(self):
@@ -68,7 +71,6 @@ class Layer(SQLSubModel):
@type.setter
def type(self, type):
self._type = type
self.modified()
@property
def height(self):
@@ -77,7 +79,6 @@ class Layer(SQLSubModel):
@height.setter
def height(self, height):
self._height = float(height)
self.modified()
@property
def d50(self):
@@ -86,7 +87,6 @@ class Layer(SQLSubModel):
@d50.setter
def d50(self, d50):
self._d50 = float(d50)
self.modified()
@property
def sigma(self):
@@ -95,7 +95,6 @@ class Layer(SQLSubModel):
@sigma.setter
def sigma(self, sigma):
self._sigma = float(sigma)
self.modified()
@property
def critical_constraint(self):
@@ -104,14 +103,12 @@ class Layer(SQLSubModel):
@critical_constraint.setter
def critical_constraint(self, critical_constraint):
self._critical_constraint = float(critical_constraint)
self.modified()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE sedimentary_layer_layer{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE sedimentary_layer_layer(
id INTEGER NOT NULL PRIMARY KEY,
ind INTEGER NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL,
@@ -120,177 +117,92 @@ class Layer(SQLSubModel):
sigma REAL NOT NULL,
critical_constraint REAL NOT NULL,
sl INTEGER,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(sl) REFERENCES sedimentary_layer(pamhyr_id),
PRIMARY KEY(pamhyr_id, scenario)
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
cls._db_create(execute)
return True
else:
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE sedimentary_layer_layer " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "sedimentary_layer_layer"
sl = data['id2pid']['sedimentary_layer']
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, name, type, height, " +
"d50, sigma, critical_constraint, sl, scenario) " +
"SELECT pamhyr_id, ind, name, type, height, " +
"d50, sigma, critical_constraint, sl, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
cls._db_update_to_0_2_0_set_sl_pid(execute, table, sl)
@classmethod
def _db_update_to_0_2_0_set_sl_pid(cls, execute, table, sl):
els = execute(
f"SELECT pamhyr_id, sl FROM {table}"
)
if els is None:
return True
for row in els:
it = iter(row)
pid = next(it)
sl_id = next(it)
execute(
f"UPDATE {table} " +
f"SET sl = {sl[sl_id]} " +
f"WHERE pamhyr_id = {pid}"
)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
sl = data["sl"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, type, height, " +
"d50, sigma, critical_constraint, scenario " +
"SELECT id, ind, name, type, height, " +
"d50, sigma, critical_constraint " +
"FROM sedimentary_layer_layer " +
f"WHERE sl = {sl.pamhyr_id} " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
"ORDER BY ind ASC"
f"WHERE sl = {sl}"
)
for row in table:
it = iter(row)
for _ in table:
new.append(None)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
type = next(it)
height = next(it)
d50 = next(it)
sigma = next(it)
critical_constraint = next(it)
owner_scenario = next(it)
for row in table:
ind = row[1]
layer = cls(
id=pid, name=name,
type=type, height=height,
d50=d50, sigma=sigma,
critical_constraint=critical_constraint,
sl=sl, status=data['status'],
owner_scenario=owner_scenario
id=row[0], name=row[2],
type=row[3], height=row[4],
d50=row[5], sigma=row[6],
critical_constraint=row[7],
sl=sl, status=data['status']
)
if deleted:
layer.set_as_deleted()
loaded.add(pid)
new.append(layer)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
new[ind] = layer
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
ind = data["ind"]
sl = data["sl"]
execute(
sql = (
"INSERT INTO " +
"sedimentary_layer_layer(" +
"pamhyr_id, deleted, ind, name, type, height, " +
"d50, sigma, critical_constraint, sl, scenario" +
") " +
"sedimentary_layer_layer(id, ind, name, type, height, " +
"d50, sigma, critical_constraint, sl) " +
"VALUES (" +
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(self._name)}', " +
f"{self.id}, {ind}, '{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', {self._height}, " +
f"{self._d50}, {self._sigma}, {self._critical_constraint}, " +
f"{sl.pamhyr_id}, " +
f"{self._status.scenario_id}" +
f"{sl.id}" +
")"
)
execute(sql)
return True
def modified(self):
super(Layer, self).modified()
self._sl.modified()
class SedimentLayer(SQLSubModel):
_sub_classes = [Layer]
_id_cnt = 0
def __init__(self, id: int = -1,
name: str = "", comment: str = "",
status=None, owner_scenario=-1):
super(SedimentLayer, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(SedimentLayer, self).__init__()
self._status = status
self._name = name
self._comment = comment
self._layers = []
if id == -1:
self.id = SedimentLayer._id_cnt
else:
self.id = id
SedimentLayer._id_cnt = max(id, SedimentLayer._id_cnt+1)
def __str__(self):
s = f"{self.name} ({len(self)})"
@@ -300,48 +212,31 @@ class SedimentLayer(SQLSubModel):
return s
def __len__(self):
return len(
list(
filter(
lambda el: not el.is_deleted(),
self._layers
)
)
)
return len(self._layers)
@property
def layers(self):
return list(
filter(
lambda el: not el.is_deleted(),
self._layers
)
)
return self._layers.copy()
def height(self):
return list(
map(lambda layer: layer.height,
self.layers)
map(lambda layer: layer.height, self._layers)
)
@property
def name(self):
if self._name == "":
return f"SL #{self.pamhyr_id}"
return f"SL{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
def names(self):
return list(
map(
lambda layer: layer.name,
self.layers
)
map(lambda layer: layer.name, self._layers)
)
@property
@@ -351,128 +246,63 @@ class SedimentLayer(SQLSubModel):
@comment.setter
def comment(self, comment):
self._comment = comment
self.modified()
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE sedimentary_layer{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE sedimentary_layer(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
comment TEXT NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario)
comment TEXT NOT NULL
)
""")
if ext == "_tmp":
return True
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 2:
cls._db_create(execute)
return True
else:
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE sedimentary_layer " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "sedimentary_layer"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, comment, scenario) " +
"SELECT pamhyr_id, name, comment, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, deleted, name, comment, scenario " +
"FROM sedimentary_layer " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
"SELECT id, name, comment " +
"FROM sedimentary_layer "
)
if table is not None:
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
comment = next(it)
owner_scenario = next(it)
sl = cls(
id=pid,
name=name,
comment=comment,
status=data['status'],
owner_scenario=owner_scenario
id=row[0],
name=row[1],
comment=row[2],
status=data['status']
)
if deleted:
sl.set_as_deleted()
data["sl"] = sl
data["sl"] = sl.id
sl._layers = Layer._db_load(execute, data)
loaded.add(pid)
new.append(sl)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
if data is None:
data = {}
execute(
"INSERT INTO sedimentary_layer (" +
"pamhyr_id, deleted, name, comment, scenario" +
") " +
"VALUES (" +
f"{self._pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._comment)}', " +
f"{self._status.scenario_id}" +
")"
sql = (
"INSERT INTO sedimentary_layer (id, name, comment) " +
f"VALUES ({self.id}, '{self._db_format(self._name)}', " +
f"'{self._db_format(self._comment)}')"
)
execute(sql)
data["sl"] = self
@@ -484,56 +314,39 @@ class SedimentLayer(SQLSubModel):
return True
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
if predicate(self, data):
modifier(self, data)
for layer in self._layers:
layer._data_traversal(predicate, modifier, data)
def get(self, index):
return self.layers[index]
return self._layers[index]
# def set(self, index, new):
# self._layers[index] = new
# self.modified()
def set(self, index, new):
self._layers[index] = new
self._status.modified()
def insert(self, index, new):
self._layers.insert(index, new)
self.modified()
self._status.modified()
def new(self, index):
n = Layer(sl=self, status=self._status)
self.insert(index, n)
self.modified()
self._status.modified()
return n
def undelete(self, els):
for el in els:
el.set_as_not_deleted()
self.modified()
def delete(self, els):
for el in els:
el.set_as_deleted()
self.modified()
self._layers.remove(el)
self._status.modified()
def delete_i(self, indexes):
list(
els = list(
map(
lambda e: e[1].set_as_deleted(),
lambda x: x[1],
filter(
lambda e: e[0] in indexes,
lambda x: x[0] in indexes,
enumerate(self._layers)
)
)
)
self.modified()
self.delete(els)
def move_up(self, index):
if index >= 0:
@@ -542,7 +355,7 @@ class SedimentLayer(SQLSubModel):
lst = self._layers
lst[index], lst[next] = lst[next], lst[index]
self.modified()
self._status.modified()
def move_down(self, index):
if index + 1 < len(self._layers):
@@ -551,7 +364,7 @@ class SedimentLayer(SQLSubModel):
lst = self._layers
lst[index], lst[prev] = lst[prev], lst[index]
self.modified()
self._status.modified()
def compute_height_from_bottom(self, bottom_elevation: list):
sl_height = self.height()
+6 -11
View File
@@ -1,5 +1,5 @@
# SedimentLayerList.py -- Pamhyr
# Copyright (C) 2024-2025 INRAE
# Copyright (C) 2024 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
@@ -19,7 +19,7 @@
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Tools.PamhyrList import PamhyrModelList
from Model.SedimentLayer.SedimentLayer import SedimentLayer
@@ -35,17 +35,12 @@ class SedimentLayerList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM sedimentary_layer " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute(
"DELETE FROM sedimentary_layer_layer " +
f"WHERE scenario = {self._status.scenario_id}"
)
ok = True
# Delete previous data
execute("DELETE FROM sedimentary_layer")
execute("DELETE FROM sedimentary_layer_layer")
for sl in self._lst:
ok &= sl._db_save(execute, data)
+1 -1
View File
@@ -1,5 +1,5 @@
# Serializable.py -- Pamhyr pickle abstract class
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
+118 -232
View File
@@ -1,5 +1,5 @@
# SolverParametersList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -20,221 +20,18 @@ from copy import copy
from tools import trace, timer
from Solver.Solvers import solver_type_list
from Model.Scenario import Scenario
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Tools.PamhyrList import PamhyrModelList
class Parameter(SQLSubModel):
def __init__(self, id: int = -1,
class Parameter():
def __init__(self,
name="", value="",
status=None, owner_scenario=-1):
super(Parameter, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
self._status = status
self._name = name
self._value = value
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE solver_parameter{ext} (
pamhyr_id INTEGER NOT NULL,
ind INTEGER NOT NULL,
name TEXT NOT NULL,
value TEXT NOT NULL,
solver TEXT 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 == minor == "0":
release = int(release)
if release < 3:
execute(
"UPDATE solver_parameter SET name='mage_implicitation' " +
"WHERE name='mage_implication'"
)
execute(
"UPDATE solver_parameter SET name='mage_iteration_type' " +
"WHERE name='mage_iter_type'"
)
def insert(name, value, ind):
return execute(
"INSERT INTO " +
"solver_parameter(ind, name, value, solver) " +
"VALUES (" +
f"{ind}, " +
f"'{cls._db_format(name)}', " +
f"'{cls._db_format(value)}', " +
f"'{cls._db_format(solver)}'" +
")"
)
if release < 4:
cls._db_update_to_0_0_4(execute, insert)
if release < 7:
cls._db_update_to_0_0_7(execute, insert)
if release < 14:
cls._db_update_to_0_0_14(execute, data)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_0_4(cls, execute, insert):
solvers = execute(
"SELECT DISTINCT solver FROM solver_parameter"
)
for solver in solvers:
if solver != "mage8":
continue
data = execute("SELECT ind, name, value " +
"FROM solver_parameter " +
f"WHERE solver = '{solver}' " +
"ORDER BY ind ACS")
execute(
"DELETE FROM solver_parameter " +
f"WHERE solver = '{solver}'"
)
ind = 0
lst = []
for v in data:
insert(v[0], v[1], ind)
ind += 1
@classmethod
def _db_update_to_0_0_7(cls, execute, insert):
solvers = execute(
"SELECT DISTINCT solver FROM solver_parameter"
)
for solver in solvers:
if solver != "mage8":
continue
insert("mage_init_internal", "N", ind)
ind += 1
new = [
("mage_sediment_masse_volumique", "2650.0"),
("mage_sediment_angle_repos", "40.0"),
("mage_sediment_porosity", "0.40"),
("mage_distance_han", "0.0"),
("mage_distance_chargement_d50", "100.0"),
("mage_distance_chargement_sigma", "100.0"),
("mage_methode_modification_geometrie", "1"),
("mage_shields_critique", "1"),
("mage_shields_correction", "1"),
("mage_capacite_solide", "1"),
("mage_pas_de_temps_charriage", "1"),
("mage_facteur_multiplicateur", "1.0"),
]
for v in new:
insert(v[0], v[1], ind)
ind += 1
@classmethod
def _db_update_to_0_0_14(cls, execute, data):
table = "solver_parameter"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, ind, name, value, solver, scenario) " +
"SELECT pamhyr_id, ind, name, value, solver, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
@classmethod
def _db_load(cls, execute, data=None):
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
solver = data['solver']
new = []
if scenario is None:
return new
table = execute(
"SELECT pamhyr_id, ind, name, value, scenario " +
"FROM solver_parameter " +
f"WHERE solver = '{solver}' " +
f"AND scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
)
for row in table:
it = iter(row)
pid = next(it)
ind = next(it)
name = next(it)
value = next(it)
owner = next(it)
new.append(
(
ind,
cls(
id=pid, name=name, value=value,
status=status, owner_scenario=owner
)
)
)
loaded.add(pid)
data["scenario"] = scenario.parent
new += cls._db_load(execute, data)
data["scenario"] = scenario
return new
def _db_save(self, execute, data=None):
ind = data["ind"]
solver = data["solver"]
execute(
"INSERT INTO " +
"solver_parameter(pamhyr_id, ind, " +
"name, value, solver, scenario) " +
"VALUES (" +
f"{self.pamhyr_id}, " +
f"{ind}, " +
f"'{self._db_format(self.name)}', " +
f"'{self._db_format(self.value)}', " +
f"'{self._db_format(solver)}', " +
f"{self._status.scenario_id}" +
")"
)
return True
@property
def name(self):
return self._name
@@ -257,7 +54,7 @@ class Parameter(SQLSubModel):
elif key == "value":
self._value = str(value)
self.modified()
self._status.modified()
@classmethod
def from_tuple(cls, data, status):
@@ -269,8 +66,6 @@ class Parameter(SQLSubModel):
class SolverParametersList(PamhyrModelList):
_sub_classes = [Parameter]
def __init__(self, solver_type=None, status=None):
super(SolverParametersList, self).__init__()
@@ -284,21 +79,100 @@ class SolverParametersList(PamhyrModelList):
)
@classmethod
def _db_create(cls, execute, ext=""):
def _db_create(cls, execute):
execute("""
CREATE TABLE solver_parameter(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ind INTEGER NOT NULL,
name TEXT NOT NULL,
value TEXT NOT NULL,
solver TEXT NOT NULL
)
""")
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) < 3:
execute(
"UPDATE solver_parameter SET name='mage_implicitation' " +
"WHERE name='mage_implication'"
)
execute(
"UPDATE solver_parameter SET name='mage_iteration_type' " +
"WHERE name='mage_iter_type'"
)
if int(release) < 4:
solvers = execute(
"SELECT DISTINCT solver FROM solver_parameter")
for solver in solvers:
if solver != "mage8":
continue
data = execute("SELECT ind, name, value " +
"FROM solver_parameter " +
f"WHERE solver = '{solver}' " +
"ORDER BY ind ACS")
execute(
"DELETE FROM solver_parameter " +
f"WHERE solver = '{solver}'"
)
def insert(name, value, ind): return execute(
"INSERT INTO " +
"solver_parameter(ind, name, value, solver) " +
"VALUES (" +
f"{ind}, " +
f"'{cls._db_format(name)}', " +
f"'{cls._db_format(value)}', " +
f"'{cls._db_format(solver)}'" +
")"
)
ind = 0
lst = []
for v in data:
insert(v[0], v[1], ind)
ind += 1
if int(release) < 7:
insert("mage_init_internal", "N", ind)
ind += 1
new = [
("mage_sediment_masse_volumique", "2650.0"),
("mage_sediment_angle_repos", "40.0"),
("mage_sediment_porosity", "0.40"),
("mage_distance_han", "0.0"),
("mage_distance_chargement_d50", "100.0"),
("mage_distance_chargement_sigma", "100.0"),
("mage_methode_modification_geometrie", "1"),
("mage_shields_critique", "1"),
("mage_shields_correction", "1"),
("mage_capacite_solide", "1"),
("mage_pas_de_temps_charriage", "1"),
("mage_facteur_multiplicateur", "1.0"),
]
for v in new:
insert(v[0], v[1], ind)
ind += 1
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
solvers = execute("SELECT DISTINCT solver FROM solver_parameter")
new = {}
if scenario is None:
return new
for solver in solvers:
solver = solver[0]
if solver not in solver_type_list:
@@ -307,10 +181,18 @@ class SolverParametersList(PamhyrModelList):
st = solver_type_list[solver]
n = cls(solver_type=st, status=status)
data['solver'] = solver
lst = Parameter._db_load(execute, data)
table = execute(
"SELECT ind, name, value " +
"FROM solver_parameter " +
f"WHERE solver = '{solver}'"
)
n._lst = list(map(lambda t: t[1], sorted(lst)))
for row in table:
ind = row[0]
name = row[1]
value = row[2]
n.set_value(name, value)
new[solver] = n
@@ -320,16 +202,22 @@ class SolverParametersList(PamhyrModelList):
t = self._solver._type
execute(
"DELETE FROM solver_parameter " +
f"WHERE solver = '{t}' " +
f"AND scenario = {self._status.scenario_id}"
f"WHERE solver = '{t}'"
)
data["solver"] = t
ind = 0
for param in self._lst:
data["ind"] = ind
param._db_save(execute, data)
sql = (
"INSERT INTO " +
"solver_parameter(ind, name, value, solver) " +
"VALUES (" +
f"{ind}, " +
f"'{self._db_format(param.name)}', " +
f"'{self._db_format(param.value)}', " +
f"'{self._db_format(t)}'" +
")"
)
execute(sql)
ind += 1
return True
@@ -354,9 +242,9 @@ class SolverParametersList(PamhyrModelList):
def set(self, index, new):
self._lst[index] = new
new.modified()
self._status.modified()
def set_value(self, pid, key, value, owner):
def set_value(self, key, value):
for p in self._lst:
if p["name"] == key:
p["value"] = value
@@ -365,11 +253,9 @@ class SolverParametersList(PamhyrModelList):
self._lst.append(
Parameter(
id=-1,
name=key,
value=value,
status=self._status,
owner_scenario=self._status.scenario_id
status=self._status
)
)
self._status.modified()
-103
View File
@@ -1,103 +0,0 @@
# Status.py -- Pamhyr model status class
# Copyright (C) 2023-2025 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 enum import Enum, auto
logger = logging.getLogger()
class Status(Enum):
EDIT = auto()
READ_ONLY = auto()
class StudyStatus(object):
def __init__(self, scenario=None):
super(StudyStatus, self).__init__()
self._status = Status.EDIT
self._scenario = scenario
self._saved = True
@property
def scenario_id(self):
if self._scenario is None:
return -1
return self._scenario.id
def is_editable(self):
return self._status == Status.EDIT
def set_as_editable(self):
self._status = Status.EDIT
def is_read_only(self):
return self._status == Status.READ_ONLY
def set_as_read_only(self):
self._status = Status.READ_ONLY
@property
def scenario(self):
return self._scenario
@scenario.setter
def scenario(self, scenario):
logger.debug(f"Set scenario to {scenario}")
self._scenario = scenario
@property
def version(self):
if self._scenario is None:
return 0
return self._scenario.revision
@version.setter
def version(self, version):
if self._scenario is None:
return
self._scenario.revision = version
def str_display(self):
if self._scenario is None:
return ""
return f"{self._scenario.name}"
def is_saved(self):
return self._saved
def save(self):
logger.debug("model status set as saved")
self._saved = True
def _set_as_not_saved(self):
self._saved = False
def _set_as_saved(self):
self._saved = True
def modified(self):
if self._saved:
self.version += 1
self._set_as_not_saved()
+36 -91
View File
@@ -1,5 +1,5 @@
# Stricklers.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -19,10 +19,10 @@
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario
class Stricklers(SQLSubModel):
_id_cnt = 0
_sub_classes = []
def __init__(self, id: int = -1,
@@ -30,11 +30,16 @@ class Stricklers(SQLSubModel):
comment: str = "",
minor: float = 35.0,
medium: float = 15.0,
status=None, owner_scenario=-1):
super(Stricklers, self).__init__(
id=id, status=status,
owner_scenario=owner_scenario
)
status=None):
super(Stricklers, self).__init__()
self._status = status
if id == -1:
self.id = Stricklers._id_cnt
else:
self.id = id
Stricklers._id_cnt = max(Stricklers._id_cnt + 1, id + 1)
self._name = name
self._comment = comment
@@ -43,123 +48,67 @@ class Stricklers(SQLSubModel):
self._medium = medium
@classmethod
def _db_create(cls, execute, ext=""):
execute(f"""
CREATE TABLE stricklers{ext} (
{cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE,
def _db_create(cls, execute):
execute("""
CREATE TABLE stricklers(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT,
comment TEXT,
minor REAL NOT NULL,
medium REAL NOT NULL,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario)
medium REAL NOT NULL
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
major, minor, release = version.strip().split(".")
if major == minor == "0":
cls._db_update_to_0_2_0(execute, data)
if major == "0" and minor == "1":
if int(release) < 2:
execute(
"ALTER TABLE stricklers " +
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
return cls._update_submodel(execute, version, data)
@classmethod
def _db_update_to_0_2_0(cls, execute, data):
table = "stricklers"
cls.update_db_add_pamhyr_id(execute, table, data)
Scenario.update_db_add_scenario(execute, table)
cls._db_create(execute, ext="_tmp")
execute(
f"INSERT INTO {table}_tmp " +
"(pamhyr_id, name, comment, minor, medium, scenario) " +
"SELECT pamhyr_id, name, comment, minor, medium, scenario " +
f"FROM {table}"
)
execute(f"DROP TABLE {table}")
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
stricklers = []
status = data["status"]
scenario = data["scenario"]
loaded = data['loaded_pid']
if scenario is None:
return []
table = execute(
"SELECT pamhyr_id, deleted, " +
"name, comment, minor, medium, scenario " +
"FROM stricklers " +
f"WHERE scenario = {scenario.id} " +
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
"SELECT id, name, comment, minor, medium " +
"FROM stricklers"
)
if table is None:
return None
for row in table:
it = iter(row)
pid = next(it)
deleted = (next(it) == 1)
name = next(it)
comment = next(it)
minor = next(it)
medium = next(it)
owner_scenario = next(it)
id = row[0]
name = row[1]
comment = row[2]
minor = row[3]
medium = row[4]
new = cls(
id=pid, name=name, comment=comment,
id=id,
name=name,
comment=comment,
minor=minor, medium=medium,
status=status, owner_scenario=owner_scenario
status=status
)
if deleted:
new.set_as_deleted()
stricklers.append(new)
loaded.add(pid)
data["scenario"] = scenario.parent
stricklers += cls._db_load(execute, data)
data["scenario"] = scenario
return stricklers
def _db_save(self, execute, data=None):
if not self.must_be_saved():
return True
execute(
sql = (
"INSERT INTO " +
"stricklers(pamhyr_id, deleted, " +
"name, comment, minor, medium, scenario) " +
"stricklers(id, name, comment, minor, medium) " +
"VALUES (" +
f"{self.pamhyr_id}, " +
f"{self._db_format(self.is_deleted())}, " +
f"{self.id}, " +
f"'{self._db_format(self.name)}', " +
f"'{self._db_format(self.comment)}', " +
f"{float(self.minor)}, {float(self.medium)}, " +
f"{self._status.scenario_id}"
f"{float(self.minor)}, {float(self.medium)}" +
")"
)
execute(sql)
return True
@@ -169,13 +118,12 @@ class Stricklers(SQLSubModel):
@property
def name(self):
if self._name == "":
return f"K #{self.pamhyr_id}"
return f"K{self.id + 1}"
return self._name
@name.setter
def name(self, name):
self._name = name
self.modified()
@property
def comment(self):
@@ -184,7 +132,6 @@ class Stricklers(SQLSubModel):
@comment.setter
def comment(self, comment):
self._comment = comment
self.modified()
@property
def minor(self):
@@ -193,7 +140,6 @@ class Stricklers(SQLSubModel):
@minor.setter
def minor(self, minor):
self._minor = float(minor)
self.modified()
@property
def medium(self):
@@ -202,4 +148,3 @@ class Stricklers(SQLSubModel):
@medium.setter
def medium(self, medium):
self._medium = float(medium)
self.modified()
+9 -14
View File
@@ -1,5 +1,5 @@
# StricklersList.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -18,7 +18,8 @@
from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Saved import SavedStatus
from Model.Tools.PamhyrList import PamhyrModelList
from Model.Stricklers.Stricklers import Stricklers
@@ -32,8 +33,8 @@ class StricklersList(PamhyrModelList):
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
@@ -47,10 +48,7 @@ class StricklersList(PamhyrModelList):
return new
def _db_save(self, execute, data=None):
execute(
"DELETE FROM stricklers " +
f"WHERE scenario = {self._status.scenario_id}"
)
execute("DELETE FROM stricklers")
objs = self._lst
return self._save_submodel(execute, objs, data)
@@ -60,12 +58,8 @@ class StricklersList(PamhyrModelList):
return self.lst
def new(self, index):
s = Stricklers(
status=self._status,
owner_scenario=self._status.scenario_id
)
s = Stricklers(status=self._status)
self.insert(index, s)
self.modified()
return s
@timer
@@ -80,4 +74,5 @@ class StricklersList(PamhyrModelList):
reverse=reverse,
)
self.modified()
if self._status is not None:
self._status.modified()
+32 -257
View File
@@ -1,5 +1,5 @@
# Study.py -- Pamhyr Study class
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -22,12 +22,10 @@ import logging
from datetime import datetime
from tools import timer, timestamp
from functools import reduce
from Model.Tools.PamhyrDB import SQLModel
from Model.Scenarios import Scenarios
from Model.Scenario import Scenario
from Model.Status import StudyStatus
from Model.Saved import SavedStatus
from Model.Serializable import Serializable
from Model.Except import NotImplementedMethodeError
from Model.River import River
@@ -37,18 +35,13 @@ logger = logging.getLogger()
class Study(SQLModel):
_version = "0.2.3"
_sub_classes = [
Scenario,
River,
]
def __init__(self, filename=None, init_new=True, copy=False):
if copy:
return
def __init__(self, filename=None, init_new=True):
# Metadata
self._version = "0.0.11"
self.creation_date = datetime.now()
self.last_modification_date = datetime.now()
self.last_save_date = datetime.now()
@@ -56,7 +49,7 @@ class Study(SQLModel):
self._filename = filename
super(Study, self).__init__(filename=filename)
self.status = StudyStatus()
self.status = SavedStatus()
# Study general information
self._name = ""
@@ -67,22 +60,12 @@ class Study(SQLModel):
if init_new:
# Study data
s0 = Scenario(
id=0, name='default',
description='Default scenario',
)
self.scenarios = Scenarios(status=self.status)
self.scenarios[0] = s0
self.status.scenario = s0
self._river = River(status=self.status)
else:
self._init_db_file(filename, is_new=False)
self._old_save_id = 0
self._river_scenario_cache = {}
@classmethod
def checkers(cls):
lst = [
@@ -101,12 +84,6 @@ class Study(SQLModel):
def river(self):
return self._river
def is_editable(self):
return self.status.is_editable()
def is_read_only(self):
return self.status.is_read_only()
@property
def is_saved(self):
return self.status.is_saved()
@@ -130,11 +107,15 @@ class Study(SQLModel):
is_new = False
fname = fname + "." + str(self._old_save_id)
if os.path.exists(self.filename):
if os.path.exists(self.filename) and ".backup" not in self.filename:
filename = os.path.join(fdir, "_PAMHYR_", "__old__", fname)
logger.debug(f"Backup previous version copy: {filename}")
shutil.copy(self.filename, filename)
self._old_save_id += 1
is_new = True
if ".backup" in self.filename:
is_new = True
if not os.path.exists(self.filename):
is_new = True
@@ -231,10 +212,11 @@ class Study(SQLModel):
def _create(self):
# Info (metadata)
self.execute(
"CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)"
"INSERT INTO info VALUES ('study_release', '0')"
)
self.execute(
"INSERT INTO info VALUES ('current_scenario', '0')"
"CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)"
)
self.execute(
"INSERT INTO info VALUES ('version', " +
@@ -270,7 +252,7 @@ class Study(SQLModel):
if version[0] == self._version:
return True
logger.info(f"Update database from {version[0]} to {self._version}")
logger.debug("Update database")
major, minor, release = version[0].split('.')
if major == "0" and minor == "0" and int(release) < 10:
@@ -278,53 +260,19 @@ class Study(SQLModel):
"INSERT INTO info VALUES ('study_release', '0')"
)
if major == "0" and int(minor) < 2:
self._add_into_info_if_not_exists('current_scenario', '0')
if major == "0" and int(minor) < 2:
# Need to temporary disable the sqlite foreign keys
# checking to update db dans change the table id fk to
# table pamhyr_id fk
self.execute(
"PRAGMA foreign_keys = OFF;"
)
ok = self._update_submodel(version[0], data={})
if major == "0" and int(minor) < 2:
# Reactivate foreign keys checking
self.execute(
"PRAGMA foreign_keys = ON;"
)
if ok:
if self._update_submodel(version[0]):
self.execute(
f"UPDATE info SET value='{self._version}' WHERE key='version'"
)
logger.info("Update database done.")
return True
logger.info("Update failed!")
logger.info("TODO: update failed")
raise NotImplementedMethodeError(self, self._update)
def _add_into_info_if_not_exists(self, key, value):
rows = self.execute(f"SELECT value FROM info WHERE key='{key}'")
if rows is None or len(rows) == 0:
self.execute(
f"INSERT INTO info VALUES ('{key}', '{value}')"
)
@classmethod
def _load(cls, filename):
new = cls(init_new=False, filename=filename)
def sql_exec(sql):
return new.execute(
sql, fetch_one=False, commit=True
)
version = new.execute(
"SELECT value FROM info WHERE key='study_release'"
)
@@ -342,65 +290,31 @@ class Study(SQLModel):
)
new.creation_date = datetime.fromtimestamp(
float(
new.execute(
"SELECT value FROM info WHERE key='creation_date'"
)[0]
)
float(new.execute(
"SELECT value FROM info WHERE key='creation_date'")[0])
)
new.last_save_date = datetime.fromtimestamp(
float(
new.execute(
"SELECT value FROM info WHERE key='last_save_date'"
)[0]
)
float(new.execute(
"SELECT value FROM info WHERE key='last_save_date'")[0])
)
data = {
"study": new,
"status": new.status
}
# Scenarios
new.scenarios = Scenarios._db_load(
sql_exec,
data=data
)
scenario_id = new.execute(
"SELECT value FROM info WHERE key='current_scenario'"
)
logger.debug(f"Load with scenario {scenario_id[0]}")
scenario = new.scenarios[int(scenario_id[0])]
new.status.scenario = scenario
if reduce(
lambda a, s: a or (s.parent is scenario),
new.scenarios.lst,
False
):
new.status.set_as_read_only()
data["scenario"] = scenario
data["loaded_pid"] = set()
# Load river data
new._river = River._db_load(
sql_exec, data=data
lambda sql: new.execute(
sql,
fetch_one=False,
commit=True
),
data={"status": new.status}
)
new._river._db_load_results(sql_exec, data=data)
new.status._set_as_saved()
return new
def _save(self, progress=None):
progress = progress if progress is not None else lambda: None
self.execute(
"INSERT OR REPLACE INTO info VALUES ('study_release', " +
"INSERT INTO info VALUES ('study_release', " +
f"'{self.status.version}')"
)
@@ -438,28 +352,16 @@ class Study(SQLModel):
)
progress()
self.execute(
f"UPDATE info SET " +
f"value='{self.status.scenario_id}' " +
"WHERE key='current_scenario'"
)
progress()
self._save_submodel(
[self.scenarios, self._river],
data=progress
)
self._save_submodel([self._river], data=progress)
self.commit()
def sql_save_request_count(self, *args, **kargs):
def sql_save_request_count(self):
return self._count()
def _count(self):
cnt = self._save_count(
[self.scenarios, self._river]
)
cnt = self._save_count([self._river])
logger.debug(cnt)
return cnt + 7
return cnt + 6
def close(self):
"""Close db connection
@@ -468,130 +370,3 @@ class Study(SQLModel):
Nothing.
"""
self._close()
def new_scenario_from_current(self, switch=True):
new = self.scenarios.new(self.status.scenario)
if switch:
self.status.scenario = new
self.status.set_as_editable()
return new
def reload_from_scenario(self, scenario):
river = self.load_scenario(scenario)
self._river = river
self.status.scenario = scenario
if reduce(
lambda a, s: a or (s.parent is scenario),
self.scenarios.lst,
False
):
self.status.set_as_read_only()
else:
self.status.set_as_editable()
def load_scenario(self, scenario):
if scenario in self._river_scenario_cache:
return self._river_scenario_cache[scenario]
def sql_exec(sql):
return self.execute(
sql, fetch_one=False, commit=True
)
old_scenario = self.status.scenario
self.status.scenario = scenario
data = {
"status": self.status,
"loaded_pid": set(),
"scenario": scenario
}
# Load river data
river = River._db_load(
sql_exec, data=data
)
data["study"] = self
river._db_load_results(sql_exec, data=data)
self._river_scenario_cache[scenario] = river
self.status.scenario = old_scenario
return river
def copy_from_scenario(self, scenario):
new = self._copy()
new._river = self.load_scenario(scenario)
def set_status(obj):
obj._status = new.status
new.river._data_traversal(
modifier=lambda obj, data: set_status(obj),
)
return new
def _copy(self):
"""
This method make a copy of current study. This copy is like an
empty shell, it's not fully functional. Study object use
SQLite connection to file, this copy as no valid connection.
(!) Please use this copy as read only object!
"""
new = Study(copy=True)
new._filename = ""
new.creation_date = self.creation_date
new.last_modification_date = self.last_modification_date
new.last_save_date = self.last_save_date
new._name = self._name
new.description = self.description
new._time_system = self._time_system
new._date = self._date
new.status = StudyStatus()
new.scenarios = self.scenarios
new.status.scenario = self.status.scenario
new._river = self._river
new._river_scenario_cache = self._river_scenario_cache
return new
def duplicate_current_scenario(self):
source = self.status.scenario_id
new = self.scenarios.new(
self.status.scenario.parent
)
new.set_pos(self.status.scenario.x + 100,
self.status.scenario.y + 100)
new.name = self.status.scenario.name + " (copy)"
self.status.scenario = new
self.river._data_traversal(
predicate=lambda obj, data: obj._owner_scenario == source,
modifier=lambda obj, data: obj.set_owner_scenario(),
data={}
)
self.status.set_as_editable()
return new
@property
def results(self):
return self._river.results
@results.setter
def results(self, results):
self._river.results = results
+18 -224
View File
@@ -1,5 +1,5 @@
# PamhyrDB.py -- Pamhyr abstract model database classes
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -26,8 +26,6 @@ from functools import reduce
from SQL import SQL
from Model.Except import NotImplementedMethodeError
from Model.Tools.PamhyrID import PamhyrID
logger = logging.getLogger()
# Top level model class
@@ -44,7 +42,7 @@ class SQLModel(SQL):
if exists and is_new:
os.remove(db)
self._db = sqlite3.connect(db, check_same_thread=False)
self._db = sqlite3.connect(db)
self._cur = self._db.cursor()
if is_new:
@@ -73,9 +71,10 @@ class SQLModel(SQL):
return True
def _create(self):
raise NotImplementedMethodeError(self, self._create)
def _update_submodel(self, version, data=None):
def _update_submodel(self, version):
def fn(sql): return self.execute(
sql,
fetch_one=False,
@@ -84,7 +83,7 @@ class SQLModel(SQL):
ok = True
for cls in self._sub_classes:
ok &= cls._db_update(fn, version, data=data)
ok &= cls._db_update(fn, version)
self.commit()
return ok
@@ -95,22 +94,18 @@ class SQLModel(SQL):
def _save_submodel(self, objs, data=None):
progress = data if data is not None else lambda: None
def fn(sql, *args, **kargs):
if "fetch_one" not in kargs:
kargs["fetch_one"] = False
if "commit" not in kargs:
kargs["commit"] = False
def fn(sql):
res = self.execute(
sql, *args, **kargs
sql,
fetch_one=False,
commit=False
)
progress()
return res
ok = True
for obj in objs:
ok &= obj._db_save(fn, data={})
ok &= obj._db_save(fn)
self.commit()
return ok
@@ -121,7 +116,7 @@ class SQLModel(SQL):
def _count(self):
raise NotImplementedMethodeError(self, self._count)
def _save_count(self, objs, *args, data={}, **kargs):
def _save_count(self, objs, data=None):
counter = {
"insert": 0,
"update": 0,
@@ -129,7 +124,7 @@ class SQLModel(SQL):
"other": 0,
}
def fn(sql, *args, **kargs):
def fn(sql):
if "insert" in sql.lower():
counter["insert"] = counter["insert"] + 1
elif "update" in sql.lower():
@@ -142,7 +137,7 @@ class SQLModel(SQL):
ok = True
for obj in objs:
ok &= obj._db_save(fn, data=data)
ok &= obj._db_save(fn)
logger.debug(counter)
@@ -159,103 +154,15 @@ class SQLModel(SQL):
# Sub model class
class SQLSubModel(PamhyrID):
class SQLSubModel(object):
_sub_classes = []
def __init__(self, id: int = -1, status=None,
owner_scenario=-1, **kwargs):
super(SQLSubModel, self).__init__(id=id, **kwargs)
self._status = status
# Deletion status of the model object. This status MUST be set
# to True if an object that exists in a parent scenario is
# deleted in current scenario.
self._deleted = False
# The 'owner_scenario' is the id of the scenario to which the
# object belongs. This id CAN be different to current
# scenario, but in case of object modification, this id MUST
# be set to current scenario id. (This action is made in
# 'modified' method.)
self._owner_scenario = 0
if owner_scenario == -1:
if status is not None:
self._owner_scenario = self._status.scenario_id
else:
self._owner_scenario = owner_scenario
def must_be_saved(self):
"""Return True if this object MUST be save in the save file.
Returns:
True if this object MUST be save, otherelse False
"""
return self._owner_scenario == self._status.scenario_id
def modified(self):
"""Set study status to modified and update the object
owner_scenario to current scenario
Returns:
Nothing
"""
if self._status is None:
return
self._owner_scenario = self._status.scenario_id
self._status.modified()
def set_owner_scenario(self):
"""Set study status the object owner_scenario to current
scenario if predicate(scenario_id) is true
Args:
predicate: Function predicate on owner_scenario id
Returns:
Nothing
"""
if self._status is None:
return
self._owner_scenario = self._status.scenario_id
self._status.modified()
def is_deleted(self):
"""This object is deleted?
Returns:
True if this object is deleted, otherelse False
"""
return self._deleted
def set_as_deleted(self):
"""Set object deleted flag to True.
Returns:
Nothing
"""
self._deleted = True
self.modified()
def set_as_not_deleted(self):
"""Set object deleted flag to False.
Returns:
Nothing
"""
self._deleted = False
self.modified()
def _db_format(self, value):
# Replace ''' by '&#39;' to preserve SQL injection
if type(value) is str:
value = value.replace("'", "&#39;")
elif type(value) is bool:
value = 'TRUE' if value else 'FALSE'
elif value is None:
value = "NULL"
return value
@classmethod
@@ -276,16 +183,12 @@ class SQLSubModel(PamhyrID):
raise NotImplementedMethodeError(cls, cls._db_create)
@classmethod
def _update_submodel(cls, execute, version, data=None):
ok = True
def _update_submodel(cls, execute, version):
for sc in cls._sub_classes:
ok &= sc._db_update(execute, version, data)
return ok
sc._db_update(execute, version)
@classmethod
def _db_update(cls, execute, version, data=None):
def _db_update(cls, execute, version):
"""Update data base scheme
Args:
@@ -297,93 +200,6 @@ class SQLSubModel(PamhyrID):
"""
raise NotImplementedMethodeError(cls, cls._db_update)
@classmethod
def _db_update_to_0_2_0_set_node_pid(cls, execute, table, nodes):
els = execute(
f"SELECT pamhyr_id, node FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
node_id = next(it)
if node_id == -1:
continue
execute(
f"UPDATE {table} " +
f"SET node = {nodes[node_id]} " +
f"WHERE pamhyr_id = {pid}"
)
@classmethod
def _db_update_to_0_2_0_set_reach_pid(cls, execute, table, reachs):
els = execute(
f"SELECT pamhyr_id, reach FROM {table}"
)
if els is None:
return True
for row in els:
it = iter(row)
pid = next(it)
reach_id = next(it)
if reach_id == -1:
continue
execute(
f"UPDATE {table} " +
f"SET reach = {reachs[reach_id]} " +
f"WHERE pamhyr_id = {pid}"
)
return True
@classmethod
def _db_update_to_0_1_1_assoc_section_from_rk(
cls, execute, table,
reach_column="reach",
rk_column="rk",
section_column="section",
origin_version="0.1.0"):
kid = "pamhyr_id"
if origin_version == "0.0.*":
kid = "id"
els = execute(
"SELECT " +
f"{kid}, {reach_column}, {rk_column} " +
f"FROM {table}"
)
for row in els:
it = iter(row)
pid = next(it)
reach_id = next(it)
rk = next(it)
if reach_id == -1 or reach_id is None:
continue
section_id = -1
section = execute(
f"SELECT pamhyr_id FROM geometry_profileXYZ " +
f"WHERE reach == {reach_id} AND rk == {rk}"
)
if len(section) != 0:
section_id = section[0][0]
execute(
f"UPDATE {table} " +
f"SET {section_column} = {section_id} " +
f"WHERE {kid} = {pid}"
)
@classmethod
def _db_load(cls, execute, data=None):
"""Load instance of this class from SQL data base
@@ -398,12 +214,8 @@ class SQLSubModel(PamhyrID):
raise NotImplementedMethodeError(cls, cls._db_load)
def _save_submodel(self, execute, objs, data=None):
ok = True
for o in objs:
ok &= o._db_save(execute, data=data)
return ok
o._db_save(execute, data=data)
def _db_save(self, execute, data=None):
"""Save class data to data base
@@ -417,21 +229,3 @@ class SQLSubModel(PamhyrID):
save
"""
raise NotImplementedMethodeError(self, self._db_save)
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
if predicate(self, data):
modifier(self, data)
+6 -31
View File
@@ -1,5 +1,5 @@
# PamhyrDict.py -- Pamhyr abstract dict for model classes
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -32,9 +32,9 @@ class PamhyrModelDict(SQLSubModel):
_sub_classes = []
def __init__(self, status=None):
super(PamhyrModelDict, self).__init__(
status=status
)
super(PamhyrModelDict, self).__init__()
self._status = status
self._dict = {}
@@ -43,8 +43,8 @@ class PamhyrModelDict(SQLSubModel):
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
@@ -59,12 +59,6 @@ class PamhyrModelDict(SQLSubModel):
def __contains__(self, key):
return key in self._dict
def __getitem__(self, key):
return self.get(key)
def __setitem__(self, key, value):
self.set(key, value)
def set(self, key, new):
self._dict[key] = new
self._status.modified()
@@ -82,24 +76,5 @@ class PamhyrModelDict(SQLSubModel):
self.set(key, new)
return new
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
for key in self._dict:
self._dict[key]\
._data_traversal(predicate, modifier, data)
def new(self, key):
raise NotImplementedMethodeError(self, self.new)
-99
View File
@@ -1,99 +0,0 @@
# PamhyrID.py -- Pamhyr
# Copyright (C) 2024-2025 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
logger = logging.getLogger()
class PamhyrID(object):
_pamhyr_id_cnt = 0
def __init__(self, id: int = -1, **kwargs):
super(PamhyrID, self).__init__(**kwargs)
self._pamhyr_id = self.get_new_pamhyr_id(id)
@classmethod
def get_new_pamhyr_id(cls, id):
pid = int(id)
if pid < 0:
pid = PamhyrID._pamhyr_id_cnt
PamhyrID._pamhyr_id_cnt = max(
id + 1,
PamhyrID._pamhyr_id_cnt + 1
)
return pid
@property
def id(self):
return self._pamhyr_id
@property
def pamhyr_id(self):
return self._pamhyr_id
@classmethod
def update_db_add_pamhyr_id(cls, execute, table,
data=None):
execute(
f"ALTER TABLE {table} " +
f"ADD COLUMN pamhyr_id INTEGER NOT NULL DEFAULT -1"
)
id2pid = cls.update_db_add_pamhyr_id_init_id2pid(table, data)
rows = execute(f"SELECT id FROM {table}")
if rows is None:
return True
for row in rows:
id = row[0]
pid = cls.get_new_pamhyr_id(-1)
id2pid[table][id] = pid
execute(
f"UPDATE {table} " +
f"SET pamhyr_id = {pid} " +
f"WHERE id = {id}"
)
return True
@classmethod
def update_db_add_pamhyr_id_init_id2pid(cls, table, data):
id2pid = {}
if "id2pid" in data:
id2pid = data["id2pid"]
else:
data["id2pid"] = id2pid
if table not in id2pid:
id2pid[table] = {}
return id2pid
@classmethod
def create_db_add_pamhyr_id(cls):
return "pamhyr_id INTEGER NOT NULL"
+9 -43
View File
@@ -1,5 +1,5 @@
# PamhyrList.py -- Pamhyr Abstract List object for the Model
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
@@ -28,7 +28,8 @@ logger = logging.getLogger()
class PamhyrModelList(SQLSubModel):
_sub_classes = []
_sub_classes = [
]
def __init__(self, status=None):
super(PamhyrModelList, self).__init__()
@@ -45,8 +46,8 @@ class PamhyrModelList(SQLSubModel):
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
@@ -85,24 +86,6 @@ class PamhyrModelList(SQLSubModel):
if self._status is not None:
self._status.modified()
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
for el in self._lst:
el._data_traversal(predicate, modifier, data)
def new(self, index):
"""Create new elements and add it to list
@@ -184,7 +167,8 @@ class PamhyrModelList(SQLSubModel):
class PamhyrModelListWithTab(SQLSubModel):
_tabs_list = []
_sub_classes = []
_sub_classes = [
]
def __init__(self, status=None):
super(PamhyrModelListWithTab, self).__init__()
@@ -203,8 +187,8 @@ class PamhyrModelListWithTab(SQLSubModel):
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version, data=None):
return cls._update_submodel(execute, version, data)
def _db_update(cls, execute, version):
return cls._update_submodel(execute, version)
@classmethod
def _db_load(cls, execute, data=None):
@@ -213,24 +197,6 @@ class PamhyrModelListWithTab(SQLSubModel):
def _db_save(self, execute, data=None):
raise NotImplementedMethodeError(self, self._db_save)
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
for tab in self._tabs:
for el in self._tabs[tab]:
el._data_traversal(predicate, modifier, data)
################
# MODEL METHOD #
################
-383
View File
@@ -1,383 +0,0 @@
# PamhyrList.py -- Pamhyr Abstract List object for the Model
# Copyright (C) 2023-2025 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 copy import copy
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
logger = logging.getLogger()
class PamhyrModelList(SQLSubModel):
_sub_classes = []
def __init__(self, status=None):
super(PamhyrModelList, self).__init__()
self._status = status
self._lst = []
#######
# SQL #
#######
@classmethod
def _db_create(cls, execute):
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):
raise NotImplementedMethodeError(cls, cls._db_load)
def _db_save(self, execute, data=None):
raise NotImplementedMethodeError(self, self._db_save)
################
# MODEL METHOD #
################
@property
def lst(self):
"""Return the PamhyrList as a Python list
Return the PamhyrList as a Python list. This list is the
current list witout deleted object used in the PamhyrList
object. **If you need to modify it, please make a copy.**
Returns: The Python list
"""
return list(
filter(
lambda el: not el.is_deleted(),
self._lst
)
)
def __len__(self):
return len(self.lst)
def index(self, el):
return self.lst.index(el)
def get(self, index):
if len(self.lst) <= index:
return None
return self.lst[index]
def set(self, index, new):
self.lst[index] = new
if self._status is not None:
self._status.modified()
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
for el in self._lst:
el._data_traversal(predicate, modifier, data)
def new(self, index):
"""Create new elements and add it to list
Args:
index: The index of new elements
Returns:
The new elements
"""
raise NotImplementedMethodeError(self, self.new)
def insert(self, index, new):
if new in self._lst:
new.set_as_not_deleted()
else:
self._lst.insert(index, new)
if self._status is not None:
self._status.modified()
def undelete(self, lst):
"""Delete a list of elements
Args:
lst: The list of elements
Returns:
Nothing
"""
for el in lst:
el.set_as_not_deleted()
def delete(self, lst):
"""Delete a list of elements
Args:
lst: The list of elements
Returns:
Nothing
"""
for el in lst:
el.set_as_deleted()
def delete_i(self, indexes):
"""Delete elements from list of indexes
Args:
indexes: The elements indexes
Returns:
Nothing
"""
lst = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._lst)
)
)
)
self.delete(lst)
def sort(self, reverse=False, key=None):
self._lst.sort(reverse=reverse, key=key)
if self._status is not None:
self._status.modified()
def move_up(self, index):
if index < len(self._lst):
next = index - 1
lst = self._lst
lst[index], lst[next] = lst[next], lst[index]
if self._status is not None:
self._status.modified()
def move_down(self, index):
if index >= 0:
prev = index + 1
lst = self._lst
lst[index], lst[prev] = lst[prev], lst[index]
if self._status is not None:
self._status.modified()
class PamhyrModelListWithTab(SQLSubModel):
_tabs_list = []
_sub_classes = []
def __init__(self, status=None):
super(PamhyrModelListWithTab, self).__init__()
self._status = status
self._tabs = {}
for tab in self._tabs_list:
self._tabs[tab] = []
#######
# SQL #
#######
@classmethod
def _db_create(cls, execute):
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):
raise NotImplementedMethodeError(cls, cls._db_load)
def _db_save(self, execute, data=None):
raise NotImplementedMethodeError(self, self._db_save)
def _data_traversal(self,
predicate=lambda obj, data: True,
modifier=lambda obj, data: None,
data={}):
"""Traversal data and execute modifier fonction if predicate
true
Args:
predicate: Function predicate, take current obj and data as input
modifier: Function modifier, take current obj and data as input
Returns:
Nothing
"""
for tab in self._tabs_list:
for el in self._tabs[tab]:
el._data_traversal(predicate, modifier, data)
################
# MODEL METHOD #
################
def len(self, lst):
"""Size of tab list
Args:
lst: The tab name
Returns:
The size of the tab list
"""
return len(self.get_tab(lst))
def get_tab(self, lst):
"""Get tab list (copy) from name
Args:
lst: The tab name
Returns:
Nothing
"""
# return self._tabs[lst].copy()
return list(
filter(
lambda el: not el.is_deleted(),
self._tabs[lst]
)
)
def set(self, lst, index, new):
old = self.get_tab(lst)[index]
old.set_as_deleted()
self._tabs[lst][index] = new
self._status.modified()
def get(self, lst, index):
if len(self.get_tab(lst)) == 0:
return None
return self.get_tab(lst)[index]
def new(self, lst, index):
"""Create new elements and add it to list
Args:
lst: The tab name
index: The index of new elements
Returns:
The new elements
"""
raise NotImplementedMethodeError(self, self.new)
def insert(self, lst, index, new):
if new in self._tabs[lst]:
new.set_as_not_deleted()
else:
self._tabs[lst].insert(index, new)
self._status.modified()
def undelete(self, lst, els):
"""Delete a list of elements
Args:
lst: The tab name
els: The list of elements
Returns:
Nothing
"""
for el in els:
el.set_as_not_deleted()
def delete(self, lst, els):
"""Delete a list of elements
Args:
lst: The tab name
els: The list of elements
Returns:
Nothing
"""
for el in els:
el.set_as_deleted()
def delete_i(self, lst, indexes):
"""Delete elements from specific tab
Args:
lst: The tab name
indexes: The elements indexes
Returns:
Nothing
"""
els = list(
map(
lambda x: x[1],
filter(
lambda x: x[0] in indexes,
enumerate(self._tabs[lst])
)
)
)
self.delete(lst, els)
def sort(self, lst, reverse=False, key=None):
self._tabs[lst].sort(reverse=reverse, key=key)
self._status.modified()
def move_up(self, lst, index):
if index < len(self._tabs[lst]):
next = index - 1
lst = self._tabs[lst]
lst[index], lst[next] = lst[next], lst[index]
self._status.modified()
def move_down(self, lst, index):
if index >= 0:
prev = index + 1
lst = self._tabs[lst]
lst[index], lst[prev] = lst[prev], lst[index]
self._status.modified()
+1 -1
View File
@@ -1,5 +1,5 @@
# __init__.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
# Copyright (C) 2023-2024 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
Loaded 100 of 368 files, more files were not shown because too many files have changed in this diff. Show more