mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
382
Commits
v1.2.0rc0
...
test_sound
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22771c7f3a | ||
|
|
6d60a2017c | ||
|
|
02db91f3d7 | ||
|
|
d934b5e381 | ||
|
|
279fa0bf5e | ||
|
|
abe24611b3 | ||
|
|
17a18dde18 | ||
|
|
044818072e | ||
|
|
8bd791b2c4 | ||
|
|
c8d0d0c212 | ||
|
|
d31be61af8 | ||
|
|
a8a408b5d5 | ||
|
|
ff9d25412c | ||
|
|
de7f3e63b1 | ||
|
|
879c151524 | ||
|
|
7990a60d8b | ||
|
|
8aa0e25da2 | ||
|
|
1e6a578890 | ||
|
|
1dca9b98df | ||
|
|
de180b3635 | ||
|
|
5ca763ac8c | ||
|
|
188486f33a | ||
|
|
bc08d06bb1 | ||
|
|
05e039011a | ||
|
|
7640123299 | ||
|
|
e6dd983f97 | ||
|
|
002ebc5928 | ||
|
|
84486c03ed | ||
|
|
dc7dfb2414 | ||
|
|
ee01beb844 | ||
|
|
68b62a5630 | ||
|
|
990d44dd07 | ||
|
|
819ae41c00 | ||
|
|
bf95b7f7e6 | ||
|
|
861b78b928 | ||
|
|
08c414a66e | ||
|
|
57a80d00aa | ||
|
|
70eae5f7d2 | ||
|
|
e2a65e2cdd | ||
|
|
f03b74614e | ||
|
|
532b3fd7fd | ||
|
|
64df4129d4 | ||
|
|
f6e35e2045 | ||
|
|
a97717a624 | ||
|
|
3f650de52f | ||
|
|
1f4bda7976 | ||
|
|
a4423e45f8 | ||
|
|
1386c592bf | ||
|
|
016f3fc73c | ||
|
|
7ef3184699 | ||
|
|
5f6c823301 | ||
|
|
a42c46f4d2 | ||
|
|
fb09cf254e | ||
|
|
315b913fd0 | ||
|
|
0383dc18ae | ||
|
|
f16856d23b | ||
|
|
e640fa1bc0 | ||
|
|
df3b3b604d | ||
|
|
21e6c410e2 | ||
|
|
19045329dc | ||
|
|
4586ef79f7 | ||
|
|
bc4557e21f | ||
|
|
f97d4ce3c5 | ||
|
|
f6e08f203c | ||
|
|
5dcbc6b4e1 | ||
|
|
ebe906fce0 | ||
|
|
cd4f626218 | ||
|
|
c1eb2cf447 | ||
|
|
5fec95cc4d | ||
|
|
46b943c800 | ||
|
|
fa8fe508a7 | ||
|
|
911a2bbbff | ||
|
|
2a926a8d73 | ||
|
|
d222842313 | ||
|
|
2942b6e442 | ||
|
|
926cade437 | ||
|
|
13b4413b36 | ||
|
|
706a900c8e | ||
|
|
da03f39425 | ||
|
|
f516db0e48 | ||
|
|
8744e1d73e | ||
|
|
6113d9f4cd | ||
|
|
1dd92cb089 | ||
|
|
8f6255c2cd | ||
|
|
6c4f6e269c | ||
|
|
82b0837d8c | ||
|
|
137489cc1f | ||
|
|
4dc4dfdda0 | ||
|
|
7d42c164fc | ||
|
|
ae15311471 | ||
|
|
dbcdace348 | ||
|
|
9375827949 | ||
|
|
739f401fac | ||
|
|
5f5a47679d | ||
|
|
26d1d63edb | ||
|
|
1b2f98494b | ||
|
|
381bc37d13 | ||
|
|
e7b63fe270 | ||
|
|
80ae4c36ad | ||
|
|
936bfc753a | ||
|
|
f0ccf1f4f7 | ||
|
|
3a3c4d9d73 | ||
|
|
41cffd13e0 | ||
|
|
9270dd0ff1 | ||
|
|
ba8a50e0b9 | ||
|
|
9c69f2ce6d | ||
|
|
e7a27c798a | ||
|
|
ddc612328e | ||
|
|
d05d1e6914 | ||
|
|
8bcf776813 | ||
|
|
589e591d3b | ||
|
|
e2e8c5ec9e | ||
|
|
fce034419d | ||
|
|
d9aea9eb3c | ||
|
|
4cddd43f5a | ||
|
|
becc1696cb | ||
|
|
cf4957551f | ||
|
|
794b06d55a | ||
|
|
f51f742e00 | ||
|
|
31fe765e51 | ||
|
|
40cc188815 | ||
|
|
303f6bb35b | ||
|
|
9043b6e2a3 | ||
|
|
0608b9175e | ||
|
|
ea7fda6b28 | ||
|
|
090326b75c | ||
|
|
b582e133a1 | ||
|
|
78e6f7e5a7 | ||
|
|
8173eb8de9 | ||
|
|
591cd8950c | ||
|
|
7c4c772f43 | ||
|
|
3a78d6d6f7 | ||
|
|
bbf85a6048 | ||
|
|
6dfa876809 | ||
|
|
aff86a2ffa | ||
|
|
1eb925f3dd | ||
|
|
701d76240e | ||
|
|
37ea9e9083 | ||
|
|
6457b3ad25 | ||
|
|
cba5b948de | ||
|
|
4ba232a7e4 | ||
|
|
05f3f0fc7e | ||
|
|
89a5d78ced | ||
|
|
5fc4e3903a | ||
|
|
1b70e22a58 | ||
|
|
22aeda105e | ||
|
|
7181122a3a | ||
|
|
76ce34c4ea | ||
|
|
c58620fdc2 | ||
|
|
a6341dedd2 | ||
|
|
ba7171326c | ||
|
|
78b4269a69 | ||
|
|
a30bf8e364 | ||
|
|
bd1edbaf30 | ||
|
|
bdf9264963 | ||
|
|
befe1a550d | ||
|
|
f4d3f2b28f | ||
|
|
2a1c8c1e57 | ||
|
|
a3da09cf5b | ||
|
|
017f26c03d | ||
|
|
1e2a123693 | ||
|
|
2916afb974 | ||
|
|
37963b7b21 | ||
|
|
e5b4c998cb | ||
|
|
4bf0ad40b1 | ||
|
|
420d766ae5 | ||
|
|
645a76b0e8 | ||
|
|
6288f039d2 | ||
|
|
3585b29b03 | ||
|
|
96bac8ee1f | ||
|
|
e7d14e7aa3 | ||
|
|
6b14ed7efe | ||
|
|
3d40f599d6 | ||
|
|
6ec677a047 | ||
|
|
7bb0c9d57e | ||
|
|
ec2a1da6b6 | ||
|
|
a072a28929 | ||
|
|
fc7d732e0a | ||
|
|
cc5d86328b | ||
|
|
e3cbe3cf1f | ||
|
|
9482aaf523 | ||
|
|
e0cee0fbef | ||
|
|
f9a8512477 | ||
|
|
1457db721b | ||
|
|
7774d2d7af | ||
|
|
e6d3564d9b | ||
|
|
d62e703439 | ||
|
|
0346080e16 | ||
|
|
76559f6af8 | ||
|
|
04ce79462c | ||
|
|
ffa065a3e6 | ||
|
|
0e1e3665a6 | ||
|
|
c64bcd1ff7 | ||
|
|
dd84b83302 | ||
|
|
084223037c | ||
|
|
072840a18c | ||
|
|
3ef13666dc | ||
|
|
e344678ac5 | ||
|
|
1f79e6510d | ||
|
|
b94c37dd3b | ||
|
|
b61c4da8f1 | ||
|
|
ba60585550 | ||
|
|
0ed7660e72 | ||
|
|
8b4ff41b96 | ||
|
|
8769fa260d | ||
|
|
2667737490 | ||
|
|
7cb3982a1f | ||
|
|
2336dd22f3 | ||
|
|
f13ab03192 | ||
|
|
0df813b0d2 | ||
|
|
2faebfd402 | ||
|
|
7094ff3e62 | ||
|
|
c6ab2647c1 | ||
|
|
b62d1429c0 | ||
|
|
23a0bc33fa | ||
|
|
93340a48b1 | ||
|
|
0379a8ee02 | ||
|
|
6cce02650b | ||
|
|
6e79fe4cd5 | ||
|
|
f66d8c7a34 | ||
|
|
826e532605 | ||
|
|
fc35f2fbcc | ||
|
|
216239bfce | ||
|
|
739eb56006 | ||
|
|
468405d4ea | ||
|
|
31af83ba02 | ||
|
|
45fddb9459 | ||
|
|
05dcd3bcca | ||
|
|
89a898410f | ||
|
|
4a0456cbce | ||
|
|
a525d8a2b0 | ||
|
|
d7fcf62e12 | ||
|
|
4ac86b9acb | ||
|
|
00c9ecde52 | ||
|
|
ae49908a42 | ||
|
|
37cf42c524 | ||
|
|
c43a26870e | ||
|
|
5a6574921a | ||
|
|
7fa86c29e7 | ||
|
|
a3f20796d7 | ||
|
|
835d73789e | ||
|
|
f0ac170d2f | ||
|
|
b154e3d03b | ||
|
|
633e5a53dc | ||
|
|
3f98cf8625 | ||
|
|
36fe9a51c4 | ||
|
|
74ab3aa522 | ||
|
|
cfdf175f9d | ||
|
|
c07a863018 | ||
|
|
243941db51 | ||
|
|
d6c1316178 | ||
|
|
f35048b89f | ||
|
|
24d92b2ac0 | ||
|
|
c702862bc4 | ||
|
|
400f6be9e3 | ||
|
|
5fc6951060 | ||
|
|
5ba38d5cd6 | ||
|
|
3e8132646d | ||
|
|
5aef0bbf3c | ||
|
|
bb3dadc0c7 | ||
|
|
7bb176e7c7 | ||
|
|
8c16e4ed58 | ||
|
|
e9315d9be7 | ||
|
|
46738cec6b | ||
|
|
e576bb15f1 | ||
|
|
26d18d26e9 | ||
|
|
8bac416ab5 | ||
|
|
e6aa840633 | ||
|
|
4b976d3b5f | ||
|
|
e3315c8f81 | ||
|
|
6bcbd006f1 | ||
|
|
1b8f9b2abc | ||
|
|
f3ad4414b6 | ||
|
|
8930c39fbe | ||
|
|
7c2908569e | ||
|
|
5598464081 | ||
|
|
40d90424a3 | ||
|
|
bb33e8077a | ||
|
|
2859d0f4ff | ||
|
|
cab1bceae8 | ||
|
|
48c389a27c | ||
|
|
4199eb6ac5 | ||
|
|
02c6430ed2 | ||
|
|
05ee0e86b1 | ||
|
|
1ed6274f8b | ||
|
|
7857a2ea61 | ||
|
|
c01effbf4c | ||
|
|
a2ff3ecd9a | ||
|
|
a5ecc2383c | ||
|
|
e73f76024e | ||
|
|
cd7a81d828 | ||
|
|
81264fa63e | ||
|
|
5e3dbc18c3 | ||
|
|
e43c20c1e6 | ||
|
|
f65039d748 | ||
|
|
d0e70ddddc | ||
|
|
3de96a8d9d | ||
|
|
b3fd673f33 | ||
|
|
fb12f498a7 | ||
|
|
fd54116d55 | ||
|
|
83ecdc9a88 | ||
|
|
be1e868fc4 | ||
|
|
a1e6e646f9 | ||
|
|
29df56c8b4 | ||
|
|
064b3efa13 | ||
|
|
5cc5628956 | ||
|
|
8afa3d1e30 | ||
|
|
8f0535dc5c | ||
|
|
a412f2f18f | ||
|
|
8f4d233965 | ||
|
|
cc84b96024 | ||
|
|
4ff81a0b63 | ||
|
|
9b40b7eb39 | ||
|
|
5c3464a303 | ||
|
|
71bea2eea4 | ||
|
|
ed0a549027 | ||
|
|
267f30849f | ||
|
|
b8ed3d8954 | ||
|
|
b634acec67 | ||
|
|
3035ea8d8b | ||
|
|
abe7046d4f | ||
|
|
b60b922fef | ||
|
|
85d3345043 | ||
|
|
60ee889794 | ||
|
|
9ec21d815d | ||
|
|
ac32995e01 | ||
|
|
8c99268bb8 | ||
|
|
237b2670f6 | ||
|
|
d50f617a85 | ||
|
|
1cfc7e6638 | ||
|
|
8b9bb68ac5 | ||
|
|
71b631fbe4 | ||
|
|
7274ff5e2d | ||
|
|
770ecdfa99 | ||
|
|
4023ae73ba | ||
|
|
7f87d22249 | ||
|
|
6225f8940e | ||
|
|
6a5183cc0c | ||
|
|
037f5ecfa5 | ||
|
|
63657151ea | ||
|
|
c4e1a4c485 | ||
|
|
4222503961 | ||
|
|
af779a1880 | ||
|
|
84a5bed66d | ||
|
|
fa9ffccb19 | ||
|
|
4a333ff988 | ||
|
|
ce8c4ebd91 | ||
|
|
bc157fcde7 | ||
|
|
9a0476aa46 | ||
|
|
c7b5b40eda | ||
|
|
74c61262b7 | ||
|
|
2290e2eb6d | ||
|
|
0d33072a7e | ||
|
|
19d9a29459 | ||
|
|
d2e5eecf22 | ||
|
|
b9ca6bf4a9 | ||
|
|
aa68acea3d | ||
|
|
bf7b18f158 | ||
|
|
3675dc5b9d | ||
|
|
1dfdcb0413 | ||
|
|
32cda58064 | ||
|
|
4081f96d68 | ||
|
|
16350b4dec | ||
|
|
d8fe1d8b81 | ||
|
|
5525fff1fe | ||
|
|
328f5656bf | ||
|
|
9ec080a107 | ||
|
|
1bae755f1c | ||
|
|
9deccb465e | ||
|
|
2ff635d0cd | ||
|
|
9f4b297d23 | ||
|
|
aea308a5a2 | ||
|
|
5c4948bd6d | ||
|
|
d78d9167a0 | ||
|
|
9c9fcc3dd8 | ||
|
|
4271fbb85f | ||
|
|
ba4603e850 | ||
|
|
f2bc31c97b | ||
|
|
62a1726ef1 | ||
|
|
f19941f6da | ||
|
|
3917b84b56 | ||
|
|
3ecf97ac82 |
No files matched your search
+9
-3
@@ -41,8 +41,14 @@ __old__
|
||||
|
||||
*-venv
|
||||
|
||||
mage8
|
||||
adists
|
||||
mage8/
|
||||
adists/
|
||||
rubarbe/
|
||||
|
||||
linux/
|
||||
windows/
|
||||
|
||||
install/
|
||||
|
||||
### END CUSTOM ###
|
||||
|
||||
@@ -624,7 +630,7 @@ TSWLatexianTemp*
|
||||
|
||||
# auto folder when using emacs and auctex
|
||||
./auto/*
|
||||
*.el
|
||||
# *.el
|
||||
|
||||
# expex forward references with \gathertags
|
||||
*-tags.tex
|
||||
|
||||
+52
-19
@@ -1,5 +1,5 @@
|
||||
# .gitlab-ci.yml -- Pamhyr gitlab-ci
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -24,13 +24,20 @@ stages:
|
||||
- antivirus
|
||||
- release
|
||||
|
||||
#########################
|
||||
# PYINSTALLER VARIABLES #
|
||||
#########################
|
||||
|
||||
variables:
|
||||
HIDDEN_IMPORTS: "--hiddenimport rasterio --collect-submodules rasterio"
|
||||
|
||||
#############
|
||||
# DOWNLOADS #
|
||||
#############
|
||||
|
||||
variables:
|
||||
MAGE_8_VERSION: "v8.3.6"
|
||||
ADISTS_VERSION: "v1.0.2"
|
||||
MAGE_8_VERSION: "permalink/latest"
|
||||
ADISTS_VERSION: "permalink/latest"
|
||||
|
||||
dl-mage8-doc:
|
||||
stage: downloads
|
||||
@@ -41,7 +48,7 @@ dl-mage8-doc:
|
||||
script:
|
||||
- mkdir -p doc
|
||||
- cd doc
|
||||
- curl -L -o mage8.pdf https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/$MAGE_8_VERSION/downloads/documents/Mage-8_Documentation.pdf
|
||||
- curl -L -o mage8.pdf https://forge.inrae.fr/river-hydraulics/mage/-/releases/$MAGE_8_VERSION/downloads/documents/Mage-8_Documentation.pdf
|
||||
artifacts:
|
||||
paths:
|
||||
- doc/mage8.pdf
|
||||
@@ -53,17 +60,13 @@ dl-mage8-linux:
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
script:
|
||||
- curl -L -o mage8.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/$MAGE_8_VERSION/downloads/packages/mage_linux.tgz
|
||||
- curl -L -o mage8.tgz https://forge.inrae.fr/river-hydraulics/mage/-/releases/$MAGE_8_VERSION/downloads/packages/mage_linux.tgz
|
||||
- mkdir -p mage8-linux
|
||||
- cd mage8-linux
|
||||
- tar xvf ../mage8.tgz
|
||||
artifacts:
|
||||
paths:
|
||||
- mage8-linux/mage
|
||||
- mage8-linux/mage_as7
|
||||
- mage8-linux/mage_extraire
|
||||
- mage8-linux/mailleurTT
|
||||
- mage8-linux/libbief.so
|
||||
|
||||
dl-mage8-windows:
|
||||
stage: downloads
|
||||
@@ -72,17 +75,13 @@ dl-mage8-windows:
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
script:
|
||||
- curl -L -o mage8.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/$MAGE_8_VERSION/downloads/packages/mage_windows.tgz
|
||||
- curl -L -o mage8.tgz https://forge.inrae.fr/river-hydraulics/mage/-/releases/$MAGE_8_VERSION/downloads/packages/mage_windows.tgz
|
||||
- mkdir -p mage8-windows
|
||||
- cd mage8-windows
|
||||
- tar xvf ../mage8.tgz
|
||||
artifacts:
|
||||
paths:
|
||||
- mage8-windows/mage.exe
|
||||
- mage8-windows/mage_as7.exe
|
||||
- mage8-windows/mage_extraire.exe
|
||||
- mage8-windows/mailleurTT.exe
|
||||
- mage8-windows/libbief.dll
|
||||
|
||||
dl-adists-linux:
|
||||
stage: downloads
|
||||
@@ -93,7 +92,7 @@ dl-adists-linux:
|
||||
script:
|
||||
- mkdir -p adists-linux
|
||||
- cd adists-linux
|
||||
- curl -L -o adists https://gitlab.irstea.fr/jean-baptiste.faure/adists/-/releases/$ADISTS_VERSION/downloads/executables/adists
|
||||
- curl -L -o adists https://forge.inrae.fr/river-hydraulics/adists/-/releases/$ADISTS_VERSION/downloads/executables/adists
|
||||
artifacts:
|
||||
paths:
|
||||
- adists-linux/adists
|
||||
@@ -107,7 +106,7 @@ dl-adists-windows:
|
||||
script:
|
||||
- mkdir -p adists-windows
|
||||
- cd adists-windows
|
||||
- curl -L -o adists.exe https://gitlab.irstea.fr/jean-baptiste.faure/adists/-/releases/$ADISTS_VERSION/downloads/executables/adists_win64.exe
|
||||
- curl -L -o adists.exe https://forge.inrae.fr/river-hydraulics/adists/-/releases/$ADISTS_VERSION/downloads/executables/adists_win64.exe
|
||||
artifacts:
|
||||
paths:
|
||||
- adists-windows/adists.exe
|
||||
@@ -300,7 +299,7 @@ build-linux:
|
||||
- pip3 install -r ../requirements.txt
|
||||
- pip3 install -U -r ../requirements.txt
|
||||
# Run Pyinstaller
|
||||
- pyinstaller -y --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
|
||||
- pyinstaller -y $HIDDEN_IMPORTS --paths linux-venv/lib/python3.8/site-packages ../src/pamhyr.py
|
||||
# Create directory
|
||||
- mkdir -p pamhyr
|
||||
- mkdir -p pamhyr/_internal
|
||||
@@ -403,7 +402,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 --hide-console hide-early -y ..\src\pamhyr.py
|
||||
- pyinstaller -i ../src/View/ui/ressources/icon.ico $HIDDEN_IMPORTS --hide-console hide-early -y ..\src\pamhyr.py
|
||||
# Create directory
|
||||
- mkdir pamhyr
|
||||
- dir
|
||||
@@ -582,6 +581,40 @@ 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:
|
||||
@@ -601,7 +634,7 @@ tag-release:
|
||||
- job: pkg-hash
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH == 'master' && $CI_COMMIT_TAG
|
||||
artifacts:
|
||||
paths:
|
||||
- linux/pamhyr-gnulinux.tar.xz
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Sylvain COULIBALY, INRAE, 2020 - 2022
|
||||
Théophile TERRAZ, INRAE, 2022 - 2024
|
||||
Pierre-Antoine ROUBY, INRAE, 2023 - 2024
|
||||
Pierre-Antoine ROUBY, INRAE, 2023 - 2025
|
||||
@@ -18,6 +18,7 @@ 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)
|
||||
|
||||
+20
-15
@@ -1,18 +1,23 @@
|
||||
#! /bin/sh
|
||||
|
||||
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
|
||||
FILES="documentation.org scenario.org"
|
||||
|
||||
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
|
||||
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
|
||||
@@ -0,0 +1,661 @@
|
||||
# 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
|
||||
Whitespace-only changes.
@@ -0,0 +1,525 @@
|
||||
# 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.
|
After Width: | Height: | Size: 196 KiB |
Binary file not shown.
@@ -12,3 +12,5 @@ shapely>=2.0.1
|
||||
lxml>=4.9.3
|
||||
platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
rasterio==1.3.11
|
||||
#fortranformat==2.0.3
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
guix shell --manifest=manifest.scm -- python3 ./src/pamhyr.py $@
|
||||
@@ -0,0 +1,23 @@
|
||||
;; 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"))
|
||||
@@ -12,3 +12,5 @@ shapely>=2.0.1
|
||||
lxml>=4.9.3
|
||||
platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
rasterio==1.3.11
|
||||
#fortranformat==2.0.3
|
||||
@@ -1,5 +1,5 @@
|
||||
# Adists.py -- Pamhyr study checkers
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Checker.py -- Pamhyr abstract checker class
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Mage.py -- Pamhyr MAGE checkers
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Study.py -- Pamhyr study checkers
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# AMeshingTool.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
# Internal.py -- Pamhyr
|
||||
# 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 numpy import sign, interp
|
||||
from copy import deepcopy
|
||||
|
||||
from tools import logger_color_red, logger_color_reset, logger_exception
|
||||
from Meshing.AMeshingTool import AMeshingTool
|
||||
|
||||
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class InternalMeshing(AMeshingTool):
|
||||
def __init__(self):
|
||||
super(InternalMeshing, self).__init__()
|
||||
|
||||
###########
|
||||
# Meshing #
|
||||
###########
|
||||
|
||||
def meshing(self, reach,
|
||||
step: float = 50,
|
||||
limites=[-1, -1],
|
||||
linear: bool = True):
|
||||
if reach is None or len(reach.profiles) == 0:
|
||||
return reach
|
||||
|
||||
if limites[0] > limites[1]:
|
||||
lim = limites[1]
|
||||
limites[1] = limites[0]
|
||||
limites[0] = lim
|
||||
elif limites[0] == limites[1]:
|
||||
return reach
|
||||
|
||||
# we never modify original profiles, we work on copies
|
||||
m_profiles = self.st_to_m(reach, limites)
|
||||
new_profiles = self.interpolate_transversal_step(m_profiles,
|
||||
step)
|
||||
|
||||
for new_profiles2 in new_profiles:
|
||||
for p in new_profiles2:
|
||||
p.hard_purge_aligned_points()
|
||||
|
||||
return new_profiles
|
||||
|
||||
def st_to_m(self, reach, limites):
|
||||
|
||||
gl = reach.compute_guidelines()
|
||||
profiles = [reach.profile(i).copy()
|
||||
for i in range(limites[0], limites[1]+1)
|
||||
]
|
||||
|
||||
# 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]
|
||||
]
|
||||
|
||||
gl1 = [""] + guide_list
|
||||
gl2 = guide_list + [""]
|
||||
max_values = [0] * (len(guide_list) + 1)
|
||||
max_values_index = [0] * (len(guide_list) + 1)
|
||||
# we work between each couple of guidelines
|
||||
for j, p in enumerate(profiles):
|
||||
index1 = 0
|
||||
index2 = p.named_point_index(guide_list[0])
|
||||
if index2 - index1 > max_values[0]:
|
||||
max_values[0] = index2 - index1
|
||||
max_values_index[0] = j
|
||||
for i in range(len(guide_list) - 1):
|
||||
index1 = p.named_point_index(guide_list[i])
|
||||
index2 = p.named_point_index(guide_list[i+1])
|
||||
if index2 - index1 > max_values[i+1]:
|
||||
max_values[i+1] = index2 - index1
|
||||
max_values_index[i+1] = j
|
||||
index1 = p.named_point_index(guide_list[-1])
|
||||
index2 = len(p) - 1
|
||||
if index2 - index1 > max_values[-1]:
|
||||
max_values[-1] = index2 - index1
|
||||
max_values_index[-1] = j
|
||||
|
||||
for i in range(len(max_values)):
|
||||
for isect in range(max_values_index[i], len(profiles)-1):
|
||||
self.compl_sect(profiles[isect],
|
||||
profiles[isect+1],
|
||||
gl1[i], gl2[i])
|
||||
for isect in reversed(range(0, max_values_index[i])):
|
||||
self.compl_sect(profiles[isect+1],
|
||||
profiles[isect],
|
||||
gl1[i], gl2[i])
|
||||
return profiles
|
||||
|
||||
def interpolate_transversal_step(self,
|
||||
profiles,
|
||||
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)
|
||||
if np[-1] < 0:
|
||||
np[-1] = 0
|
||||
|
||||
new_profiles = []
|
||||
for i in range(len(profiles)-1):
|
||||
new_profiles2 = []
|
||||
d = 1.0/float(np[i]+1)
|
||||
for j in range(np[i]):
|
||||
p = profiles[i].copy()
|
||||
# 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)
|
||||
p.name = f'interpol{p.rk}'
|
||||
new_profiles2.append(p)
|
||||
new_profiles.append(new_profiles2)
|
||||
return new_profiles
|
||||
|
||||
def compl_sect(self, sect1, sect2, tag1, tag2):
|
||||
# sect1: reference section
|
||||
# sect2: section to complete
|
||||
# start1 and 2: indices of the first point of the bed to complete
|
||||
# end = start + len
|
||||
# len1 and 2: number of intervals
|
||||
# len1: target len
|
||||
|
||||
if tag1 == '': # left end point
|
||||
start1 = 0
|
||||
start2 = 0
|
||||
else:
|
||||
start1 = sect1.named_point_index(tag1)
|
||||
start2 = sect2.named_point_index(tag1)
|
||||
|
||||
if tag2 == '': # right end point
|
||||
end1 = sect1.nb_points-1
|
||||
end2 = sect2.nb_points-1
|
||||
else:
|
||||
end1 = sect1.named_point_index(tag2)
|
||||
end2 = sect2.named_point_index(tag2)
|
||||
|
||||
len1 = end1 - start1
|
||||
len2 = end2 - start2
|
||||
|
||||
if len1 < len2:
|
||||
self.compl_sect(sect2, sect1, start2, start1,
|
||||
len2, len1, tag1, tag2)
|
||||
return
|
||||
elif len1 == len2:
|
||||
return
|
||||
|
||||
ltot1 = 0.0
|
||||
ltot2 = 0.0
|
||||
alpha = []
|
||||
beta = []
|
||||
# remove names
|
||||
sect2.points[start2].name = ''
|
||||
sect2.points[end2].name = ''
|
||||
for i in range(len1):
|
||||
ltot1 += sect1.point(start1+i).dist(sect1.point(start1+i+1))
|
||||
alpha.append(ltot1)
|
||||
alpha = list(map(lambda x: x/ltot1, alpha)) # target ratios
|
||||
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
|
||||
for i in range(len1-len2):
|
||||
p = sect2.point(start2).copy()
|
||||
sect2.insert_point(start2, p)
|
||||
if tag1 == '': # left end point
|
||||
sect2.point(start2).name = ''
|
||||
if tag2 == '': # left end point
|
||||
sect2.point(start2+1).name = ''
|
||||
elif ltot1 < 0.0001:
|
||||
sect2.add_npoints(len1-len2)
|
||||
else: # regular case
|
||||
# loop on the points to insert
|
||||
for k in range(len1-len2):
|
||||
trouve = False
|
||||
for j0 in range(len2):
|
||||
if trouve:
|
||||
break
|
||||
undeplus = True
|
||||
for j1 in range(j0, len2):
|
||||
if undeplus:
|
||||
if beta[j1] <= alpha[j1]:
|
||||
undeplus = False
|
||||
break
|
||||
if undeplus:
|
||||
if j0 == len2:
|
||||
trouve = True
|
||||
else:
|
||||
j1 = j0+1
|
||||
if beta[j0]-alpha[j0] > alpha[j1]-beta[j0]:
|
||||
trouve = True
|
||||
|
||||
if trouve:
|
||||
len2 += 1
|
||||
for i in reversed(range(j0, len2)):
|
||||
beta[i+1] = beta[i]
|
||||
if (beta[j0] == beta[j0-1]):
|
||||
# rien
|
||||
pass
|
||||
else:
|
||||
ratio = (alpha[j0] - beta[j0-1]) \
|
||||
/ (beta[j0] - beta[j0-1])
|
||||
if ratio < 0.0:
|
||||
print(f"ratio négatif {ratio}")
|
||||
# on double le point a gauche
|
||||
p = sect2.point(start2+j0-1).copy()
|
||||
sect2.insert_point(start2+j0-1, p)
|
||||
sect2.point(start2+j0-1).name = ''
|
||||
beta[j0] = beta[j0-1]
|
||||
else:
|
||||
p = sect2.point(start2+j0).copy()
|
||||
sect2.insert_point(start2+j0, p)
|
||||
sect2.point(start2+j0+1).name = ''
|
||||
sect2.point(start2+j0+1).x = \
|
||||
(1.0-ratio) * sect2.point(start2+j0).x + \
|
||||
ratio * sect2.point(start2+j0+2).x
|
||||
sect2.point(start2+j0+1).y = \
|
||||
(1.0-ratio) * sect2.point(start2+j0).y + \
|
||||
ratio * sect2.point(start2+j0+2).y
|
||||
sect2.point(start2+j0+1).z = \
|
||||
(1.0-ratio) * sect2.point(start2+j0).z + \
|
||||
ratio * sect2.point(start2+j0+2).z
|
||||
beta[j0] = (1.-ratio)*beta[j0-1] \
|
||||
+ ratio*beta[j0+1]
|
||||
|
||||
if len1 > len2:
|
||||
for i in range(len1 - len2):
|
||||
p = sect2.point(start2+len2).copy()
|
||||
sect2.insert_point(start2+len2, p)
|
||||
sect2.point(start2+len2).name = ''
|
||||
sect2.point(start2).name = tag1
|
||||
sect2.point(start2+len2).name = tag2
|
||||
sect2.modified()
|
||||
|
||||
def update_rk(self, reach, begin_rk, end_rk,
|
||||
step: float = 50,
|
||||
limites=[-1, -1],
|
||||
origin=0,
|
||||
directrices=['un', 'np'],
|
||||
lplan: bool = False,
|
||||
lm: int = 3,
|
||||
linear: bool = False,
|
||||
origin_value=0.0,
|
||||
orientation=0):
|
||||
if reach is None or len(reach.profiles) == 0:
|
||||
return []
|
||||
|
||||
nprof = reach.number_profiles
|
||||
if origin >= nprof or origin < 0:
|
||||
logger.warning(
|
||||
f"Origin outside reach"
|
||||
)
|
||||
return reach.get_rk()
|
||||
|
||||
# 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
|
||||
|
||||
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()))
|
||||
|
||||
if origin < nprof - 1:
|
||||
for i in range(origin+1, nprof):
|
||||
# 2D
|
||||
d1 = reach.profiles[i-1].named_point(
|
||||
directrices[0]
|
||||
).dist_2d(reach.profiles[i].named_point(directrices[0]))
|
||||
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)
|
||||
if origin > 0:
|
||||
for i in reversed(range(0, origin)):
|
||||
# 2D
|
||||
d1 = reach.profiles[i+1].named_point(
|
||||
directrices[0]
|
||||
).dist_2d(reach.profiles[i].named_point(directrices[0]))
|
||||
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)
|
||||
|
||||
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
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
# Mage.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import logging
|
||||
import tempfile
|
||||
from numpy import sign
|
||||
|
||||
from ctypes import (
|
||||
cdll,
|
||||
@@ -343,7 +344,9 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
)
|
||||
)
|
||||
)
|
||||
logger.info("Waiting ...")
|
||||
proc.waitForFinished()
|
||||
logger.info(f"Waiting ... Done (status : {proc.exitCode()})")
|
||||
|
||||
if proc.exitCode() != 0:
|
||||
logger.error(
|
||||
@@ -352,7 +355,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
f"{logger_color_reset()}"
|
||||
)
|
||||
outputs = proc.readAllStandardOutput()
|
||||
logger.debug(outputs)
|
||||
logger.warning(outputs)
|
||||
|
||||
errors = proc.readAllStandardError()
|
||||
logger.error(
|
||||
@@ -418,7 +421,9 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
)
|
||||
)
|
||||
)
|
||||
logger.debug("Waiting ...")
|
||||
proc.waitForFinished()
|
||||
logger.debug(f"Waiting ... Done (status : {proc.exitCode()})")
|
||||
|
||||
if proc.exitCode() != 0:
|
||||
logger.error(
|
||||
@@ -427,7 +432,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
f"{logger_color_reset()}"
|
||||
)
|
||||
outputs = proc.readAllStandardOutput()
|
||||
logger.debug(outputs)
|
||||
logger.warning(outputs)
|
||||
|
||||
errors = proc.readAllStandardError()
|
||||
logger.error(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# __init__.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# test_Model.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -21,7 +21,6 @@ import unittest
|
||||
import warnings
|
||||
import tempfile
|
||||
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Study import Study
|
||||
from Model.River import River
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# AddFile.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -22,31 +22,25 @@ 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):
|
||||
super(AddFile, self).__init__()
|
||||
|
||||
if id == -1:
|
||||
self.id = AddFile._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._status = status
|
||||
status=None, owner_scenario=-1):
|
||||
super(AddFile, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._enabled = enabled
|
||||
self._name = f"File {self.id}" if name == "" else name
|
||||
self._name = f"File #{self._pamhyr_id}" if name == "" else name
|
||||
self._path = path
|
||||
self._text = text
|
||||
|
||||
AddFile._id_cnt = max(id, AddFile._id_cnt+1)
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = None
|
||||
|
||||
@@ -71,7 +65,7 @@ class AddFile(SQLSubModel):
|
||||
elif key == "text":
|
||||
self._text = value
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -80,7 +74,7 @@ class AddFile(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def is_enabled(self):
|
||||
return self._enabled
|
||||
@@ -92,7 +86,7 @@ class AddFile(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
@@ -101,7 +95,7 @@ class AddFile(SQLSubModel):
|
||||
@path.setter
|
||||
def path(self, path):
|
||||
self._path = path
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
@@ -110,38 +104,80 @@ class AddFile(SQLSubModel):
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
self._text = text
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE additional_files(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE additional_files{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
enabled BOOLEAN NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
text TEXT NOT NULL
|
||||
text 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):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
release = int(release)
|
||||
|
||||
if major == minor == "0":
|
||||
if int(release) < 8:
|
||||
if 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 id, enabled, name, path, text " +
|
||||
"FROM additional_files"
|
||||
"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))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
@@ -149,30 +185,45 @@ 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']
|
||||
status=data['status'], owner_scenario=owner_scenario
|
||||
)
|
||||
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):
|
||||
sql = (
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"additional_files(id, enabled, name, path, text) " +
|
||||
"additional_files(pamhyr_id, enabled, deleted, " +
|
||||
"name, path, text, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._enabled}, " +
|
||||
f"{self._pamhyr_id}, " +
|
||||
f"{self._db_format(self._enabled)}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._path)}', " +
|
||||
f"'{self._db_format(self._text)}'" +
|
||||
f"'{self._db_format(self._text)}', " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
@@ -1,5 +1,5 @@
|
||||
# AddFileList.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.AdditionalFile.AddFile import AddFile
|
||||
|
||||
|
||||
@@ -38,7 +38,10 @@ class AddFileList(PamhyrModelList):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM additional_files")
|
||||
execute(
|
||||
"DELETE FROM additional_files " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for af in self._lst:
|
||||
ok &= af._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BoundaryCondition.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,24 +27,193 @@ from tools import (
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class BoundaryCondition(SQLSubModel):
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(BoundaryCondition, self).__init__()
|
||||
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._status = status
|
||||
self._types = types
|
||||
self._data = [data0, data1]
|
||||
|
||||
if id == -1:
|
||||
self.id = BoundaryCondition._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
@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]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._type = ""
|
||||
@@ -53,37 +222,66 @@ class BoundaryCondition(SQLSubModel):
|
||||
self._header = []
|
||||
self._types = [float, float]
|
||||
|
||||
BoundaryCondition._id_cnt = max(BoundaryCondition._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE boundary_condition(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
tab TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
FOREIGN KEY(node) REFERENCES river_node(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
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)
|
||||
)
|
||||
""")
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def _get_ctor_from_type(cls, t):
|
||||
@@ -110,83 +308,117 @@ 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 id, name, type, node " +
|
||||
"SELECT pamhyr_id, deleted, name, type, node, scenario " +
|
||||
"FROM boundary_condition " +
|
||||
f"WHERE tab = '{tab}'"
|
||||
f"WHERE tab = '{tab}' " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
|
||||
)
|
||||
|
||||
for row in table:
|
||||
t = row[2]
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
t = next(it)
|
||||
node = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
ctor = cls._get_ctor_from_type(t)
|
||||
|
||||
bc = ctor(
|
||||
id=row[0],
|
||||
name=row[1],
|
||||
status=data['status']
|
||||
id=pid, name=name,
|
||||
status=data['status'],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
bc.set_as_deleted()
|
||||
|
||||
bc.node = None
|
||||
if row[3] != -1:
|
||||
bc.node = next(filter(lambda n: n.id == row[3], data["nodes"]),
|
||||
None)
|
||||
if node != -1:
|
||||
bc.node = next(filter(lambda n: n.id == node, nodes), None)
|
||||
|
||||
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)
|
||||
data["bc"] = bc
|
||||
bc._data = Data._db_load(execute, data=data)
|
||||
|
||||
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(f"DELETE FROM boundary_condition WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM boundary_condition_data WHERE bc = {self.id}")
|
||||
execute(
|
||||
"DELETE FROM boundary_condition_data " +
|
||||
f"WHERE bc = {self._pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
node = -1
|
||||
if self._node is not None:
|
||||
node = self._node.id
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"boundary_condition(id, name, type, tab, node) " +
|
||||
"boundary_condition(" +
|
||||
"pamhyr_id, deleted, name, type, tab, node, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._type)}', '{tab}', {node}" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["bc"] = self
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data0 = self._db_format(str(d[0]))
|
||||
data1 = self._db_format(str(d[1]))
|
||||
data["ind"] = ind
|
||||
|
||||
d._db_save(execute, data)
|
||||
|
||||
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(self._data)
|
||||
return len(
|
||||
list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def compatibility(cls):
|
||||
@@ -211,14 +443,14 @@ class BoundaryCondition(SQLSubModel):
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"B{self.id + 1}"
|
||||
return f"B #{self.pamhyr_id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def bctype(self):
|
||||
@@ -231,7 +463,7 @@ class BoundaryCondition(SQLSubModel):
|
||||
@node.setter
|
||||
def node(self, node):
|
||||
self._node = node
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def has_node(self):
|
||||
return self._node is not None
|
||||
@@ -242,7 +474,12 @@ class BoundaryCondition(SQLSubModel):
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
return list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
|
||||
def get_type_column(self, column):
|
||||
if 0 <= column < 2:
|
||||
@@ -276,51 +513,79 @@ class BoundaryCondition(SQLSubModel):
|
||||
new_0 = self._types[0](data[0].replace(",", "."))
|
||||
new_1 = self._types[1](data[1].replace(",", "."))
|
||||
|
||||
return (new_0, new_1)
|
||||
return Data(
|
||||
new_0, new_1,
|
||||
status=self._status
|
||||
)
|
||||
|
||||
def add(self, index: int):
|
||||
value = (self._default_0, self._default_1)
|
||||
value = Data(
|
||||
self._default_0, self._default_1,
|
||||
status=self._status
|
||||
)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
if value in self._data:
|
||||
value.set_as_not_deleted()
|
||||
else:
|
||||
self._data.insert(index, value)
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self.modified()
|
||||
|
||||
def hard_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._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda e: e not in els,
|
||||
self.data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
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()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def index(self, bc):
|
||||
self._data.index(bc)
|
||||
|
||||
def get_i(self, index):
|
||||
return self.data[index]
|
||||
if len(self._data) == 0:
|
||||
return None
|
||||
|
||||
return self._data[index]
|
||||
|
||||
def get_range(self, _range):
|
||||
lst = []
|
||||
@@ -329,10 +594,10 @@ class BoundaryCondition(SQLSubModel):
|
||||
return lst
|
||||
|
||||
def _set_i_c_v(self, index, column, value):
|
||||
v = list(self._data[index])
|
||||
v = self._data[index]
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = tuple(v)
|
||||
self._status.modified()
|
||||
self._data[index] = v
|
||||
self.modified()
|
||||
|
||||
def set_i_0(self, index: int, value):
|
||||
self._set_i_c_v(index, 0, value)
|
||||
@@ -364,14 +629,14 @@ class BoundaryCondition(SQLSubModel):
|
||||
next = index - 1
|
||||
d = self._data
|
||||
d[index], d[next] = d[next], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
d = self._data
|
||||
d[index], d[prev] = d[prev], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def reach(self, river):
|
||||
r = []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BoundaryConditionList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelListWithTab
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelListWithTab
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
|
||||
@@ -41,9 +41,6 @@ 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(
|
||||
@@ -53,10 +50,10 @@ class BoundaryConditionList(PamhyrModelListWithTab):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM boundary_condition")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
execute(
|
||||
"DELETE FROM boundary_condition " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for tab in self._tabs:
|
||||
data["tab"] = tab
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BoundaryConditionTypes.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,8 +22,12 @@ from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition
|
||||
|
||||
|
||||
class NotDefined(BoundaryCondition):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(NotDefined, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "ND"
|
||||
self._header = ["x", "y"]
|
||||
@@ -34,9 +38,12 @@ class NotDefined(BoundaryCondition):
|
||||
|
||||
|
||||
class PonctualContribution(BoundaryCondition):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None, owner_scenario=-1):
|
||||
super(PonctualContribution, self).__init__(
|
||||
id=id, name=name, status=status)
|
||||
id=id, name=name, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "PC"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -48,8 +55,12 @@ class PonctualContribution(BoundaryCondition):
|
||||
|
||||
|
||||
class TimeOverZ(BoundaryCondition):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(TimeOverZ, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "TZ"
|
||||
self._header = ["time", "z"]
|
||||
@@ -61,9 +72,12 @@ class TimeOverZ(BoundaryCondition):
|
||||
|
||||
|
||||
class TimeOverDischarge(BoundaryCondition):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None, owner_scenario=-1):
|
||||
super(TimeOverDischarge, self).__init__(
|
||||
id=id, name=name, status=status)
|
||||
id=id, name=name, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "TD"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -75,8 +89,12 @@ class TimeOverDischarge(BoundaryCondition):
|
||||
|
||||
|
||||
class ZOverDischarge(BoundaryCondition):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(ZOverDischarge, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "ZD"
|
||||
self._header = ["z", "discharge"]
|
||||
@@ -92,8 +110,12 @@ class ZOverDischarge(BoundaryCondition):
|
||||
|
||||
|
||||
class Solid(BoundaryCondition):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(Solid, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self.d50 = 0.002
|
||||
self.sigma = 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BoundaryConditionsAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,25 +27,193 @@ from tools import (
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class BoundaryConditionAdisTS(SQLSubModel):
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
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]
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
pollutant: int = -1, status=None):
|
||||
super(BoundaryConditionAdisTS, self).__init__()
|
||||
pollutant: int = -1, status=None,
|
||||
owner_scenario=-1):
|
||||
super(BoundaryConditionAdisTS, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = BoundaryConditionAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._type = ""
|
||||
self._node = None
|
||||
self._pollutant = pollutant
|
||||
@@ -53,117 +221,210 @@ class BoundaryConditionAdisTS(SQLSubModel):
|
||||
self._header = []
|
||||
self._types = [self.time_convert, float]
|
||||
|
||||
BoundaryConditionAdisTS._id_cnt = max(
|
||||
BoundaryConditionAdisTS._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE boundary_condition_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
pollutant INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
|
||||
FOREIGN KEY(node) REFERENCES river_node(id)
|
||||
{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)
|
||||
)
|
||||
""")
|
||||
|
||||
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)
|
||||
)
|
||||
""")
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
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_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 id, pollutant, type, node " +
|
||||
"FROM boundary_condition_adists"
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
bc = cls(
|
||||
id=row[0],
|
||||
pollutant=row[1],
|
||||
status=data['status']
|
||||
)
|
||||
it = iter(row)
|
||||
|
||||
bc.type = row[2]
|
||||
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
|
||||
)
|
||||
if deleted:
|
||||
bc.set_as_deleted()
|
||||
|
||||
bc.type = bc_type
|
||||
|
||||
bc.node = None
|
||||
if row[3] != -1:
|
||||
tmp = next(filter(
|
||||
lambda n: n.id == row[3], data["nodes"]),
|
||||
None)
|
||||
tmp = next(
|
||||
filter(
|
||||
lambda n: n.id == node, nodes
|
||||
),
|
||||
None
|
||||
)
|
||||
if tmp is not None:
|
||||
bc.node = tmp.id
|
||||
else:
|
||||
bc.node = -1
|
||||
|
||||
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))
|
||||
data["bca"] = bc
|
||||
bc._data = Data._db_load(execute, data=data)
|
||||
|
||||
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(f"DELETE FROM boundary_condition_adists WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM boundary_condition_data_adists" +
|
||||
f" WHERE bc = {self.id}")
|
||||
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} "
|
||||
)
|
||||
|
||||
node = -1
|
||||
if self._node is not None:
|
||||
node = self._node
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"boundary_condition_adists(id, pollutant, type, node) " +
|
||||
"boundary_condition_adists(" +
|
||||
"pamhyr_id, deleted, pollutant, type, " +
|
||||
"node, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._pollutant}, " +
|
||||
f"'{self._db_format(self._type)}', {node}" +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"{self._pollutant}, " +
|
||||
f"'{self._db_format(self._type)}', {node}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["bca"] = self
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data0 = self._db_format(str(d[0]))
|
||||
data1 = self._db_format(str(d[1]))
|
||||
data["ind"] = ind
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"boundary_condition_data_adists(data0, data1, bc) " +
|
||||
f"VALUES ('{data0}', {data1}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
d._db_save(execute, data)
|
||||
|
||||
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)
|
||||
|
||||
@@ -222,3 +483,82 @@ class BoundaryConditionAdisTS(SQLSubModel):
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
|
||||
@property
|
||||
def _default_0(self):
|
||||
return self._types[0](0)
|
||||
|
||||
@property
|
||||
def _default_1(self):
|
||||
return self._types[1](0.0)
|
||||
|
||||
def new_from_data(self, header, data):
|
||||
new_0 = self._default_0
|
||||
new_1 = self._default_1
|
||||
|
||||
if len(header) != 0:
|
||||
for i in [0, 1]:
|
||||
for j in range(len(header)):
|
||||
if self._header[i] == header[j]:
|
||||
if i == 0:
|
||||
new_0 = self._types[i](data[j].replace(",", "."))
|
||||
else:
|
||||
new_1 = self._types[i](data[j].replace(",", "."))
|
||||
else:
|
||||
new_0 = self._types[0](data[0].replace(",", "."))
|
||||
new_1 = self._types[1](data[1].replace(",", "."))
|
||||
|
||||
return (new_0, new_1)
|
||||
|
||||
def add(self, index: int):
|
||||
value = (self._default_0, self._default_1)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data = list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
filter(
|
||||
lambda e: e[0] not in indexes,
|
||||
enumerate(self.data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def index(self, bc):
|
||||
self._data.index(bc)
|
||||
|
||||
def get_i(self, index):
|
||||
return self.data[index]
|
||||
|
||||
def get_range(self, _range):
|
||||
lst = []
|
||||
for r in _range:
|
||||
lst.append(r)
|
||||
return lst
|
||||
|
||||
def _set_i_c_v(self, index, column, value):
|
||||
v = list(self._data[index])
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = tuple(v)
|
||||
self._status.modified()
|
||||
|
||||
def set_i_0(self, index: int, value):
|
||||
self._set_i_c_v(index, 0, value)
|
||||
|
||||
def set_i_1(self, index: int, value):
|
||||
self._set_i_c_v(index, 1, value)
|
||||
@@ -1,5 +1,5 @@
|
||||
# BoundaryConditionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS \
|
||||
@@ -45,14 +45,20 @@ class BoundaryConditionsAdisTSList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM boundary_condition_adists")
|
||||
execute("DELETE FROM boundary_condition_data_adists")
|
||||
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}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for bc in self._lst:
|
||||
bc._db_save(execute, data=data)
|
||||
for bca in self._lst:
|
||||
bca._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# D90AdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
|
||||
@@ -33,107 +34,162 @@ class D90AdisTS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
D90AdisTSSpec,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
status=None):
|
||||
super(D90AdisTS, self).__init__()
|
||||
status=None, owner_scenario=-1):
|
||||
super(D90AdisTS, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
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 = []
|
||||
|
||||
D90AdisTS._id_cnt = max(
|
||||
D90AdisTS._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@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
|
||||
)
|
||||
""")
|
||||
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()}
|
||||
)
|
||||
""")
|
||||
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
created = False
|
||||
|
||||
if major == "0" and int(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 id, name, d90, enabled " +
|
||||
"FROM d90_adists"
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
d90_id = row[0]
|
||||
name = row[1]
|
||||
d90 = row[2]
|
||||
enabled = (row[3] == 1)
|
||||
it = iter(row)
|
||||
|
||||
D90 = cls(
|
||||
id=d90_id,
|
||||
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,
|
||||
name=name,
|
||||
status=data['status']
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
d90.set_as_deleted()
|
||||
|
||||
D90.d90 = d90
|
||||
D90.enabled = enabled
|
||||
d90.d90 = value_d90
|
||||
d90.enabled = enabled
|
||||
|
||||
data['d90_default_id'] = d90_id
|
||||
D90._data = D90AdisTSSpec._db_load(execute, data)
|
||||
data['d90_default_id'] = pid
|
||||
d90._data = D90AdisTSSpec._db_load(execute, data)
|
||||
|
||||
new.append(D90)
|
||||
loaded.add(pid)
|
||||
new.append(d90)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(f"DELETE FROM d90_adists WHERE id = {self.id}")
|
||||
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} "
|
||||
)
|
||||
|
||||
d90 = -1.
|
||||
if self.d90 is not None:
|
||||
d90 = self.d90
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"d90_adists(" +
|
||||
"id, name, d90, enabled" +
|
||||
"pamhyr_id, deleted, " +
|
||||
"name, d90, enabled, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{d90}, {self._enabled}" +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{d90}, {self._enabled}, {self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
data['d90_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM d90_spec " +
|
||||
f"WHERE d90_default = {self.id}"
|
||||
"DELETE FROM d90_adists_spec " +
|
||||
f"WHERE d90_default = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id} "
|
||||
)
|
||||
|
||||
for d90_spec in self._data:
|
||||
@@ -141,6 +197,16 @@ 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)
|
||||
|
||||
@@ -151,7 +217,7 @@ class D90AdisTS(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def d90(self):
|
||||
@@ -160,7 +226,7 @@ class D90AdisTS(SQLSubModel):
|
||||
@d90.setter
|
||||
def d90(self, d90):
|
||||
self._d90 = d90
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -169,28 +235,45 @@ class D90AdisTS(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = D90AdisTSSpec(status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return n
|
||||
|
||||
def delete(self, data):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda x: x not in data,
|
||||
self._data
|
||||
list(
|
||||
map(
|
||||
lambda x: x.set_as_deleted(),
|
||||
data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
for ind in indexes:
|
||||
del self._data[ind]
|
||||
self._status.modified()
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self._data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
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()
|
||||
@@ -1,5 +1,5 @@
|
||||
# D90AdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.D90AdisTS.D90AdisTS import D90AdisTS
|
||||
|
||||
|
||||
@@ -42,7 +42,10 @@ class D90AdisTSList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM d90_adists")
|
||||
execute(
|
||||
"DELETE FROM d90_adists " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# D90AdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,25 +22,21 @@ 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):
|
||||
super(D90AdisTSSpec, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = D90AdisTSSpec._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
status=None, owner_scenario=-1):
|
||||
super(D90AdisTSSpec, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name_section = name
|
||||
self._reach = None
|
||||
@@ -49,62 +45,129 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
self._d90 = None
|
||||
self._enabled = True
|
||||
|
||||
D90AdisTSSpec._id_cnt = max(D90AdisTSSpec._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
)
|
||||
""")
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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 id, d90_default, name, reach, start_rk, end_rk, " +
|
||||
"d90, enabled " +
|
||||
"FROM d90_spec " +
|
||||
f"WHERE d90_default = {data['d90_default_id']} "
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[2]
|
||||
reach = row[3]
|
||||
start_rk = row[4]
|
||||
end_rk = row[5]
|
||||
d90 = row[6]
|
||||
enabled = (row[7] == 1)
|
||||
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)
|
||||
|
||||
new_spec = cls(
|
||||
id=id,
|
||||
id=pid,
|
||||
name=name,
|
||||
status=data['status']
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
new_spec.is_deleted()
|
||||
|
||||
new_spec.reach = reach
|
||||
new_spec.start_rk = start_rk
|
||||
@@ -112,29 +175,36 @@ 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']
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"d90_spec(id, d90_default, name, reach, " +
|
||||
"start_rk, end_rk, d90, enabled) " +
|
||||
"d90_adists_spec(pamhyr_id, deleted, " +
|
||||
"d90_default, name, reach, " +
|
||||
"start_rk, end_rk, d90, enabled, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}" +
|
||||
f"{d90_default}, " +
|
||||
f"'{self._db_format(self._name_section)}', " +
|
||||
f"{self._reach}, " +
|
||||
f"{self._start_rk}, " +
|
||||
f"{self._end_rk}, " +
|
||||
f"{self._d90}, " +
|
||||
f"{self._enabled}" +
|
||||
f"{self._start_rk}, {self._end_rk}, " +
|
||||
f"{self._d90}, {self._enabled}" +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@@ -145,7 +215,7 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
@@ -154,7 +224,7 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
@@ -163,7 +233,7 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
@@ -172,7 +242,7 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def d90(self):
|
||||
@@ -181,7 +251,7 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@d90.setter
|
||||
def d90(self, d90):
|
||||
self._d90 = d90
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -190,4 +260,4 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# DIFAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
|
||||
@@ -33,19 +34,16 @@ class DIFAdisTS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
DIFAdisTSSpec,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
status=None):
|
||||
super(DIFAdisTS, self).__init__()
|
||||
status=None, owner_scenario=-1):
|
||||
super(DIFAdisTS, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = DIFAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._method = None
|
||||
self._dif = None
|
||||
@@ -55,76 +53,129 @@ class DIFAdisTS(SQLSubModel):
|
||||
self._types = ["iwasa", "fisher", "elder", "constante", "generique"]
|
||||
self._data = []
|
||||
|
||||
DIFAdisTS._id_cnt = max(
|
||||
DIFAdisTS._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@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
|
||||
)
|
||||
""")
|
||||
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()}
|
||||
)
|
||||
""")
|
||||
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
created = False
|
||||
|
||||
if major == "0" and int(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 id, name, method, dif, b, c, enabled " +
|
||||
"FROM dif_adists"
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
dif_id = row[0]
|
||||
name = row[1]
|
||||
method = row[2]
|
||||
dif = row[3]
|
||||
b = row[4]
|
||||
c = row[5]
|
||||
enabled = (row[6] == 1)
|
||||
it = iter(row)
|
||||
|
||||
DIF = cls(
|
||||
id=dif_id,
|
||||
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,
|
||||
name=name,
|
||||
status=data['status']
|
||||
status=status,
|
||||
owner_scenario=owner_scenario,
|
||||
)
|
||||
|
||||
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'] = dif_id
|
||||
DIF._data = DIFAdisTSSpec._db_load(execute, data)
|
||||
data['dif_default_id'] = pid
|
||||
dif._data = DIFAdisTSSpec._db_load(execute, data)
|
||||
|
||||
new.append(DIF)
|
||||
loaded.add(pid)
|
||||
new.append(dif)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(f"DELETE FROM dif_adists WHERE id = {self.id}")
|
||||
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} "
|
||||
)
|
||||
|
||||
method = ""
|
||||
if self.method is not None:
|
||||
@@ -145,12 +196,13 @@ class DIFAdisTS(SQLSubModel):
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"dif_adists(" +
|
||||
"id, name, method, dif, b, c, enabled" +
|
||||
"pamhyr_id, name, method, dif, b, c, enabled, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._method)}', " +
|
||||
f"{dif}, {b}, {c}, {self._enabled}" +
|
||||
f"{dif.id}, {b}, {c}, {self._enabled}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
@@ -158,8 +210,9 @@ class DIFAdisTS(SQLSubModel):
|
||||
|
||||
data['dif_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM dif_spec " +
|
||||
f"WHERE dif_default = {self.id}"
|
||||
"DELETE FROM dif_adists_spec " +
|
||||
f"WHERE dif_default = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id} "
|
||||
)
|
||||
|
||||
for dif_spec in self._data:
|
||||
@@ -167,6 +220,16 @@ 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)
|
||||
|
||||
@@ -177,7 +240,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
@@ -186,7 +249,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@method.setter
|
||||
def method(self, method):
|
||||
self._method = method
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
@@ -199,7 +262,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@dif.setter
|
||||
def dif(self, dif):
|
||||
self._dif = dif
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
@@ -208,7 +271,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@b.setter
|
||||
def b(self, b):
|
||||
self._b = b
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
@@ -217,7 +280,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@c.setter
|
||||
def c(self, c):
|
||||
self._c = c
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -226,28 +289,45 @@ class DIFAdisTS(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = DIFAdisTSSpec(status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return n
|
||||
|
||||
def delete(self, data):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda x: x not in data,
|
||||
self._data
|
||||
list(
|
||||
map(
|
||||
lambda x: x.set_as_deleted(),
|
||||
data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
for ind in indexes:
|
||||
del self._data[ind]
|
||||
self._status.modified()
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self._data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
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()
|
||||
@@ -1,5 +1,5 @@
|
||||
# DIFAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
|
||||
|
||||
|
||||
@@ -42,7 +42,10 @@ class DIFAdisTSList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM dif_adists")
|
||||
execute(
|
||||
"DELETE FROM dif_adists " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# DIFAdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,6 +22,7 @@ from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -54,63 +55,137 @@ class DIFAdisTSSpec(SQLSubModel):
|
||||
DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
)
|
||||
""")
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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 id, dif_default, method, reach, start_rk, end_rk, " +
|
||||
"dif, b, c, enabled " +
|
||||
"FROM dif_spec " +
|
||||
f"WHERE dif_default = {data['dif_default_id']} "
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
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)
|
||||
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)
|
||||
|
||||
new_spec = cls(
|
||||
id=id,
|
||||
method=method,
|
||||
status=data['status']
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
new_spec.set_as_deleted()
|
||||
|
||||
new_spec.reach = reach
|
||||
new_spec.start_rk = start_rk
|
||||
@@ -120,31 +195,37 @@ 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']
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"dif_spec(id, dif_default, method, reach, " +
|
||||
"start_rk, end_rk, dif, b, c, enabled) " +
|
||||
"dif_adists_spec(pamhyr_id, deleted, " +
|
||||
"dif_default, method, reach, " +
|
||||
"start_rk, end_rk, dif, b, c, enabled, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}" +
|
||||
f"{dif_default}, " +
|
||||
f"'{self._db_format(self._method)}', " +
|
||||
f"{self._reach}, " +
|
||||
f"{self._start_rk}, " +
|
||||
f"{self._end_rk}, " +
|
||||
f"{self._dif}, " +
|
||||
f"{self._b}, " +
|
||||
f"{self._c}, " +
|
||||
f"{self._start_rk}, {self._end_rk}, " +
|
||||
f"{self._dif}, {self._b}, {self._c}, " +
|
||||
f"{self._enabled}" +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Except.py -- Pamhyr model exceptions
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
+187
-84
@@ -1,5 +1,5 @@
|
||||
# Friction.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -21,6 +21,7 @@ import logging
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
from numpy import interp
|
||||
|
||||
@@ -28,49 +29,49 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class Friction(SQLSubModel):
|
||||
_id_cnt = 0
|
||||
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
|
||||
)
|
||||
|
||||
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, Friction._id_cnt+1)
|
||||
|
||||
self._name = name
|
||||
self._edge = None
|
||||
self._reach = reach
|
||||
self._begin_rk = 0.0
|
||||
self._end_rk = 0.0
|
||||
self._begin_strickler = None
|
||||
self._end_strickler = None
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE friction(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE friction{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
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,
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id),
|
||||
FOREIGN KEY(begin_strickler) REFERENCES stricklers(id),
|
||||
FOREIGN KEY(end_strickler) REFERENCES stricklers(id)
|
||||
{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)
|
||||
)
|
||||
""")
|
||||
|
||||
return True
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 11:
|
||||
@@ -78,49 +79,164 @@ class Friction(SQLSubModel):
|
||||
"RENAME COLUMN begin_kp TO begin_rk")
|
||||
execute("ALTER TABLE friction RENAME COLUMN end_kp TO end_rk")
|
||||
|
||||
return True
|
||||
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}"
|
||||
)
|
||||
|
||||
@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 id, ind, begin_rk, end_rk, " +
|
||||
"begin_strickler, end_strickler " +
|
||||
f"FROM friction WHERE reach = {reach.id}"
|
||||
"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"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
ind = row[1]
|
||||
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)
|
||||
|
||||
# Get stricklers
|
||||
if int(row[4]) == -1:
|
||||
bs = None
|
||||
else:
|
||||
bs = next(filter(lambda s: s.id == row[4], stricklers))
|
||||
begin_strickler = None
|
||||
if begin_strickler_pid != -1:
|
||||
begin_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))
|
||||
end_strickler = None
|
||||
if end_strickler_pid != -1:
|
||||
end_strickler = next(
|
||||
filter(
|
||||
lambda s: s.id == begin_strickler_pid,
|
||||
stricklers
|
||||
)
|
||||
)
|
||||
|
||||
# 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 = cls(
|
||||
id=pid, status=data['status'],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
f.set_as_deleted()
|
||||
|
||||
new.append((ind, sec))
|
||||
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
|
||||
|
||||
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
|
||||
@@ -133,47 +249,34 @@ class Friction(SQLSubModel):
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"friction(id, ind, begin_rk, end_rk, " +
|
||||
"reach, begin_strickler, end_strickler) " +
|
||||
"friction(pamhyr_id, deleted, ind, begin_rk, end_rk, " +
|
||||
"reach, begin_strickler, end_strickler, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {ind}, " +
|
||||
f"{self._begin_rk}, {self._end_rk}, " +
|
||||
f"{self._edge.id}, " +
|
||||
f"{b_s_id}, {e_s_id}" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def edge(self):
|
||||
return self._edge
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._edge
|
||||
return self._reach
|
||||
|
||||
@edge.setter
|
||||
def edge(self, edge):
|
||||
self._edge = edge
|
||||
if (edge is not None and
|
||||
@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._edge.reach.get_rk_min()
|
||||
self._end_rk = self._edge.reach.get_rk_max()
|
||||
self._begin_rk = self._reach.reach.get_rk_min()
|
||||
self._end_rk = self._reach.reach.get_rk_max()
|
||||
self._status.modified()
|
||||
|
||||
def has_edge(self):
|
||||
return self._edge is not None
|
||||
def has_reach(self):
|
||||
return self._reach is not None
|
||||
|
||||
def has_coefficient(self):
|
||||
return (
|
||||
@@ -181,7 +284,7 @@ class Friction(SQLSubModel):
|
||||
)
|
||||
|
||||
def is_full_defined(self):
|
||||
return self.has_edge() and self.has_coefficient()
|
||||
return self.has_reach() and self.has_coefficient()
|
||||
|
||||
@property
|
||||
def begin_rk(self):
|
||||
@@ -189,11 +292,11 @@ class Friction(SQLSubModel):
|
||||
|
||||
@begin_rk.setter
|
||||
def begin_rk(self, begin_rk):
|
||||
if self._edge is None:
|
||||
if self._reach is None:
|
||||
self._begin_rk = begin_rk
|
||||
else:
|
||||
_min = self._edge.reach.get_rk_min()
|
||||
_max = self._edge.reach.get_rk_max()
|
||||
_min = self._reach.reach.get_rk_min()
|
||||
_max = self._reach.reach.get_rk_max()
|
||||
|
||||
if _min <= begin_rk <= _max:
|
||||
self._begin_rk = begin_rk
|
||||
@@ -206,11 +309,11 @@ class Friction(SQLSubModel):
|
||||
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
if self._edge is None:
|
||||
if self._reach is None:
|
||||
self._end_rk = end_rk
|
||||
else:
|
||||
_min = self._edge.reach.get_rk_min()
|
||||
_max = self._edge.reach.get_rk_max()
|
||||
_min = self._reach.reach.get_rk_min()
|
||||
_max = self._reach.reach.get_rk_max()
|
||||
|
||||
if _min <= end_rk <= _max:
|
||||
self._end_rk = end_rk
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# FrictionList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -20,9 +20,10 @@ import logging
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
from functools import reduce
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
|
||||
from Model.Friction.Friction import Friction
|
||||
|
||||
@@ -39,30 +40,30 @@ class FrictionList(PamhyrModelList):
|
||||
execute("ALTER TABLE `section` RENAME TO `friction`")
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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)
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
ilst = Friction._db_load(
|
||||
new._lst = 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}")
|
||||
execute(
|
||||
f"DELETE FROM friction WHERE reach = {reach.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
ok = True
|
||||
ind = 0
|
||||
@@ -86,19 +87,38 @@ 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, index):
|
||||
n = Friction(status=self._status)
|
||||
def new(self, reach, index):
|
||||
n = Friction(reach=reach, 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
|
||||
@@ -1,5 +1,5 @@
|
||||
# Point.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -16,14 +16,18 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
|
||||
class Point(object):
|
||||
def __init__(self, name: str = "", profile=None, status=None):
|
||||
super(Point, self).__init__()
|
||||
|
||||
self._status = status
|
||||
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
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._profile = profile
|
||||
@@ -43,6 +47,8 @@ class Point(object):
|
||||
else:
|
||||
self._sl = sl
|
||||
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
@@ -50,9 +56,9 @@ class Point(object):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def point_is_named(self):
|
||||
def is_named(self):
|
||||
"""
|
||||
Returns:
|
||||
True if the point is named.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# PointXY.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
+218
-40
@@ -1,5 +1,5 @@
|
||||
# PointXYZ.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -16,30 +16,40 @@
|
||||
|
||||
# -*- 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, SQLSubModel):
|
||||
|
||||
class PointXYZ(Point):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0,
|
||||
name: str = "", profile=None, status=None):
|
||||
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):
|
||||
super(PointXYZ, self).__init__(
|
||||
name=name, profile=profile, status=status)
|
||||
id=id, name=name, profile=profile, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._x = float(x)
|
||||
self._y = float(y)
|
||||
self._z = float(z)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE geometry_pointXYZ(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
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,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
x INTEGER NOT NULL,
|
||||
@@ -47,17 +57,19 @@ class PointXYZ(Point, SQLSubModel):
|
||||
z INTEGER NOT NULL,
|
||||
profile INTEGER NOT NULL,
|
||||
sl INTEGER,
|
||||
FOREIGN KEY(profile) REFERENCES geometry_profileXYZ(id),
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
|
||||
{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)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
cls._update_submodel(execute, version)
|
||||
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 2:
|
||||
@@ -68,63 +80,174 @@ class PointXYZ(Point, SQLSubModel):
|
||||
REFERENCES sedimentary_layer(id)
|
||||
"""
|
||||
)
|
||||
return True
|
||||
|
||||
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}"
|
||||
)
|
||||
|
||||
@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 ind, name, x, y, z, sl " +
|
||||
"SELECT pamhyr_id, ind, deleted, " +
|
||||
"name, x, y, z, sl, scenario " +
|
||||
"FROM geometry_pointXYZ " +
|
||||
f"WHERE profile = {profile.id}"
|
||||
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"
|
||||
)
|
||||
|
||||
# Fill points list with new point
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
name = row[1]
|
||||
x = row[2]
|
||||
y = row[3]
|
||||
z = row[4]
|
||||
sl = row[5]
|
||||
it = iter(row)
|
||||
|
||||
new = cls(
|
||||
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,
|
||||
name=name,
|
||||
x=x, y=y, z=z,
|
||||
profile=profile,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
point.set_as_deleted()
|
||||
|
||||
if sl == -1 or sl is None:
|
||||
new._sl = None
|
||||
point._sl = None
|
||||
else:
|
||||
new._sl = next(
|
||||
point._sl = next(
|
||||
filter(
|
||||
lambda s: s.id == sl,
|
||||
lambda s: s.pamhyr_id == sl,
|
||||
data["sediment_layers_list"].sediment_layers
|
||||
)
|
||||
)
|
||||
|
||||
yield ind, new
|
||||
loaded.add(pid)
|
||||
new.append((ind, point))
|
||||
|
||||
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
|
||||
|
||||
profile = data["profile"]
|
||||
ind = data["ind"]
|
||||
|
||||
sl = self._sl.id if self._sl is not None else -1
|
||||
sl = self._sl.pamhyr_id if self._sl is not None else -1
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"geometry_pointXYZ(ind, name, x, y, z, profile, sl) " +
|
||||
"geometry_pointXYZ(pamhyr_id, deleted, ind, name, " +
|
||||
"x, y, z, profile, sl, scenario) " +
|
||||
"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.id}, {sl}" +
|
||||
f"{profile.pamhyr_id}, {sl}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@@ -150,9 +273,44 @@ class PointXYZ(Point, SQLSubModel):
|
||||
|
||||
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
|
||||
@@ -160,7 +318,7 @@ class PointXYZ(Point, SQLSubModel):
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
self._x = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
@@ -169,7 +327,7 @@ class PointXYZ(Point, SQLSubModel):
|
||||
@y.setter
|
||||
def y(self, value):
|
||||
self._y = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
@@ -178,7 +336,10 @@ class PointXYZ(Point, SQLSubModel):
|
||||
@z.setter
|
||||
def z(self, value):
|
||||
self._z = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def get_coordinate(self):
|
||||
return (self._x, self._y, self._z)
|
||||
|
||||
def is_nan(self):
|
||||
"""
|
||||
@@ -226,5 +387,22 @@ class PointXYZ(Point, SQLSubModel):
|
||||
a = PointXYZ.distance(p1, p2)
|
||||
b = PointXYZ.distance(p2, p3)
|
||||
c = PointXYZ.distance(p3, p1)
|
||||
s = (a + b + c) / 2
|
||||
return (s*(s-a) * (s-b)*(s-c)) ** 0.5
|
||||
|
||||
s = float((a + b + c) / 2)
|
||||
res = abs(
|
||||
s * (s - a) * (s - b) * (s - c)
|
||||
) ** 0.5
|
||||
|
||||
return res
|
||||
|
||||
def dist_from_seg(self, p1, p2):
|
||||
a2 = pow(PointXYZ.distance(self, p1), 2)
|
||||
b2 = pow(PointXYZ.distance(self, p2), 2)
|
||||
c2 = pow(PointXYZ.distance(p1, p2), 2)
|
||||
if c2 < 0.00000001:
|
||||
# si les deux points sont confondus
|
||||
d = 0.0
|
||||
else:
|
||||
d = np.sqrt(abs(a2 - pow((c2 - b2 + a2) / (2*np.sqrt(c2)), 2)))
|
||||
|
||||
return d
|
||||
@@ -1,5 +1,5 @@
|
||||
# Profile.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -28,23 +28,15 @@ 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):
|
||||
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, Profile._id_cnt+1)
|
||||
status=None, owner_scenario=-1):
|
||||
super(Profile, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._num = int(num)
|
||||
self._code1 = int(code1)
|
||||
@@ -66,28 +58,31 @@ class Profile(object):
|
||||
return len(self.points)
|
||||
|
||||
def _get_points_list(self):
|
||||
# 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]
|
||||
)
|
||||
)
|
||||
)
|
||||
return list(self._points)
|
||||
|
||||
@property
|
||||
def points(self):
|
||||
if not isinstance(self._points, list):
|
||||
self._points = self._get_points_list()
|
||||
|
||||
return self._points
|
||||
return list(
|
||||
filter(
|
||||
lambda p: not p.is_deleted(),
|
||||
self._points
|
||||
)
|
||||
)
|
||||
|
||||
def point(self, index):
|
||||
return self.points[index]
|
||||
|
||||
def named_point(self, name):
|
||||
return next((p for p in self.points if p.name == name), None)
|
||||
|
||||
def named_point_index(self, name):
|
||||
return next(
|
||||
(p for p in enumerate(self.points) if p[1].name == name), None
|
||||
)[0]
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
@@ -103,7 +98,7 @@ class Profile(object):
|
||||
@num.setter
|
||||
def num(self, value: int):
|
||||
self._num = int(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def code1(self):
|
||||
@@ -116,7 +111,7 @@ class Profile(object):
|
||||
@code1.setter
|
||||
def code1(self, value: int):
|
||||
self._code1 = int(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def code2(self):
|
||||
@@ -129,7 +124,7 @@ class Profile(object):
|
||||
@code2.setter
|
||||
def code2(self, value: int):
|
||||
self._code2 = int(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def nb_points(self):
|
||||
@@ -146,7 +141,7 @@ class Profile(object):
|
||||
@rk.setter
|
||||
def rk(self, value: float):
|
||||
self._rk = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -159,7 +154,7 @@ class Profile(object):
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
self._name = value.strip()
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def sl(self):
|
||||
@@ -172,7 +167,7 @@ class Profile(object):
|
||||
@sl.setter
|
||||
def sl(self, value):
|
||||
self._sl = value
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def profile_type(self):
|
||||
@@ -185,7 +180,7 @@ class Profile(object):
|
||||
@profile_type.setter
|
||||
def profile_type(self, value: str):
|
||||
self._profile_type = value
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def point(self, i: int):
|
||||
if i < len(self.points):
|
||||
@@ -200,33 +195,37 @@ class Profile(object):
|
||||
The list of named point
|
||||
"""
|
||||
return [point for point in self.points
|
||||
if point.point_is_named()]
|
||||
if point.is_named()]
|
||||
|
||||
def insert_point(self, index: int, point: Point):
|
||||
"""Insert point at index.
|
||||
|
||||
Args:
|
||||
index: The index of new profile.
|
||||
index: The index of new point.
|
||||
point: The point.
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
self.points.insert(index, point)
|
||||
self._status.modified()
|
||||
if point in self._points:
|
||||
point.set_as_not_deleted()
|
||||
else:
|
||||
self._points.insert(index, point)
|
||||
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes: list):
|
||||
self._points = list(
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] not in indexes,
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self.points)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_points(self, points):
|
||||
"""Delete some elements in profile list
|
||||
@@ -237,31 +236,31 @@ class Profile(object):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
self._points = list(
|
||||
filter(
|
||||
lambda p: p not in points,
|
||||
self.points
|
||||
list(
|
||||
map(
|
||||
lambda p: p.set_as_deleted(),
|
||||
points
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.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._status.modified()
|
||||
self.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._status.modified()
|
||||
self.modified()
|
||||
|
||||
# Sort
|
||||
|
||||
@@ -274,32 +273,32 @@ class Profile(object):
|
||||
def predicate(p): return p.z
|
||||
|
||||
self._points = sorted(
|
||||
self.points,
|
||||
self._points,
|
||||
key=predicate,
|
||||
reverse=is_reversed
|
||||
)
|
||||
self._status.modified()
|
||||
self.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._status.modified()
|
||||
self.modified()
|
||||
|
||||
@timer
|
||||
def reverse(self):
|
||||
self._points.reverse()
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
# Sediment Layers
|
||||
|
||||
|
||||
+380
-123
@@ -1,5 +1,5 @@
|
||||
# ProfileXYZ.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -16,17 +16,20 @@
|
||||
|
||||
# -*- 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
|
||||
from tools import timer, flatten
|
||||
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
|
||||
@@ -48,13 +51,14 @@ 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):
|
||||
status=None, owner_scenario=-1):
|
||||
"""ProfileXYZ constructor
|
||||
|
||||
Args:
|
||||
@@ -69,15 +73,16 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"""
|
||||
super(ProfileXYZ, self).__init__(
|
||||
id=id,
|
||||
num=num,
|
||||
name=name,
|
||||
rk=rk,
|
||||
num=num, 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
|
||||
@@ -87,10 +92,11 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
self.station_up_to_date = False
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE geometry_profileXYZ(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
reach INTEGER NOT NULL,
|
||||
@@ -99,15 +105,21 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
code1 INTEGER NOT NULL,
|
||||
code2 INTEGER NOT NULL,
|
||||
sl INTEGER,
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id),
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
|
||||
{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)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
rl = int(release)
|
||||
@@ -128,83 +140,215 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"""
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version)
|
||||
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}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
profiles = []
|
||||
new = []
|
||||
|
||||
status = data["status"]
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
reach = data["reach"]
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT id, ind, name, rk, num, code1, code2, sl " +
|
||||
"SELECT pamhyr_id, ind, deleted, name, rk, num, " +
|
||||
"code1, code2, sl, scenario " +
|
||||
"FROM geometry_profileXYZ " +
|
||||
f"WHERE reach = {reach.id}"
|
||||
f"WHERE reach = {reach.id} " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
|
||||
"ORDER BY ind ASC"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
ind = row[1]
|
||||
name = row[2]
|
||||
rk = row[3]
|
||||
num = row[5]
|
||||
code1 = row[5]
|
||||
code2 = row[6]
|
||||
sl = row[7]
|
||||
it = iter(row)
|
||||
|
||||
new = cls(
|
||||
id=id, num=num,
|
||||
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,
|
||||
name=name, rk=rk,
|
||||
code1=code1, code2=code2,
|
||||
reach=reach,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
profile.set_as_deleted()
|
||||
|
||||
if sl == -1 or sl is None:
|
||||
new._sl = None
|
||||
profile._sl = None
|
||||
else:
|
||||
new._sl = next(
|
||||
profile._sl = next(
|
||||
filter(
|
||||
lambda s: s.id == sl,
|
||||
data["sediment_layers_list"].sediment_layers
|
||||
), None)
|
||||
),
|
||||
None
|
||||
)
|
||||
|
||||
data["profile"] = new
|
||||
new._points = PointXYZ._db_load(execute, data.copy())
|
||||
loaded_save = data['loaded_pid']
|
||||
data['loaded_pid'] = set()
|
||||
data["profile"] = profile
|
||||
|
||||
yield ind, new
|
||||
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
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
ok = True
|
||||
ind = data["ind"]
|
||||
|
||||
sl = self._sl.id if self._sl is not None else -1
|
||||
sl = self._sl.pamhyr_id if self._sl is not None else -1
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"geometry_profileXYZ(id, ind, name, reach, " +
|
||||
"rk, num, code1, code2, sl) " +
|
||||
"geometry_profileXYZ(pamhyr_id, deleted, ind, name, reach, " +
|
||||
"rk, num, code1, code2, sl, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {ind}, '{self._db_format(self._name)}', " +
|
||||
f"{self.reach.id}, {self.rk}, {self.num}, " +
|
||||
f"{self.code1}, {self.code1}, {sl}" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
points = self.points
|
||||
|
||||
data["profile"] = self
|
||||
execute(f"DELETE FROM geometry_pointXYZ WHERE profile = {self.id}")
|
||||
execute(
|
||||
"DELETE FROM geometry_pointXYZ " +
|
||||
f"WHERE profile = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
ind = 0
|
||||
for point in points:
|
||||
for point in self._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
|
||||
@@ -237,6 +381,47 @@ 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(
|
||||
@@ -277,6 +462,12 @@ 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]
|
||||
|
||||
@@ -317,11 +508,20 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
Nothing.
|
||||
"""
|
||||
for point in list_points:
|
||||
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
|
||||
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()
|
||||
|
||||
def get_point_i(self, index: int) -> PointXYZ:
|
||||
"""Get point at index.
|
||||
@@ -380,11 +580,13 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
point_xyz = PointXYZ(0., 0., 0., profile=self, status=self._status)
|
||||
point_xyz = PointXYZ(
|
||||
x=0., y=0., z=0., profile=self,
|
||||
status=self._status
|
||||
)
|
||||
self.points.append(point_xyz)
|
||||
self._status.modified()
|
||||
self.tab_up_to_date = False
|
||||
self.station_up_to_date = False
|
||||
|
||||
self.modified()
|
||||
|
||||
def insert(self, index: int):
|
||||
"""Insert a new point at index.
|
||||
@@ -395,11 +597,16 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
Returns:
|
||||
The new point.
|
||||
"""
|
||||
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
|
||||
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()
|
||||
|
||||
return point
|
||||
|
||||
def filter_isnan(self, lst):
|
||||
@@ -542,8 +749,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
if zz[i] >= z and zz[i+1] < z:
|
||||
y = np.interp(
|
||||
z,
|
||||
[zz[i], zz[i+1]],
|
||||
[station[i], station[i+1]]
|
||||
[zz[i+1], zz[i]],
|
||||
[station[i+1], station[i]]
|
||||
)
|
||||
line.append([y, z])
|
||||
|
||||
@@ -632,11 +839,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
for i in range(self.number_points-1):
|
||||
if zz[i] > z and zz[i+1] <= z:
|
||||
y = np.interp(
|
||||
z,
|
||||
[zz[i], zz[i+1]],
|
||||
[station[i], station[i+1]]
|
||||
)
|
||||
fact = (z - zz[i]) / (zz[i+1] - zz[i])
|
||||
y = station[i] + fact * (station[i+1] - station[i])
|
||||
start.append(y)
|
||||
|
||||
end = []
|
||||
@@ -645,11 +849,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
for i in reversed(range(self.number_points-1)):
|
||||
if zz[i] <= z and zz[i+1] > z:
|
||||
y = np.interp(
|
||||
z,
|
||||
[zz[i], zz[i+1]],
|
||||
[station[i], station[i+1]]
|
||||
)
|
||||
fact = (z - zz[i]) / (zz[i+1] - zz[i])
|
||||
y = station[i] + fact * (station[i+1] - station[i])
|
||||
end.append(y)
|
||||
|
||||
if len(start) != len(end):
|
||||
@@ -680,33 +881,31 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
# Interpolate points at river left side
|
||||
if (i_left > 0):
|
||||
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")
|
||||
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")
|
||||
else:
|
||||
pt_left = self.point(0)
|
||||
|
||||
# Interpolate points at river right side
|
||||
if (i_right < self.number_points - 1):
|
||||
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")
|
||||
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")
|
||||
else:
|
||||
pt_right = self.point(self.number_points - 1)
|
||||
|
||||
@@ -754,13 +953,13 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
last_point_not_nan = self._last_point_not_nan(points)
|
||||
|
||||
for index, point in enumerate(points):
|
||||
if point.point_is_named():
|
||||
if point.is_named():
|
||||
index_first_named_point = index
|
||||
first_named_point = point
|
||||
break
|
||||
|
||||
for point in reversed(points):
|
||||
if point.point_is_named():
|
||||
if point.is_named():
|
||||
last_named_point = point
|
||||
break
|
||||
|
||||
@@ -831,54 +1030,112 @@ 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 = 2 # we consider the first and last point as named
|
||||
area = [0.0]
|
||||
nb_named = 0 # we consider the first and last point as named
|
||||
area = []
|
||||
|
||||
for i in range(1, self.nb_points-1):
|
||||
if self.point(i).point_is_named():
|
||||
area.append(9999999.999)
|
||||
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))
|
||||
nb_named += 1
|
||||
else:
|
||||
area.append(
|
||||
area.append((
|
||||
PointXYZ.areatriangle3d(
|
||||
self.point(i-1),
|
||||
self.point(i),
|
||||
self.point(i+1))
|
||||
)
|
||||
|
||||
area.append(0.0)
|
||||
p,
|
||||
self.point(i+1)
|
||||
),
|
||||
p
|
||||
))
|
||||
|
||||
while self.nb_points > max(np_purge, nb_named):
|
||||
to_rm = np.argmin(area[1:self.nb_points - 1]) + 1
|
||||
ind, (min_area, p) = min(
|
||||
enumerate(area),
|
||||
key=lambda t: t[1][0]
|
||||
)
|
||||
|
||||
self.delete_i([to_rm])
|
||||
area.pop(to_rm)
|
||||
p.set_as_deleted()
|
||||
area.pop(ind)
|
||||
|
||||
for i in [to_rm-1, to_rm]:
|
||||
if (i == 0):
|
||||
continue
|
||||
for i in [ind-1, ind]:
|
||||
p = self.point(i)
|
||||
|
||||
if (i == self.nb_points - 1):
|
||||
continue
|
||||
|
||||
if self.point(i).point_is_named():
|
||||
area[i] = 9999999.999
|
||||
if p.is_named() or i == 0 or i == self.nb_points - 1:
|
||||
area[i] = (9999999.999, p)
|
||||
else:
|
||||
area[i] = PointXYZ.areatriangle3d(
|
||||
self.point(i-1),
|
||||
self.point(i),
|
||||
self.point(i+1)
|
||||
area[i] = (
|
||||
PointXYZ.areatriangle3d(
|
||||
self.point(i-1), p, self.point(i+1)
|
||||
),
|
||||
p
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
def add_npoints(self, npoints):
|
||||
# add npoints in a profile
|
||||
for k in range(npoints):
|
||||
disti = 0.0
|
||||
i = 1
|
||||
for j in range(len(self.points)-1):
|
||||
distj = self.point(j).dist(self.point(j+1))
|
||||
if distj > disti:
|
||||
disti = distj
|
||||
i = j
|
||||
self.insert_point(i, self.point(i).copy())
|
||||
self.point(i+1).name = ''
|
||||
self.point(i+1).x = 0.5 * self.point(i).x + 0.5 * self.point(i+2).x
|
||||
self.point(i+1).y = 0.5 * self.point(i).y + 0.5 * self.point(i+2).y
|
||||
self.point(i+1).z = 0.5 * self.point(i).z + 0.5 * self.point(i+2).z
|
||||
|
||||
def copy(self):
|
||||
p = ProfileXYZ(id=self.id,
|
||||
name=self.name,
|
||||
rk=self.rk,
|
||||
reach=self.reach,
|
||||
num=self.num,
|
||||
status=self._status)
|
||||
for i, k in enumerate(self.points):
|
||||
p.insert_point(i, k.copy())
|
||||
|
||||
return p
|
||||
|
||||
def purge_aligned_points(self):
|
||||
|
||||
align = True
|
||||
while (align):
|
||||
align = False
|
||||
for i in range(1, self.number_points - 1):
|
||||
d = self.point(i).dist_from_seg(self.point(i-1),
|
||||
self.point(i+1))
|
||||
if d < 0.00001 and self.point(i).name == "":
|
||||
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()
|
||||
+118
-83
@@ -1,5 +1,5 @@
|
||||
# Reach.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -45,7 +45,6 @@ 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] = []
|
||||
@@ -53,41 +52,59 @@ 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):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"], parent=data["reach"])
|
||||
|
||||
new._profiles = ProfileXYZ._db_load(
|
||||
execute,
|
||||
data=data.copy()
|
||||
new._profiles = sorted(
|
||||
ProfileXYZ._db_load(
|
||||
execute,
|
||||
data=data.copy()
|
||||
),
|
||||
key=lambda p: p._db_ind
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
profiles = self.profiles
|
||||
ok = True
|
||||
|
||||
# Delete old data
|
||||
execute(f"DELETE FROM geometry_profileXYZ WHERE reach = {self.id}")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
execute(
|
||||
"DELETE FROM geometry_profileXYZ " +
|
||||
f"WHERE reach = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
ind = 0
|
||||
for profile in profiles:
|
||||
for profile in self._profiles:
|
||||
data["ind"] = ind
|
||||
profile._db_save(execute, data)
|
||||
ok &= profile._db_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
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)
|
||||
|
||||
def profile(self, i):
|
||||
"""Returns profile at index i
|
||||
@@ -111,31 +128,18 @@ 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(
|
||||
map(
|
||||
lambda p: p[1],
|
||||
sorted(
|
||||
self._profiles,
|
||||
key=lambda p: p[0]
|
||||
)
|
||||
)
|
||||
)
|
||||
return list(self._profiles)
|
||||
|
||||
def __len__(self):
|
||||
if not isinstance(self._profiles, list):
|
||||
self._profiles = self._get_profiles_list()
|
||||
|
||||
return len(self._profiles)
|
||||
return len(self.profiles)
|
||||
|
||||
@property
|
||||
def profiles(self):
|
||||
if not isinstance(self._profiles, list):
|
||||
self._profiles = self._get_profiles_list()
|
||||
|
||||
return self._profiles
|
||||
return list(filter(lambda p: not p.is_deleted(), self._profiles))
|
||||
|
||||
@profiles.setter
|
||||
def profiles(self, profiles):
|
||||
@@ -163,15 +167,6 @@ 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
|
||||
|
||||
@@ -183,10 +178,9 @@ class Reach(SQLSubModel):
|
||||
"""
|
||||
profile = ProfileXYZ(reach=self, status=self._status)
|
||||
|
||||
self.profiles.insert(index, profile)
|
||||
self._update_profile_numbers()
|
||||
self._profiles.insert(index, profile)
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
return profile
|
||||
|
||||
@@ -199,9 +193,12 @@ class Reach(SQLSubModel):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
self.profiles.insert(index, profile)
|
||||
self._update_profile_numbers()
|
||||
self._status.modified()
|
||||
if profile in self._profiles:
|
||||
self.undelete([profile])
|
||||
else:
|
||||
self._profiles.insert(index, profile)
|
||||
|
||||
self.modified()
|
||||
|
||||
def delete(self, indexes):
|
||||
"""Delete some elements in profile list
|
||||
@@ -212,24 +209,36 @@ class Reach(SQLSubModel):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
profiles = set(
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
lambda p: p[1].set_as_deleted(),
|
||||
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(
|
||||
filter(
|
||||
lambda p: p not in profiles,
|
||||
self.profiles
|
||||
map(
|
||||
lambda p: p[1],
|
||||
filter(
|
||||
lambda e: e[0] not in indexes,
|
||||
enumerate(self._profiles)
|
||||
)
|
||||
)
|
||||
)
|
||||
self._update_profile_numbers()
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_profiles(self, profiles):
|
||||
"""Delete some elements in profile list
|
||||
@@ -240,35 +249,41 @@ class Reach(SQLSubModel):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
self._profiles = list(
|
||||
filter(
|
||||
lambda p: p not in profiles,
|
||||
self.profiles
|
||||
list(
|
||||
map(
|
||||
lambda p: p.set_as_deleted(),
|
||||
profiles
|
||||
)
|
||||
)
|
||||
self._update_profile_numbers()
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def undelete(self, lst):
|
||||
for el in lst:
|
||||
el.set_as_not_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def purge(self):
|
||||
self._profiles = []
|
||||
self._update_profile_numbers()
|
||||
self._status.modified()
|
||||
for el in self._profiles:
|
||||
el.set_as_deleted()
|
||||
|
||||
self.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._status.modified()
|
||||
self.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._status.modified()
|
||||
self.modified()
|
||||
|
||||
def get_x(self):
|
||||
return list(
|
||||
@@ -496,7 +511,7 @@ class Reach(SQLSubModel):
|
||||
self._compute_guidelines_cache(guide_set, named_points,
|
||||
complete, incomplete)
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return (complete, incomplete)
|
||||
|
||||
def _map_guidelines_points(self, func, full=False):
|
||||
@@ -537,28 +552,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._status.modified()
|
||||
self.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._status.modified()
|
||||
self.modified()
|
||||
|
||||
# Import/Export
|
||||
|
||||
@@ -614,8 +629,7 @@ class Reach(SQLSubModel):
|
||||
|
||||
profiles.append(prof)
|
||||
|
||||
self.profiles = profiles + self.profiles
|
||||
self._update_profile_numbers()
|
||||
self._profiles = profiles + self._profiles
|
||||
self._recompute_rk()
|
||||
|
||||
return profiles
|
||||
@@ -633,6 +647,8 @@ 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",
|
||||
@@ -645,12 +661,15 @@ class Reach(SQLSubModel):
|
||||
d[profile_header[i]] = data
|
||||
|
||||
prof = ProfileXYZ(
|
||||
**d, reach=self, status=self._status
|
||||
id=-1,
|
||||
**d,
|
||||
reach=self,
|
||||
status=self._status
|
||||
)
|
||||
prof.import_points(profile)
|
||||
|
||||
imported_profiles.append(prof)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
except FileNotFoundError as e:
|
||||
logger.error(e)
|
||||
exception_message_box(e)
|
||||
@@ -658,9 +677,8 @@ class Reach(SQLSubModel):
|
||||
logger.error(e)
|
||||
e.alert()
|
||||
finally:
|
||||
self.profiles = imported_profiles + self.profiles
|
||||
self._profiles = self._profiles + imported_profiles
|
||||
|
||||
self._update_profile_numbers()
|
||||
return imported_profiles
|
||||
|
||||
@timer
|
||||
@@ -894,3 +912,20 @@ 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,5 +1,5 @@
|
||||
# Vector_1d.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# HydraulicStructures.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -21,6 +21,7 @@ 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 (
|
||||
@@ -34,49 +35,97 @@ class BasicHS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
BHSValue,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(BasicHS, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = BasicHS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
status=None, owner_scenario=-1):
|
||||
super(BasicHS, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._type = ""
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
BasicHS._id_cnt = max(BasicHS._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE hydraulic_structures_basic(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
hs INTEGER,
|
||||
FOREIGN KEY(hs) REFERENCES hydraulic_structures(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(hs) REFERENCES hydraulic_structures(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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 True
|
||||
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}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_ctor_from_type(cls, t):
|
||||
@@ -94,58 +143,85 @@ 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 id, name, type, enabled, hs " +
|
||||
"SELECT pamhyr_id, deleted, name, type, enabled, hs, scenario " +
|
||||
"FROM hydraulic_structures_basic " +
|
||||
f"WHERE hs = {data['hs_id']} "
|
||||
f"WHERE hs = {data['hs_id']} " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
bhs_id = row[0]
|
||||
name = row[1]
|
||||
type = row[2]
|
||||
enabled = (row[3] == 1)
|
||||
hs_id = row[4]
|
||||
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)
|
||||
|
||||
ctor = cls._get_ctor_from_type(type)
|
||||
bhs = ctor(
|
||||
id=bhs_id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
id=bhs_pid, name=name,
|
||||
status=status, owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
bhs.set_as_deleted()
|
||||
|
||||
bhs.enabled = enabled
|
||||
|
||||
data['bhs_id'] = bhs_id
|
||||
data['bhs_id'] = bhs_pid
|
||||
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']
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"hydraulic_structures_basic(id, name, type, enabled, hs) " +
|
||||
"hydraulic_structures_basic(pamhyr_id, deleted, " +
|
||||
"name, type, enabled, hs, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{self.pamhyr_id}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._type)}', " +
|
||||
f"{self._db_format(self.enabled)}, " +
|
||||
f"{hs_id} " +
|
||||
f"{hs_id}, " +
|
||||
f"{self._status.scenario_id}"
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data['bhs_id'] = self.id
|
||||
data['bhs_id'] = self.pamhyr_id
|
||||
execute(
|
||||
"DELETE FROM hydraulic_structures_basic_value " +
|
||||
f"WHERE bhs = {self.id}"
|
||||
f"WHERE bhs = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for values in self._data:
|
||||
@@ -153,20 +229,30 @@ 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.id + 1}"
|
||||
return f"{self._type} #{self.pamhyr_id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
@@ -175,7 +261,7 @@ class BasicHS(SQLSubModel):
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
self._type = type
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -184,7 +270,7 @@ class BasicHS(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Types.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,10 +27,11 @@ from Model.HydraulicStructures.Basic.Value import (
|
||||
|
||||
class NotDefined(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(NotDefined, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "ND"
|
||||
@@ -39,197 +40,208 @@ class NotDefined(BasicHS):
|
||||
|
||||
class DischargeWeir(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(DischargeWeir, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "S1"
|
||||
self._data = [
|
||||
BHSValue("width", float, 1.0, status=status),
|
||||
BHSValue("elevation", float, 1.0, status=status),
|
||||
BHSValue("discharge_coefficient", float, 0.4, status=status),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class TrapezoidalWeir(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(TrapezoidalWeir, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "S2"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class TriangularWeir(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(TriangularWeir, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "S3"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class RectangularOrifice(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(RectangularOrifice, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "OR"
|
||||
self._data = [
|
||||
BHSValue("width", float, 0.0, status=status),
|
||||
BHSValue("elevation", float, 0.0, status=status),
|
||||
BHSValue("loading_elevation", float, 9999.999,
|
||||
BHSValue(-1, "width", float, 0.0, status=status),
|
||||
BHSValue(-1, "elevation", float, 0.0, status=status),
|
||||
BHSValue(-1, "loading_elevation", float, 9999.999,
|
||||
status=status),
|
||||
BHSValue("discharge_coefficient", float, 0.4, status=status),
|
||||
BHSValue("maximal_loading_elevation", float, 9999.999,
|
||||
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
|
||||
BHSValue(-1, "maximal_loading_elevation", float, 9999.999,
|
||||
status=status),
|
||||
]
|
||||
|
||||
|
||||
class CircularOrifice(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(CircularOrifice, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "OC"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class VaultedOrifice(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(VaultedOrifice, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "OV"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class RectangularGate(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(RectangularGate, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "V1"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class SimplifiedRectangularGate(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(SimplifiedRectangularGate, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "V2"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class Borda(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(Borda, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "BO"
|
||||
self._data = [
|
||||
BHSValue("step_space", float, 0.1, status=status),
|
||||
BHSValue("weir", float, 0.15, status=status),
|
||||
BHSValue("coefficient", float, 0.4, status=status),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
class CheckValve(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(CheckValve, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "CV"
|
||||
self._data = [
|
||||
BHSValue("width", float, 0.0, status=status),
|
||||
BHSValue("elevation", float, 0.0, status=status),
|
||||
BHSValue("loading_elevation", float, 9999.999,
|
||||
BHSValue(-1, "width", float, 0.0, status=status),
|
||||
BHSValue(-1, "elevation", float, 0.0, status=status),
|
||||
BHSValue(-1, "loading_elevation", float, 9999.999,
|
||||
status=status),
|
||||
BHSValue("discharge_coefficient", float, 0.4, status=status),
|
||||
BHSValue("maximal_loading_elevation", float, 9999.999,
|
||||
BHSValue(-1, "discharge_coefficient", float, 0.4, status=status),
|
||||
BHSValue(-1, "maximal_loading_elevation", float, 9999.999,
|
||||
status=status),
|
||||
]
|
||||
|
||||
|
||||
class UserDefined(BasicHS):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(UserDefined, self).__init__(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._type = "UD"
|
||||
self._data = [
|
||||
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),
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Value.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -17,45 +17,92 @@
|
||||
# -*- 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, name: str = "", type=float, value=0.0,
|
||||
status=None):
|
||||
super(BHSValue, self).__init__()
|
||||
|
||||
self._status = status
|
||||
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
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._type = type
|
||||
self._value = type(value)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE hydraulic_structures_basic_value(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE hydraulic_structures_basic_value{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
bhs INTEGER,
|
||||
FOREIGN KEY(bhs) REFERENCES hydraulic_structures_basic(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(bhs) REFERENCES hydraulic_structures_basic(id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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 True
|
||||
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}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _str_to_type(cls, type):
|
||||
@@ -86,44 +133,62 @@ 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 name, type, value " +
|
||||
"SELECT pamhyr_id, name, type, value, scenario " +
|
||||
"FROM hydraulic_structures_basic_value " +
|
||||
f"WHERE bhs = '{bhs_id}'"
|
||||
f"WHERE bhs = '{bhs_id}' " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
name = row[0]
|
||||
type = cls._str_to_type(row[1])
|
||||
value = row[2]
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
name = next(it)
|
||||
type = cls._str_to_type(next(it))
|
||||
value = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
val = cls(
|
||||
name=name,
|
||||
type=type,
|
||||
value=value,
|
||||
status=data['status']
|
||||
id=pid, name=name,
|
||||
type=type, value=value,
|
||||
status=status, owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
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"]
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"hydraulic_structures_basic_value(name, type, value, bhs) " +
|
||||
"hydraulic_structures_basic_value" +
|
||||
"(pamhyr_id, name, type, value, bhs, scenario) " +
|
||||
"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"{bhs_id}, " +
|
||||
f"{self._status.scenario_id}"
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@@ -142,4 +207,4 @@ class BHSValue(SQLSubModel):
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._value = self._type(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# HydraulicStructures.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,6 +22,7 @@ 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
|
||||
@@ -36,163 +37,289 @@ class HydraulicStructure(SQLSubModel):
|
||||
_sub_classes = [
|
||||
BasicHS,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(HydraulicStructure, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = HydraulicStructure._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
status=None, owner_scenario=-1):
|
||||
super(HydraulicStructure, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._input_rk = None
|
||||
self._output_rk = None
|
||||
self._input_section = None
|
||||
self._output_section = None
|
||||
self._input_reach = None
|
||||
self._output_reach = None
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
HydraulicStructure._id_cnt = max(
|
||||
HydraulicStructure._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
|
||||
execute("""
|
||||
CREATE TABLE hydraulic_structures(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
input_rk REAL NOT NULL,
|
||||
output_rk REAL NOT NULL,
|
||||
input_reach INTEGER,
|
||||
output_reach INTEGER,
|
||||
FOREIGN KEY(input_reach) REFERENCES river_reach(id),
|
||||
FOREIGN KEY(output_reach) REFERENCES river_reach(id)
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
rl = int(release)
|
||||
rl = int(release)
|
||||
|
||||
if major == minor == "0":
|
||||
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
|
||||
RENAME COLUMN input_kp TO input_rk
|
||||
"""
|
||||
)
|
||||
execute(
|
||||
"""
|
||||
ALTER TABLE hydraulic_structures
|
||||
RENAME COLUMN output_kp TO output_rk
|
||||
"""
|
||||
"ALTER TABLE hydraulic_structures " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return True
|
||||
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
|
||||
)
|
||||
|
||||
@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 id, name, enabled, " +
|
||||
"input_rk, output_rk, " +
|
||||
"input_reach, output_reach " +
|
||||
"FROM hydraulic_structures "
|
||||
"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))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
new.append(cls._db_load_new(execute, data, row))
|
||||
|
||||
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)
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
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):
|
||||
print("save hs unit")
|
||||
execute(f"DELETE FROM hydraulic_structures WHERE id = {self.id}")
|
||||
execute(
|
||||
"DELETE FROM hydraulic_structures " +
|
||||
f"WHERE pamhyr_id = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
input_reach_id = -1
|
||||
if self._input_reach is not None:
|
||||
input_reach_id = self._input_reach.id
|
||||
input_reach_id = self._input_reach.pamhyr_id
|
||||
|
||||
output_reach_id = -1
|
||||
if self._output_reach is not None:
|
||||
output_reach_id = self._output_reach.id
|
||||
output_reach_id = self._output_reach.pamhyr_id
|
||||
|
||||
input_rk = -1
|
||||
if self.input_rk is not None:
|
||||
input_rk = self.input_rk
|
||||
input_section = 'NULL'
|
||||
if self.input_section is not None:
|
||||
input_section = self.input_section.pamhyr_id
|
||||
|
||||
output_rk = -1
|
||||
if self.output_rk is not None:
|
||||
output_rk = self.output_rk
|
||||
output_section = 'NULL'
|
||||
if self.output_section is not None:
|
||||
output_section = self.output_section.pamhyr_id
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"hydraulic_structures(" +
|
||||
" id, name, enabled, input_rk, output_rk, " +
|
||||
" input_reach, output_reach" +
|
||||
" pamhyr_id, deleted, name, enabled, " +
|
||||
" input_section, output_section, " +
|
||||
" input_reach, output_reach, " +
|
||||
" scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{self.pamhyr_id}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{self._db_format(self.enabled)}, " +
|
||||
f"{input_rk}, {output_rk}, " +
|
||||
f"{input_reach_id}, {output_reach_id}" +
|
||||
f"{input_section}, {output_section}, " +
|
||||
f"{input_reach_id}, {output_reach_id}, " +
|
||||
f"{self._status.scenario_id}"
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data['hs_id'] = self.id
|
||||
data['hs_id'] = self.pamhyr_id
|
||||
execute(
|
||||
"DELETE FROM hydraulic_structures_basic " +
|
||||
f"WHERE hs = {self.id}"
|
||||
f"WHERE hs = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for basic in self._data:
|
||||
@@ -200,41 +327,71 @@ 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._data)
|
||||
return len(self.lst)
|
||||
|
||||
@property
|
||||
def lst(self):
|
||||
return list(
|
||||
filter(
|
||||
lambda bhs: not bhs.is_deleted(),
|
||||
self._data,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"HS{self.id + 1}"
|
||||
return f"HS #{self.pamhyr_id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def input_rk(self):
|
||||
return self._input_rk
|
||||
if self._input_section is None:
|
||||
return None
|
||||
|
||||
@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()
|
||||
return self._input_section
|
||||
|
||||
@property
|
||||
def output_rk(self):
|
||||
return self._output_rk
|
||||
if self._output_section is None:
|
||||
return None
|
||||
|
||||
@output_rk.setter
|
||||
def output_rk(self, output_rk):
|
||||
self._output_rk = output_rk
|
||||
self._status.modified()
|
||||
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()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -243,7 +400,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def input_reach(self):
|
||||
@@ -252,7 +409,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
@input_reach.setter
|
||||
def input_reach(self, input_reach):
|
||||
self._input_reach = input_reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def output_reach(self):
|
||||
@@ -261,26 +418,34 @@ class HydraulicStructure(SQLSubModel):
|
||||
@output_reach.setter
|
||||
def output_reach(self, output_reach):
|
||||
self._output_reach = output_reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def basic_structures(self):
|
||||
return self._data.copy()
|
||||
return self.lst.copy()
|
||||
|
||||
def basic_structure(self, index: int):
|
||||
return self._data[index]
|
||||
if len(self._data) == 0:
|
||||
return None
|
||||
|
||||
return self.lst[index]
|
||||
|
||||
def add(self, index: int):
|
||||
value = NotDefined(status=self._status)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
|
||||
self.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value: BasicHS):
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
if value in self._data:
|
||||
value.set_as_not_deleted()
|
||||
else:
|
||||
self._data.insert(index, value)
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self.modified()
|
||||
|
||||
def hard_delete_i(self, indexes):
|
||||
self._data = list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
@@ -290,20 +455,30 @@ class HydraulicStructure(SQLSubModel):
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda e: e not in els,
|
||||
self._data
|
||||
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._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
for el in els:
|
||||
el.set_as_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# HydraulicStructuresList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt 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")
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
execute(
|
||||
"DELETE FROM hydraulic_structures " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for hs in self._lst:
|
||||
hs._db_save(execute, data=data)
|
||||
@@ -53,9 +53,12 @@ class HydraulicStructureList(PamhyrModelList):
|
||||
return True
|
||||
|
||||
def new(self, lst, index):
|
||||
n = HydraulicStructure(status=self._status)
|
||||
n = HydraulicStructure(
|
||||
status=self._status,
|
||||
owner_scenario=self._status.scenario_id
|
||||
)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return n
|
||||
|
||||
def __copy__(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# InitialConditions.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -24,32 +24,34 @@ 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, name: str = "",
|
||||
comment: str = "", reach=None,
|
||||
rk: float = 0.0, discharge: float = 0.0,
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "", comment: str = "",
|
||||
reach=None, section=None,
|
||||
discharge: float = 0.0,
|
||||
height: float = 0.0,
|
||||
status=None):
|
||||
super(Data, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
status=None,
|
||||
owner_scenario=-1):
|
||||
super(Data, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
self._reach = reach
|
||||
|
||||
self._name = name
|
||||
self._comment = comment
|
||||
|
||||
self._rk = rk
|
||||
self._section = section
|
||||
self._discharge = discharge
|
||||
self._speed = 0.0
|
||||
self._elevation = 0.0
|
||||
self._height = height
|
||||
|
||||
if self._rk != 0.0:
|
||||
if self._section is not None:
|
||||
self._update_from_rk()
|
||||
if self._height != 0.0:
|
||||
self._update_from_height()
|
||||
@@ -57,25 +59,29 @@ class Data(SQLSubModel):
|
||||
self._update_from_discharge()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE initial_conditions(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
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,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
reach INTEGER,
|
||||
rk REAL NOT NULL,
|
||||
section INTEGER,
|
||||
discharge REAL NOT NULL,
|
||||
height REAL NOT NULL,
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 11:
|
||||
@@ -83,41 +89,138 @@ class Data(SQLSubModel):
|
||||
"ALTER TABLE initial_conditions RENAME COLUMN kp TO rk"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version)
|
||||
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
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
id = data["reach"].id
|
||||
new = []
|
||||
reach = data["reach"]
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
id = reach.pamhyr_id
|
||||
table = execute(
|
||||
"SELECT ind, name, comment, rk, discharge, height " +
|
||||
"SELECT pamhyr_id, deleted, ind, name, comment, " +
|
||||
"section, discharge, height, scenario " +
|
||||
"FROM initial_conditions " +
|
||||
f"WHERE reach = {id}"
|
||||
f"WHERE reach = {id} " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
|
||||
"ORDER BY ind ASC"
|
||||
)
|
||||
|
||||
new = []
|
||||
|
||||
for _ in table:
|
||||
new.append(None)
|
||||
|
||||
ok = True
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
name = row[1]
|
||||
comment = row[2]
|
||||
rk = row[3]
|
||||
discharge = row[4]
|
||||
height = row[5]
|
||||
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
|
||||
|
||||
d = cls(
|
||||
id=pid,
|
||||
reach=data["reach"],
|
||||
status=data["status"],
|
||||
name=name,
|
||||
comment=comment,
|
||||
rk=rk,
|
||||
section=section,
|
||||
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})"
|
||||
)
|
||||
|
||||
new[ind] = d
|
||||
# Recution only if no data found
|
||||
if len(new) == 0:
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
@@ -126,13 +229,14 @@ class Data(SQLSubModel):
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"initial_conditions(ind, name, comment, rk, " +
|
||||
"discharge, height, reach) " +
|
||||
"initial_conditions(pamhyr_id, deleted, ind, name, comment, " +
|
||||
"section, discharge, height, reach, scenario) " +
|
||||
"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._rk}, {self._discharge}, {self._height}, " +
|
||||
f"{self._reach.id}" +
|
||||
f"{self._section.id}, {self._discharge}, {self._height}, " +
|
||||
f"{self._reach.id}, {self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
@@ -142,7 +246,7 @@ class Data(SQLSubModel):
|
||||
new = Data(
|
||||
name=self.name,
|
||||
comment=self._comment,
|
||||
rk=self._rk,
|
||||
section=self._section,
|
||||
discharge=self._discharge,
|
||||
height=self._height,
|
||||
reach=self._reach,
|
||||
@@ -161,8 +265,8 @@ class Data(SQLSubModel):
|
||||
val = self._name
|
||||
elif key == "comment":
|
||||
val = self._comment
|
||||
elif key == "rk":
|
||||
val = self._rk
|
||||
elif key == "rk" or key == "section":
|
||||
val = self._section
|
||||
elif key == "speed":
|
||||
val = self._speed
|
||||
elif key == "discharge":
|
||||
@@ -175,9 +279,9 @@ class Data(SQLSubModel):
|
||||
return val
|
||||
|
||||
def _update_get_min(self):
|
||||
profile = self._reach.reach.get_profiles_from_rk(self._rk)
|
||||
if len(profile) > 0:
|
||||
min = profile[0].z_min()
|
||||
profile = self._section
|
||||
if profile is not None:
|
||||
min = profile.z_min()
|
||||
else:
|
||||
min = 0.0
|
||||
|
||||
@@ -204,8 +308,8 @@ class Data(SQLSubModel):
|
||||
self._name = str(value)
|
||||
elif key == "comment":
|
||||
self._comment = str(value)
|
||||
elif key == "rk":
|
||||
self._rk = float(value)
|
||||
elif key == "rk" or key == "section":
|
||||
self._section = value
|
||||
self._update_from_rk()
|
||||
elif key == "speed":
|
||||
# Not supposed to be modified
|
||||
@@ -220,7 +324,7 @@ class Data(SQLSubModel):
|
||||
self._height = float(value)
|
||||
self._update_from_height()
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
|
||||
class InitialConditions(SQLSubModel):
|
||||
@@ -241,8 +345,8 @@ class InitialConditions(SQLSubModel):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
@@ -270,8 +374,18 @@ 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
|
||||
@@ -283,49 +397,69 @@ class InitialConditions(SQLSubModel):
|
||||
@reach.setter
|
||||
def reach(self, new):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
return list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
|
||||
@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._status.modified()
|
||||
self.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = Data(reach=self._reach, status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def new_from_data(self, rk, discharge, elevation):
|
||||
n = Data(reach=self._reach, status=self._status)
|
||||
|
||||
n['rk'] = rk
|
||||
section = reduce(
|
||||
lambda acc, s: (
|
||||
s if s.rk == rk else acc
|
||||
),
|
||||
self._reach.reach.profiles,
|
||||
None
|
||||
)
|
||||
|
||||
n['section'] = section
|
||||
n['discharge'] = discharge
|
||||
n['elevation'] = elevation
|
||||
|
||||
return n
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
if data in self._data:
|
||||
data.set_as_not_deleted()
|
||||
else:
|
||||
self._data.insert(index, data)
|
||||
|
||||
self.modified()
|
||||
|
||||
def delete(self, data):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda x: x not in data,
|
||||
self._data
|
||||
list(
|
||||
map(
|
||||
lambda x: x.set_as_deleted(),
|
||||
filter(
|
||||
lambda x: x in data,
|
||||
self._data
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
data = list(
|
||||
@@ -341,18 +475,23 @@ class InitialConditions(SQLSubModel):
|
||||
|
||||
def sort(self, reverse=False, key=None):
|
||||
self._data.sort(reverse=reverse, key=key)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def _data_get(self, key):
|
||||
return list(
|
||||
map(
|
||||
lambda d: d[key],
|
||||
self._data
|
||||
self.data
|
||||
)
|
||||
)
|
||||
|
||||
def get_rk(self):
|
||||
return self._data_get("rk")
|
||||
return list(
|
||||
map(
|
||||
lambda d: d["rk"].rk,
|
||||
self.data
|
||||
)
|
||||
)
|
||||
|
||||
def get_elevation(self):
|
||||
return self._data_get("elevation")
|
||||
@@ -360,12 +499,13 @@ 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 = {}
|
||||
@@ -375,13 +515,17 @@ class InitialConditions(SQLSubModel):
|
||||
data_discharge[profile.rk] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_discharge[data["rk"]] = data["discharge"]
|
||||
data_discharge[data["rk"].rk] = data["discharge"]
|
||||
|
||||
incline = self._reach.reach.get_incline_median_mean()
|
||||
logger.debug(f"incline = {incline}")
|
||||
self._data = []
|
||||
for profile in profiles:
|
||||
|
||||
self.clear_data()
|
||||
|
||||
for profile in reversed(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)
|
||||
frictions = self._reach.frictions.frictions
|
||||
strickler = None
|
||||
if frictions is not None:
|
||||
@@ -396,9 +540,12 @@ class InitialConditions(SQLSubModel):
|
||||
discharge = data_discharge[profile.rk]
|
||||
else:
|
||||
discharge = (
|
||||
((width * 0.8)
|
||||
* strickler
|
||||
* (height ** (5/3))
|
||||
# ((width * 0.8)
|
||||
# * strickler
|
||||
# * (height ** (5/3))
|
||||
# * (abs(incline) ** (0.5)))
|
||||
(area * strickler
|
||||
* (radius ** (2/3))
|
||||
* (abs(incline) ** (0.5)))
|
||||
)
|
||||
|
||||
@@ -413,21 +560,17 @@ class InitialConditions(SQLSubModel):
|
||||
logger.debug(f" discharge = {discharge}")
|
||||
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["rk"] = profile.rk
|
||||
new["rk"] = profile
|
||||
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
|
||||
|
||||
@@ -438,31 +581,41 @@ class InitialConditions(SQLSubModel):
|
||||
data_height[profile.rk] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_height[data["rk"]] = data["height"]
|
||||
data_height[data["rk"].rk] = data["height"]
|
||||
|
||||
print(data_height)
|
||||
|
||||
incline = self._reach.reach.get_incline_median_mean()
|
||||
logger.debug(f"incline = {incline}")
|
||||
self._data = []
|
||||
for profile in profiles:
|
||||
|
||||
self.clear_data()
|
||||
|
||||
for profile in reversed(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
|
||||
|
||||
if not compute_height:
|
||||
height = data_height[profile.rk]
|
||||
else:
|
||||
height = (
|
||||
discharge
|
||||
/
|
||||
((width * 0.8) * strickler * (abs(incline) ** (0.5)))
|
||||
) ** (0.6)
|
||||
if abs(incline) <= 0:
|
||||
height = 0.0
|
||||
else:
|
||||
height = (
|
||||
discharge
|
||||
/ ((width * 0.8)
|
||||
* strickler
|
||||
* (abs(incline) ** (0.5)))
|
||||
) ** (0.6)
|
||||
|
||||
elevation = max(
|
||||
profile.z_min() + height,
|
||||
@@ -475,15 +628,13 @@ class InitialConditions(SQLSubModel):
|
||||
logger.debug(f" height = {height}")
|
||||
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["rk"] = profile.rk
|
||||
new["section"] = profile
|
||||
new["discharge"] = discharge
|
||||
new["elevation"] = elevation
|
||||
|
||||
previous_elevation = elevation
|
||||
self._data.append(new)
|
||||
|
||||
self._data.reverse()
|
||||
|
||||
def generate_height(self,
|
||||
elevation1: float,
|
||||
elevation2: float,
|
||||
@@ -499,9 +650,9 @@ class InitialConditions(SQLSubModel):
|
||||
data_discharge[profile.rk] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_discharge[data["rk"]] = data["discharge"]
|
||||
data_discharge[data["rk"].rk] = data["discharge"]
|
||||
|
||||
self._data = []
|
||||
self.clear_data()
|
||||
for profile in profiles:
|
||||
|
||||
if not compute_discharge:
|
||||
@@ -512,7 +663,7 @@ class InitialConditions(SQLSubModel):
|
||||
[upstream_rk, downstream_rk],
|
||||
[elevation1, elevation2])
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["rk"] = profile.rk
|
||||
new["rk"] = profile
|
||||
new["discharge"] = d
|
||||
new["elevation"] = elevation
|
||||
self._data.append(new)
|
||||
@@ -1,5 +1,5 @@
|
||||
# InitialConditionsDict.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -64,7 +64,8 @@ class InitialConditionsDict(PamhyrModelDict):
|
||||
|
||||
execute(
|
||||
"DELETE FROM initial_conditions " +
|
||||
f"WHERE reach = '{reach.id}'"
|
||||
f"WHERE reach = '{reach.id}' " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
ok &= self._dict[reach]._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# InitialConditionsAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
@@ -34,19 +35,17 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
_sub_classes = [
|
||||
ICAdisTSSpec,
|
||||
]
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
pollutant: int = -1, status=None):
|
||||
super(InitialConditionsAdisTS, self).__init__()
|
||||
pollutant: int = -1, status=None,
|
||||
owner_scenario=-1):
|
||||
super(InitialConditionsAdisTS, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = InitialConditionsAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._pollutant = pollutant
|
||||
self._concentration = None
|
||||
@@ -56,81 +55,159 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
InitialConditionsAdisTS._id_cnt = max(
|
||||
InitialConditionsAdisTS._id_cnt + 1,
|
||||
self.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
)
|
||||
""")
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == 0 and minor < 1:
|
||||
created = False
|
||||
|
||||
if major == "0" and int(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 id, pollutant, name, concentration, eg, em, ed, " +
|
||||
"enabled " +
|
||||
"FROM initial_conditions_adists"
|
||||
"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))})"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
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)
|
||||
it = iter(row)
|
||||
|
||||
IC = cls(
|
||||
id=IC_id,
|
||||
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,
|
||||
name=name,
|
||||
status=data['status']
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
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'] = IC_id
|
||||
IC._data = ICAdisTSSpec._db_load(execute, data)
|
||||
data['ic_default_id'] = pid
|
||||
ic._data = ICAdisTSSpec._db_load(execute, data)
|
||||
|
||||
new.append(IC)
|
||||
loaded.add(pid)
|
||||
new.append(ic)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(f"DELETE FROM initial_conditions_adists WHERE id = {self.id}")
|
||||
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}"
|
||||
)
|
||||
|
||||
pollutant = -1
|
||||
if self.pollutant is not None:
|
||||
@@ -155,12 +232,14 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"initial_conditions_adists(" +
|
||||
"id, pollutant, name, concentration, " +
|
||||
"eg, em, ed, enabled" +
|
||||
"pamhyr_id, deleted, pollutant, name, concentration, " +
|
||||
"eg, em, ed, enabled, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {pollutant}, '{self._db_format(self._name)}', " +
|
||||
f"{concentration}, {eg}, {em}, {ed}, {self._enabled}" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
|
||||
@@ -168,8 +247,9 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
|
||||
data['ic_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM initial_conditions_spec " +
|
||||
f"WHERE ic_default = {self.id}"
|
||||
"DELETE FROM initial_conditions_adists_spec " +
|
||||
f"WHERE ic_default = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for ic_spec in self._data:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# InitialConditionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTS \
|
||||
import InitialConditionsAdisTS
|
||||
|
||||
@@ -43,7 +43,10 @@ class InitialConditionsAdisTSList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM initial_conditions_adists")
|
||||
execute(
|
||||
"DELETE FROM initial_conditions_adists " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# InitialConditionsAdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,14 +22,13 @@ 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 = [
|
||||
]
|
||||
_id_cnt = 0
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
@@ -37,11 +36,6 @@ 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
|
||||
@@ -53,73 +47,142 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
self._rate = None
|
||||
self._enabled = True
|
||||
|
||||
ICAdisTSSpec._id_cnt = max(ICAdisTSSpec._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
)
|
||||
""")
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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 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']} "
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
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)
|
||||
it = iter(row)
|
||||
|
||||
# new_spec = [name, reach, start_rk, end_rk,
|
||||
# concentration, eg, em, ed, rate, enabled]
|
||||
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 = cls(
|
||||
id=id,
|
||||
name=name,
|
||||
status=data['status']
|
||||
id=id, name=name,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
new_spec.set_as_deleted()
|
||||
|
||||
new_spec.reach = reach
|
||||
new_spec.start_rk = start_rk
|
||||
@@ -131,30 +194,37 @@ 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_spec(id, ic_default, name, reach, " +
|
||||
"start_rk, end_rk, concentration, eg, em, ed, rate, enabled) " +
|
||||
"initial_conditions_adists_spec(id, deleted, " +
|
||||
"ic_default, name, reach, " +
|
||||
"start_rk, end_rk, concentration, eg, em, ed, rate, " +
|
||||
"enabled, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"{ic_default}, " +
|
||||
f"'{self._db_format(self._name_section)}', " +
|
||||
f"{self._reach}, " +
|
||||
f"{self._start_rk}, " +
|
||||
f"{self._end_rk}, " +
|
||||
f"{self._start_rk}, {self._end_rk}, " +
|
||||
f"{self._concentration}, " +
|
||||
f"{self._eg}, " +
|
||||
f"{self._em}, " +
|
||||
f"{self._ed}, " +
|
||||
f"{self._rate}, " +
|
||||
f"{self._enabled}" +
|
||||
f"{self._eg}, {self._em}, {self._ed}, " +
|
||||
f"{self._rate}, {self._enabled}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# LateralContribution.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,66 +27,226 @@ from tools import (
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class LateralContribution(SQLSubModel):
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(LateralContribution, self).__init__()
|
||||
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._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = LateralContribution._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._name = name
|
||||
self._type = ""
|
||||
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)
|
||||
self._types = types
|
||||
self._data = [data0, data1]
|
||||
|
||||
@classmethod
|
||||
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,
|
||||
edge INTEGER,
|
||||
begin_rk REAL NOT NULL,
|
||||
end_rk REAL NOT NULL,
|
||||
FOREIGN KEY(edge) REFERENCES river_reach(id)
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE lateral_contribution_data(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
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,
|
||||
FOREIGN KEY(lc) REFERENCES lateral_contribution(id)
|
||||
{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):
|
||||
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]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._type = ""
|
||||
self._reach = None
|
||||
self._begin_section = None
|
||||
self._end_section = None
|
||||
self._data = []
|
||||
self._header = []
|
||||
self._types = [float, float]
|
||||
|
||||
@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,
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
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) < 11:
|
||||
@@ -103,7 +263,45 @@ class LateralContribution(SQLSubModel):
|
||||
"""
|
||||
)
|
||||
|
||||
return True
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def _get_ctor_from_type(cls, t):
|
||||
@@ -124,84 +322,142 @@ 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 id, name, type, edge, begin_rk, end_rk " +
|
||||
f"FROM lateral_contribution WHERE tab = '{tab}'"
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
for row in table:
|
||||
t = row[2]
|
||||
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)
|
||||
|
||||
ctor = cls._get_ctor_from_type(t)
|
||||
lc = ctor(
|
||||
id=row[0],
|
||||
name=row[1],
|
||||
status=data['status']
|
||||
id=pid, name=name,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
lc.edge = None
|
||||
lc.reach = None
|
||||
lc._begin_section = None
|
||||
lc._end_section = None
|
||||
|
||||
if row[3] != -1:
|
||||
lc.edge = next(filter(lambda e: e.id == row[3], data["edges"]))
|
||||
lc._begin_rk = row[4]
|
||||
lc._end_rk = row[5]
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
data["lc"] = lc
|
||||
lc._data = Data._db_load(execute, data=data)
|
||||
|
||||
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(f"DELETE FROM lateral_contribution WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM lateral_contribution_data WHERE lc = {self.id}")
|
||||
execute(
|
||||
"DELETE FROM lateral_contribution_data " +
|
||||
f"WHERE lc = {self._pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
edge = -1
|
||||
if self._edge is not None:
|
||||
edge = self._edge.id
|
||||
reach = -1
|
||||
begin_section = -1
|
||||
end_section = -1
|
||||
|
||||
sql = (
|
||||
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(
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution(id, name, type, tab, " +
|
||||
"edge, begin_rk, end_rk) " +
|
||||
"lateral_contribution(" +
|
||||
"pamhyr_id, deleted, name, type, tab, " +
|
||||
"reach, begin_section, end_section, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._type)}', '{tab}', {edge}, " +
|
||||
f"{self._begin_rk}, {self._end_rk}" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data0 = self._db_format(str(d[0]))
|
||||
data1 = self._db_format(str(d[1]))
|
||||
data["ind"] = ind
|
||||
|
||||
d._db_save(execute, data)
|
||||
|
||||
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(self._data)
|
||||
return len(
|
||||
list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def compatibility(cls):
|
||||
@@ -226,67 +482,66 @@ class LateralContribution(SQLSubModel):
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"L{self.id + 1}"
|
||||
return f"LC #{self.id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def lctype(self):
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def edge(self):
|
||||
return self._edge
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@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()
|
||||
@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]
|
||||
|
||||
def has_edge(self):
|
||||
return self._edge is not None
|
||||
self.modified()
|
||||
|
||||
def has_reach(self):
|
||||
return self._reach is not None
|
||||
|
||||
@property
|
||||
def begin_rk(self):
|
||||
return self._begin_rk
|
||||
if self._begin_section is None:
|
||||
return 0
|
||||
|
||||
@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()
|
||||
return self._begin_section.rk
|
||||
|
||||
if _min <= begin_rk <= _max:
|
||||
self._begin_rk = begin_rk
|
||||
@property
|
||||
def begin_section(self):
|
||||
return self._begin_section
|
||||
|
||||
self._status.modified()
|
||||
@begin_section.setter
|
||||
def begin_section(self, section):
|
||||
self._begin_section = section
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
return self._end_rk
|
||||
if self._end_section is None:
|
||||
return 0
|
||||
|
||||
@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()
|
||||
return self._end_section.rk
|
||||
|
||||
if _min <= end_rk <= _max:
|
||||
self._end_rk = end_rk
|
||||
@property
|
||||
def end_section(self):
|
||||
return self._end_section
|
||||
|
||||
self._status.modified()
|
||||
@end_section.setter
|
||||
def end_section(self, section):
|
||||
self._end_section = section
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
@@ -294,7 +549,12 @@ class LateralContribution(SQLSubModel):
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
return list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
|
||||
def get_type_column(self, column):
|
||||
if 0 <= column < 2:
|
||||
@@ -328,17 +588,17 @@ class LateralContribution(SQLSubModel):
|
||||
new_0 = self._types[0](data[0].replace(",", "."))
|
||||
new_1 = self._types[1](data[1].replace(",", "."))
|
||||
|
||||
return (new_0, new_1)
|
||||
return Data(new_0, new_1, status=self._status)
|
||||
|
||||
def add(self, index: int):
|
||||
value = (self._default_0, self._default_1)
|
||||
value = Data(self._default_0, self._default_1, status=self._status)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data = list(
|
||||
@@ -350,7 +610,7 @@ class LateralContribution(SQLSubModel):
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
self._data = list(
|
||||
@@ -359,14 +619,14 @@ class LateralContribution(SQLSubModel):
|
||||
self.data
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def get_i(self, index):
|
||||
return self.data[index]
|
||||
@@ -378,10 +638,10 @@ class LateralContribution(SQLSubModel):
|
||||
return lst
|
||||
|
||||
def _set_i_c_v(self, index, column, value):
|
||||
v = list(self._data[index])
|
||||
v = self._data[index]
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = tuple(v)
|
||||
self._status.modified()
|
||||
self._data[index] = v
|
||||
self.modified()
|
||||
|
||||
def set_i_0(self, index: int, value):
|
||||
self._set_i_c_v(index, 0, value)
|
||||
@@ -392,9 +652,9 @@ class LateralContribution(SQLSubModel):
|
||||
@timer
|
||||
def convert(self, cls):
|
||||
new = cls(name=self.name, status=self._status)
|
||||
new.edge = self.edge
|
||||
new.begin_rk = self.begin_rk
|
||||
new.end_rk = self.end_rk
|
||||
new.reach = self.reach
|
||||
new.begin_section = self.begin_section
|
||||
new.end_section = self.end_section
|
||||
|
||||
for i, _ in enumerate(self.data):
|
||||
new.add(i)
|
||||
@@ -408,7 +668,7 @@ class LateralContribution(SQLSubModel):
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return new
|
||||
|
||||
def move_up(self, index):
|
||||
@@ -416,11 +676,11 @@ class LateralContribution(SQLSubModel):
|
||||
next = index - 1
|
||||
d = self._data
|
||||
d[index], d[next] = d[next], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
d = self._data
|
||||
d[index], d[prev] = d[prev], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# LateralContributionList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelListWithTab
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelListWithTab
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.LateralContribution.LateralContribution import LateralContribution
|
||||
@@ -50,7 +50,10 @@ class LateralContributionList(PamhyrModelListWithTab):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM lateral_contribution")
|
||||
execute(
|
||||
"DELETE FROM lateral_contribution " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# LateralContributionTypes.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,8 +22,12 @@ from Model.LateralContribution.LateralContribution import LateralContribution
|
||||
|
||||
|
||||
class NotDefined(LateralContribution):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(NotDefined, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "ND"
|
||||
self._header = ["x", "y"]
|
||||
@@ -34,8 +38,12 @@ class NotDefined(LateralContribution):
|
||||
|
||||
|
||||
class LateralContrib(LateralContribution):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(LateralContrib, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "LC"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -47,8 +55,12 @@ class LateralContrib(LateralContribution):
|
||||
|
||||
|
||||
class Rain(LateralContribution):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(Rain, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "RA"
|
||||
self._header = ["time", "discharge"]
|
||||
@@ -60,8 +72,12 @@ class Rain(LateralContribution):
|
||||
|
||||
|
||||
class Evaporation(LateralContribution):
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(Evaporation, self).__init__(id=id, name=name, status=status)
|
||||
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
|
||||
)
|
||||
|
||||
self._type = "EV"
|
||||
self._header = ["time", "discharge"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# LateralContributionAdisTS.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,137 +27,390 @@ from tools import (
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class LateralContributionAdisTS(SQLSubModel):
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
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]
|
||||
|
||||
def __init__(self, id: int = -1, pollutant: int = -1,
|
||||
name: str = "", status=None):
|
||||
super(LateralContributionAdisTS, self).__init__()
|
||||
name: str = "", status=None,
|
||||
owner_scenario=-1):
|
||||
super(LateralContributionAdisTS, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = LateralContributionAdisTS._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._pollutant = pollutant
|
||||
self._edge = None
|
||||
self._reach = 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):
|
||||
execute("""
|
||||
CREATE TABLE lateral_contribution_adists(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
pollutant INTEGER NOT NULL,
|
||||
edge INTEGER NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
begin_rk REAL NOT NULL,
|
||||
end_rk REAL NOT NULL,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
|
||||
FOREIGN KEY(edge) REFERENCES river_reach(id)
|
||||
{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)
|
||||
)
|
||||
""")
|
||||
|
||||
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)
|
||||
)
|
||||
""")
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
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 = "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 id, pollutant, edge, begin_rk, end_rk " +
|
||||
"FROM lateral_contribution_adists"
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
lc = cls(
|
||||
id=row[0],
|
||||
pollutant=row[1],
|
||||
status=data['status']
|
||||
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
|
||||
)
|
||||
if deleted:
|
||||
lca.set_as_deleted()
|
||||
|
||||
lc.edge = row[2]
|
||||
lc.begin_rk = row[3]
|
||||
lc.end_rk = row[4]
|
||||
lca.reach = reach
|
||||
lca.begin_rk = brk
|
||||
lca.end_rk = erk
|
||||
|
||||
values = execute(
|
||||
"SELECT data0," +
|
||||
" data1 FROM lateral_contribution_data_adists " +
|
||||
f"WHERE lc = '{lc.id}'"
|
||||
)
|
||||
data["lca"] = lca
|
||||
lca._data = Data._db_load(execute, data=data)
|
||||
|
||||
# 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))
|
||||
loaded.add(pid)
|
||||
new.append(lca)
|
||||
|
||||
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
|
||||
|
||||
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(
|
||||
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}"
|
||||
)
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution_adists(id, " +
|
||||
"pollutant, edge, begin_rk, end_rk) " +
|
||||
"lateral_contribution_adists(id, deleted," +
|
||||
"pollutant, reach, begin_rk, end_rk, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._pollutant}, {self.edge}, " +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"{self._pollutant}, {self.reach}, " +
|
||||
f"{self._begin_rk}, {self._end_rk}" +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data0 = self._db_format(str(d[0]))
|
||||
data1 = self._db_format(str(d[1]))
|
||||
data["ind"] = ind
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution_data_adists(data0, data1, lc) " +
|
||||
f"VALUES ('{data0}', {data1}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
d._db_save(execute, data)
|
||||
|
||||
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)
|
||||
return len(
|
||||
list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def time_convert(cls, data):
|
||||
@@ -176,13 +429,13 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
return int(data)
|
||||
|
||||
@property
|
||||
def edge(self):
|
||||
return self._edge
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@edge.setter
|
||||
def edge(self, edge):
|
||||
self._edge = edge
|
||||
self._status.modified()
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
@@ -191,7 +444,7 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
@header.setter
|
||||
def header(self, header):
|
||||
self._header = header
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
@@ -208,7 +461,7 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
@begin_rk.setter
|
||||
def begin_rk(self, begin_rk):
|
||||
self._begin_rk = begin_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
@@ -217,4 +470,4 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# LateralContributionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.LateralContributionsAdisTS.LateralContributionAdisTS \
|
||||
@@ -45,8 +45,14 @@ class LateralContributionsAdisTSList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM lateral_contribution_adists")
|
||||
execute("DELETE FROM lateral_condition_data_adists")
|
||||
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}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
+16
-22
@@ -1,5 +1,5 @@
|
||||
# Edge.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -16,28 +16,18 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Network.Node import Node
|
||||
|
||||
|
||||
class Edge(object):
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int, name: str,
|
||||
class Edge(SQLSubModel):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
node1: Node = None,
|
||||
node2: Node = None,
|
||||
status=None):
|
||||
super(Edge, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
type(self)._id_cnt += 1
|
||||
self.id = type(self)._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
**kwargs):
|
||||
super(Edge, self).__init__(id=id, **kwargs)
|
||||
|
||||
self._name = name
|
||||
|
||||
self.node1 = node1
|
||||
self.node2 = node2
|
||||
|
||||
@@ -49,7 +39,7 @@ class Edge(object):
|
||||
if name == "name":
|
||||
ret = self.name
|
||||
elif name == "id":
|
||||
ret = self.id
|
||||
ret = self.pamhyr_id
|
||||
elif name == "node1":
|
||||
ret = self.node1
|
||||
elif name == "node2":
|
||||
@@ -63,7 +53,7 @@ class Edge(object):
|
||||
if name == "name":
|
||||
self._name = value
|
||||
elif name == "id":
|
||||
self.id = value
|
||||
self.pamhyr_id = value
|
||||
elif name == "node1":
|
||||
self.node1 = value
|
||||
elif name == "node2":
|
||||
@@ -71,7 +61,11 @@ class Edge(object):
|
||||
elif name == "enable":
|
||||
self._enable = value
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.pamhyr_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -85,14 +79,14 @@ class Edge(object):
|
||||
|
||||
def enable(self, enable=True):
|
||||
self._enable = enable
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def disable(self):
|
||||
self._enable = False
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def reverse(self):
|
||||
tmp = self.node1
|
||||
self.node1 = self.node2
|
||||
self.node2 = tmp
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
+40
-34
@@ -1,5 +1,5 @@
|
||||
# Graph.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -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(object):
|
||||
def __init__(self, status=None):
|
||||
super(Graph, self).__init__()
|
||||
|
||||
self._status = status
|
||||
class Graph(SQLSubModel):
|
||||
def __init__(self, **kwargs):
|
||||
super(Graph, self).__init__(**kwargs)
|
||||
|
||||
self._node_ctor = Node
|
||||
self._edge_ctor = Edge
|
||||
@@ -38,13 +38,13 @@ class Graph(object):
|
||||
return f"Graph {{nodes: {self._nodes}, edges: {self._edges}}}"
|
||||
|
||||
def nodes(self):
|
||||
return self._nodes
|
||||
return list(filter(lambda n: not n.is_deleted(), 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 self._edges
|
||||
return list(filter(lambda e: not e.is_deleted(), self._edges))
|
||||
|
||||
def enable_edges(self):
|
||||
return list(
|
||||
@@ -54,44 +54,44 @@ class Graph(object):
|
||||
def _enable_edges(self):
|
||||
"""Return a generator"""
|
||||
return filter(
|
||||
lambda e: e.is_enable(),
|
||||
self._edges
|
||||
lambda e: e.is_enable() and not e.is_deleted(),
|
||||
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(object):
|
||||
node = list(
|
||||
filter(
|
||||
lambda n: n.name == node_name,
|
||||
self._nodes
|
||||
self.nodes()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -112,7 +112,7 @@ class Graph(object):
|
||||
edge = list(
|
||||
filter(
|
||||
lambda e: e.name == edge_name,
|
||||
self._edges
|
||||
self.edges()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -121,18 +121,23 @@ class Graph(object):
|
||||
|
||||
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(
|
||||
-1,
|
||||
"",
|
||||
x=x, y=y,
|
||||
id=-1, name="", x=x, y=y,
|
||||
status=self._status
|
||||
)
|
||||
return node
|
||||
|
||||
def _add_node(self, node):
|
||||
self._nodes.append(node)
|
||||
self._status.modified()
|
||||
if node in self._nodes:
|
||||
node.set_as_not_deleted()
|
||||
else:
|
||||
self._nodes.append(node)
|
||||
|
||||
self.modified()
|
||||
return node
|
||||
|
||||
def add_node(self, x: float = 0.0, y: float = 0.0):
|
||||
@@ -143,9 +148,9 @@ class Graph(object):
|
||||
return self._add_node(node)
|
||||
|
||||
def remove_node(self, node):
|
||||
self._nodes.remove(node)
|
||||
node.set_as_deleted()
|
||||
self._remove_associated_edge(node)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def _remove_associated_edge(self, node: str):
|
||||
edges = list(
|
||||
@@ -164,8 +169,7 @@ class Graph(object):
|
||||
|
||||
def _create_edge(self, n1: Node, n2: Node):
|
||||
edge = self._edge_ctor(
|
||||
-1,
|
||||
"", n1, n2,
|
||||
node1=n1, node2=n2,
|
||||
status=self._status
|
||||
)
|
||||
return edge
|
||||
@@ -176,10 +180,12 @@ class Graph(object):
|
||||
# e.node2 == edge.node2),
|
||||
# self._edges)):
|
||||
# return None
|
||||
if edge in self._edges:
|
||||
edge.set_as_not_deleted()
|
||||
else:
|
||||
self._edges.append(edge)
|
||||
|
||||
self._edges.append(edge)
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
return edge
|
||||
|
||||
def add_edge(self, n1: Node, n2: Node):
|
||||
@@ -193,8 +199,8 @@ class Graph(object):
|
||||
return self._create_edge(n1, n2)
|
||||
|
||||
def remove_edge(self, edge):
|
||||
self._edges.remove(edge)
|
||||
self._status.modified()
|
||||
edge.set_as_deleted()
|
||||
self.modified()
|
||||
|
||||
def is_upstream_node(self, node):
|
||||
return reduce(
|
||||
|
||||
+10
-19
@@ -1,5 +1,5 @@
|
||||
# Node.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -16,24 +16,15 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Network.Point import Point
|
||||
|
||||
|
||||
class Node(object):
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int, name: str,
|
||||
class Node(SQLSubModel):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
x: float = 0.0, y: float = 0.0,
|
||||
status=None):
|
||||
super(Node, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
type(self)._id_cnt += 1
|
||||
self.id = type(self)._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
**kwargs):
|
||||
super(Node, self).__init__(id=id, **kwargs)
|
||||
|
||||
self._name = name
|
||||
self.pos = Point(x, y)
|
||||
@@ -44,7 +35,7 @@ class Node(object):
|
||||
if key == "name":
|
||||
ret = self._name
|
||||
elif key == "id":
|
||||
ret = self.id
|
||||
ret = self.pamhyr_id
|
||||
elif key == "pos":
|
||||
ret = f"({self.pos.x},{self.pos.y})"
|
||||
|
||||
@@ -54,9 +45,9 @@ class Node(object):
|
||||
if key == "name":
|
||||
self._name = value
|
||||
elif key == "id":
|
||||
self.id = value
|
||||
self.pamhyr_id = value
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -76,4 +67,4 @@ class Node(object):
|
||||
def setPos(self, x, y):
|
||||
self.pos.x = x
|
||||
self.pos.y = y
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# Point.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# __init__.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# test_Network.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@ import os
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Status import StudyStatus
|
||||
|
||||
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 = SavedStatus()
|
||||
status = StudyStatus()
|
||||
g = Graph(status=status)
|
||||
|
||||
return g
|
||||
@@ -60,6 +60,9 @@ 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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# OutputRKAdists.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,33 +27,27 @@ 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):
|
||||
super(OutputRKAdists, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = OutputRKAdists._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
rk=None, title: str = "", status=None,
|
||||
owner_scenario=-1):
|
||||
super(OutputRKAdists, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._reach = reach
|
||||
self._rk = rk
|
||||
self._title = str(title)
|
||||
self._enabled = True
|
||||
|
||||
OutputRKAdists._id_cnt = max(
|
||||
OutputRKAdists._id_cnt + 1, self.id)
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
@@ -82,70 +76,129 @@ class OutputRKAdists(SQLSubModel):
|
||||
self._status.modified()
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
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)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
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 = "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"]
|
||||
status = data["status"]
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT id, reach, rk, title " +
|
||||
f"FROM OutputRKAdists"
|
||||
"SELECT pamhyr_id, deleted, reach, rk, title, scenario " +
|
||||
f"FROM output_rk_adists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
id_reach = row[1]
|
||||
id_rk = row[2]
|
||||
title = row[3]
|
||||
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)
|
||||
|
||||
new_output = cls(
|
||||
id=id, reach=id_reach,
|
||||
id=pid, reach=id_reach,
|
||||
rk=id_rk, title=title,
|
||||
status=status
|
||||
status=status,
|
||||
owner_scenario=owner_scenario,
|
||||
)
|
||||
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(f"DELETE FROM OutputRKAdists WHERE id = {self.id}")
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"OutputRKAdists(id, reach, rk, title) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._reach}, {self._rk}, " +
|
||||
f"'{self._db_format(self._title)}'" +
|
||||
")"
|
||||
execute(
|
||||
"DELETE FROM output_rk_adists " +
|
||||
f"WHERE pamhyr_id = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id} "
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"output_rk_adists(pamhyr_id, deleted, " +
|
||||
"reach, rk, title, scenario) " +
|
||||
"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}" +
|
||||
")"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# OutputRKListAdists.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.OutputRKAdists.OutputRKAdists import OutputRKAdists
|
||||
|
||||
|
||||
@@ -38,7 +38,10 @@ class OutputRKAdistsList(PamhyrModelList):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM OutputRKAdists")
|
||||
execute(
|
||||
"DELETE FROM output_rk_adists " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Pollutants.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -27,32 +27,273 @@ from tools import (
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Pollutants(SQLSubModel):
|
||||
class PollutantCharacteristics(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "", status=None):
|
||||
super(Pollutants, self).__init__()
|
||||
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
|
||||
|
||||
if id == -1:
|
||||
self.id = Pollutants._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
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
|
||||
|
||||
self._name = str(name)
|
||||
# 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]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
if name is None or name == "":
|
||||
self.name = f"pol{self.id}"
|
||||
else:
|
||||
self._name = str(name)
|
||||
self._enabled = True
|
||||
|
||||
self._data = []
|
||||
|
||||
Pollutants._id_cnt = max(
|
||||
Pollutants._id_cnt + 1, self.id)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
@@ -64,106 +305,151 @@ class Pollutants(SQLSubModel):
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
return self._data
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE Pollutants(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
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()}
|
||||
)
|
||||
""")
|
||||
|
||||
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)
|
||||
)
|
||||
""")
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
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 = "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 id, name " +
|
||||
f"FROM Pollutants"
|
||||
"SELECT pamhyr_id, deleted, name FROM pollutants " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
|
||||
new_pollutant = cls(
|
||||
id=id, name=name,
|
||||
id=pid, name=name,
|
||||
status=status
|
||||
)
|
||||
if deleted:
|
||||
new_pollutant.set_as_deleted()
|
||||
|
||||
new_data = []
|
||||
table = execute(
|
||||
"SELECT * " +
|
||||
"FROM Pollutants_characteristics " +
|
||||
f"WHERE pollutant = {id}"
|
||||
data["pollutant"] = new_pollutant
|
||||
new_pollutant._data = PollutantCharacteristics._db_load(
|
||||
execute, data.copy()
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for t in table:
|
||||
new_data = t[1:-1]
|
||||
|
||||
new_pollutant._data.append(new_data)
|
||||
|
||||
loaded.add(pid)
|
||||
new.append(new_pollutant)
|
||||
|
||||
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(f"DELETE FROM Pollutants WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM Pollutants_characteristics" +
|
||||
f" WHERE pollutant = {self.id}")
|
||||
ok = True
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"DELETE FROM pollutants " +
|
||||
f"WHERE pamhyr_id = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
execute(
|
||||
f"DELETE FROM pollutants_characteristics " +
|
||||
f"WHERE pollutant = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"Pollutants(id, name) " +
|
||||
"pollutants(pamhyr_id, deleted, name, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"'{self._db_format(self._name)}'" +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
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)
|
||||
|
||||
for d in self._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
|
||||
d._data_traversal(predicate, modifier, data)
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -178,8 +464,6 @@ class Pollutants(SQLSubModel):
|
||||
return len(self._data) != 0
|
||||
|
||||
def new_from_data(self, data):
|
||||
|
||||
print("from_data before : ", data)
|
||||
try:
|
||||
new = [int(data[0])]
|
||||
new += [float(d) for d in data[1:]]
|
||||
@@ -187,8 +471,6 @@ class Pollutants(SQLSubModel):
|
||||
logger.error(e)
|
||||
new = None
|
||||
|
||||
print("from_data after : ", new)
|
||||
|
||||
return new
|
||||
|
||||
def __len__(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# PollutantsList.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -38,7 +38,10 @@ class PollutantsList(PamhyrModelList):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM Pollutants")
|
||||
execute(
|
||||
"DELETE FROM Pollutants " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# REPLine.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -22,31 +22,25 @@ 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):
|
||||
super(REPLine, self).__init__()
|
||||
|
||||
if id == -1:
|
||||
self.id = REPLine._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._status = status
|
||||
status=None, owner_scenario=-1):
|
||||
super(REPLine, self).__init__(
|
||||
id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._enabled = enabled
|
||||
self._name = f"Line{self.id}" if name == "" else name
|
||||
self._name = f"Line #{self._pamhyr_id}" if name == "" else name
|
||||
self._line = line
|
||||
self._solvers = solvers
|
||||
|
||||
REPLine._id_cnt = max(id, REPLine._id_cnt+1)
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = None
|
||||
|
||||
@@ -113,35 +107,75 @@ class REPLine(SQLSubModel):
|
||||
self._status.modified()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE rep_lines(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE rep_lines{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
enabled BOOLEAN NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
line TEXT NOT NULL,
|
||||
solvers TEXT NOT NULL
|
||||
solvers 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):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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 id, enabled, name, line, solvers " +
|
||||
"FROM rep_lines"
|
||||
"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))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
@@ -149,32 +183,47 @@ 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']
|
||||
id=id, enabled=enabled,
|
||||
name=name, line=line, solvers=solvers,
|
||||
status=data['status'], owner_scenario=owner_scenario
|
||||
)
|
||||
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)
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"rep_lines(id, enabled, name, line, solvers) " +
|
||||
"rep_lines(pamhyr_id, enabled, deleted, " +
|
||||
"name, line, solvers, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._enabled}, " +
|
||||
f"{self._pamhyr_id}, {self._enabled}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._line)}', " +
|
||||
f"'{self._db_format(solvers)}'" +
|
||||
f"'{self._db_format(solvers)}', " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
@@ -1,5 +1,5 @@
|
||||
# REPLineList.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.REPLine.REPLine import REPLine
|
||||
|
||||
|
||||
@@ -38,7 +38,10 @@ class REPLineList(PamhyrModelList):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM rep_lines")
|
||||
execute(
|
||||
"DELETE FROM rep_lines " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for af in self._lst:
|
||||
ok &= af._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Reservoir.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,78 +19,295 @@
|
||||
import logging
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Reservoir(SQLSubModel):
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(Reservoir, self).__init__()
|
||||
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._status = status
|
||||
self._data = [elevation, surface]
|
||||
|
||||
if id == -1:
|
||||
self.id = Reservoir._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
@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]
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._node = None
|
||||
self._data = []
|
||||
|
||||
Reservoir._id_cnt = max(Reservoir._id_cnt + 1, self.id)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE reservoir(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE reservoir{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
FOREIGN KEY(node) REFERENCES river_node(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
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)
|
||||
)
|
||||
""")
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 5:
|
||||
cls._db_create(execute)
|
||||
return True
|
||||
|
||||
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}")
|
||||
|
||||
@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 id, name, node " +
|
||||
"FROM reservoir "
|
||||
"SELECT pamhyr_id, deleted, name, node, scenario " +
|
||||
"FROM reservoir " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
node_id = row[2]
|
||||
new_reservoir = cls(id, name, status=data["status"])
|
||||
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()
|
||||
|
||||
new_reservoir._node = None
|
||||
if node_id != -1:
|
||||
@@ -100,67 +317,88 @@ class Reservoir(SQLSubModel):
|
||||
)
|
||||
)
|
||||
|
||||
new_data = []
|
||||
table = execute(
|
||||
"SELECT elevation, surface " +
|
||||
"FROM reservoir_data " +
|
||||
f"WHERE reservoir = {id} " +
|
||||
"ORDER BY ind ASC"
|
||||
data["reservoir"] = new_reservoir
|
||||
new_reservoir._data = Data._db_load(
|
||||
execute, data=data
|
||||
)
|
||||
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(f"DELETE FROM reservoir WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM reservoir_data WHERE reservoir = {self.id}")
|
||||
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}"
|
||||
)
|
||||
|
||||
node_id = -1
|
||||
node_id = 'NULL'
|
||||
if self._node is not None:
|
||||
node_id = self._node.id
|
||||
node_id = self._node.pamhyr_id
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"reservoir(id, name, node) " +
|
||||
"reservoir(pamhyr_id, deleted, name, node, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{node_id}" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["reservoir"] = self
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"reservoir_data(ind, elevation, surface, reservoir) " +
|
||||
f"VALUES ({ind}, '{d[0]}', {d[1]}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
data["ind"] = ind
|
||||
|
||||
d._db_save(execute, data)
|
||||
|
||||
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"R{self.id + 1}"
|
||||
return f"Reservoir #{self.pamhyr_id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
@@ -169,7 +407,7 @@ class Reservoir(SQLSubModel):
|
||||
@node.setter
|
||||
def node(self, node):
|
||||
self._node = node
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def has_node(self):
|
||||
return self._node is not None
|
||||
@@ -202,42 +440,63 @@ class Reservoir(SQLSubModel):
|
||||
return (new_0, new_1)
|
||||
|
||||
def add(self, index: int):
|
||||
value = (self._default_elevation, self._default_surface)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
return value
|
||||
data = Data(
|
||||
elevation=self._default_elevation,
|
||||
surface=self._default_surface,
|
||||
status=self._status,
|
||||
)
|
||||
|
||||
self._data.insert(index, data)
|
||||
self.modified()
|
||||
return data
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
if type(value) is tuple or type(value) is list:
|
||||
value = Data(
|
||||
elevation=value[0],
|
||||
surface=value[1]
|
||||
)
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
|
||||
def hard_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._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda e: e not in els,
|
||||
self.data
|
||||
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._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
for el in els:
|
||||
el.set_as_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
self._data.sort(reverse=_reverse, key=lambda d: d[0])
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
self.modified()
|
||||
|
||||
def get_i(self, index):
|
||||
return self.data[index]
|
||||
@@ -246,13 +505,12 @@ class Reservoir(SQLSubModel):
|
||||
lst = []
|
||||
for r in _range:
|
||||
lst.append(r)
|
||||
|
||||
return lst
|
||||
|
||||
def _set_i_c_v(self, index, column, value):
|
||||
v = list(self._data[index])
|
||||
v[column] = value
|
||||
self._data[index] = tuple(v)
|
||||
self._status.modified()
|
||||
self._data[index][column] = value
|
||||
self.modified()
|
||||
|
||||
def set_i_elevation(self, index: int, value):
|
||||
self._set_i_c_v(index, 0, value)
|
||||
@@ -265,11 +523,11 @@ class Reservoir(SQLSubModel):
|
||||
next = index - 1
|
||||
d = self._data
|
||||
d[index], d[next] = d[next], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
d = self._data
|
||||
d[index], d[prev] = d[prev], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# ReservoirList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Reservoir.Reservoir import Reservoir
|
||||
|
||||
|
||||
@@ -39,8 +39,14 @@ class ReservoirList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM reservoir")
|
||||
execute("DELETE FROM reservoir_data")
|
||||
execute(
|
||||
"DELETE FROM reservoir " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM reservoir_data " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Results.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -14,32 +14,47 @@
|
||||
# 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 Results(object):
|
||||
def __init__(self, study=None, solver=None,
|
||||
class Results(SQLSubModel):
|
||||
_sub_classes = [River]
|
||||
|
||||
def __init__(self, id=-1, 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,
|
||||
}
|
||||
|
||||
if solver is not None:
|
||||
self.set("solver_type", solver._type)
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
date = self._meta_data["creation_date"]
|
||||
@@ -60,6 +75,13 @@ class Results(object):
|
||||
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]
|
||||
|
||||
@@ -69,3 +91,151 @@ class Results(object):
|
||||
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)
|
||||
|
||||
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}"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
return True
|
||||
@@ -1,5 +1,5 @@
|
||||
# Results.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# River.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -14,15 +14,27 @@
|
||||
# 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(object):
|
||||
class Profile(SQLSubModel):
|
||||
def __init__(self, profile, study):
|
||||
super(Profile, self).__init__(
|
||||
id=-1, status=study.status,
|
||||
owner_scenario=study.status.scenario.id
|
||||
)
|
||||
|
||||
self._study = study
|
||||
self._profile = profile # Source profile in the study
|
||||
self._data = {} # Dict of dict {<ts>: {<key>: <value>, ...}, ...}
|
||||
@@ -52,9 +64,11 @@ class Profile(object):
|
||||
return self._data[timestamp]
|
||||
|
||||
def get_key(self, key):
|
||||
return list(
|
||||
map(lambda ts: self._data[ts][key], self._data)
|
||||
res = list(
|
||||
map(lambda ts: self._data[ts][key],
|
||||
sorted(self._data.keys()))
|
||||
)
|
||||
return res
|
||||
|
||||
def get_ts_key(self, timestamp, key):
|
||||
if timestamp in self._data:
|
||||
@@ -65,15 +79,210 @@ class Profile(object):
|
||||
def has_sediment(self):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE results_data{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
result INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
section INTEGER NOT NULL,
|
||||
len_data INTEGER NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
|
||||
FOREIGN KEY(section)
|
||||
REFERENCES geometry_profileXYZ(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, result, key, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return True
|
||||
|
||||
@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 = {}
|
||||
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 = list(
|
||||
self._profiles = []
|
||||
if with_init:
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
self._profile_mask = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
lambda p: p.name[0:8] != 'interpol', self._profiles
|
||||
)
|
||||
)
|
||||
|
||||
@@ -92,6 +301,10 @@ class Reach(object):
|
||||
def profiles(self):
|
||||
return self._profiles.copy()
|
||||
|
||||
@property
|
||||
def profile_mask(self):
|
||||
return self._profile_mask
|
||||
|
||||
def profile(self, id):
|
||||
return self._profiles[id]
|
||||
|
||||
@@ -101,9 +314,44 @@ class Reach(object):
|
||||
def has_sediment(self):
|
||||
return any(map(lambda profile: profile.has_sediment(), self._profiles))
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@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
|
||||
@@ -137,3 +385,30 @@ class River(object):
|
||||
self._reachs
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
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,5 +1,5 @@
|
||||
# River.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
+417
-79
@@ -1,5 +1,5 @@
|
||||
# River.py -- Pamhyr river model
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
# -*- 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
|
||||
@@ -55,64 +59,149 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
|
||||
import LateralContributionsAdisTSList
|
||||
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
|
||||
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
|
||||
from Model.Results.Results import Results
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class RiverNode(Node, SQLSubModel):
|
||||
class RiverNode(Node):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: str, name: str,
|
||||
x: float, y: float,
|
||||
status=None):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
x: float = 0.0, y: float = 0.0,
|
||||
status=None, owner_scenario=-1):
|
||||
super(RiverNode, self).__init__(
|
||||
id, name,
|
||||
x, y,
|
||||
status=status
|
||||
id=id, name=name, x=x, y=y,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._locker = None
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE river_node(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL
|
||||
y REAL NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
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}")
|
||||
|
||||
@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:
|
||||
# Update id counter
|
||||
cls._id_cnt = max(cls._id_cnt, row[0])
|
||||
# Create new node
|
||||
nodes.append(cls(*row, status=data["status"]))
|
||||
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
|
||||
|
||||
return nodes
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
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(
|
||||
"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}" +
|
||||
")"
|
||||
)
|
||||
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
|
||||
@@ -122,100 +211,204 @@ class RiverNode(Node, SQLSubModel):
|
||||
self._locker = locker
|
||||
|
||||
|
||||
class RiverReach(Edge, SQLSubModel):
|
||||
class RiverReach(Edge):
|
||||
_sub_classes = [
|
||||
Reach,
|
||||
FrictionList,
|
||||
]
|
||||
|
||||
def __init__(self, id: str, name: str,
|
||||
def __init__(self, id: str = -1, name: str = "",
|
||||
node1: RiverNode = None,
|
||||
node2: RiverNode = None,
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(RiverReach, self).__init__(
|
||||
id, name,
|
||||
node1, node2,
|
||||
status=status
|
||||
id=id, name=name,
|
||||
node1=node1, node2=node2,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._reach = Reach(status=self._status, parent=self)
|
||||
self._frictions = FrictionList(status=self._status)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE river_reach(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
enable BOOLEAN NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
node1 INTEGER,
|
||||
node2 INTEGER,
|
||||
FOREIGN KEY(node1) REFERENCES river_node(id),
|
||||
FOREIGN KEY(node2) REFERENCES river_node(id)
|
||||
{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)
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
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}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
reachs = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
if scenario is None:
|
||||
return reachs
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, enable, node1, node2 FROM river_reach"
|
||||
"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))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
# 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.id == row[3], data["nodes"]))
|
||||
node2 = next(filter(lambda n: n.id == row[4], data["nodes"]))
|
||||
it = iter(row)
|
||||
|
||||
new = cls(id, name, node1, node2, status=data["status"])
|
||||
new.enable(enable=enable)
|
||||
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)
|
||||
|
||||
# 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"]
|
||||
)
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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):
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"river_reach(id, name, enable, node1, node2) " +
|
||||
"river_reach(" +
|
||||
"pamhyr_id, deleted, name, enabled, " +
|
||||
"node1, node2, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{self._db_format(self.is_enable())},"
|
||||
f"{self.node1.id}, {self.node2.id}" +
|
||||
f"{self.node1.pamhyr_id}, {self.node2.pamhyr_id}, " +
|
||||
f"{self._status.scenario_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
|
||||
@@ -224,17 +417,46 @@ class RiverReach(Edge, SQLSubModel):
|
||||
def frictions(self):
|
||||
return self._frictions
|
||||
|
||||
def _split(self, profile, new_node):
|
||||
name = self.name
|
||||
node1 = self.node1
|
||||
node2 = self.node2
|
||||
|
||||
class River(Graph, SQLSubModel):
|
||||
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):
|
||||
_sub_classes = [
|
||||
StricklersList,
|
||||
SedimentLayerList,
|
||||
RiverNode,
|
||||
RiverReach,
|
||||
BoundaryConditionList,
|
||||
LateralContributionList,
|
||||
InitialConditionsDict,
|
||||
StricklersList,
|
||||
SolverParametersList,
|
||||
SedimentLayerList,
|
||||
ReservoirList,
|
||||
HydraulicStructureList,
|
||||
AddFileList,
|
||||
@@ -246,10 +468,13 @@ class River(Graph, SQLSubModel):
|
||||
LateralContributionsAdisTSList,
|
||||
D90AdisTSList,
|
||||
DIFAdisTSList,
|
||||
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
|
||||
@@ -280,19 +505,20 @@ class River(Graph, SQLSubModel):
|
||||
self._D90AdisTS = D90AdisTSList(status=self._status)
|
||||
self._DIFAdisTS = DIFAdisTSList(status=self._status)
|
||||
|
||||
self._results = {}
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
cls._update_submodel(execute, version)
|
||||
return True
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(data["status"])
|
||||
new = cls(status=data["status"])
|
||||
|
||||
# Stricklers (Stricklers is load in first because it's needed
|
||||
# for reachs)
|
||||
@@ -300,65 +526,84 @@ class River(Graph, SQLSubModel):
|
||||
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)
|
||||
|
||||
@@ -374,6 +619,12 @@ class River(Graph, SQLSubModel):
|
||||
|
||||
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)
|
||||
|
||||
@@ -399,6 +650,9 @@ class River(Graph, SQLSubModel):
|
||||
objs.append(self._D90AdisTS)
|
||||
objs.append(self._DIFAdisTS)
|
||||
|
||||
for solv_type in self.results:
|
||||
objs.append(self.results[solv_type])
|
||||
|
||||
self._save_submodel(execute, objs, data)
|
||||
return True
|
||||
|
||||
@@ -422,15 +676,63 @@ class River(Graph, SQLSubModel):
|
||||
execute(
|
||||
f"DELETE FROM {table} " +
|
||||
"WHERE " +
|
||||
f"scenario = {self._status.scenario_id} AND (" +
|
||||
" OR ".join(
|
||||
map(
|
||||
lambda n: f"( id <> {n.id} )", els
|
||||
lambda n: f"( pamhyr_id <> {n.pamhyr_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,
|
||||
]
|
||||
|
||||
for solver in self._parameters:
|
||||
objs.append(self._parameters[solver])
|
||||
|
||||
return objs
|
||||
|
||||
def init_default(self):
|
||||
self.init_default_network()
|
||||
self.init_default_sediment()
|
||||
@@ -468,6 +770,9 @@ execution...
|
||||
|
||||
Last export at: @date."""
|
||||
|
||||
def reachs(self):
|
||||
return self.edges()
|
||||
|
||||
@property
|
||||
def boundary_condition(self):
|
||||
return self._boundary_condition
|
||||
@@ -589,3 +894,36 @@ 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
|
||||
@@ -1,53 +0,0 @@
|
||||
# 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
|
||||
@@ -0,0 +1,282 @@
|
||||
# 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 = 0.0, y: int = 0.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
|
||||
@@ -0,0 +1,80 @@
|
||||
# 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()
|
||||
@@ -1,5 +1,5 @@
|
||||
# SedimentLayer.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -21,23 +21,26 @@ 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):
|
||||
super(Layer, self).__init__()
|
||||
|
||||
self._status = status
|
||||
sl=None, status=None,
|
||||
owner_scenario=-1):
|
||||
super(Layer, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._sl = sl
|
||||
self._name = name
|
||||
self._type = type
|
||||
|
||||
@@ -46,23 +49,17 @@ 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(id, Layer._id_cnt+1)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"Layer{self.id + 1}"
|
||||
return f"Layer #{self.pamhyr_id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
@@ -71,6 +68,7 @@ class Layer(SQLSubModel):
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
self._type = type
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
@@ -79,6 +77,7 @@ class Layer(SQLSubModel):
|
||||
@height.setter
|
||||
def height(self, height):
|
||||
self._height = float(height)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def d50(self):
|
||||
@@ -87,6 +86,7 @@ class Layer(SQLSubModel):
|
||||
@d50.setter
|
||||
def d50(self, d50):
|
||||
self._d50 = float(d50)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def sigma(self):
|
||||
@@ -95,6 +95,7 @@ class Layer(SQLSubModel):
|
||||
@sigma.setter
|
||||
def sigma(self, sigma):
|
||||
self._sigma = float(sigma)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def critical_constraint(self):
|
||||
@@ -103,12 +104,14 @@ 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):
|
||||
execute("""
|
||||
CREATE TABLE sedimentary_layer_layer(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
ind INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
@@ -117,92 +120,177 @@ class Layer(SQLSubModel):
|
||||
sigma REAL NOT NULL,
|
||||
critical_constraint REAL NOT NULL,
|
||||
sl INTEGER,
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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)
|
||||
|
||||
return True
|
||||
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}"
|
||||
)
|
||||
|
||||
@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 id, ind, name, type, height, " +
|
||||
"d50, sigma, critical_constraint " +
|
||||
"SELECT pamhyr_id, deleted, name, type, height, " +
|
||||
"d50, sigma, critical_constraint, scenario " +
|
||||
"FROM sedimentary_layer_layer " +
|
||||
f"WHERE sl = {sl}"
|
||||
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"
|
||||
)
|
||||
|
||||
for _ in table:
|
||||
new.append(None)
|
||||
|
||||
for row in table:
|
||||
ind = row[1]
|
||||
it = iter(row)
|
||||
|
||||
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)
|
||||
|
||||
layer = cls(
|
||||
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']
|
||||
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
|
||||
)
|
||||
if deleted:
|
||||
layer.set_as_deleted()
|
||||
|
||||
new[ind] = layer
|
||||
loaded.add(pid)
|
||||
new.append(layer)
|
||||
|
||||
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
|
||||
|
||||
ind = data["ind"]
|
||||
sl = data["sl"]
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"sedimentary_layer_layer(id, ind, name, type, height, " +
|
||||
"d50, sigma, critical_constraint, sl) " +
|
||||
"sedimentary_layer_layer(" +
|
||||
"pamhyr_id, deleted, ind, name, type, height, " +
|
||||
"d50, sigma, critical_constraint, sl, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {ind}, '{self._db_format(self._name)}', " +
|
||||
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"{ind}, '{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._type)}', {self._height}, " +
|
||||
f"{self._d50}, {self._sigma}, {self._critical_constraint}, " +
|
||||
f"{sl.id}" +
|
||||
f"{sl.pamhyr_id}, " +
|
||||
f"{self._status.scenario_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):
|
||||
super(SedimentLayer, self).__init__()
|
||||
status=None, owner_scenario=-1):
|
||||
super(SedimentLayer, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
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)})"
|
||||
|
||||
@@ -212,31 +300,48 @@ class SedimentLayer(SQLSubModel):
|
||||
return s
|
||||
|
||||
def __len__(self):
|
||||
return len(self._layers)
|
||||
return len(
|
||||
list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._layers
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def layers(self):
|
||||
return self._layers.copy()
|
||||
return list(
|
||||
filter(
|
||||
lambda el: not el.is_deleted(),
|
||||
self._layers
|
||||
)
|
||||
)
|
||||
|
||||
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.id + 1}"
|
||||
return f"SL #{self.pamhyr_id}"
|
||||
|
||||
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
|
||||
@@ -246,62 +351,128 @@ class SedimentLayer(SQLSubModel):
|
||||
@comment.setter
|
||||
def comment(self, comment):
|
||||
self._comment = comment
|
||||
self.modified()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE sedimentary_layer(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
comment TEXT NOT NULL
|
||||
comment TEXT NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
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)
|
||||
|
||||
return True
|
||||
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}")
|
||||
|
||||
@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 id, name, comment " +
|
||||
"FROM sedimentary_layer "
|
||||
"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))}) "
|
||||
)
|
||||
|
||||
for row in table:
|
||||
sl = cls(
|
||||
id=row[0],
|
||||
name=row[1],
|
||||
comment=row[2],
|
||||
status=data['status']
|
||||
)
|
||||
if table is not None:
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
|
||||
data["sl"] = sl.id
|
||||
sl._layers = Layer._db_load(execute, data)
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
comment = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
new.append(sl)
|
||||
sl = cls(
|
||||
id=pid,
|
||||
name=name,
|
||||
comment=comment,
|
||||
status=data['status'],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
sl.set_as_deleted()
|
||||
|
||||
data["sl"] = sl
|
||||
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 data is None:
|
||||
data = {}
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
sql = (
|
||||
"INSERT INTO sedimentary_layer (id, name, comment) " +
|
||||
f"VALUES ({self.id}, '{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._comment)}')"
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["sl"] = self
|
||||
|
||||
@@ -313,39 +484,56 @@ class SedimentLayer(SQLSubModel):
|
||||
|
||||
return True
|
||||
|
||||
def get(self, index):
|
||||
return self._layers[index]
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
def set(self, index, new):
|
||||
self._layers[index] = new
|
||||
self._status.modified()
|
||||
for layer in self._layers:
|
||||
layer._data_traversal(predicate, modifier, data)
|
||||
|
||||
def get(self, index):
|
||||
return self.layers[index]
|
||||
|
||||
# def set(self, index, new):
|
||||
# self._layers[index] = new
|
||||
# self.modified()
|
||||
|
||||
def insert(self, index, new):
|
||||
self._layers.insert(index, new)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = Layer(sl=self, status=self._status)
|
||||
self.insert(index, n)
|
||||
self._status.modified()
|
||||
self.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:
|
||||
self._layers.remove(el)
|
||||
self._status.modified()
|
||||
el.set_as_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
els = list(
|
||||
list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self._layers)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.delete(els)
|
||||
self.modified()
|
||||
|
||||
def move_up(self, index):
|
||||
if index >= 0:
|
||||
@@ -354,7 +542,7 @@ class SedimentLayer(SQLSubModel):
|
||||
lst = self._layers
|
||||
lst[index], lst[next] = lst[next], lst[index]
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index + 1 < len(self._layers):
|
||||
@@ -363,7 +551,7 @@ class SedimentLayer(SQLSubModel):
|
||||
lst = self._layers
|
||||
lst[index], lst[prev] = lst[prev], lst[index]
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def compute_height_from_bottom(self, bottom_elevation: list):
|
||||
sl_height = self.height()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SedimentLayerList.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -19,7 +19,7 @@
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.SedimentLayer.SedimentLayer import SedimentLayer
|
||||
|
||||
|
||||
@@ -35,11 +35,16 @@ class SedimentLayerList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
ok = True
|
||||
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}"
|
||||
)
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM sedimentary_layer")
|
||||
execute("DELETE FROM sedimentary_layer_layer")
|
||||
ok = True
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Serializable.py -- Pamhyr pickle abstract class
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SolverParametersList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -20,18 +20,221 @@ 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():
|
||||
def __init__(self,
|
||||
class Parameter(SQLSubModel):
|
||||
def __init__(self, id: int = -1,
|
||||
name="", value="",
|
||||
status=None):
|
||||
self._status = status
|
||||
status=None, owner_scenario=-1):
|
||||
super(Parameter, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
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
|
||||
@@ -54,7 +257,7 @@ class Parameter():
|
||||
elif key == "value":
|
||||
self._value = str(value)
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@classmethod
|
||||
def from_tuple(cls, data, status):
|
||||
@@ -66,6 +269,8 @@ class Parameter():
|
||||
|
||||
|
||||
class SolverParametersList(PamhyrModelList):
|
||||
_sub_classes = [Parameter]
|
||||
|
||||
def __init__(self, solver_type=None, status=None):
|
||||
super(SolverParametersList, self).__init__()
|
||||
|
||||
@@ -79,100 +284,21 @@ class SolverParametersList(PamhyrModelList):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
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
|
||||
)
|
||||
""")
|
||||
|
||||
def _db_create(cls, execute, ext=""):
|
||||
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:
|
||||
@@ -181,18 +307,10 @@ class SolverParametersList(PamhyrModelList):
|
||||
st = solver_type_list[solver]
|
||||
n = cls(solver_type=st, status=status)
|
||||
|
||||
table = execute(
|
||||
"SELECT ind, name, value " +
|
||||
"FROM solver_parameter " +
|
||||
f"WHERE solver = '{solver}'"
|
||||
)
|
||||
data['solver'] = solver
|
||||
lst = Parameter._db_load(execute, data)
|
||||
|
||||
for row in table:
|
||||
ind = row[0]
|
||||
name = row[1]
|
||||
value = row[2]
|
||||
|
||||
n.set_value(name, value)
|
||||
n._lst = list(map(lambda t: t[1], sorted(lst)))
|
||||
|
||||
new[solver] = n
|
||||
|
||||
@@ -202,22 +320,16 @@ class SolverParametersList(PamhyrModelList):
|
||||
t = self._solver._type
|
||||
execute(
|
||||
"DELETE FROM solver_parameter " +
|
||||
f"WHERE solver = '{t}'"
|
||||
f"WHERE solver = '{t}' " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
data["solver"] = t
|
||||
|
||||
ind = 0
|
||||
for param in self._lst:
|
||||
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)
|
||||
data["ind"] = ind
|
||||
param._db_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
@@ -242,9 +354,9 @@ class SolverParametersList(PamhyrModelList):
|
||||
|
||||
def set(self, index, new):
|
||||
self._lst[index] = new
|
||||
self._status.modified()
|
||||
new.modified()
|
||||
|
||||
def set_value(self, key, value):
|
||||
def set_value(self, pid, key, value, owner):
|
||||
for p in self._lst:
|
||||
if p["name"] == key:
|
||||
p["value"] = value
|
||||
@@ -253,9 +365,11 @@ class SolverParametersList(PamhyrModelList):
|
||||
|
||||
self._lst.append(
|
||||
Parameter(
|
||||
id=-1,
|
||||
name=key,
|
||||
value=value,
|
||||
status=self._status
|
||||
status=self._status,
|
||||
owner_scenario=self._status.scenario_id
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# 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()
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stricklers.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -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,16 +30,11 @@ class Stricklers(SQLSubModel):
|
||||
comment: str = "",
|
||||
minor: float = 35.0,
|
||||
medium: float = 15.0,
|
||||
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)
|
||||
status=None, owner_scenario=-1):
|
||||
super(Stricklers, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._comment = comment
|
||||
@@ -48,67 +43,123 @@ class Stricklers(SQLSubModel):
|
||||
self._medium = medium
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE stricklers(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE stricklers{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT,
|
||||
comment TEXT,
|
||||
minor REAL NOT NULL,
|
||||
medium REAL NOT NULL
|
||||
medium 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):
|
||||
return cls._update_submodel(execute, version)
|
||||
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}")
|
||||
|
||||
@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 id, name, comment, minor, medium " +
|
||||
"FROM stricklers"
|
||||
"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))})"
|
||||
)
|
||||
|
||||
if table is None:
|
||||
return None
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
comment = row[2]
|
||||
minor = row[3]
|
||||
medium = row[4]
|
||||
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)
|
||||
|
||||
new = cls(
|
||||
id=id,
|
||||
name=name,
|
||||
comment=comment,
|
||||
id=pid, name=name, comment=comment,
|
||||
minor=minor, medium=medium,
|
||||
status=status
|
||||
status=status, owner_scenario=owner_scenario
|
||||
)
|
||||
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):
|
||||
sql = (
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"stricklers(id, name, comment, minor, medium) " +
|
||||
"stricklers(pamhyr_id, deleted, " +
|
||||
"name, comment, minor, medium, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"{self.pamhyr_id}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self.name)}', " +
|
||||
f"'{self._db_format(self.comment)}', " +
|
||||
f"{float(self.minor)}, {float(self.medium)}" +
|
||||
f"{float(self.minor)}, {float(self.medium)}, " +
|
||||
f"{self._status.scenario_id}"
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@@ -118,12 +169,13 @@ class Stricklers(SQLSubModel):
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"K{self.id + 1}"
|
||||
return f"K #{self.pamhyr_id}"
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def comment(self):
|
||||
@@ -132,6 +184,7 @@ class Stricklers(SQLSubModel):
|
||||
@comment.setter
|
||||
def comment(self, comment):
|
||||
self._comment = comment
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def minor(self):
|
||||
@@ -140,6 +193,7 @@ class Stricklers(SQLSubModel):
|
||||
@minor.setter
|
||||
def minor(self, minor):
|
||||
self._minor = float(minor)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def medium(self):
|
||||
@@ -148,3 +202,4 @@ class Stricklers(SQLSubModel):
|
||||
@medium.setter
|
||||
def medium(self, medium):
|
||||
self._medium = float(medium)
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# StricklersList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -18,8 +18,7 @@
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Stricklers.Stricklers import Stricklers
|
||||
|
||||
|
||||
@@ -33,8 +32,8 @@ class StricklersList(PamhyrModelList):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
@@ -48,7 +47,10 @@ class StricklersList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM stricklers")
|
||||
execute(
|
||||
"DELETE FROM stricklers " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
objs = self._lst
|
||||
return self._save_submodel(execute, objs, data)
|
||||
@@ -58,8 +60,12 @@ class StricklersList(PamhyrModelList):
|
||||
return self.lst
|
||||
|
||||
def new(self, index):
|
||||
s = Stricklers(status=self._status)
|
||||
s = Stricklers(
|
||||
status=self._status,
|
||||
owner_scenario=self._status.scenario_id
|
||||
)
|
||||
self.insert(index, s)
|
||||
self.modified()
|
||||
return s
|
||||
|
||||
@timer
|
||||
@@ -74,5 +80,4 @@ class StricklersList(PamhyrModelList):
|
||||
reverse=reverse,
|
||||
)
|
||||
|
||||
if self._status is not None:
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
+260
-34
@@ -1,5 +1,5 @@
|
||||
# Study.py -- Pamhyr Study class
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,10 +22,12 @@ import logging
|
||||
from datetime import datetime
|
||||
|
||||
from tools import timer, timestamp
|
||||
from functools import reduce
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLModel
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Serializable import Serializable
|
||||
from Model.Scenarios import Scenarios
|
||||
from Model.Scenario import Scenario
|
||||
from Model.Status import StudyStatus
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.River import River
|
||||
|
||||
@@ -35,13 +37,18 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class Study(SQLModel):
|
||||
_version = "0.2.1"
|
||||
|
||||
_sub_classes = [
|
||||
Scenario,
|
||||
River,
|
||||
]
|
||||
|
||||
def __init__(self, filename=None, init_new=True):
|
||||
def __init__(self, filename=None, init_new=True, copy=False):
|
||||
if copy:
|
||||
return
|
||||
|
||||
# Metadata
|
||||
self._version = "0.0.11"
|
||||
self.creation_date = datetime.now()
|
||||
self.last_modification_date = datetime.now()
|
||||
self.last_save_date = datetime.now()
|
||||
@@ -49,7 +56,7 @@ class Study(SQLModel):
|
||||
self._filename = filename
|
||||
super(Study, self).__init__(filename=filename)
|
||||
|
||||
self.status = SavedStatus()
|
||||
self.status = StudyStatus()
|
||||
|
||||
# Study general information
|
||||
self._name = ""
|
||||
@@ -60,12 +67,22 @@ 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 = [
|
||||
@@ -84,6 +101,12 @@ 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()
|
||||
@@ -107,15 +130,11 @@ class Study(SQLModel):
|
||||
|
||||
is_new = False
|
||||
fname = fname + "." + str(self._old_save_id)
|
||||
if os.path.exists(self.filename) and ".backup" not in self.filename:
|
||||
if os.path.exists(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
|
||||
@@ -211,13 +230,12 @@ class Study(SQLModel):
|
||||
|
||||
def _create(self):
|
||||
# Info (metadata)
|
||||
self.execute(
|
||||
"INSERT INTO info VALUES ('study_release', '0')"
|
||||
)
|
||||
|
||||
self.execute(
|
||||
"CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)"
|
||||
)
|
||||
self.execute(
|
||||
"INSERT INTO info VALUES ('current_scenario', '0')"
|
||||
)
|
||||
self.execute(
|
||||
"INSERT INTO info VALUES ('version', " +
|
||||
f"'{self._db_format(self._version)}')",
|
||||
@@ -252,7 +270,7 @@ class Study(SQLModel):
|
||||
if version[0] == self._version:
|
||||
return True
|
||||
|
||||
logger.debug("Update database")
|
||||
logger.info(f"Update database from {version[0]} to {self._version}")
|
||||
|
||||
major, minor, release = version[0].split('.')
|
||||
if major == "0" and minor == "0" and int(release) < 10:
|
||||
@@ -260,23 +278,58 @@ class Study(SQLModel):
|
||||
"INSERT INTO info VALUES ('study_release', '0')"
|
||||
)
|
||||
|
||||
if self._update_submodel(version[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:
|
||||
self.execute(
|
||||
f"UPDATE info SET value='{self._version}' WHERE key='version'"
|
||||
)
|
||||
logger.info("Update database done.")
|
||||
|
||||
return True
|
||||
|
||||
logger.info("TODO: update failed")
|
||||
logger.info("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'"
|
||||
)
|
||||
new.status.version = int(version[0])
|
||||
if version is not None:
|
||||
new.status.version = int(version[0])
|
||||
|
||||
# TODO: Load metadata
|
||||
new.name = new.execute("SELECT value FROM info WHERE key='name'")[0]
|
||||
@@ -289,31 +342,65 @@ 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(
|
||||
lambda sql: new.execute(
|
||||
sql,
|
||||
fetch_one=False,
|
||||
commit=True
|
||||
),
|
||||
data={"status": new.status}
|
||||
sql_exec, data=data
|
||||
)
|
||||
|
||||
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 INTO info VALUES ('study_release', " +
|
||||
"INSERT OR REPLACE INTO info VALUES ('study_release', " +
|
||||
f"'{self.status.version}')"
|
||||
)
|
||||
|
||||
@@ -351,16 +438,28 @@ class Study(SQLModel):
|
||||
)
|
||||
progress()
|
||||
|
||||
self._save_submodel([self._river], data=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.commit()
|
||||
|
||||
def sql_save_request_count(self):
|
||||
def sql_save_request_count(self, *args, **kargs):
|
||||
return self._count()
|
||||
|
||||
def _count(self):
|
||||
cnt = self._save_count([self._river])
|
||||
cnt = self._save_count(
|
||||
[self.scenarios, self._river]
|
||||
)
|
||||
logger.debug(cnt)
|
||||
return cnt + 6
|
||||
return cnt + 7
|
||||
|
||||
def close(self):
|
||||
"""Close db connection
|
||||
@@ -369,3 +468,130 @@ 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
|
||||
+224
-18
@@ -1,5 +1,5 @@
|
||||
# PamhyrDB.py -- Pamhyr abstract model database classes
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -26,6 +26,8 @@ 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
|
||||
@@ -42,7 +44,7 @@ class SQLModel(SQL):
|
||||
if exists and is_new:
|
||||
os.remove(db)
|
||||
|
||||
self._db = sqlite3.connect(db)
|
||||
self._db = sqlite3.connect(db, check_same_thread=False)
|
||||
self._cur = self._db.cursor()
|
||||
|
||||
if is_new:
|
||||
@@ -71,10 +73,9 @@ class SQLModel(SQL):
|
||||
return True
|
||||
|
||||
def _create(self):
|
||||
|
||||
raise NotImplementedMethodeError(self, self._create)
|
||||
|
||||
def _update_submodel(self, version):
|
||||
def _update_submodel(self, version, data=None):
|
||||
def fn(sql): return self.execute(
|
||||
sql,
|
||||
fetch_one=False,
|
||||
@@ -83,7 +84,7 @@ class SQLModel(SQL):
|
||||
|
||||
ok = True
|
||||
for cls in self._sub_classes:
|
||||
ok &= cls._db_update(fn, version)
|
||||
ok &= cls._db_update(fn, version, data=data)
|
||||
|
||||
self.commit()
|
||||
return ok
|
||||
@@ -94,18 +95,22 @@ class SQLModel(SQL):
|
||||
def _save_submodel(self, objs, data=None):
|
||||
progress = data if data is not None else lambda: None
|
||||
|
||||
def fn(sql):
|
||||
def fn(sql, *args, **kargs):
|
||||
if "fetch_one" not in kargs:
|
||||
kargs["fetch_one"] = False
|
||||
|
||||
if "commit" not in kargs:
|
||||
kargs["commit"] = False
|
||||
|
||||
res = self.execute(
|
||||
sql,
|
||||
fetch_one=False,
|
||||
commit=False
|
||||
sql, *args, **kargs
|
||||
)
|
||||
progress()
|
||||
return res
|
||||
|
||||
ok = True
|
||||
for obj in objs:
|
||||
ok &= obj._db_save(fn)
|
||||
ok &= obj._db_save(fn, data={})
|
||||
|
||||
self.commit()
|
||||
return ok
|
||||
@@ -116,7 +121,7 @@ class SQLModel(SQL):
|
||||
def _count(self):
|
||||
raise NotImplementedMethodeError(self, self._count)
|
||||
|
||||
def _save_count(self, objs, data=None):
|
||||
def _save_count(self, objs, *args, data={}, **kargs):
|
||||
counter = {
|
||||
"insert": 0,
|
||||
"update": 0,
|
||||
@@ -124,7 +129,7 @@ class SQLModel(SQL):
|
||||
"other": 0,
|
||||
}
|
||||
|
||||
def fn(sql):
|
||||
def fn(sql, *args, **kargs):
|
||||
if "insert" in sql.lower():
|
||||
counter["insert"] = counter["insert"] + 1
|
||||
elif "update" in sql.lower():
|
||||
@@ -137,7 +142,7 @@ class SQLModel(SQL):
|
||||
|
||||
ok = True
|
||||
for obj in objs:
|
||||
ok &= obj._db_save(fn)
|
||||
ok &= obj._db_save(fn, data=data)
|
||||
|
||||
logger.debug(counter)
|
||||
|
||||
@@ -154,15 +159,103 @@ class SQLModel(SQL):
|
||||
# Sub model class
|
||||
|
||||
|
||||
class SQLSubModel(object):
|
||||
class SQLSubModel(PamhyrID):
|
||||
_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 ''' to preserve SQL injection
|
||||
if type(value) is str:
|
||||
value = value.replace("'", "'")
|
||||
elif type(value) is bool:
|
||||
value = 'TRUE' if value else 'FALSE'
|
||||
elif value is None:
|
||||
value = "NULL"
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
@@ -183,12 +276,16 @@ class SQLSubModel(object):
|
||||
raise NotImplementedMethodeError(cls, cls._db_create)
|
||||
|
||||
@classmethod
|
||||
def _update_submodel(cls, execute, version):
|
||||
def _update_submodel(cls, execute, version, data=None):
|
||||
ok = True
|
||||
|
||||
for sc in cls._sub_classes:
|
||||
sc._db_update(execute, version)
|
||||
ok &= sc._db_update(execute, version, data)
|
||||
|
||||
return ok
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
"""Update data base scheme
|
||||
|
||||
Args:
|
||||
@@ -200,6 +297,93 @@ class SQLSubModel(object):
|
||||
"""
|
||||
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
|
||||
@@ -214,8 +398,12 @@ class SQLSubModel(object):
|
||||
raise NotImplementedMethodeError(cls, cls._db_load)
|
||||
|
||||
def _save_submodel(self, execute, objs, data=None):
|
||||
ok = True
|
||||
|
||||
for o in objs:
|
||||
o._db_save(execute, data=data)
|
||||
ok &= o._db_save(execute, data=data)
|
||||
|
||||
return ok
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
"""Save class data to data base
|
||||
@@ -229,3 +417,21 @@ class SQLSubModel(object):
|
||||
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)
|
||||
@@ -1,5 +1,5 @@
|
||||
# PamhyrDict.py -- Pamhyr abstract dict for model classes
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -32,9 +32,9 @@ class PamhyrModelDict(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, status=None):
|
||||
super(PamhyrModelDict, self).__init__()
|
||||
|
||||
self._status = status
|
||||
super(PamhyrModelDict, self).__init__(
|
||||
status=status
|
||||
)
|
||||
|
||||
self._dict = {}
|
||||
|
||||
@@ -43,8 +43,8 @@ class PamhyrModelDict(SQLSubModel):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
@@ -59,6 +59,12 @@ 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()
|
||||
@@ -76,5 +82,24 @@ 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)
|
||||
@@ -0,0 +1,99 @@
|
||||
# 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"
|
||||
@@ -1,5 +1,5 @@
|
||||
# PamhyrList.py -- Pamhyr Abstract List object for the Model
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -28,8 +28,7 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class PamhyrModelList(SQLSubModel):
|
||||
_sub_classes = [
|
||||
]
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, status=None):
|
||||
super(PamhyrModelList, self).__init__()
|
||||
@@ -46,8 +45,8 @@ class PamhyrModelList(SQLSubModel):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
@@ -86,6 +85,24 @@ 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
|
||||
|
||||
@@ -167,8 +184,7 @@ class PamhyrModelList(SQLSubModel):
|
||||
|
||||
class PamhyrModelListWithTab(SQLSubModel):
|
||||
_tabs_list = []
|
||||
_sub_classes = [
|
||||
]
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, status=None):
|
||||
super(PamhyrModelListWithTab, self).__init__()
|
||||
@@ -187,8 +203,8 @@ class PamhyrModelListWithTab(SQLSubModel):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
@@ -197,6 +213,24 @@ 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 #
|
||||
################
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
# 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,5 +1,5 @@
|
||||
# __init__.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
|
||||
+160
-6
@@ -1,5 +1,5 @@
|
||||
# test_Model.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -20,7 +20,7 @@ import os
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
from Model.Saved import SavedStatus
|
||||
from Model.Status import StudyStatus
|
||||
from Model.Study import Study
|
||||
from Model.River import River
|
||||
|
||||
@@ -32,7 +32,11 @@ class StudyTestCase(unittest.TestCase):
|
||||
self.assertEqual(study.description, "bar")
|
||||
|
||||
def test_open_study(self):
|
||||
study = Study.open("../tests_cases/Enlargement/Enlargement.pamhyr")
|
||||
study = Study.open(
|
||||
os.path.join(
|
||||
"..", "tests_cases", "Enlargement", "Enlargement.pamhyr"
|
||||
)
|
||||
)
|
||||
self.assertNotEqual(study, None)
|
||||
self.assertEqual(study.name, "Enlargement")
|
||||
|
||||
@@ -59,15 +63,165 @@ class StudyTestCase(unittest.TestCase):
|
||||
self.assertNotEqual(study.river, None)
|
||||
|
||||
|
||||
class StudyScenarioTestCase(unittest.TestCase):
|
||||
def test_create_study(self):
|
||||
study = Study.new("foo", "bar")
|
||||
self.assertEqual(study.name, "foo")
|
||||
self.assertEqual(study.description, "bar")
|
||||
self.assertEqual(study.status.scenario_id, 0)
|
||||
|
||||
def test_create_new_scenario_study(self):
|
||||
study = Study.new("foo", "bar")
|
||||
old = study.status.scenario
|
||||
new = study.new_scenario_from_current()
|
||||
|
||||
self.assertEqual(study.name, "foo")
|
||||
self.assertEqual(study.description, "bar")
|
||||
|
||||
self.assertNotEqual(study.status.scenario_id, 0)
|
||||
self.assertEqual(study.status.scenario_id, new.id)
|
||||
|
||||
self.assertNotEqual(old, new)
|
||||
|
||||
def test_open_study(self):
|
||||
study = Study.open(
|
||||
os.path.join(
|
||||
"..", "tests_cases", "Enlargement", "Enlargement.pamhyr"
|
||||
)
|
||||
)
|
||||
self.assertNotEqual(study, None)
|
||||
self.assertEqual(study.name, "Enlargement")
|
||||
self.assertEqual(study.status.scenario_id, 0)
|
||||
|
||||
def test_open_study_2(self):
|
||||
study = Study.open(
|
||||
os.path.join(
|
||||
"..", "tests_cases", "MassZero", "TestMultibiefs.pamhyr"
|
||||
)
|
||||
)
|
||||
self.assertNotEqual(study, None)
|
||||
self.assertEqual(study.name, "TestMultibiefs")
|
||||
self.assertEqual(study.status.scenario_id, 0)
|
||||
|
||||
def test_save_open_study(self):
|
||||
study = Study.new("foo", "bar")
|
||||
new = study.new_scenario_from_current()
|
||||
nid = new.id
|
||||
|
||||
dir = tempfile.mkdtemp()
|
||||
f = os.path.join(dir, "foo.pamhyr")
|
||||
|
||||
# Save study
|
||||
study.filename = f
|
||||
study.save()
|
||||
study.close()
|
||||
|
||||
# Reopen study
|
||||
study = Study.open(f)
|
||||
|
||||
# Check
|
||||
self.assertNotEqual(study, None)
|
||||
self.assertEqual(study.name, "foo")
|
||||
self.assertEqual(study.description, "bar")
|
||||
self.assertEqual(study.status.scenario_id, nid)
|
||||
|
||||
def test_save_study_add_files(self):
|
||||
study = Study.new("foo", "bar")
|
||||
study.river.additional_files.new(0)
|
||||
|
||||
dir = tempfile.mkdtemp()
|
||||
f = os.path.join(dir, "foo.pamhyr")
|
||||
study.filename = f
|
||||
study.save()
|
||||
study.close()
|
||||
|
||||
study = Study.open(f)
|
||||
self.assertNotEqual(study, None)
|
||||
self.assertEqual(study.name, "foo")
|
||||
self.assertEqual(study.description, "bar")
|
||||
self.assertEqual(len(study.river.additional_files), 1)
|
||||
|
||||
new = study.new_scenario_from_current()
|
||||
nid = new.id
|
||||
|
||||
study.river.additional_files.new(1)
|
||||
|
||||
study.save()
|
||||
study.close()
|
||||
|
||||
study = Study.open(f)
|
||||
self.assertEqual(len(study.river.additional_files), 2)
|
||||
|
||||
def test_save_study_add_files_reload(self):
|
||||
study = Study.new("foo", "bar")
|
||||
study.river.additional_files.new(0)
|
||||
|
||||
dir = tempfile.mkdtemp()
|
||||
f = os.path.join(dir, "foo.pamhyr")
|
||||
study.filename = f
|
||||
study.save()
|
||||
|
||||
new = study.new_scenario_from_current()
|
||||
nid = new.id
|
||||
|
||||
study.river.additional_files.new(1)
|
||||
self.assertEqual(len(study.river.additional_files), 2)
|
||||
|
||||
study.save()
|
||||
study.reload_from_scenario(study.scenarios[0])
|
||||
self.assertEqual(len(study.river.additional_files), 1)
|
||||
|
||||
study.reload_from_scenario(study.scenarios[nid])
|
||||
self.assertEqual(len(study.river.additional_files), 2)
|
||||
|
||||
def test_scenario_river_nodes(self):
|
||||
study = Study.new("foo", "bar")
|
||||
study.river.additional_files.new(0)
|
||||
|
||||
self.assertNotEqual(study.river, None)
|
||||
|
||||
dir = tempfile.mkdtemp()
|
||||
f = os.path.join(dir, "foo.pamhyr")
|
||||
|
||||
study.filename = f
|
||||
study.save()
|
||||
|
||||
# Add nodes
|
||||
n0 = study.river.add_node()
|
||||
n1 = study.river.add_node(x=1.0, y=0.0)
|
||||
n2 = study.river.add_node(x=0.0, y=1.0)
|
||||
|
||||
self.assertEqual(study.river.nodes_counts(), 3)
|
||||
|
||||
study.save()
|
||||
|
||||
# New scenario
|
||||
new = study.new_scenario_from_current()
|
||||
nid = new.id
|
||||
|
||||
n3 = study.river.add_node(x=0.0, y=1.0)
|
||||
study.save()
|
||||
|
||||
self.assertEqual(study.river.nodes_counts(), 4)
|
||||
|
||||
study.reload_from_scenario(study.scenarios[0])
|
||||
self.assertEqual(study.river.nodes_counts(), 3)
|
||||
|
||||
study.reload_from_scenario(study.scenarios[nid])
|
||||
self.assertEqual(study.river.nodes_counts(), 4)
|
||||
|
||||
study.save()
|
||||
|
||||
|
||||
class RiverTestCase(unittest.TestCase):
|
||||
def test_create_river(self):
|
||||
status = SavedStatus()
|
||||
status = StudyStatus()
|
||||
river = River(status=status)
|
||||
|
||||
self.assertNotEqual(river, None)
|
||||
|
||||
def test_create_river_nodes(self):
|
||||
status = SavedStatus()
|
||||
status = StudyStatus()
|
||||
river = River(status=status)
|
||||
|
||||
self.assertNotEqual(river, None)
|
||||
@@ -86,7 +240,7 @@ class RiverTestCase(unittest.TestCase):
|
||||
self.assertEqual(nodes[2], n2)
|
||||
|
||||
def test_create_river_edges(self):
|
||||
status = SavedStatus()
|
||||
status = StudyStatus()
|
||||
river = River(status=status)
|
||||
|
||||
self.assertNotEqual(river, None)
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
# Modules.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -42,6 +42,7 @@ class Modules(IterableFlag):
|
||||
# General
|
||||
STUDY = auto()
|
||||
CONFIG = auto()
|
||||
SCENARIOS = auto()
|
||||
|
||||
# Modelling
|
||||
NETWORK = auto()
|
||||
@@ -66,6 +67,7 @@ class Modules(IterableFlag):
|
||||
def values(cls):
|
||||
return [
|
||||
cls.STUDY, cls.CONFIG,
|
||||
cls.SCENARIOS,
|
||||
cls.NETWORK,
|
||||
cls.GEOMETRY,
|
||||
cls.BOUNDARY_CONDITION,
|
||||
@@ -102,6 +104,7 @@ class Modules(IterableFlag):
|
||||
@classmethod
|
||||
def modelling_list(cls):
|
||||
return [
|
||||
cls.SCENARIOS,
|
||||
cls.NETWORK,
|
||||
cls.GEOMETRY,
|
||||
cls.BOUNDARY_CONDITION,
|
||||
@@ -116,6 +119,7 @@ class Modules(IterableFlag):
|
||||
@classmethod
|
||||
def modelling_display_name(cls):
|
||||
return {
|
||||
cls.SCENARIOS: "Scenarios",
|
||||
cls.NETWORK: "Network",
|
||||
cls.GEOMETRY: "Geometry",
|
||||
cls.BOUNDARY_CONDITION: "Boundary condition",
|
||||
@@ -145,6 +149,7 @@ class Modules(IterableFlag):
|
||||
|
||||
|
||||
_impact = {
|
||||
Modules.SCENARIOS: Modules.values(),
|
||||
Modules.NETWORK: [
|
||||
Modules.GEOMETRY, Modules.BOUNDARY_CONDITION,
|
||||
Modules.LATERAL_CONTRIBUTION, Modules.FRICTION,
|
||||
|
||||
+17
-8
@@ -1,5 +1,5 @@
|
||||
# SQL.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# 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
|
||||
@@ -22,7 +22,7 @@ import sqlite3
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from tools import timer
|
||||
from tools import timer, logger_exception
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -36,7 +36,7 @@ class SQL(object):
|
||||
exist_ok=True
|
||||
)
|
||||
|
||||
self._db = sqlite3.connect(db)
|
||||
self._db = sqlite3.connect(db, check_same_thread=False)
|
||||
self._cur = self._db.cursor()
|
||||
|
||||
if not exists:
|
||||
@@ -101,24 +101,33 @@ class SQL(object):
|
||||
# Replace ''' by ''' to preserve SQL injection
|
||||
if type(value) is str:
|
||||
value = value.replace("'", "'")
|
||||
elif value is None:
|
||||
value = "NULL"
|
||||
return value
|
||||
|
||||
@timer
|
||||
def execute(self, cmd, fetch_one=True, commit=False):
|
||||
logger.debug(f"SQL - {cmd}")
|
||||
def execute(self, cmd, *args, fetch_one=True, commit=False, **kargs):
|
||||
logger.debug(f"SQL - {cmd} + {', '.join(map(str, args))}")
|
||||
|
||||
value = None
|
||||
try:
|
||||
res = self._cur.execute(cmd)
|
||||
res = self._cur.execute(cmd, args)
|
||||
|
||||
if commit:
|
||||
self._db.commit()
|
||||
|
||||
value = self._fetch(res, fetch_one)
|
||||
except sqlite3.Warning as w:
|
||||
logger.warning(f"SQL - {cmd}")
|
||||
logger.warning(w)
|
||||
except sqlite3.Error as er:
|
||||
logger.error(f"SQL - {cmd}")
|
||||
logger_exception(er)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
finally:
|
||||
return value
|
||||
|
||||
logger.debug(f" - {value}")
|
||||
return value
|
||||
|
||||
def _create(self):
|
||||
logger.warning("TODO: Create")
|
||||
|
||||
Loaded 100 of 366 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user