mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
823
Commits
v1.1.0
...
7ef7bc13dc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ef7bc13dc | ||
|
|
a1d85274b9 | ||
|
|
e7b9bc92a2 | ||
|
|
7d8e414fe2 | ||
|
|
476bcc9c55 | ||
|
|
ae006f52f4 | ||
|
|
3583a93ae0 | ||
|
|
212a00a43c | ||
|
|
e5dbba362a | ||
|
|
bdc09addfa | ||
|
|
51b3987a4a | ||
|
|
e7290fa81b | ||
|
|
b5432498c6 | ||
|
|
807592eab2 | ||
|
|
394c0779fc | ||
|
|
5b3f777100 | ||
|
|
8b36905842 | ||
|
|
b203e8e062 | ||
|
|
4d4df1bde2 | ||
|
|
876780a349 | ||
|
|
3b83377f3c | ||
|
|
e64c45b621 | ||
|
|
70dae9ab1b | ||
|
|
0bb3c5bc65 | ||
|
|
54f5c7c548 | ||
|
|
56217186ff | ||
|
|
82fb2bcc48 | ||
|
|
6dc42b637a | ||
|
|
e6d12713e3 | ||
|
|
9456cf32fc | ||
|
|
9b1fb4e7d2 | ||
|
|
c09c62f077 | ||
|
|
7a2e93a995 | ||
|
|
5327c29641 | ||
|
|
be865102a8 | ||
|
|
37f1b43b93 | ||
|
|
16f0775783 | ||
|
|
f2c4285b4c | ||
|
|
ee95f0e89e | ||
|
|
1306977954 | ||
|
|
476fa81bc2 | ||
|
|
e8f1f0ac43 | ||
|
|
cfbc2d96a1 | ||
|
|
1adf24ce77 | ||
|
|
a61d130baa | ||
|
|
0a1760cee6 | ||
|
|
3e094af325 | ||
|
|
762e4d70a0 | ||
|
|
6c4f863b96 | ||
|
|
b87f82f74d | ||
|
|
ffad53a331 | ||
|
|
662a218e41 | ||
|
|
0501ae9bec | ||
|
|
7fd38eec00 | ||
|
|
4bab4580ad | ||
|
|
21d2e6ad20 | ||
|
|
d0a8f369ef | ||
|
|
fef2e1a40c | ||
|
|
20c2cb7ea4 | ||
|
|
64a26e987e | ||
|
|
eb233847de | ||
|
|
7bb810d427 | ||
|
|
e49724072b | ||
|
|
3664ba93e9 | ||
|
|
a9903e987b | ||
|
|
f9a88a211c | ||
|
|
32535af3e6 | ||
|
|
fa8948c1b6 | ||
|
|
2d5455c09d | ||
|
|
83680bbe57 | ||
|
|
1d1a00a0ff | ||
|
|
ffd95b9fdf | ||
|
|
ecb2544cab | ||
|
|
9c66021b53 | ||
|
|
8255fc3c1b | ||
|
|
32e7413aad | ||
|
|
d87db5fd48 | ||
|
|
388368adad | ||
|
|
4d014d5a1d | ||
|
|
181a6c226f | ||
|
|
1975ed7eaa | ||
|
|
565a7530fb | ||
|
|
32cbc6bc3e | ||
|
|
7d7a05c04c | ||
|
|
6ac268b668 | ||
|
|
19c72892a8 | ||
|
|
9717968e08 | ||
|
|
e19c3229bd | ||
|
|
b3c6f1031b | ||
|
|
c2d93b4a4a | ||
|
|
76430322dd | ||
|
|
31ae0ab8e0 | ||
|
|
8182d434ec | ||
|
|
da78d0cdb1 | ||
|
|
850db144ac | ||
|
|
0f6709075f | ||
|
|
e86b84b0b9 | ||
|
|
e08d212986 | ||
|
|
7b88d955e5 | ||
|
|
d1c34fb437 | ||
|
|
611f9cdfa3 | ||
|
|
bd2fc22e96 | ||
|
|
efa16837d8 | ||
|
|
db7aad26fd | ||
|
|
229b3da2d1 | ||
|
|
48d6728e1e | ||
|
|
c51c06e6ec | ||
|
|
a425c9a6ac | ||
|
|
e79ae8ebf5 | ||
|
|
e3020d15ba | ||
|
|
83e0ecbdc7 | ||
|
|
3087023caa | ||
|
|
7e080767da | ||
|
|
44c3a91d72 | ||
|
|
26dc7d8761 | ||
|
|
e573fa4197 | ||
|
|
7aa0cf4361 | ||
|
|
c3faf45f75 | ||
|
|
fb8b5aa77f | ||
|
|
8183a284af | ||
|
|
09997a0074 | ||
|
|
c44597dfbd | ||
|
|
b1632eaea5 | ||
|
|
a2461af084 | ||
|
|
5c366839f3 | ||
|
|
311df46a2c | ||
|
|
a235f61044 | ||
|
|
0b80b86d31 | ||
|
|
0c7ede5f43 | ||
|
|
445c1ac7ba | ||
|
|
c687e05d8f | ||
|
|
5fb6fecc24 | ||
|
|
3dfbdc328e | ||
|
|
97d87f2455 | ||
|
|
a78be47e2b | ||
|
|
eed2ce4243 | ||
|
|
78ba7b70b4 | ||
|
|
f86cd4602c | ||
|
|
ee28afac86 | ||
|
|
81ff6372e9 | ||
|
|
61156f1c3e | ||
|
|
e26b170b05 | ||
|
|
ddbaa0fb94 | ||
|
|
64d2b2678a | ||
|
|
3a3b5ea2dd | ||
|
|
7cb3ac8a80 | ||
|
|
190ca5d171 | ||
|
|
5d8ec51417 | ||
|
|
d922436ada | ||
|
|
4537a829af | ||
|
|
363b9c7132 | ||
|
|
f9d83ac68c | ||
|
|
63b5cbaba7 | ||
|
|
1989914aef | ||
|
|
2c0b155baf | ||
|
|
4d20c2457a | ||
|
|
2dd9003721 | ||
|
|
94ceb44fd4 | ||
|
|
ab50d2adf4 | ||
|
|
0eb979256f | ||
|
|
5ce94a63d3 | ||
|
|
42ea50d62e | ||
|
|
0df4cf0fd5 | ||
|
|
ccf3da114b | ||
|
|
348417977d | ||
|
|
914a1f8866 | ||
|
|
cbbbeeac0b | ||
|
|
c9ea726bfb | ||
|
|
fa88e05d91 | ||
|
|
a6915020cb | ||
|
|
ea2287215e | ||
|
|
e940dead70 | ||
|
|
b6a6d54a22 | ||
|
|
11a95cb391 | ||
|
|
084c22836b | ||
|
|
726b81bf12 | ||
|
|
7c3de520d9 | ||
|
|
b450796df5 | ||
|
|
b1daadf9f2 | ||
|
|
c38e3ee446 | ||
|
|
606b9919e3 | ||
|
|
e3de7e7100 | ||
|
|
92fae0e13f | ||
|
|
a8b260ad38 | ||
|
|
faff877c80 | ||
|
|
0a0eeb3c3f | ||
|
|
cdcbbc2cf4 | ||
|
|
7565d8ee95 | ||
|
|
11e1302e05 | ||
|
|
99cb2883a1 | ||
|
|
e61dede7f8 | ||
|
|
cb4dfab482 | ||
|
|
377c508038 | ||
|
|
30e34b6847 | ||
|
|
102702e1df | ||
|
|
c014f0ef34 | ||
|
|
e615a6b9d4 | ||
|
|
f1dca727c6 | ||
|
|
2db1f4c7f7 | ||
|
|
b3d631943c | ||
|
|
b6895ae99c | ||
|
|
9f26a7323c | ||
|
|
2a7e322ee5 | ||
|
|
13a44264ac | ||
|
|
6165719829 | ||
|
|
51dd6d27c5 | ||
|
|
cea4dcd9cb | ||
|
|
e1c1f411c3 | ||
|
|
4550fc95d7 | ||
|
|
1f97af9c03 | ||
|
|
df9e1ec42d | ||
|
|
ac79fae1fb | ||
|
|
fe39f07981 | ||
|
|
b578e0c50c | ||
|
|
4408a8f368 | ||
|
|
d3e922af6e | ||
|
|
8adf878584 | ||
|
|
aa6973813f | ||
|
|
e21a64fcad | ||
|
|
8b6308d3c9 | ||
|
|
bc3ea9c4b9 | ||
|
|
04ea55e21a | ||
|
|
a68270ff67 | ||
|
|
c498ed2d96 | ||
|
|
e6d059bc19 | ||
|
|
cc500ceccf | ||
|
|
7a91969f41 | ||
|
|
6bd3605d10 | ||
|
|
6c6da90952 | ||
|
|
539b63f765 | ||
|
|
e755459004 | ||
|
|
a8f95e2386 | ||
|
|
55b200a48e | ||
|
|
6b8d7c2df2 | ||
|
|
eb7c4895ca | ||
|
|
a060dbd461 | ||
|
|
9379f7a27d | ||
|
|
19fedc2278 | ||
|
|
260efc17a9 | ||
|
|
a4026d6386 | ||
|
|
d6f7755d12 | ||
|
|
00b89635f3 | ||
|
|
f54dc07432 | ||
|
|
3bbc55b4be | ||
|
|
b58d3e005d | ||
|
|
870bb41ff7 | ||
|
|
0701531d37 | ||
|
|
43eb486700 | ||
|
|
82eeb9a478 | ||
|
|
432c263cdf | ||
|
|
c2d28b0110 | ||
|
|
23361d142e | ||
|
|
63836527c7 | ||
|
|
4f6e8249e0 | ||
|
|
ab2f775b95 | ||
|
|
bb0e176956 | ||
|
|
c0d4aced5e | ||
|
|
4ed865759a | ||
|
|
70e09d3b3a | ||
|
|
2bcfa4d121 | ||
|
|
5b497d61f0 | ||
|
|
242df7f648 | ||
|
|
421e6797f6 | ||
|
|
0886956ec8 | ||
|
|
9670bde56f | ||
|
|
3277cf551b | ||
|
|
ad5cc831a3 | ||
|
|
b00cca953e | ||
|
|
1f35020e3f | ||
|
|
f0994dd801 | ||
|
|
d002eebd49 | ||
|
|
e3ffac9ce0 | ||
|
|
e8a4d5eeed | ||
|
|
fc090dadce | ||
|
|
25e4786484 | ||
|
|
81e85ab488 | ||
|
|
201b1099bd | ||
|
|
b6054dd633 | ||
|
|
912843be25 | ||
|
|
bf49a7fc87 | ||
|
|
d77be00bcc | ||
|
|
ff0fa8ae8e | ||
|
|
92f4793181 | ||
|
|
5eaac204d5 | ||
|
|
7ef41ad886 | ||
|
|
ada396f26d | ||
|
|
674247db09 | ||
|
|
5fecd29666 | ||
|
|
71e1324a0f | ||
|
|
07cae542b0 | ||
|
|
b693f67b99 | ||
|
|
18a3d51d75 | ||
|
|
118a39bd5c | ||
|
|
5357eb9735 | ||
|
|
0946212c4b | ||
|
|
d6ab44d18e | ||
|
|
1db1a543c7 | ||
|
|
2009e8223b | ||
|
|
bdaa38ea5c | ||
|
|
713732b1dd | ||
|
|
f053539cdd | ||
|
|
fc6916bb3e | ||
|
|
97c26cb4fb | ||
|
|
10c98bb96e | ||
|
|
3e92a0c40d | ||
|
|
7c2d9891d0 | ||
|
|
3c3ac1f1df | ||
|
|
80af278a0f | ||
|
|
f3c461cbd9 | ||
|
|
f254eaee25 | ||
|
|
012408d19e | ||
|
|
f7e563e71a | ||
|
|
81c6d39c1a | ||
|
|
5adb0bdee5 | ||
|
|
d682443e1d | ||
|
|
64c99d46d4 | ||
|
|
0f0defb36f | ||
|
|
1e1ee67d4b | ||
|
|
5bc514c565 | ||
|
|
c065b19ef8 | ||
|
|
1da3461dfe | ||
|
|
b20a4c3c3d | ||
|
|
d66e75c183 | ||
|
|
31b4c47379 | ||
|
|
71bba499e0 | ||
|
|
7a08fa42d9 | ||
|
|
18a008462a | ||
|
|
d81da59398 | ||
|
|
3ab1eabe75 | ||
|
|
cf4f860db8 | ||
|
|
c68e3337ef | ||
|
|
dd5eecb1e2 | ||
|
|
db1ae613d6 | ||
|
|
a800816cda | ||
|
|
d4ef06c7c1 | ||
|
|
95d1d231c9 | ||
|
|
10b278e26f | ||
|
|
0550eef2eb | ||
|
|
73eb9919eb | ||
|
|
9116009b0b | ||
|
|
4e4ceb5074 | ||
|
|
d79e2ff187 | ||
|
|
d4a2594239 | ||
|
|
b666162bdf | ||
|
|
d705accaed | ||
|
|
d93917b081 | ||
|
|
ceff5f3083 | ||
|
|
97762756f8 | ||
|
|
2c49e991b4 | ||
|
|
c2a8f38ef5 | ||
|
|
f8a41fce08 | ||
|
|
624ae826eb | ||
|
|
d47dc0687e | ||
|
|
117e5222e4 | ||
|
|
b1c7a77f37 | ||
|
|
7b833390f1 | ||
|
|
ae08642116 | ||
|
|
c1905aa7e1 | ||
|
|
97ece018aa | ||
|
|
7f0102a881 | ||
|
|
786923bdbf | ||
|
|
deb9b2069f | ||
|
|
9308a73e8e | ||
|
|
869e116ad0 | ||
|
|
a2f3d22001 | ||
|
|
2e360943b2 | ||
|
|
8976f054c7 | ||
|
|
7bce725c63 | ||
|
|
5c83d67865 | ||
|
|
6e52b1681e | ||
|
|
f27b2cc586 | ||
|
|
1ed5d69bf4 | ||
|
|
5bb6cc40fe | ||
|
|
4c0a12dcf9 | ||
|
|
a308af41e0 | ||
|
|
02db91f3d7 | ||
|
|
4cf4015579 | ||
|
|
291b97ac9b | ||
|
|
b04e367e72 | ||
|
|
81d58122d6 | ||
|
|
874f592cf4 | ||
|
|
9b0bdd1e63 | ||
|
|
d934b5e381 | ||
|
|
14549330b9 | ||
|
|
2487bec6d6 | ||
|
|
4e1acfecdc | ||
|
|
c63c776989 | ||
|
|
44a8be6be3 | ||
|
|
a5a64f2080 | ||
|
|
16ee5a90e4 | ||
|
|
7ca6e69526 | ||
|
|
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 | ||
|
|
a31e2127e7 | ||
|
|
68b62a5630 | ||
|
|
990d44dd07 | ||
|
|
819ae41c00 | ||
|
|
bf95b7f7e6 | ||
|
|
861b78b928 | ||
|
|
a29df8f904 | ||
|
|
08c414a66e | ||
|
|
97ebf9324a | ||
|
|
0112ad63d9 | ||
|
|
bcbb440ad4 | ||
|
|
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 | ||
|
|
c60a0938eb | ||
|
|
4d026afb2c | ||
|
|
4e888d9db0 | ||
|
|
440996b636 | ||
|
|
ed1a1a71ac | ||
|
|
cbe3ad0084 | ||
|
|
46935fde45 | ||
|
|
ca8dc19c83 | ||
|
|
e4a31b3497 | ||
|
|
406dc2bdd4 | ||
|
|
c325f61fce | ||
|
|
d3729cfe77 | ||
|
|
fa8f07b97e | ||
|
|
52c153fa84 | ||
|
|
21bd87f74b | ||
|
|
4d08f3c271 | ||
|
|
e44bbd5a3c | ||
|
|
f827f6bf13 | ||
|
|
4be68ba98d | ||
|
|
e47edea63b | ||
|
|
8daf89d1c4 | ||
|
|
0fa3ca5fad | ||
|
|
e3229c8183 | ||
|
|
c8606cf445 | ||
|
|
09029dc993 | ||
|
|
0a97070b90 | ||
|
|
4f57179a2e | ||
|
|
d0d7d91ab7 | ||
|
|
8497d151ab | ||
|
|
8a98f2bbca | ||
|
|
181aea2805 | ||
|
|
6a762275b6 | ||
|
|
a64430691c | ||
|
|
3cd6d31731 | ||
|
|
af30fde81a | ||
|
|
589b8dcd23 | ||
|
|
4e87135bf4 | ||
|
|
f636236935 | ||
|
|
c3d732b426 | ||
|
|
8913a774ea | ||
|
|
36c3785cde | ||
|
|
5ed905df71 | ||
|
|
ea4fa81cc4 | ||
|
|
8862be991e | ||
|
|
8aea55909e | ||
|
|
1289967abb | ||
|
|
41d1fb9d59 | ||
|
|
8d9a196059 | ||
|
|
558085705f | ||
|
|
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
|
||||
|
||||
+96
-24
@@ -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,12 +24,21 @@ stages:
|
||||
- antivirus
|
||||
- release
|
||||
|
||||
#########################
|
||||
# PYINSTALLER VARIABLES #
|
||||
#########################
|
||||
|
||||
variables:
|
||||
HIDDEN_IMPORTS: "--hiddenimport rasterio --collect-submodules rasterio"
|
||||
|
||||
#############
|
||||
# DOWNLOADS #
|
||||
#############
|
||||
|
||||
variables:
|
||||
MAGE_8_VERSION: "v8.3.6"
|
||||
MAGE_8_VERSION: "permalink/latest"
|
||||
ADISTS_VERSION: "permalink/latest"
|
||||
RUBAR_VERSION: "permalink/latest"
|
||||
|
||||
dl-mage8-doc:
|
||||
stage: downloads
|
||||
@@ -40,7 +49,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
|
||||
@@ -52,17 +61,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
|
||||
@@ -71,17 +76,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
|
||||
@@ -92,7 +93,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/v1.0.1/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
|
||||
@@ -106,11 +107,41 @@ 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/v1.0.1/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
|
||||
|
||||
dl-rubar-linux:
|
||||
stage: downloads
|
||||
tags:
|
||||
- linux
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
script:
|
||||
- mkdir -p rubar-linux
|
||||
- cd rubar-linux
|
||||
- curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_linux.tgz
|
||||
- tar xf rubar.tgz
|
||||
artifacts:
|
||||
paths:
|
||||
- rubar-linux/rubarbe
|
||||
|
||||
dl-rubar-windows:
|
||||
stage: downloads
|
||||
tags:
|
||||
- linux
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
|
||||
script:
|
||||
- mkdir -p rubar-windows
|
||||
- cd rubar-windows
|
||||
- curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_windows.tgz
|
||||
- tar xf rubar.tgz
|
||||
artifacts:
|
||||
paths:
|
||||
- rubar-windows/rubarbe.exe
|
||||
|
||||
#############
|
||||
# CONFIGURE #
|
||||
#############
|
||||
@@ -149,13 +180,13 @@ unittest:
|
||||
- job: set-version
|
||||
artifacts: true
|
||||
script:
|
||||
- python3 -m venv venv
|
||||
- python3.12 -m venv venv
|
||||
- . venv/bin/activate
|
||||
- pip3 install -U pip
|
||||
- pip3 install -r ./full-requirements.txt
|
||||
- pip3 install -U -r ./full-requirements.txt
|
||||
- cd src
|
||||
- python3 -m unittest discover -t .
|
||||
- python3.12 -m unittest discover -t .
|
||||
|
||||
test-pep8:
|
||||
stage: test
|
||||
@@ -167,7 +198,7 @@ test-pep8:
|
||||
- job: unittest
|
||||
script:
|
||||
# Setup virtual env
|
||||
- python3 -m venv venv
|
||||
- python3.12 -m venv venv
|
||||
- . venv/bin/activate
|
||||
- pip3 install -U pip
|
||||
- pip3 install -r ./requirements.txt
|
||||
@@ -245,7 +276,7 @@ build-developers-doc:
|
||||
- ./build.sh
|
||||
# Create html documentation
|
||||
- cd ../../src
|
||||
- python3 -m venv doc-venv
|
||||
- python3.12 -m venv doc-venv
|
||||
- . doc-venv/bin/activate
|
||||
- pip3 install -U pip
|
||||
- pip3 install -r ../full-requirements.txt
|
||||
@@ -273,6 +304,8 @@ build-linux:
|
||||
needs:
|
||||
- job: dl-adists-linux
|
||||
artifacts: true
|
||||
- job: dl-rubar-linux
|
||||
artifacts: true
|
||||
- job: dl-mage8-linux
|
||||
artifacts: true
|
||||
- job: dl-mage8-doc
|
||||
@@ -293,13 +326,13 @@ build-linux:
|
||||
- mkdir -p linux
|
||||
- cd linux
|
||||
# Setup virtual env
|
||||
- python3 -m venv linux-venv
|
||||
- python3.12 -m venv linux-venv
|
||||
- . linux-venv/bin/activate
|
||||
- pip3 install -U pip
|
||||
- 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.12/site-packages ../src/pamhyr.py
|
||||
# Create directory
|
||||
- mkdir -p pamhyr
|
||||
- mkdir -p pamhyr/_internal
|
||||
@@ -314,6 +347,9 @@ build-linux:
|
||||
# Copy adists
|
||||
- mkdir -p pamhyr/adists
|
||||
- cp -v ../adists-linux/* pamhyr/adists/
|
||||
# Copy rubar
|
||||
- mkdir -p pamhyr/rubar
|
||||
- cp -v ../rubar-linux/* pamhyr/rubar/
|
||||
# Copy Pamhyr
|
||||
- cp -r dist/pamhyr/* pamhyr/
|
||||
# Pamhyr script to force x11
|
||||
@@ -363,7 +399,7 @@ build-linux:
|
||||
- cp ../doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr pamhyr/doc/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr
|
||||
# Copy TP charriage
|
||||
- mkdir -p pamhyr/doc/TP_charriage/
|
||||
- cp ../doc/users/TP_charriage/* pamhyr/doc/TP_charriage
|
||||
- cp -r ../doc/users/TP_charriage/* pamhyr/doc/TP_charriage
|
||||
artifacts:
|
||||
paths:
|
||||
- linux/pamhyr
|
||||
@@ -379,6 +415,8 @@ build-windows:
|
||||
artifacts: true
|
||||
- job: dl-adists-windows
|
||||
artifacts: true
|
||||
- job: dl-rubar-windows
|
||||
artifacts: true
|
||||
- job: dl-mage8-doc
|
||||
artifacts: true
|
||||
- job: set-version
|
||||
@@ -402,7 +440,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
|
||||
@@ -581,6 +619,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_TAG && $CI_COMMIT_TAG !~ /^v/
|
||||
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:
|
||||
@@ -600,7 +672,7 @@ tag-release:
|
||||
- job: pkg-hash
|
||||
artifacts: true
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_TAG =~ /^v/
|
||||
artifacts:
|
||||
paths:
|
||||
- linux/pamhyr-gnulinux.tar.xz
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
Sylvain COULIBALY, INRAE, 2020 - 2022
|
||||
Théophile TERRAZ, INRAE, 2022 - 2024
|
||||
Pierre-Antoine ROUBY, INRAE, 2023 - 2024
|
||||
Pierre-Antoine ROUBY, INRAE, 2023 - 2026
|
||||
Dylan JEANNIN, INRAE, 2026
|
||||
@@ -3,7 +3,7 @@
|
||||
Pamhyr is a free and open source graphical user interface for 1D hydro-sedimentary
|
||||
modelling of rivers.
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
@@ -18,11 +18,12 @@ modelling of rivers.
|
||||
+ Define river sedimentary layers
|
||||
+ Save the study in a unique file
|
||||
+ Run Mage 8 on a study and visualize results
|
||||
+ Create and edit study alternative scenario tree
|
||||
|
||||
Let see the
|
||||
[documentation](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home)
|
||||
([:fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-fr),
|
||||
[:gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-en))
|
||||
[documentation](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home)
|
||||
([:fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-fr),
|
||||
[:gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-en))
|
||||
for more details.
|
||||
|
||||
## Install
|
||||
@@ -30,11 +31,11 @@ for more details.
|
||||
### GNU/Linux
|
||||
|
||||
See documentation:
|
||||
- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20GNULinux)
|
||||
- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20GNULinux)
|
||||
- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20GNULinux)
|
||||
- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20GNULinux)
|
||||
|
||||
### Windows
|
||||
|
||||
See documentation:
|
||||
- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20Windows)
|
||||
- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20Windows)
|
||||
- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20Windows)
|
||||
- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20Windows)
|
||||
+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
|
||||
@@ -1375,7 +1375,7 @@ contribution.
|
||||
#+INCLUDE: "../../AUTHORS" src text
|
||||
|
||||
[fn:p2-gitlab] The {{{pamhyr2}}} Gitlab project page:
|
||||
https://gitlab.irstea.fr/theophile.terraz/pamhyr
|
||||
https://gitlab.com/pamhyr/pamhyr2
|
||||
[fn:inrae] The INRAE web site: https://www.inrae.fr/
|
||||
|
||||
** Translate
|
||||
|
||||
@@ -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: 70 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
@@ -424,7 +424,7 @@ Create a new version of {{{pamhyr}}} called {{{pamhyr2}}}
|
||||
|
||||
** Contribution
|
||||
#+LaTeX: \begin{center}
|
||||
\faIcon{gitlab} https://gitlab.irstea.fr/theophile.terraz/pamhyr/
|
||||
\faIcon{gitlab} https://gitlab.com/pamhyr/pamhyr2/
|
||||
#+LaTeX: \end{center}
|
||||
|
||||
*** For users
|
||||
@@ -462,7 +462,7 @@ Create a new version of {{{pamhyr}}} called {{{pamhyr2}}}
|
||||
# #+END_EXPORT
|
||||
|
||||
#+LaTeX: \begin{center}
|
||||
\faIcon{gitlab} https://gitlab.irstea.fr/theophile.terraz/pamhyr/
|
||||
\faIcon{gitlab} https://gitlab.com/pamhyr/pamhyr2/
|
||||
#+LaTeX: \end{center}
|
||||
|
||||
#+LaTeX: \begin{center}
|
||||
|
||||
@@ -157,12 +157,12 @@
|
||||
%%
|
||||
|
||||
%% FIXME: Delete me for the first realease
|
||||
\usepackage{draftwatermark}
|
||||
\SetWatermarkLightness{0.8}
|
||||
\SetWatermarkAngle{25}
|
||||
\SetWatermarkScale{3}
|
||||
\SetWatermarkFontSize{1cm}
|
||||
\SetWatermarkText{Work in progress}
|
||||
%% \usepackage{draftwatermark}
|
||||
%% \SetWatermarkLightness{0.8}
|
||||
%% \SetWatermarkAngle{25}
|
||||
%% \SetWatermarkScale{3}
|
||||
%% \SetWatermarkFontSize{1cm}
|
||||
%% \SetWatermarkText{Work in progress}
|
||||
|
||||
%% Icons
|
||||
|
||||
|
||||
Binary file not shown.
@@ -179,13 +179,13 @@ Ouvrez la fen
|
||||
\par\end{center}
|
||||
|
||||
Éditez la condition aux limites \textit{Fines} en la mettant en surbrillance et en cliquant sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/edit.png}.\\
|
||||
Créez une condition aux limites de type concentration constante de 60 $kg/m³$ en insérant deux lignes \textit{Temps/Concentration} à l'aide du bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png}.
|
||||
Créez une condition aux limites de type concentration constante de 0.060 $kg/m³$ en insérant deux lignes \textit{Temps/Concentration} à l'aide du bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png}.
|
||||
|
||||
\begin{center}
|
||||
\includegraphics[width=15cm]{img/CL_edit.jpeg}
|
||||
\par\end{center}
|
||||
|
||||
Procédez de la même façon pour la classe de sédiments \textit{Sables}, mais avec une concentration de 30 $kg/m³$.\\
|
||||
Procédez de la même façon pour la classe de sédiments \textit{Sables}, mais avec une concentration de 0.030 $kg/m³$.\\
|
||||
\\
|
||||
Fermez les fenêtres d'éditions des conditions aux limites et des polluants.
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ Auteurs : & Céline Berni & celine.berni@inrae.fr\tabularnewline
|
||||
% \pagebreak{}
|
||||
|
||||
|
||||
\section{Préambule}
|
||||
\section*{Préambule}
|
||||
|
||||
Dans ce document, nous expliciterons les fonctionnalités de PamHyr utiles à la modélisation de scénarii d'aménagement de la rivière Hogneau comme décrit dans le document de TP.
|
||||
Il est utile au préalable d'avoir suivi le tutoriel pour la modélisation de la rivière Hogneau fourni avec le logiciel (Tuto1-fr.pdf).
|
||||
@@ -121,16 +121,36 @@ Suivre le tutoriel 1 vous permettra de construire le modèle avec le scénario d
|
||||
Notez que l'outil PamHyr ne permet pas au stade actuel de comparer plusieurs scenarii.
|
||||
Vous devrez pour ce faire exporter les vos résultats à partir de la fenêtre \textit{Résultats} qui s'ouvre en cliquant sur \texttt{[Résultats] => [Visualiser les derniers résultats]} à partir de la fenêtre principale.
|
||||
|
||||
Le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/export.png} vous permet d'exporter les résultats au format CSV pour une utilisation en dehors de Pamhyr2.
|
||||
Vous pouvez soit vous placer en un point kilométrique (PK) donné (celui marqué en rouge sur la visualisation du profil en long ou sélectionné dans le tableau) et exporter la hauteur d'eau, le niveau d'eau le débit ou autre en fonction du temps (sélectionnez alors \texttt{Temps (s)} pour l'axe X), soit vous placer à un instant donné et exporter la grandeur de votre choix pour tous les points kilométriques de votre maillage (sélectionnez alors \texttt{Point Kilométrique (m)} pour l'axe X).
|
||||
Si vous choisissez cette seconde option, cocher \texttt{envelop} vous permettra d'exporter en plus la valeur maximale au cours du temps pour la grandeur choisie.
|
||||
|
||||
Ainsi dans l'exemple ci-dessous, les données exportées le seront au point kilométrique 34000\,m, à l'instant $t=1$ jour et 12h.
|
||||
\begin{center}
|
||||
\includegraphics[width=15cm]{img/export_resultats.png}
|
||||
\includegraphics[width=12cm]{img/export_resultats2.png}
|
||||
\includegraphics[width=15cm]{img/export_resultats_commentee.png}
|
||||
%\includegraphics[width=12cm]{../../../src/View/ui/ressources/export_resultats2.png}
|
||||
\par\end{center}
|
||||
|
||||
Le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/export.png} vous permet d'exporter les résultats au format CSV pour une utilisation en dehors de Pamhyr2.
|
||||
|
||||
Deux options s'offrent à vous :
|
||||
\begin{itemize}
|
||||
\item Vous souhaitez avoir une vue d'ensemble du cours d'eau (et tracer une courbe de remous par exemple). Vous allez exporter une variable en un instant donné tout le long du cours d'eau. Il faut alors vous placer à un instant donné (à l'aide du curseur en bas à gauche) et exporter la grandeur de votre choix pour tous les points kilométriques de votre maillage. Dans la fenêtre d'export, vous devrez sélectionner \texttt{Point Kilométrique (m)} pour l'axe X, et la variable que vous souhaitez exporter pour l'axe Y.
|
||||
|
||||
Cocher \texttt{enveloppe} vous permettra d'exporter en plus les valeurs minimale et maximale au cours du temps pour la grandeur choisie.
|
||||
|
||||
Par exemple, vous choisissez d'exporter la cote du fond et la cote de l'eau et vous cochez \texttt{enveloppe}. Vous obtiendrez un tableau avec les colonnes suivantes :
|
||||
\begin{itemize}
|
||||
\item \texttt{Point Kilométrique (m)} : toutes les valeurs de PK le long de votre bief
|
||||
\item \texttt{Cote du fond (m)} : la cote du fond correspondante à chaque PK
|
||||
\item \texttt{Cote de l'eau (m)} : la cote de l'eau correspondante à chaque PK à l'instant sélectionné (voir figure ci-dessous)
|
||||
\item \texttt{Cote de l'eau min (m)} : la cote de l'eau minimale atteinte correspondante à chaque PK
|
||||
\item \texttt{Cote de l'eau max (m)} : la cote de l'eau maximale atteinte correspondante à chaque PK
|
||||
\end{itemize}
|
||||
ces deux dernières colonnes constituant l'enveloppe de la cote de l'eau.
|
||||
\item Vous souhaitez regarder l'évolution au cours du temps de ce qu'il se passe en un point précis du cours d'eau (regarder l'hydrogramme de crue à l'aval d'un aménagement par exemple). Vous devez alors vous placer au point kilométrique (PK) qui vous intéresse (le PK sélectionné est marqué en rouge sur la visualisation du profil en long ou sélectionné dans le tableau) et exporter la hauteur d'eau, le niveau d'eau, le débit ou autre en fonction du temps. Dans la fenêtre d'export, vous devrez sélectionner \texttt{Temps (s)} pour l'axe X.
|
||||
\end{itemize}
|
||||
|
||||
Ainsi dans l'exemple ci-dessus, les données exportées le seront au point kilométrique 34000\,m, à l'instant $t=1$ jour et 12h.
|
||||
|
||||
|
||||
\pagebreak{}
|
||||
|
||||
\section{Premier scénario d'aménagement : abaissement du seuil}
|
||||
|
||||
Parmi les 3 options d'aménagement évoquées par la collectivité locale, jouer sur le contrôle aval en abaissant le seuil qui marque la jonction avec le canal de Condé-Pommeroeul peut faire baisser le niveau à l’amont.
|
||||
@@ -156,6 +176,8 @@ Sur quelle distance le seuil aval influence-t-il la hauteur d'eau ?
|
||||
Est-ce suffisant pour limiter la hauteur d'eau au niveau du pont de l'A2 ($PK \approx 33000$\,m) ?
|
||||
\end{remark}
|
||||
|
||||
\pagebreak{}
|
||||
|
||||
\section{Second scénario d'aménagement : élargissement du lit moyen}
|
||||
|
||||
Ce scénario d'aménagement consiste à essayer d’augmenter le laminage en créant un lit moyen en rive droite entre les PKs 32 015 et 33 920\,m. Pour rappel, cette localisation est imposée pour des raisons de disponibilité des terrains et on ne peut pas envisager une largeur du lit moyen supérieure à 50 mètres.
|
||||
@@ -166,8 +188,9 @@ Pourquoi élargir le lit moyen peut-il avoir un effet sur le niveau d'eau durant
|
||||
|
||||
Pour élargir le lit moyen, la géométrie doit être modifiée.
|
||||
Il est judicieux d'enregistrer votre étude sous un autre nom avant de procéder à ces modifications (cliquez sur \texttt{[Fichier] => [Sauvegarder sous]}).
|
||||
Le plus simple est de repartir de la géométrie avant maillage avant de modifier les transects.
|
||||
Vous pouvez supprimer les sections interpolées en les sélectionnant dans la fenêtre \textit{Géométrie} et en appuyant sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/del.png}.
|
||||
|
||||
Vous aurez un certain nombre de sections à modifier. Pour limiter leur nombre, le plus simple est de repartir de la géométrie initiale (avant maillage) avant de modifier les transects et de mailler à nouveau après les modifications.
|
||||
Vous pouvez supprimer les sections interpolées (notées interpol\textit{i}) en les sélectionnant dans la fenêtre \textit{Géométrie} et en appuyant sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/del.png}.
|
||||
|
||||
Cliquez sur \texttt{[Géométrie] => [Modifier la géométrie]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/geometry.png} pour définir la géométrie du bief.
|
||||
Sélectionnez la section en travers que vous souhaitez modifier.
|
||||
@@ -197,6 +220,8 @@ Quel est l'effet de cet aménagement sur l'hydrogramme de crue ?
|
||||
Peut-on s'attendre à ce résultat compte tenu du volume qu'il est possible de stocker en lit moyen et du volume total de la crue ?
|
||||
\end{remark}
|
||||
|
||||
\pagebreak{}
|
||||
|
||||
\section{Troisième scénario d'aménagement : casier de stockage}
|
||||
|
||||
Ce troisième scénario consiste à faire baisser le débit dans la rivière en créant un déversement vers un casier de stockage en rive gauche juste en aval du pont de l'A2.
|
||||
@@ -216,7 +241,7 @@ Cliquez sur \texttt{[Réseau] => [Modifier le réseau]} ou sur l'icône \include
|
||||
Le réseau pour la configuration de référence ne comportait qu'un seul bief entre deux noeuds.
|
||||
Pour ajouter un casier, il faut ajouter un noeud pour la diffluence, un noeud en amont du casier et un noeud en aval du casier.
|
||||
Le casier ne peut en effet pas être en limite aval du modèle ni directement au niveau de la confluence.
|
||||
Pour ajouter un noeud, cliquez \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour sélectionner l'outil ``d'addition'' et cliquez ensuite dans la fenêtre grise à l'endroit ou vous souhaitez ajouter un noeud.
|
||||
Pour ajouter un noeud, cliquez sur \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour sélectionner l'outil ``d'addition'' et cliquez ensuite dans la fenêtre grise à l'endroit ou vous souhaitez ajouter un noeud.
|
||||
De la même façon vous pourrez avec cet outil ajouter un bief entre deux noeuds en cliquant successivement sur l'un puis sur l'autre.
|
||||
Vous pourrez alors créer un réseau tel que celui ci-dessous où DevLat est le noeud marquant la diffluence.
|
||||
\begin{center}
|
||||
@@ -236,6 +261,11 @@ Il vous faudra supprimer les sections dans l'un que vous implémenterez dans l'a
|
||||
L'outil d'import de géométrie pourra vous aider.
|
||||
Il faudra le cas échéant réimplémenter également les ouvrages.
|
||||
|
||||
Vous devrez ensuite implémenter le casier sur le noeud nommé casier. Cliquez sur \texttt{[Hydraulique] => [Casiers]}. Vous pouvez ajouter un casier en cliquant sur \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png}. Vous devrez ensuite configurer le casier en cliquant sur l'icône \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}. Pour chaque altitude, vous pouvez définir l'aire de stockage (en ha). Vous pourrez ainsi configurer votre casier de la façon suivante :
|
||||
\begin{center}
|
||||
\includegraphics[width=10cm]{img/Casier.png}
|
||||
\par\end{center}
|
||||
|
||||
Lancez le calcul pour chacune des crues proposées avec ce nouvel aménagement.
|
||||
|
||||
\begin{remark}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
@@ -101,7 +101,7 @@ Authors : & Pierre-Antoine Rouby & pierre-antoine.rouby@inrae.fr\tabularnewline
|
||||
|
||||
\section{Install Pamhyr2}
|
||||
|
||||
Pamhyr2 can be downloaded from \url{https://gitlab.irstea.fr/theophile.terraz/pamhyr}.
|
||||
Pamhyr2 can be downloaded from \url{https://gitlab.com/pamhyr/pamhyr2}.
|
||||
\begin{center}
|
||||
\includegraphics[width=15cm]{img/dl.png}
|
||||
\par\end{center}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,235 @@
|
||||
# Exported from Pamhyr2
|
||||
0 1 0 37 0.000000 p1013
|
||||
0.0000 0.0000 14.0000
|
||||
0.0000 0.0134 12.9933
|
||||
0.0000 0.9213 12.5393
|
||||
0.0000 1.8292 12.0854
|
||||
0.0000 2.7370 11.6315
|
||||
0.0000 3.6449 11.1775
|
||||
0.0000 4.5528 10.7236
|
||||
0.0000 5.4607 10.2697
|
||||
0.0000 6.3685 9.8157
|
||||
0.0000 7.2764 9.3618
|
||||
0.0000 8.1843 8.9079
|
||||
0.0000 9.0921 8.4539
|
||||
0.0000 10.0000 8.0000 FD
|
||||
0.0000 10.8333 8.0000
|
||||
0.0000 11.6667 8.0000
|
||||
0.0000 12.5000 8.0000
|
||||
0.0000 13.3333 8.0000
|
||||
0.0000 14.1667 8.0000
|
||||
0.0000 15.0000 8.0000
|
||||
0.0000 15.8333 8.0000
|
||||
0.0000 16.6667 8.0000
|
||||
0.0000 17.5000 8.0000
|
||||
0.0000 18.3333 8.0000
|
||||
0.0000 19.1667 8.0000
|
||||
0.0000 20.0000 8.0000 FG
|
||||
0.0000 20.9079 8.4539
|
||||
0.0000 21.8157 8.9079
|
||||
0.0000 22.7236 9.3618
|
||||
0.0000 23.6315 9.8157
|
||||
0.0000 24.5393 10.2697
|
||||
0.0000 25.4472 10.7236
|
||||
0.0000 26.3551 11.1775
|
||||
0.0000 27.2630 11.6315
|
||||
0.0000 28.1708 12.0854
|
||||
0.0000 29.0787 12.5393
|
||||
0.0000 29.9866 12.9933
|
||||
0.0000 30.0000 14.0000
|
||||
999.9990 999.9990 999.9990
|
||||
1 2 0 37 350.000000 p1025
|
||||
350.0000 0.0000 13.6500
|
||||
350.0000 0.0134 12.6433
|
||||
350.0000 0.9213 12.1893
|
||||
350.0000 1.8292 11.7354
|
||||
350.0000 2.7370 11.2815
|
||||
350.0000 3.6449 10.8275
|
||||
350.0000 4.5528 10.3736
|
||||
350.0000 5.4607 9.9197
|
||||
350.0000 6.3685 9.4657
|
||||
350.0000 7.2764 9.0118
|
||||
350.0000 8.1843 8.5579
|
||||
350.0000 9.0921 8.1039
|
||||
350.0000 10.0000 7.6500 FD
|
||||
350.0000 10.8333 7.6500
|
||||
350.0000 11.6667 7.6500
|
||||
350.0000 12.5000 7.6500
|
||||
350.0000 13.3333 7.6500
|
||||
350.0000 14.1667 7.6500
|
||||
350.0000 15.0000 7.6500
|
||||
350.0000 15.8333 7.6500
|
||||
350.0000 16.6667 7.6500
|
||||
350.0000 17.5000 7.6500
|
||||
350.0000 18.3333 7.6500
|
||||
350.0000 19.1667 7.6500
|
||||
350.0000 20.0000 7.6500 FG
|
||||
350.0000 20.9079 8.1039
|
||||
350.0000 21.8157 8.5579
|
||||
350.0000 22.7236 9.0118
|
||||
350.0000 23.6315 9.4657
|
||||
350.0000 24.5393 9.9197
|
||||
350.0000 25.4472 10.3736
|
||||
350.0000 26.3551 10.8275
|
||||
350.0000 27.2630 11.2815
|
||||
350.0000 28.1708 11.7354
|
||||
350.0000 29.0787 12.1893
|
||||
350.0000 29.9866 12.6433
|
||||
350.0000 30.0000 13.6500
|
||||
999.9990 999.9990 999.9990
|
||||
2 3 0 37 370.000000 p1026
|
||||
370.0000 0.0000 13.6300
|
||||
370.0000 0.0134 12.6233
|
||||
370.0000 0.9213 12.1693
|
||||
370.0000 1.8292 11.7154
|
||||
370.0000 2.7370 11.2615
|
||||
370.0000 3.6449 10.8075
|
||||
370.0000 4.5528 10.3536
|
||||
370.0000 5.4607 9.8997
|
||||
370.0000 6.3685 9.4457
|
||||
370.0000 7.2764 8.9918
|
||||
370.0000 8.1843 8.5379
|
||||
370.0000 9.0921 8.0839
|
||||
370.0000 10.0000 7.6300 FD
|
||||
370.0000 11.2500 7.6300
|
||||
370.0000 12.5000 7.6300
|
||||
370.0000 13.7500 7.6300
|
||||
370.0000 15.0000 7.6300
|
||||
370.0000 16.2500 7.6300
|
||||
370.0000 17.5000 7.6300
|
||||
370.0000 18.7500 7.6300
|
||||
370.0000 20.0000 7.6300
|
||||
370.0000 21.2500 7.6300
|
||||
370.0000 22.5000 7.6300
|
||||
370.0000 23.7500 7.6300
|
||||
370.0000 25.0000 7.6300 FG
|
||||
370.0000 25.9079 8.0839
|
||||
370.0000 26.8157 8.5379
|
||||
370.0000 27.7236 8.9918
|
||||
370.0000 28.6315 9.4457
|
||||
370.0000 29.5393 9.8997
|
||||
370.0000 30.4472 10.3536
|
||||
370.0000 31.3551 10.8075
|
||||
370.0000 32.2630 11.2615
|
||||
370.0000 33.1708 11.7154
|
||||
370.0000 34.0787 12.1693
|
||||
370.0000 34.9866 12.6233
|
||||
370.0000 35.0000 13.6300
|
||||
999.9990 999.9990 999.9990
|
||||
3 4 0 37 630.000000 p1035
|
||||
630.0000 0.0000 13.3700
|
||||
630.0000 0.0134 12.3633
|
||||
630.0000 0.9213 11.9093
|
||||
630.0000 1.8292 11.4554
|
||||
630.0000 2.7370 11.0015
|
||||
630.0000 3.6449 10.5475
|
||||
630.0000 4.5528 10.0936
|
||||
630.0000 5.4607 9.6397
|
||||
630.0000 6.3685 9.1857
|
||||
630.0000 7.2764 8.7318
|
||||
630.0000 8.1843 8.2779
|
||||
630.0000 9.0921 7.8239
|
||||
630.0000 10.0000 7.3700 FD
|
||||
630.0000 11.2500 7.3700
|
||||
630.0000 12.5000 7.3700
|
||||
630.0000 13.7500 7.3700
|
||||
630.0000 15.0000 7.3700
|
||||
630.0000 16.2500 7.3700
|
||||
630.0000 17.5000 7.3700
|
||||
630.0000 18.7500 7.3700
|
||||
630.0000 20.0000 7.3700
|
||||
630.0000 21.2500 7.3700
|
||||
630.0000 22.5000 7.3700
|
||||
630.0000 23.7500 7.3700
|
||||
630.0000 25.0000 7.3700 FG
|
||||
630.0000 25.9079 7.8239
|
||||
630.0000 26.8157 8.2779
|
||||
630.0000 27.7236 8.7318
|
||||
630.0000 28.6315 9.1857
|
||||
630.0000 29.5393 9.6397
|
||||
630.0000 30.4472 10.0936
|
||||
630.0000 31.3551 10.5475
|
||||
630.0000 32.2630 11.0015
|
||||
630.0000 33.1708 11.4554
|
||||
630.0000 34.0787 11.9093
|
||||
630.0000 34.9866 12.3633
|
||||
630.0000 35.0000 13.3700
|
||||
999.9990 999.9990 999.9990
|
||||
4 5 0 37 650.000000 p1036
|
||||
650.0000 0.0000 13.3500
|
||||
650.0000 0.0134 12.3433
|
||||
650.0000 0.9213 11.8893
|
||||
650.0000 1.8292 11.4354
|
||||
650.0000 2.7370 10.9815
|
||||
650.0000 3.6449 10.5275
|
||||
650.0000 4.5528 10.0736
|
||||
650.0000 5.4607 9.6197
|
||||
650.0000 6.3685 9.1657
|
||||
650.0000 7.2764 8.7118
|
||||
650.0000 8.1843 8.2579
|
||||
650.0000 9.0921 7.8039
|
||||
650.0000 10.0000 7.3500 FD
|
||||
650.0000 10.8333 7.3500
|
||||
650.0000 11.6667 7.3500
|
||||
650.0000 12.5000 7.3500
|
||||
650.0000 13.3333 7.3500
|
||||
650.0000 14.1667 7.3500
|
||||
650.0000 15.0000 7.3500
|
||||
650.0000 15.8333 7.3500
|
||||
650.0000 16.6667 7.3500
|
||||
650.0000 17.5000 7.3500
|
||||
650.0000 18.3333 7.3500
|
||||
650.0000 19.1667 7.3500
|
||||
650.0000 20.0000 7.3500 FG
|
||||
650.0000 20.9079 7.8039
|
||||
650.0000 21.8157 8.2579
|
||||
650.0000 22.7236 8.7118
|
||||
650.0000 23.6315 9.1657
|
||||
650.0000 24.5393 9.6197
|
||||
650.0000 25.4472 10.0736
|
||||
650.0000 26.3551 10.5275
|
||||
650.0000 27.2630 10.9815
|
||||
650.0000 28.1708 11.4354
|
||||
650.0000 29.0787 11.8893
|
||||
650.0000 29.9866 12.3433
|
||||
650.0000 30.0000 13.3500
|
||||
999.9990 999.9990 999.9990
|
||||
5 6 0 37 1000.000000 p1048
|
||||
1000.0000 0.0000 13.0000
|
||||
1000.0000 0.0134 11.9933
|
||||
1000.0000 0.9213 11.5393
|
||||
1000.0000 1.8292 11.0854
|
||||
1000.0000 2.7370 10.6315
|
||||
1000.0000 3.6449 10.1775
|
||||
1000.0000 4.5528 9.7236
|
||||
1000.0000 5.4607 9.2697
|
||||
1000.0000 6.3685 8.8157
|
||||
1000.0000 7.2764 8.3618
|
||||
1000.0000 8.1843 7.9079
|
||||
1000.0000 9.0921 7.4539
|
||||
1000.0000 10.0000 7.0000 FD
|
||||
1000.0000 10.8333 7.0000
|
||||
1000.0000 11.6667 7.0000
|
||||
1000.0000 12.5000 7.0000
|
||||
1000.0000 13.3333 7.0000
|
||||
1000.0000 14.1667 7.0000
|
||||
1000.0000 15.0000 7.0000
|
||||
1000.0000 15.8333 7.0000
|
||||
1000.0000 16.6667 7.0000
|
||||
1000.0000 17.5000 7.0000
|
||||
1000.0000 18.3333 7.0000
|
||||
1000.0000 19.1667 7.0000
|
||||
1000.0000 20.0000 7.0000 FG
|
||||
1000.0000 20.9079 7.4539
|
||||
1000.0000 21.8157 7.9079
|
||||
1000.0000 22.7236 8.3618
|
||||
1000.0000 23.6315 8.8157
|
||||
1000.0000 24.5393 9.2697
|
||||
1000.0000 25.4472 9.7236
|
||||
1000.0000 26.3551 10.1775
|
||||
1000.0000 27.2630 10.6315
|
||||
1000.0000 28.1708 11.0854
|
||||
1000.0000 29.0787 11.5393
|
||||
1000.0000 29.9866 11.9933
|
||||
1000.0000 30.0000 13.0000
|
||||
999.9990 999.9990 999.9990
|
||||
@@ -0,0 +1,235 @@
|
||||
# Exported from Pamhyr2
|
||||
0 1 0 37 0.000000 p1210
|
||||
0.0000 0.0000 14.0000
|
||||
0.0000 0.0134 12.9933
|
||||
0.0000 0.9213 12.5393
|
||||
0.0000 1.8292 12.0854
|
||||
0.0000 2.7370 11.6315
|
||||
0.0000 3.6449 11.1775
|
||||
0.0000 4.5528 10.7236
|
||||
0.0000 5.4607 10.2697
|
||||
0.0000 6.3685 9.8157
|
||||
0.0000 7.2764 9.3618
|
||||
0.0000 8.1843 8.9079
|
||||
0.0000 9.0921 8.4539
|
||||
0.0000 10.0000 8.0000 FD
|
||||
0.0000 10.8333 8.0000
|
||||
0.0000 11.6667 8.0000
|
||||
0.0000 12.5000 8.0000
|
||||
0.0000 13.3333 8.0000
|
||||
0.0000 14.1667 8.0000
|
||||
0.0000 15.0000 8.0000
|
||||
0.0000 15.8333 8.0000
|
||||
0.0000 16.6667 8.0000
|
||||
0.0000 17.5000 8.0000
|
||||
0.0000 18.3333 8.0000
|
||||
0.0000 19.1667 8.0000
|
||||
0.0000 20.0000 8.0000 FG
|
||||
0.0000 20.9079 8.4539
|
||||
0.0000 21.8157 8.9079
|
||||
0.0000 22.7236 9.3618
|
||||
0.0000 23.6315 9.8157
|
||||
0.0000 24.5393 10.2697
|
||||
0.0000 25.4472 10.7236
|
||||
0.0000 26.3551 11.1775
|
||||
0.0000 27.2630 11.6315
|
||||
0.0000 28.1708 12.0854
|
||||
0.0000 29.0787 12.5393
|
||||
0.0000 29.9866 12.9933
|
||||
0.0000 30.0000 14.0000
|
||||
999.9990 999.9990 999.9990
|
||||
1 2 0 37 300.000000 p1225
|
||||
300.0000 0.0000 13.7000
|
||||
300.0000 0.0134 12.6933
|
||||
300.0000 0.9213 12.2393
|
||||
300.0000 1.8292 11.7854
|
||||
300.0000 2.7370 11.3315
|
||||
300.0000 3.6449 10.8775
|
||||
300.0000 4.5528 10.4236
|
||||
300.0000 5.4607 9.9697
|
||||
300.0000 6.3685 9.5157
|
||||
300.0000 7.2764 9.0618
|
||||
300.0000 8.1843 8.6079
|
||||
300.0000 9.0921 8.1539
|
||||
300.0000 10.0000 7.7000 FD
|
||||
300.0000 10.8333 7.7000
|
||||
300.0000 11.6667 7.7000
|
||||
300.0000 12.5000 7.7000
|
||||
300.0000 13.3333 7.7000
|
||||
300.0000 14.1667 7.7000
|
||||
300.0000 15.0000 7.7000
|
||||
300.0000 15.8333 7.7000
|
||||
300.0000 16.6667 7.7000
|
||||
300.0000 17.5000 7.7000
|
||||
300.0000 18.3333 7.7000
|
||||
300.0000 19.1667 7.7000
|
||||
300.0000 20.0000 7.7000 FG
|
||||
300.0000 20.9079 8.1539
|
||||
300.0000 21.8157 8.6079
|
||||
300.0000 22.7236 9.0618
|
||||
300.0000 23.6315 9.5157
|
||||
300.0000 24.5393 9.9697
|
||||
300.0000 25.4472 10.4236
|
||||
300.0000 26.3551 10.8775
|
||||
300.0000 27.2630 11.3315
|
||||
300.0000 28.1708 11.7854
|
||||
300.0000 29.0787 12.2393
|
||||
300.0000 29.9866 12.6933
|
||||
300.0000 30.0000 13.7000
|
||||
999.9990 999.9990 999.9990
|
||||
2 3 0 37 310.000000 p1226
|
||||
310.0000 0.0000 13.1900
|
||||
310.0000 0.0134 12.1833
|
||||
310.0000 0.9213 11.7293
|
||||
310.0000 1.8292 11.2754
|
||||
310.0000 2.7370 10.8215
|
||||
310.0000 3.6449 10.3675
|
||||
310.0000 4.5528 9.9136
|
||||
310.0000 5.4607 9.4597
|
||||
310.0000 6.3685 9.0057
|
||||
310.0000 7.2764 8.5518
|
||||
310.0000 8.1843 8.0979
|
||||
310.0000 9.0921 7.6439
|
||||
310.0000 10.0000 7.1900 FD
|
||||
310.0000 10.8333 7.1900
|
||||
310.0000 11.6667 7.1900
|
||||
310.0000 12.5000 7.1900
|
||||
310.0000 13.3333 7.1900
|
||||
310.0000 14.1667 7.1900
|
||||
310.0000 15.0000 7.1900
|
||||
310.0000 15.8333 7.1900
|
||||
310.0000 16.6667 7.1900
|
||||
310.0000 17.5000 7.1900
|
||||
310.0000 18.3333 7.1900
|
||||
310.0000 19.1667 7.1900
|
||||
310.0000 20.0000 7.1900 FG
|
||||
310.0000 20.9079 7.6439
|
||||
310.0000 21.8157 8.0979
|
||||
310.0000 22.7236 8.5518
|
||||
310.0000 23.6315 9.0057
|
||||
310.0000 24.5393 9.4597
|
||||
310.0000 25.4472 9.9136
|
||||
310.0000 26.3551 10.3675
|
||||
310.0000 27.2630 10.8215
|
||||
310.0000 28.1708 11.2754
|
||||
310.0000 29.0787 11.7293
|
||||
310.0000 29.9866 12.1833
|
||||
310.0000 30.0000 13.1900
|
||||
999.9990 999.9990 999.9990
|
||||
3 4 0 37 390.000000 p1230
|
||||
390.0000 0.0000 13.1900
|
||||
390.0000 0.0134 12.1833
|
||||
390.0000 0.9213 11.7293
|
||||
390.0000 1.8292 11.2754
|
||||
390.0000 2.7370 10.8215
|
||||
390.0000 3.6449 10.3675
|
||||
390.0000 4.5528 9.9136
|
||||
390.0000 5.4607 9.4597
|
||||
390.0000 6.3685 9.0057
|
||||
390.0000 7.2764 8.5518
|
||||
390.0000 8.1843 8.0979
|
||||
390.0000 9.0921 7.6439
|
||||
390.0000 10.0000 7.1900 FD
|
||||
390.0000 10.8333 7.1900
|
||||
390.0000 11.6667 7.1900
|
||||
390.0000 12.5000 7.1900
|
||||
390.0000 13.3333 7.1900
|
||||
390.0000 14.1667 7.1900
|
||||
390.0000 15.0000 7.1900
|
||||
390.0000 15.8333 7.1900
|
||||
390.0000 16.6667 7.1900
|
||||
390.0000 17.5000 7.1900
|
||||
390.0000 18.3333 7.1900
|
||||
390.0000 19.1667 7.1900
|
||||
390.0000 20.0000 7.1900 FG
|
||||
390.0000 20.9079 7.6439
|
||||
390.0000 21.8157 8.0979
|
||||
390.0000 22.7236 8.5518
|
||||
390.0000 23.6315 9.0057
|
||||
390.0000 24.5393 9.4597
|
||||
390.0000 25.4472 9.9136
|
||||
390.0000 26.3551 10.3675
|
||||
390.0000 27.2630 10.8215
|
||||
390.0000 28.1708 11.2754
|
||||
390.0000 29.0787 11.7293
|
||||
390.0000 29.9866 12.1833
|
||||
390.0000 30.0000 13.1900
|
||||
999.9990 999.9990 999.9990
|
||||
4 5 0 37 400.000000 p1231
|
||||
400.0000 0.0000 13.6000
|
||||
400.0000 0.0134 12.5933
|
||||
400.0000 0.9213 12.1393
|
||||
400.0000 1.8292 11.6854
|
||||
400.0000 2.7370 11.2315
|
||||
400.0000 3.6449 10.7775
|
||||
400.0000 4.5528 10.3236
|
||||
400.0000 5.4607 9.8697
|
||||
400.0000 6.3685 9.4157
|
||||
400.0000 7.2764 8.9618
|
||||
400.0000 8.1843 8.5079
|
||||
400.0000 9.0921 8.0539
|
||||
400.0000 10.0000 7.6000 FD
|
||||
400.0000 10.8333 7.6000
|
||||
400.0000 11.6667 7.6000
|
||||
400.0000 12.5000 7.6000
|
||||
400.0000 13.3333 7.6000
|
||||
400.0000 14.1667 7.6000
|
||||
400.0000 15.0000 7.6000
|
||||
400.0000 15.8333 7.6000
|
||||
400.0000 16.6667 7.6000
|
||||
400.0000 17.5000 7.6000
|
||||
400.0000 18.3333 7.6000
|
||||
400.0000 19.1667 7.6000
|
||||
400.0000 20.0000 7.6000 FG
|
||||
400.0000 20.9079 8.0539
|
||||
400.0000 21.8157 8.5079
|
||||
400.0000 22.7236 8.9618
|
||||
400.0000 23.6315 9.4157
|
||||
400.0000 24.5393 9.8697
|
||||
400.0000 25.4472 10.3236
|
||||
400.0000 26.3551 10.7775
|
||||
400.0000 27.2630 11.2315
|
||||
400.0000 28.1708 11.6854
|
||||
400.0000 29.0787 12.1393
|
||||
400.0000 29.9866 12.5933
|
||||
400.0000 30.0000 13.6000
|
||||
999.9990 999.9990 999.9990
|
||||
5 6 0 37 1000.000000 p1261
|
||||
1000.0000 0.0000 13.0000
|
||||
1000.0000 0.0134 11.9933
|
||||
1000.0000 0.9213 11.5393
|
||||
1000.0000 1.8292 11.0854
|
||||
1000.0000 2.7370 10.6315
|
||||
1000.0000 3.6449 10.1775
|
||||
1000.0000 4.5528 9.7236
|
||||
1000.0000 5.4607 9.2697
|
||||
1000.0000 6.3685 8.8157
|
||||
1000.0000 7.2764 8.3618
|
||||
1000.0000 8.1843 7.9079
|
||||
1000.0000 9.0921 7.4539
|
||||
1000.0000 10.0000 7.0000 FD
|
||||
1000.0000 10.8333 7.0000
|
||||
1000.0000 11.6667 7.0000
|
||||
1000.0000 12.5000 7.0000
|
||||
1000.0000 13.3333 7.0000
|
||||
1000.0000 14.1667 7.0000
|
||||
1000.0000 15.0000 7.0000
|
||||
1000.0000 15.8333 7.0000
|
||||
1000.0000 16.6667 7.0000
|
||||
1000.0000 17.5000 7.0000
|
||||
1000.0000 18.3333 7.0000
|
||||
1000.0000 19.1667 7.0000
|
||||
1000.0000 20.0000 7.0000 FG
|
||||
1000.0000 20.9079 7.4539
|
||||
1000.0000 21.8157 7.9079
|
||||
1000.0000 22.7236 8.3618
|
||||
1000.0000 23.6315 8.8157
|
||||
1000.0000 24.5393 9.2697
|
||||
1000.0000 25.4472 9.7236
|
||||
1000.0000 26.3551 10.1775
|
||||
1000.0000 27.2630 10.6315
|
||||
1000.0000 28.1708 11.0854
|
||||
1000.0000 29.0787 11.5393
|
||||
1000.0000 29.9866 11.9933
|
||||
1000.0000 30.0000 13.0000
|
||||
999.9990 999.9990 999.9990
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,10 +1,10 @@
|
||||
PyQt5==5.15.4
|
||||
PyQt5==5.15.11
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.12.2
|
||||
PyQt5-sip==12.18.0
|
||||
PyQtWebEngine==5.15.6
|
||||
QScintilla>=2.14.1
|
||||
pyqtgraph>=0.12.1
|
||||
matplotlib>=3.7.1
|
||||
matplotlib==3.8.3
|
||||
numpy>=1.24.2
|
||||
colorama>=0.4.3
|
||||
pyinstaller>=5.11.0
|
||||
@@ -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" "python-psutil"))
|
||||
@@ -46,6 +46,10 @@ copy /y ..\mage8-windows\mage_extraire.exe pamhyr\mage8\
|
||||
copy /y ..\mage8-windows\mailleurTT.exe pamhyr\mage8\
|
||||
copy /y ..\mage8-windows\libbief.dll pamhyr\mage8\
|
||||
|
||||
rem rubar
|
||||
mkdir pamhyr\rubar
|
||||
copy /y ..\rubar-windows\rubarbe.exe pamhyr\rubar\
|
||||
|
||||
rem adists
|
||||
mkdir pamhyr\adists
|
||||
copy /y ..\adists-windows\adists.exe pamhyr\adists\
|
||||
@@ -68,14 +72,24 @@ copy /y ..\doc\dev\images\* pamhyr\doc\images
|
||||
copy /y ..\doc\images\* pamhyr\doc\images
|
||||
|
||||
rem Tuto
|
||||
mkdir pamhyr\doc\Tuto1\
|
||||
mkdir pamhyr\doc\Tuto1\data
|
||||
copy /y ..\doc\users\Tuto1\Hogneau_Engees2022.pdf pamhyr\doc\Tuto1\Hogneau_Engees2022.pdf
|
||||
copy /y ..\doc\users\Tuto1\step-by-step.pdf pamhyr\doc\Tuto1\Tuto1-en.pdf
|
||||
copy /y ..\doc\users\Tuto1\pas-a-pas.pdf pamhyr\doc\Tuto1\Tuto1-fr.pdf
|
||||
copy /y ..\doc\users\Tuto1\*.pamhyr pamhyr\doc\Tuto1\
|
||||
copy /y ..\doc\users\Tuto1\data\* pamhyr\doc\Tuto1\data\
|
||||
mkdir pamhyr\doc\TP_Hydraulique_Hogneau\
|
||||
mkdir pamhyr\doc\TP_Hydraulique_Hogneau\data
|
||||
copy /y ..\doc\users\TP_Hydraulique_Hogneau\Hogneau_Engees2022.pdf pamhyr\doc\TP_Hydraulique_Hogneau\Hogneau_Engees2022.pdf
|
||||
copy /y ..\doc\users\TP_Hydraulique_Hogneau\step-by-step.pdf pamhyr\doc\TP_Hydraulique_Hogneau\Tuto1-en.pdf
|
||||
copy /y ..\doc\users\TP_Hydraulique_Hogneau\pas-a-pas.pdf pamhyr\doc\TP_Hydraulique_Hogneau\Tuto1-fr.pdf
|
||||
copy /y ..\doc\users\TP_Hydraulique_Hogneau\Tuto-2-fr.pdf pamhyr\doc\TP_Hydraulique_Hogneau\Tuto2-fr.pdf
|
||||
copy /y ..\doc\users\TP_Hydraulique_Hogneau\*.pamhyr pamhyr\doc\TP_Hydraulique_Hogneau\
|
||||
copy /y ..\doc\users\TP_Hydraulique_Hogneau\data\* pamhyr\doc\TP_Hydraulique_Hogneau\data\
|
||||
|
||||
rem TP charriage
|
||||
mkdir pamhyr\doc\TP_charriage
|
||||
copy /y ..\doc\users\TP_charriage\ pamhyr\doc\TP_charriage
|
||||
mkdir pamhyr\doc\TP_charriage\Donnees_TP_Pamhyr2_charriage
|
||||
copy /y ..\doc\users\TP_charriage\TP_Pamhyr2_charriage.pdf pamhyr\doc\TP_charriage\
|
||||
copy /y ..\doc\users\TP_charriage\Donnees_TP_Pamhyr2_charriage\* pamhyr\doc\TP_charriage\Donnees_TP_Pamhyr2_charriage\
|
||||
|
||||
rem TP AdisTS
|
||||
mkdir pamhyr\doc\TP_AdisTS_Vieux_Rhone\
|
||||
mkdir pamhyr\doc\TP_AdisTS_Vieux_Rhone\data
|
||||
copy /y ..\doc\users\TP_AdisTS_Vieux_Rhone\*.pdf pamhyr\doc\TP_AdisTS_Vieux_Rhone\
|
||||
copy /y ..\doc\users\TP_AdisTS_Vieux_Rhone\*.pamhyr pamhyr\doc\TP_AdisTS_Vieux_Rhone\
|
||||
copy /y ..\doc\users\TP_AdisTS_Vieux_Rhone\data\* pamhyr\doc\TP_AdisTS_Vieux_Rhone\data\
|
||||
+6
-3
@@ -1,10 +1,10 @@
|
||||
PyQt5==5.15.4
|
||||
PyQt5==5.15.11
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.12.2
|
||||
PyQt5-sip==12.18.0
|
||||
#PyQtWebEngine==5.15.6
|
||||
QScintilla>=2.14.1
|
||||
pyqtgraph>=0.12.1
|
||||
matplotlib>=3.7.1
|
||||
matplotlib==3.8.3
|
||||
numpy>=1.24.2
|
||||
colorama>=0.4.3
|
||||
pyinstaller>=5.11.0
|
||||
@@ -12,3 +12,6 @@ shapely>=2.0.1
|
||||
lxml>=4.9.3
|
||||
platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
rasterio==1.3.11
|
||||
#fortranformat==2.0.3
|
||||
psutil>=7.2.2
|
||||
@@ -1,5 +1,5 @@
|
||||
# Adists.py -- Pamhyr study checkers
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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-2026 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-2026 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-2026 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-2026 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,364 @@
|
||||
# Internal.py -- Pamhyr
|
||||
# Copyright (C) 2023-2026 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):
|
||||
|
||||
# pre process
|
||||
|
||||
profiles = self.get_profiles(reach, limites)
|
||||
if profiles is None:
|
||||
return []
|
||||
|
||||
for p in profiles:
|
||||
if len(p) < 1:
|
||||
return []
|
||||
|
||||
guide_list = self.get_guide_list(reach, profiles)
|
||||
if guide_list is None:
|
||||
return []
|
||||
|
||||
# we never modify original profiles, we work on the copies
|
||||
m_profiles = self.st_to_m(profiles, guide_list)
|
||||
new_profiles = self.interpolate_transversal_step(m_profiles, step)
|
||||
|
||||
for new_profiles2 in new_profiles:
|
||||
for p in new_profiles2:
|
||||
p.hard_purge_aligned_points()
|
||||
|
||||
return new_profiles
|
||||
|
||||
def st_to_m(self, profiles, guide_list):
|
||||
guide_list = ["un"] + guide_list + ["np"]
|
||||
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):
|
||||
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]:
|
||||
max_values[i] = index2 - index1
|
||||
max_values_index[i] = 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],
|
||||
guide_list[i],
|
||||
guide_list[i+1])
|
||||
|
||||
for isect in reversed(range(0, max_values_index[i])):
|
||||
self.compl_sect(profiles[isect+1],
|
||||
profiles[isect],
|
||||
guide_list[i],
|
||||
guide_list[i+1])
|
||||
|
||||
return profiles
|
||||
|
||||
def interpolate_transversal_step(self,
|
||||
profiles,
|
||||
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
|
||||
|
||||
start1 = sect1.named_point_index(tag1)
|
||||
start2 = sect2.named_point_index(tag1)
|
||||
|
||||
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, 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)
|
||||
for i in range(len2):
|
||||
ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1))
|
||||
beta.append(ltot2)
|
||||
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)
|
||||
len2 += 1
|
||||
elif ltot1 < 0.0001:
|
||||
sect2.add_npoints(len1-len2)
|
||||
len2 = len1
|
||||
else: # regular case
|
||||
alpha = list(map(lambda x: x/ltot1, alpha)) # target ratios
|
||||
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
|
||||
for i in range(len1 - len2):
|
||||
beta.append(1.0)
|
||||
beta.append(0.0) # beta[-1]
|
||||
# loop on the points to insert
|
||||
for k in range(len1-len2):
|
||||
trouve = False
|
||||
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:
|
||||
logger.warning(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 = ''
|
||||
if tag1 != "un":
|
||||
sect2.point(start2).name = tag1
|
||||
if tag2 != "np":
|
||||
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
|
||||
if abs(sgn) < 0.5:
|
||||
sgn = 1.0
|
||||
|
||||
new_rk = [0.0]*nprof
|
||||
new_rk[origin] = origin_value
|
||||
|
||||
frictions = reach._parent.frictions
|
||||
|
||||
old_begin_rk = list(map(lambda x: x * old_orientation,
|
||||
begin_rk))
|
||||
old_end_rk = list(map(lambda x: x * old_orientation,
|
||||
end_rk))
|
||||
old_rk = list(map(lambda x: x * old_orientation,
|
||||
reach.get_rk()))
|
||||
|
||||
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
|
||||
|
||||
def get_guide_list(self, reach, profiles):
|
||||
|
||||
gl = reach.compute_guidelines()
|
||||
|
||||
# we make sure that the lines are in the left-to-right order
|
||||
guide_list = [
|
||||
[
|
||||
x.name
|
||||
for x in p.named_points()
|
||||
if x.name in gl[0]
|
||||
]
|
||||
for p in profiles]
|
||||
|
||||
# we make sure the lines never cross
|
||||
for gl in guide_list:
|
||||
for i in range(len(guide_list[0])):
|
||||
if gl[i] != guide_list[0][i]:
|
||||
return None
|
||||
|
||||
return guide_list[0]
|
||||
|
||||
def get_profiles(self, reach, limites):
|
||||
|
||||
if reach is None or len(reach.profiles) == 0:
|
||||
return None
|
||||
|
||||
if limites[0] > limites[1]:
|
||||
lim = limites[1]
|
||||
limites[1] = limites[0]
|
||||
limites[0] = lim
|
||||
elif limites[0] == limites[1]:
|
||||
return None
|
||||
|
||||
profiles = [reach.profile(i).copy()
|
||||
for i in range(limites[0], limites[1]+1)
|
||||
]
|
||||
|
||||
return profiles
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
# Mage.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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-2026 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-2026 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-2026 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,79 @@ 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)
|
||||
d50 REAL DEFAULT 0.002,
|
||||
sigma REAL DEFAULT 1,
|
||||
{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"
|
||||
)
|
||||
|
||||
if major == "0" and int(minor) <= 2:
|
||||
if int(release) < 4:
|
||||
execute(
|
||||
"ALTER TABLE boundary_condition " +
|
||||
"ADD COLUMN d50 REAL DEFAULT 0.002"
|
||||
)
|
||||
execute(
|
||||
"ALTER TABLE boundary_condition " +
|
||||
"ADD COLUMN sigma REAL DEFAULT 1"
|
||||
)
|
||||
|
||||
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 +321,132 @@ 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, d50, sigma, " +
|
||||
"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)
|
||||
d50 = next(it)
|
||||
sigma = 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()
|
||||
|
||||
if t == "SL":
|
||||
bc.d50 = d50
|
||||
bc.sigma = sigma
|
||||
|
||||
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 = (
|
||||
d50 = 0.002
|
||||
sigma = 1
|
||||
if self._type == "SL":
|
||||
d50 = self._d50
|
||||
sigma = self._sigma
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"boundary_condition(id, name, type, tab, node) " +
|
||||
"boundary_condition(" +
|
||||
"pamhyr_id, deleted, name, type, tab, node, d50, sigma, 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"{d50}, {sigma}, " +
|
||||
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 +471,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,18 +491,41 @@ 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
|
||||
|
||||
@property
|
||||
def d50(self):
|
||||
return self._d50
|
||||
|
||||
@d50.setter
|
||||
def d50(self, value):
|
||||
self._d50 = float(value)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def sigma(self):
|
||||
return self._sigma
|
||||
|
||||
@sigma.setter
|
||||
def sigma(self, value):
|
||||
self._sigma = float(value)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self._header.copy()
|
||||
|
||||
@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 +559,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 +640,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 +675,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-2026 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-2026 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-2026 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,194 @@ 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 minor == "0":
|
||||
if int(release) < 11:
|
||||
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 scenario = {scenario.id} " +
|
||||
# f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
|
||||
f"AND bca = {bca.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, deleted, data0, data1, bca, scenario) " +
|
||||
f"VALUES ({self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{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,119 +222,229 @@ 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) < 2:
|
||||
if cls.is_table_exists(execute, "boundary_condition_adists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
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
|
||||
|
||||
if pol_id not in pid_pol:
|
||||
# ⚠️ cas important : probablement déjà migré
|
||||
logger.warning(
|
||||
f"pol_id {pol_id} not in pid_pol " +
|
||||
"→ probably already migrated"
|
||||
)
|
||||
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"]
|
||||
pollutant = data.get("pollutant")
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
table = execute(
|
||||
"SELECT id, pollutant, type, node " +
|
||||
"FROM boundary_condition_adists"
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
sql = (
|
||||
"SELECT pamhyr_id, deleted, pollutant, type, node, scenario "
|
||||
"FROM boundary_condition_adists " +
|
||||
f"WHERE scenario = {scenario.id} "
|
||||
)
|
||||
if pollutant is not None:
|
||||
sql += f"AND pollutant = {pollutant.id} "
|
||||
|
||||
table = execute(sql)
|
||||
|
||||
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)
|
||||
if node != -1:
|
||||
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)
|
||||
return len(
|
||||
list(
|
||||
filter(
|
||||
lambda el: el is not None and not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def time_convert(cls, data):
|
||||
@@ -190,7 +469,7 @@ class BoundaryConditionAdisTS(SQLSubModel):
|
||||
@node.setter
|
||||
def node(self, node):
|
||||
self._node = node
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
@@ -199,7 +478,7 @@ class BoundaryConditionAdisTS(SQLSubModel):
|
||||
@header.setter
|
||||
def header(self, header):
|
||||
self._header = header
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
@@ -208,7 +487,7 @@ class BoundaryConditionAdisTS(SQLSubModel):
|
||||
@pollutant.setter
|
||||
def pollutant(self, pollutant):
|
||||
self._pollutant = pollutant
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
@@ -217,8 +496,95 @@ class BoundaryConditionAdisTS(SQLSubModel):
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
self._type = type
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
return list(
|
||||
filter(
|
||||
lambda el: el is not None and not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
|
||||
@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 = Data(
|
||||
self._default_0, self._default_1,
|
||||
status=self._status
|
||||
)
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self.data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self.modified()
|
||||
|
||||
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 = self._data[index]
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = v
|
||||
self.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-2026 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 \
|
||||
@@ -38,21 +38,31 @@ class BoundaryConditionsAdisTSList(PamhyrModelList):
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = BoundaryConditionAdisTS._db_load(
|
||||
execute, data
|
||||
)
|
||||
previous_pollutant = data.get("pollutant")
|
||||
data.pop("pollutant", None)
|
||||
|
||||
new._lst = BoundaryConditionAdisTS._db_load(execute, data)
|
||||
|
||||
if previous_pollutant is not None:
|
||||
data["pollutant"] = previous_pollutant
|
||||
|
||||
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-2026 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,160 @@ 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:
|
||||
if int(release) < 6:
|
||||
created = False
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "d90_adists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
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 +195,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 +215,7 @@ class D90AdisTS(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def d90(self):
|
||||
@@ -160,7 +224,7 @@ class D90AdisTS(SQLSubModel):
|
||||
@d90.setter
|
||||
def d90(self, d90):
|
||||
self._d90 = d90
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -169,28 +233,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-2026 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-2026 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(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "d90_spec"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data):
|
||||
table = "d90_spec"
|
||||
table_new = "d90_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, d90_default, name, reach, " +
|
||||
" start_rk, end_rk, d90, enabled, scenario) " +
|
||||
"SELECT pamhyr_id, d90_default, name, reach, " +
|
||||
" start_rk, end_rk, 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)
|
||||
cls._db_update_to_0_2_0_set_reach_pid(execute, table_new, reachs)
|
||||
|
||||
@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_default 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_default = {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.set_as_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-2026 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,59 +53,98 @@ 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:
|
||||
if int(release) < 6:
|
||||
created = False
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "dif_adists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
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)
|
||||
|
||||
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=dif_id,
|
||||
id=pid,
|
||||
name=name,
|
||||
status=data['status']
|
||||
status=status,
|
||||
owner_scenario=owner_scenario,
|
||||
)
|
||||
|
||||
DIF.method = method
|
||||
@@ -116,15 +153,27 @@ class DIFAdisTS(SQLSubModel):
|
||||
DIF.c = c
|
||||
DIF.enabled = enabled
|
||||
|
||||
data['dif_default_id'] = dif_id
|
||||
data['dif_default_id'] = pid
|
||||
DIF._data = DIFAdisTSSpec._db_load(execute, data)
|
||||
|
||||
loaded.add(pid)
|
||||
new.append(DIF)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
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:
|
||||
@@ -139,18 +188,19 @@ class DIFAdisTS(SQLSubModel):
|
||||
b = self.b
|
||||
|
||||
c = -1.
|
||||
if self.dif is not None:
|
||||
if self.c is not None:
|
||||
c = self.c
|
||||
|
||||
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}, {b}, {c}, {self._enabled}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
@@ -158,8 +208,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 +218,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 +238,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
@@ -186,7 +247,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@method.setter
|
||||
def method(self, method):
|
||||
self._method = method
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
@@ -199,7 +260,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@dif.setter
|
||||
def dif(self, dif):
|
||||
self._dif = dif
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
@@ -208,7 +269,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@b.setter
|
||||
def b(self, b):
|
||||
self._b = b
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
@@ -217,7 +278,7 @@ class DIFAdisTS(SQLSubModel):
|
||||
@c.setter
|
||||
def c(self, c):
|
||||
self._c = c
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -226,28 +287,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-2026 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-2026 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()
|
||||
|
||||
@@ -32,16 +33,14 @@ class DIFAdisTSSpec(SQLSubModel):
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, method: str = "",
|
||||
status=None):
|
||||
super(DIFAdisTSSpec, self).__init__()
|
||||
status=None, owner_scenario=-1):
|
||||
super(DIFAdisTSSpec, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = DIFAdisTSSpec._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
self._method = method
|
||||
self._reach = None
|
||||
self._start_rk = None
|
||||
@@ -51,66 +50,134 @@ class DIFAdisTSSpec(SQLSubModel):
|
||||
self._c = None
|
||||
self._enabled = True
|
||||
|
||||
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(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "dif_spec"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
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,28 +187,35 @@ class DIFAdisTSSpec(SQLSubModel):
|
||||
new_spec.c = c
|
||||
new_spec.enabled = enabled
|
||||
|
||||
loaded.add(id)
|
||||
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 = (
|
||||
"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._enabled}" +
|
||||
f"{self._start_rk}, {self._end_rk}, " +
|
||||
f"{self._dif}, {self._b}, {self._c}, " +
|
||||
f"{self._enabled}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
# Ensemble.py -- Pamhyr
|
||||
# Copyright (C) 2026 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 struct
|
||||
import logging
|
||||
import itertools
|
||||
|
||||
import numpy as np
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Scenario import Scenario
|
||||
from Model.Ensembles.Function import Uniform
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Ensemble(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
function=None, params=[0., 100.],
|
||||
data_type=None, target_data=None,
|
||||
status=None, owner_scenario=-1):
|
||||
super(Ensemble, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._data_type = data_type
|
||||
self._function = function
|
||||
self._params = params
|
||||
self._target_data = target_data
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE ensemble{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
type VARCHAR(8) NOT NULL,
|
||||
function INTEGER NOT NULL,
|
||||
function_name VARCHAR(32) NOT NULL,
|
||||
params BLOB NOT NULL,
|
||||
params_len INTEGER NOT NULL,
|
||||
data_pid INTEGER 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, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == "0" and int(minor) < 2:
|
||||
cls._db_create(execute)
|
||||
elif major == "0" and int(minor) == 2:
|
||||
if int(release) < 8:
|
||||
cls._db_create(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
scenario = data["scenario"]
|
||||
functions = data["ens_functions"]
|
||||
stricklers = data["stricklers"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, type, " +
|
||||
"function, function_name, params, params_len, data_pid, " +
|
||||
"scenario " +
|
||||
"FROM ensemble " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
|
||||
id = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
data_type = next(it)
|
||||
fid = next(it)
|
||||
fname = next(it)
|
||||
params = next(it)
|
||||
params_len = next(it)
|
||||
data_pid = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
def fn(f): return f._name == fname
|
||||
if fid != -1:
|
||||
def fn(f): return f._pamhyr_id == fid
|
||||
|
||||
function = next(
|
||||
filter(fn, functions.lst),
|
||||
None
|
||||
)
|
||||
|
||||
target_data = next(
|
||||
filter(
|
||||
lambda s: s._pamhyr_id == data_pid,
|
||||
stricklers.lst
|
||||
),
|
||||
None
|
||||
)
|
||||
|
||||
rdata = cls._decode_params(params, params_len)
|
||||
|
||||
new_ensemble = cls(
|
||||
id, name=name,
|
||||
function=function,
|
||||
params=rdata,
|
||||
data_type=data_type,
|
||||
target_data=target_data,
|
||||
status=data["status"],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
new_ensemble.set_as_deleted()
|
||||
|
||||
loaded.add(id)
|
||||
new.append(new_ensemble)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"DELETE FROM ensemble " +
|
||||
f"WHERE pamhyr_id = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
bparams, length = self._encode_params()
|
||||
|
||||
if self._function is None:
|
||||
fid = -1
|
||||
fname = "NULL"
|
||||
else:
|
||||
fid = self._function._pamhyr_id
|
||||
fname = self._function._name
|
||||
|
||||
if self._function._type == "generic":
|
||||
fid = -1
|
||||
|
||||
data_pid = -1
|
||||
if self._target_data is not None:
|
||||
data_pid = self._target_data._pamhyr_id
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"ensemble(pamhyr_id, deleted, name, type, " +
|
||||
" function, function_name, params, params_len, " +
|
||||
" data_pid, scenario) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
self.pamhyr_id, self.is_deleted(),
|
||||
self._name, self._data_type,
|
||||
fid, fname, bparams, length,
|
||||
data_pid, self._status.scenario_id
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _encode_params(self):
|
||||
length = len(self._params)
|
||||
data_format = ">" + ''.join(itertools.repeat("d", length))
|
||||
|
||||
return struct.pack(data_format, *self._params), length
|
||||
|
||||
@classmethod
|
||||
def _decode_params(cls, bparams, length):
|
||||
data_format = ">" + ''.join(itertools.repeat("d", length))
|
||||
|
||||
return struct.unpack(data_format, bparams)
|
||||
|
||||
def get_sample(self, nb):
|
||||
return np.nditer(
|
||||
self._function.get_sample(
|
||||
self._params, nb
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
return self._function
|
||||
|
||||
@function.setter
|
||||
def function(self, function):
|
||||
self._function = function
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
return self._params
|
||||
|
||||
@params.setter
|
||||
def params(self, params):
|
||||
self._params = params
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def data_type(self):
|
||||
return self._data_type
|
||||
|
||||
@data_type.setter
|
||||
def data_type(self, data_type):
|
||||
self._data_type = data_type
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def target_data(self):
|
||||
return self._target_data
|
||||
|
||||
@target_data.setter
|
||||
def target_data(self, target_data):
|
||||
self._target_data = target_data
|
||||
self._status.modified()
|
||||
@@ -0,0 +1,53 @@
|
||||
# EnsembleList.py -- Pamhyr
|
||||
# Copyright (C) 2026 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Ensembles.Ensemble import Ensemble
|
||||
|
||||
|
||||
class EnsembleList(PamhyrModelList):
|
||||
_sub_classes = [Ensemble]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
new._lst = Ensemble._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(
|
||||
"DELETE FROM ensemble " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for ens in self._lst:
|
||||
ens._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index):
|
||||
r = Ensemble(status=self._status)
|
||||
self._lst.insert(index, r)
|
||||
self._status.modified()
|
||||
return r
|
||||
@@ -0,0 +1,188 @@
|
||||
# Function.py -- Pamhyr
|
||||
# Copyright (C) 2026 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 struct
|
||||
import logging
|
||||
import itertools
|
||||
import numpy as np
|
||||
|
||||
from tools import logger_exception
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Function(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "", script: str = "",
|
||||
status=None):
|
||||
super(Function, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=0
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._type = "generic"
|
||||
self._script = script
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE ensemble_function{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
script TEXT NOT NULL,
|
||||
PRIMARY KEY(pamhyr_id)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == "0" and int(minor) < 2:
|
||||
cls._db_create(execute)
|
||||
elif major == "0" and int(minor) == 2:
|
||||
if int(release) < 8:
|
||||
cls._db_create(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, script " +
|
||||
"FROM ensemble_function " +
|
||||
f"WHERE pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
|
||||
id = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
script = next(it)
|
||||
|
||||
new_function = cls(
|
||||
id, name=name, script=script,
|
||||
status=data["status"]
|
||||
)
|
||||
if deleted:
|
||||
new_function.set_as_deleted()
|
||||
|
||||
loaded.add(id)
|
||||
new.append(new_function)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
# Only save custom function
|
||||
if self._type != "custom":
|
||||
return True
|
||||
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"ensemble_function(pamhyr_id, deleted, " +
|
||||
" name, script) " +
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
self.pamhyr_id, self.is_deleted(),
|
||||
self._name, self._script
|
||||
)
|
||||
|
||||
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 get_sample(self, r):
|
||||
raise NotImplementedMethodeError(cls, cls._load)
|
||||
|
||||
|
||||
###########################
|
||||
# FUNCTION IMPLEMENTATION #
|
||||
###########################
|
||||
|
||||
class Uniform(Function):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "uniform", script: str = "",
|
||||
status=None):
|
||||
super(Uniform, self).__init__(
|
||||
id=id, status=status,
|
||||
name=name, script=script
|
||||
)
|
||||
|
||||
def get_sample(self, ens_range, nb):
|
||||
b = ens_range[0]
|
||||
t = ens_range[1]
|
||||
|
||||
step = (t - b) / nb
|
||||
|
||||
r = np.arange(b, t, step)
|
||||
np.random.shuffle(r)
|
||||
|
||||
return r
|
||||
|
||||
class Custom(Function):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "", script: str = "",
|
||||
status=None):
|
||||
super(Custom, self).__init__(
|
||||
id=id, status=status,
|
||||
name=name, script=script
|
||||
)
|
||||
|
||||
self._type = "custom"
|
||||
|
||||
def get_sample(self, ens_range, nb):
|
||||
try:
|
||||
# Run script to (re)define the sample function
|
||||
f = eval(self._script)
|
||||
return f(ens_range, nb)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return None # TODO: Raise helpful exception
|
||||
@@ -0,0 +1,58 @@
|
||||
# FunctionList.py -- Pamhyr
|
||||
# Copyright (C) 2026 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Ensembles.Function import *
|
||||
|
||||
|
||||
class FunctionList(PamhyrModelList):
|
||||
_sub_classes = [Function]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
# Default functions
|
||||
new._lst = [
|
||||
Uniform(status=data['status']),
|
||||
]
|
||||
|
||||
# DB custom functions
|
||||
new._lst += Function._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data={}):
|
||||
execute(
|
||||
"DELETE FROM ensemble_function"
|
||||
)
|
||||
|
||||
for fun in self._lst:
|
||||
fun._db_save(execute, data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index):
|
||||
r = Custom(status=self._status)
|
||||
self._lst.insert(index, r)
|
||||
self._status.modified()
|
||||
return r
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Except.py -- Pamhyr model exceptions
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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
|
||||
|
||||
+194
-84
@@ -1,5 +1,5 @@
|
||||
# Friction.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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,165 @@ 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 is not None
|
||||
and (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 +250,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 +285,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 +293,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 +310,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
|
||||
@@ -261,3 +365,9 @@ class Friction(SQLSubModel):
|
||||
# self.end_strickler.medium])
|
||||
|
||||
return minor, medium
|
||||
|
||||
def update_rks(self, begin_rk, end_rk):
|
||||
self._begin_rk = begin_rk
|
||||
self._end_rk = end_rk
|
||||
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# FrictionList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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
|
||||
@@ -0,0 +1,368 @@
|
||||
# GeoTIFF.py -- Pamhyr
|
||||
# Copyright (C) 2024-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
try:
|
||||
import rasterio
|
||||
import rasterio.control
|
||||
import rasterio.crs
|
||||
import rasterio.sample
|
||||
import rasterio.vrt
|
||||
import rasterio._features
|
||||
|
||||
from rasterio.io import MemoryFile
|
||||
_rasterio_loaded = True
|
||||
except Exception as e:
|
||||
print(f"Module 'rasterio' is not available: {e}")
|
||||
_rasterio_loaded = False
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class GeoTIFF(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, enabled=True,
|
||||
name="", description="",
|
||||
path="", coordinates=None,
|
||||
status=None, owner_scenario=-1):
|
||||
super(GeoTIFF, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._enabled = enabled
|
||||
self._name = f"GeoTIFF #{self._pamhyr_id}" if name == "" else name
|
||||
self._description = description
|
||||
|
||||
self._file_bytes = b''
|
||||
if coordinates is None:
|
||||
self._coordinates = {
|
||||
"bottom": 0.0,
|
||||
"top": 0.0,
|
||||
"left": 0.0,
|
||||
"right": 0.0,
|
||||
}
|
||||
else:
|
||||
self._coordinates = coordinates
|
||||
self._file_name = ""
|
||||
|
||||
if path != "":
|
||||
self.read_file(path)
|
||||
|
||||
self._memfile = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = None
|
||||
|
||||
if key == "enabled":
|
||||
value = self._enabled
|
||||
elif key == "name":
|
||||
value = self.name
|
||||
elif key == "description":
|
||||
value = self.description
|
||||
elif key == "file_name":
|
||||
value = self.file_name
|
||||
elif key == "coordinates":
|
||||
value = self.coordinates
|
||||
elif key == "coordinates_bottom":
|
||||
value = self.coordinates["bottom"]
|
||||
elif key == "coordinates_top":
|
||||
value = self.coordinates["top"]
|
||||
elif key == "coordinates_left":
|
||||
value = self.coordinates["left"]
|
||||
elif key == "coordinates_right":
|
||||
value = self.coordinates["right"]
|
||||
elif key == "memfile":
|
||||
value = self.memfile
|
||||
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == "enabled":
|
||||
self.enabled = value
|
||||
elif key == "name":
|
||||
self.name = value
|
||||
elif key == "description":
|
||||
self.description = value
|
||||
elif key == "file_name":
|
||||
if self._file_name != value:
|
||||
self.read_file(value)
|
||||
elif key == "coordinates":
|
||||
self.coordinates = value
|
||||
elif key == "coordinates_bottom" or key == "bottom":
|
||||
self.coordinates["bottom"] = value
|
||||
elif key == "coordinates_top" or key == "top":
|
||||
self.coordinates["top"] = value
|
||||
elif key == "coordinates_left" or key == "left":
|
||||
self.coordinates["left"] = value
|
||||
elif key == "coordinates_right" or key == "right":
|
||||
self.coordinates["right"] = value
|
||||
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self.modified()
|
||||
|
||||
def is_enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, description):
|
||||
self._description = description
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def file_name(self):
|
||||
return self._file_name
|
||||
|
||||
@file_name.setter
|
||||
def file_name(self, file_name):
|
||||
self._file_name = file_name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def coordinates(self):
|
||||
return self._coordinates
|
||||
|
||||
@coordinates.setter
|
||||
def coordinates(self, coordinates):
|
||||
self._coordinates = coordinates
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def coord_bottom(self):
|
||||
if self._coordinates is None:
|
||||
return 0.0
|
||||
|
||||
return self._coordinates["bottom"]
|
||||
|
||||
@property
|
||||
def coord_top(self):
|
||||
if self._coordinates is None:
|
||||
return 0.0
|
||||
|
||||
return self._coordinates["top"]
|
||||
|
||||
@property
|
||||
def coord_left(self):
|
||||
if self._coordinates is None:
|
||||
return 0.0
|
||||
|
||||
return self._coordinates["left"]
|
||||
|
||||
@property
|
||||
def coord_right(self):
|
||||
if self._coordinates is None:
|
||||
return 0.0
|
||||
|
||||
return self._coordinates["right"]
|
||||
|
||||
@property
|
||||
def memfile(self):
|
||||
if not _rasterio_loaded:
|
||||
return None
|
||||
|
||||
if self._file_bytes == b'':
|
||||
return None
|
||||
|
||||
if self._memfile is None:
|
||||
self._memfile = MemoryFile()
|
||||
self._memfile.write(self._file_bytes)
|
||||
|
||||
return self._memfile
|
||||
|
||||
def read_file(self, path):
|
||||
logger.debug(f"Read GeoTIFF file at : '{path}'")
|
||||
|
||||
self._file_name = path
|
||||
self._file_bytes = b''
|
||||
self._memfile = None
|
||||
|
||||
nbytes = 0
|
||||
|
||||
with open(path, "rb") as f:
|
||||
while True:
|
||||
data = f.read(4096)
|
||||
if not data:
|
||||
break
|
||||
|
||||
nbytes += len(data)
|
||||
self._file_bytes += data
|
||||
|
||||
logger.debug(f"Read GeoTIFF: {nbytes} bytes readed")
|
||||
|
||||
def write_file(self, path):
|
||||
with open(path, "w+b") as f:
|
||||
f.write(self._file_bytes)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE geotiff{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
enabled BOOLEAN NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
file_bytes BLOB NOT NULL,
|
||||
coordinates_bottom REAL NOT NULL,
|
||||
coordinates_top REAL NOT NULL,
|
||||
coordinates_left REAL NOT NULL,
|
||||
coordinates_right REAL NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
cls._db_create(execute)
|
||||
|
||||
if major == "0" and int(minor) == 2:
|
||||
if int(release) < 3:
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, enabled, deleted, " +
|
||||
"name, description, file_name, file_bytes, " +
|
||||
"coordinates_bottom, coordinates_top, " +
|
||||
"coordinates_left, coordinates_right, " +
|
||||
"scenario " +
|
||||
"FROM geotiff " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
|
||||
id = next(it)
|
||||
enabled = (next(it) == 1)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
description = next(it)
|
||||
file_name = next(it)
|
||||
file_bytes = next(it)
|
||||
coordinates_bottom = next(it)
|
||||
coordinates_top = next(it)
|
||||
coordinates_left = next(it)
|
||||
coordinates_right = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
f = cls(
|
||||
id=id, enabled=enabled, name=name,
|
||||
description=description, coordinates={
|
||||
"bottom": coordinates_bottom,
|
||||
"top": coordinates_top,
|
||||
"left": coordinates_left,
|
||||
"right": coordinates_right,
|
||||
},
|
||||
status=data['status'],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
f.set_as_deleted()
|
||||
|
||||
f._file_bytes = file_bytes
|
||||
|
||||
loaded.add(id)
|
||||
new.append(f)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT INTO geotiff (" +
|
||||
"pamhyr_id, enabled, deleted, " +
|
||||
"name, description, file_name, file_bytes, " +
|
||||
"coordinates_bottom, coordinates_top, " +
|
||||
"coordinates_left, coordinates_right, " +
|
||||
"scenario) " +
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
self._pamhyr_id,
|
||||
self._enabled,
|
||||
self.is_deleted(),
|
||||
self.name,
|
||||
self.description,
|
||||
self.file_name,
|
||||
self._file_bytes,
|
||||
self.coordinates['bottom'],
|
||||
self.coordinates['top'],
|
||||
self.coordinates['left'],
|
||||
self.coordinates['right'],
|
||||
self._status.scenario_id,
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,59 @@
|
||||
# GeoTIFFList.py -- Pamhyr
|
||||
# Copyright (C) 2024-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.GeoTIFF.GeoTIFF import GeoTIFF
|
||||
|
||||
|
||||
class GeoTIFFList(PamhyrModelList):
|
||||
_sub_classes = [GeoTIFF]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"])
|
||||
|
||||
new._lst = GeoTIFF._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute(
|
||||
"DELETE FROM geotiff " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for gt in self._lst:
|
||||
ok &= gt._db_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
@property
|
||||
def files(self):
|
||||
return self.lst
|
||||
|
||||
def new(self, index):
|
||||
n = GeoTIFF(status=self._status)
|
||||
self.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
@@ -1,5 +1,5 @@
|
||||
# Point.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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-2026 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
|
||||
|
||||
+237
-40
@@ -1,5 +1,5 @@
|
||||
# PointXYZ.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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,24 @@ 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)
|
||||
)
|
||||
""")
|
||||
|
||||
execute(
|
||||
"CREATE INDEX idx_point " +
|
||||
"ON geometry_pointXYZ(profile, 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 +85,181 @@ 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"
|
||||
)
|
||||
|
||||
if major == "0" and minor == "2":
|
||||
if int(release) < 5:
|
||||
execute(
|
||||
"CREATE INDEX idx_point " +
|
||||
"ON geometry_pointXYZ(profile, scenario);"
|
||||
)
|
||||
|
||||
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 +285,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 +330,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 +339,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 +348,17 @@ class PointXYZ(Point, SQLSubModel):
|
||||
@z.setter
|
||||
def z(self, value):
|
||||
self._z = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def shift(self, x, y, z):
|
||||
self.x += x
|
||||
self.y += y
|
||||
self.z += z
|
||||
|
||||
self.modified()
|
||||
|
||||
def get_coordinate(self):
|
||||
return (self._x, self._y, self._z)
|
||||
|
||||
def is_nan(self):
|
||||
"""
|
||||
@@ -226,5 +406,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-2026 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,51 @@ 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):
|
||||
if name == "un":
|
||||
return self.points[0]
|
||||
elif name == "np":
|
||||
return self.points[-1]
|
||||
elif name == "talweg":
|
||||
zm = self.z_min()
|
||||
return next((p for p in self.points if p.z == zm), None)
|
||||
else:
|
||||
return next((p for p in self.points if p.name == name), None)
|
||||
|
||||
def named_point_index(self, name):
|
||||
if name == "un":
|
||||
return 0
|
||||
elif name == "np":
|
||||
return len(self.points) - 1
|
||||
elif name == "talweg":
|
||||
zm = self.z_min()
|
||||
return next(
|
||||
(p for p in enumerate(self.points) if p[1].z == zm),
|
||||
None
|
||||
)[0]
|
||||
else:
|
||||
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 +118,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 +131,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 +144,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 +161,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 +174,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 +187,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 +200,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 +215,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 +256,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 +293,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
|
||||
|
||||
|
||||
+426
-144
@@ -1,5 +1,5 @@
|
||||
# ProfileXYZ.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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 trace, 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
|
||||
@@ -86,11 +91,15 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
self._station = []
|
||||
self.station_up_to_date = False
|
||||
|
||||
self._get_water_limits_cache = {}
|
||||
self._get_water_limits_ac_cache = {}
|
||||
|
||||
@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 +108,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,84 +143,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
|
||||
)
|
||||
|
||||
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
|
||||
@@ -238,6 +384,46 @@ 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()
|
||||
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(
|
||||
@@ -278,6 +464,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]
|
||||
|
||||
@@ -318,11 +510,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.
|
||||
@@ -381,11 +582,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.
|
||||
@@ -396,11 +599,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):
|
||||
@@ -414,6 +622,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"""
|
||||
return [x for x in lst if not np.isnan(x)]
|
||||
|
||||
@timer
|
||||
def speed(self, q, z):
|
||||
area = self.wet_area(z)
|
||||
|
||||
@@ -465,6 +674,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
length += line.length
|
||||
return length
|
||||
|
||||
@timer
|
||||
def compute_wet_area(self, z):
|
||||
area = 0.0
|
||||
if len(self.tab.L) > 0:
|
||||
@@ -505,6 +715,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
if len(line.coords) > 2:
|
||||
poly = geometry.Polygon(line)
|
||||
area += poly.area
|
||||
|
||||
return area
|
||||
|
||||
def wet_radius(self, z):
|
||||
@@ -527,6 +738,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
line = geometry.LineString(list(zip(station, zz)))
|
||||
return line
|
||||
|
||||
@timer
|
||||
def wet_lines(self, z):
|
||||
points = self._points
|
||||
if len(points) < 3:
|
||||
@@ -543,8 +755,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])
|
||||
|
||||
@@ -603,7 +815,6 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
return points
|
||||
|
||||
def get_nb_wet_areas(self, z):
|
||||
|
||||
n_zones = 0
|
||||
points = self._points
|
||||
if points[0].z <= z:
|
||||
@@ -615,10 +826,13 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
return n_zones
|
||||
|
||||
@timer
|
||||
def get_all_water_limits_ac(self, z):
|
||||
"""
|
||||
Determine all water limits for z elevation.
|
||||
"""
|
||||
if z in self._get_water_limits_ac_cache:
|
||||
return self._get_water_limits_ac_cache[z]
|
||||
|
||||
points = self._points
|
||||
if len(points) < 3:
|
||||
@@ -633,11 +847,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 = []
|
||||
@@ -646,82 +857,102 @@ 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):
|
||||
logger.error(f"ERROR in get_all_water_limits_ac")
|
||||
return [], []
|
||||
|
||||
return start, list(reversed(end))
|
||||
res = start, list(reversed(end))
|
||||
self._get_water_limits_ac_cache[z] = res
|
||||
|
||||
return res
|
||||
|
||||
@timer
|
||||
def get_water_limits(self, z):
|
||||
if z in self._get_water_limits_cache:
|
||||
return self._get_water_limits_cache[z]
|
||||
|
||||
v = self.get_water_limits_compute(z)
|
||||
|
||||
return v
|
||||
|
||||
def get_water_limits_compute(self, z):
|
||||
"""
|
||||
Determine left and right limits of water elevation.
|
||||
"""
|
||||
|
||||
# Get the index of first point with elevation lesser than water
|
||||
# elevation (for the right and left river side)
|
||||
i_left = -1
|
||||
i_right = -1
|
||||
i_left = next(
|
||||
filter(
|
||||
lambda i: self.point(i).z <= z,
|
||||
range(self.number_points)
|
||||
),
|
||||
-1
|
||||
)
|
||||
|
||||
for i in range(self.number_points):
|
||||
if self.point(i).z <= z:
|
||||
i_left = i
|
||||
break
|
||||
|
||||
for i in reversed(range(self.number_points)):
|
||||
if self.point(i).z <= z:
|
||||
i_right = i
|
||||
break
|
||||
i_right = next(
|
||||
filter(
|
||||
lambda i: self.point(i).z <= z,
|
||||
reversed(range(self.number_points))
|
||||
),
|
||||
-1
|
||||
)
|
||||
|
||||
# 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")
|
||||
pi = self.point(i_left)
|
||||
pim1 = self.point(i_left - 1)
|
||||
|
||||
if abs(pim1.z - pi.z) < 1e-20:
|
||||
pt_left = pi
|
||||
else:
|
||||
fact = (z - pi.z) / (pim1.z - pi.z)
|
||||
x = pi.x + fact * (pim1.x - pi.x)
|
||||
y = pi.y + fact * (pim1.y - pi.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")
|
||||
pi = self.point(i_right)
|
||||
pip1 = self.point(i_right + 1)
|
||||
|
||||
if abs(pip1.z - pi.z) < 1e-20:
|
||||
pt_right = pi
|
||||
else:
|
||||
fact = (z - pi.z) / (pip1.z - pi.z)
|
||||
x = pi.x + fact * (pip1.x - pi.x)
|
||||
y = pi.y + fact * (pip1.y - pi.y)
|
||||
pt_right = PointXYZ(x=x, y=y, z=z, name="wl_right")
|
||||
else:
|
||||
pt_right = self.point(self.number_points - 1)
|
||||
|
||||
return pt_left, pt_right
|
||||
v = (pt_left, pt_right)
|
||||
|
||||
# Put results in cache
|
||||
self._get_water_limits_cache[z] = v
|
||||
|
||||
# Create a generator to improve results data reading speed
|
||||
yield v
|
||||
|
||||
@timer
|
||||
def compute_tabulation(self):
|
||||
sorted_points = sorted(self._points, key=lambda p: p.z)
|
||||
|
||||
self.tab.z = np.array([p.z for p in sorted_points], np.float64)
|
||||
self.tab.L = np.zeros(len(self.tab.z), np.float64)
|
||||
self.tab.A = np.zeros(len(self.tab.z), np.float64)
|
||||
|
||||
for i in range(1, len(self.tab.z)):
|
||||
self.tab.L[i] = self.compute_wet_width(self.tab.z[i])
|
||||
|
||||
dx = (self.tab.L[i] + self.tab.L[i-1])/2
|
||||
dz = self.tab.z[i] - self.tab.z[i-1]
|
||||
|
||||
self.tab.A[i] = self.tab.A[i-1] + dz * dx
|
||||
|
||||
self.tab_up_to_date = True
|
||||
@@ -755,13 +986,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
|
||||
|
||||
@@ -832,54 +1063,105 @@ 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:
|
||||
p.x = p.x + x
|
||||
p.y = p.y + y
|
||||
p.z = p.z + z
|
||||
for p in self._points:
|
||||
p.shift(x, y, z)
|
||||
|
||||
def modified(self):
|
||||
self.tab_up_to_date = False
|
||||
self.station_up_to_date = False
|
||||
self.modified()
|
||||
|
||||
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(name=self.name,
|
||||
rk=self.rk,
|
||||
reach=self.reach,
|
||||
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()
|
||||
+168
-89
@@ -1,5 +1,5 @@
|
||||
# Reach.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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,71 @@ class Reach(SQLSubModel):
|
||||
self._guidelines_is_valid = False
|
||||
self._guidelines = {}
|
||||
|
||||
super(Reach, self).__init__(
|
||||
id=parent.pamhyr_id,
|
||||
status=status,
|
||||
)
|
||||
|
||||
@property
|
||||
def pamhyr_id(self):
|
||||
return self._parent.pamhyr_id
|
||||
|
||||
@property
|
||||
def _pamhyr_id(self):
|
||||
return self._parent.pamhyr_id
|
||||
|
||||
@_pamhyr_id.setter
|
||||
def _pamhyr_id(self, id):
|
||||
return
|
||||
|
||||
@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={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
for p in self._profiles:
|
||||
p._data_traversal(predicate, modifier, data)
|
||||
|
||||
def profile(self, i):
|
||||
"""Returns profile at index i
|
||||
@@ -111,31 +140,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 +179,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 +190,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,11 +205,38 @@ 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])
|
||||
profile.num = index
|
||||
else:
|
||||
gi = self.get_global_profil_index(index)
|
||||
profile.num = gi
|
||||
self._profiles.insert(gi, profile)
|
||||
|
||||
self.modified()
|
||||
|
||||
def delete(self, indexes):
|
||||
"""Set some elements as deleted in profile list
|
||||
|
||||
Args:
|
||||
indexes: The list of index to delete
|
||||
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
gi = [self.get_global_profil_index(i) for i in indexes]
|
||||
list(
|
||||
map(
|
||||
lambda p: p[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] in gi,
|
||||
enumerate(self._profiles)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def hard_delete(self, indexes):
|
||||
"""Delete some elements in profile list
|
||||
|
||||
Args:
|
||||
@@ -212,27 +245,20 @@ class Reach(SQLSubModel):
|
||||
Returns:
|
||||
Nothing.
|
||||
"""
|
||||
profiles = set(
|
||||
gi = [self.get_global_profil_index(i) for i in indexes]
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
lambda p: p[1],
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self.profiles)
|
||||
lambda e: e[0] not in gi,
|
||||
enumerate(self._profiles)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self._profiles = list(
|
||||
filter(
|
||||
lambda p: p not in profiles,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
self._update_profile_numbers()
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete_profiles(self, profiles):
|
||||
"""Delete some elements in profile list
|
||||
"""Set profiles list as deleted
|
||||
|
||||
Args:
|
||||
profiles: The list of profile to delete
|
||||
@@ -240,35 +266,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 +528,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 +569,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 +646,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 +664,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 +678,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 +694,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 +929,47 @@ 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
|
||||
|
||||
def get_local_profil_index(self, index):
|
||||
local_index = -1
|
||||
for i, p in enumerate(self._profiles):
|
||||
if not p.is_deleted():
|
||||
local_index += 1
|
||||
if i == index:
|
||||
break
|
||||
|
||||
return local_index
|
||||
|
||||
def get_global_profil_index(self, index):
|
||||
local_index = -1
|
||||
for i, p in enumerate(self._profiles):
|
||||
if not p.is_deleted():
|
||||
local_index += 1
|
||||
if local_index == index:
|
||||
global_index = i
|
||||
break
|
||||
|
||||
return global_index
|
||||
|
||||
def update_rks(self, rks):
|
||||
for rk, profile in zip(rks, self.profiles):
|
||||
profile.rk = rk
|
||||
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# Vector_1d.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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-2026 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,84 @@ 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
|
||||
)
|
||||
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 +228,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 +260,7 @@ class BasicHS(SQLSubModel):
|
||||
@type.setter
|
||||
def type(self, type):
|
||||
self._type = type
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -184,7 +269,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-2026 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-2026 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-2026 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,292 @@ 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}")
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
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 +330,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 +403,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 +412,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 +421,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 +458,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-2026 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-2026 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,56 +89,156 @@ 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
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
ind = data["ind"]
|
||||
|
||||
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 +248,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 +267,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 +281,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 +310,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 +326,7 @@ class Data(SQLSubModel):
|
||||
self._height = float(value)
|
||||
self._update_from_height()
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
|
||||
class InitialConditions(SQLSubModel):
|
||||
@@ -241,8 +347,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 +376,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
|
||||
@@ -282,50 +398,70 @@ class InitialConditions(SQLSubModel):
|
||||
|
||||
@reach.setter
|
||||
def reach(self, new):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
self._reach = new
|
||||
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 +477,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 +501,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,28 +517,40 @@ 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
|
||||
for f in frictions:
|
||||
if f.contains_rk(profile.rk):
|
||||
strickler = f.get_friction(profile.rk)[0]
|
||||
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_discharge:
|
||||
discharge = data_discharge[profile.rk]
|
||||
if profile.rk in data_discharge:
|
||||
discharge = data_discharge[profile.rk]
|
||||
else:
|
||||
discharge = 0.0
|
||||
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)))
|
||||
)
|
||||
|
||||
@@ -411,19 +565,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()
|
||||
profiles.reverse()
|
||||
if profiles is None:
|
||||
return None
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
@@ -434,29 +586,42 @@ 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"]
|
||||
|
||||
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
|
||||
for f in frictions:
|
||||
if f.contains_rk(profile.rk):
|
||||
strickler = f.get_friction(profile.rk)[0]
|
||||
|
||||
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]
|
||||
if profile.rk in data_height:
|
||||
height = data_height[profile.rk]
|
||||
else:
|
||||
height = 0.0
|
||||
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,
|
||||
@@ -469,15 +634,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,
|
||||
@@ -493,20 +656,23 @@ 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:
|
||||
d = data_discharge[profile.rk]
|
||||
if profile.rk in data_discharge:
|
||||
d = data_discharge[profile.rk]
|
||||
else:
|
||||
d = 0.0
|
||||
else:
|
||||
d = discharge
|
||||
elevation = interp(profile.rk,
|
||||
[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-2026 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-2026 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,155 @@ 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:
|
||||
if int(release) < 6:
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "initial_conditions_adists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
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 +228,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 +243,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:
|
||||
@@ -187,7 +263,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
@@ -196,7 +272,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@pollutant.setter
|
||||
def pollutant(self, pollutant):
|
||||
self._pollutant = pollutant
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def concentration(self):
|
||||
@@ -205,7 +281,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@concentration.setter
|
||||
def concentration(self, concentration):
|
||||
self._concentration = concentration
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def eg(self):
|
||||
@@ -214,7 +290,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@eg.setter
|
||||
def eg(self, eg):
|
||||
self._eg = eg
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def em(self):
|
||||
@@ -223,7 +299,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@em.setter
|
||||
def em(self, em):
|
||||
self._em = em
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def ed(self):
|
||||
@@ -232,7 +308,7 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@ed.setter
|
||||
def ed(self, ed):
|
||||
self._ed = ed
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -241,28 +317,45 @@ class InitialConditionsAdisTS(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = ICAdisTSSpec(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 @@
|
||||
# InitialConditionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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-2026 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,26 +22,20 @@ 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):
|
||||
status=None, owner_scenario=None):
|
||||
super(ICAdisTSSpec, self).__init__()
|
||||
|
||||
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,138 @@ 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(".")
|
||||
if major == 0 and minor < 1:
|
||||
if int(release) < 6:
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "initial_conditions_adists_spec"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
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 +190,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(pamhyr_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-2026 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)
|
||||
deleted = next(it)
|
||||
data0 = lc._types[0](next(it))
|
||||
data1 = lc._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-2026 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-2026 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-2026 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) < 2:
|
||||
if cls.is_table_exists(
|
||||
execute, "lateral_contribution_data_adists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
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
|
||||
|
||||
lca = data["lca"]
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, data0, data1, scenario " +
|
||||
"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)
|
||||
|
||||
lc = cls(
|
||||
data0, data1,
|
||||
id=pid, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
lc.set_as_deleted()
|
||||
|
||||
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
|
||||
|
||||
lca = data["lca"]
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution_data_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) < 2:
|
||||
if cls.is_table_exists(execute, "lateral_contribution_adists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
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(pamhyr_id, deleted, " +
|
||||
"pollutant, reach, begin_rk, end_rk, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._pollutant}, {self.edge}, " +
|
||||
f"{self._begin_rk}, {self._end_rk}" +
|
||||
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)
|
||||
|
||||
data["lca"] = 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 " +
|
||||
"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: el is not None and 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):
|
||||
@@ -199,7 +452,12 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
return list(
|
||||
filter(
|
||||
lambda el: el is not None and not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def begin_rk(self):
|
||||
@@ -208,7 +466,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 +475,58 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def _default_0(self):
|
||||
return self._types[0](0)
|
||||
|
||||
@property
|
||||
def _default_1(self):
|
||||
return self._types[1](0.0)
|
||||
|
||||
def add(self, index: int):
|
||||
value = Data(self._default_0, self._default_1, status=self._status)
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
return value
|
||||
|
||||
def insert(self, index: int, data: Data):
|
||||
self._data.insert(index, data)
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data = list(
|
||||
map(
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] not in indexes,
|
||||
enumerate(self.data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def index(self, bc):
|
||||
self._data.index(bc)
|
||||
|
||||
def get_i(self, index: int):
|
||||
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: int, column: int, value):
|
||||
v = self._data[index]
|
||||
v[column] = self._types[column](value)
|
||||
self._data[index] = v
|
||||
self.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 @@
|
||||
# LateralContributionsAdisTSList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-2026 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-2026 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-2026 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-2026 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-2026 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-2026 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-2026 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-2026 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,125 @@ 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(".")
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "OutputRKAdists"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
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-2026 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,57 +27,96 @@ from tools import (
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS import (
|
||||
BoundaryConditionAdisTS,
|
||||
)
|
||||
|
||||
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)
|
||||
self._enabled = True
|
||||
# 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,
|
||||
]
|
||||
|
||||
self._data = []
|
||||
def __len__(self):
|
||||
return len(self.to_list())
|
||||
|
||||
Pollutants._id_cnt = max(
|
||||
Pollutants._id_cnt + 1, self.id)
|
||||
def __getitem__(self, key):
|
||||
return self.to_list()[key]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
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
|
||||
self.propagate_modified()
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data.copy()
|
||||
def propagate_modified(self):
|
||||
self.modified()
|
||||
if hasattr(self, "_pollutant") and self._pollutant:
|
||||
self._pollutant.modified()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE Pollutants(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE Pollutants_characteristics(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
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,
|
||||
@@ -88,83 +127,391 @@ class Pollutants(SQLSubModel):
|
||||
ac REAL NOT NULL,
|
||||
bc REAL NOT NULL,
|
||||
pollutant INTEGER NOT NULL,
|
||||
FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(pollutant) REFERENCES pollutants(pamhyr_id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
return True
|
||||
|
||||
@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:
|
||||
if cls.is_table_exists(execute, "Pollutants_characteristics"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
|
||||
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 id, name " +
|
||||
f"FROM Pollutants"
|
||||
"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:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
it = iter(row)
|
||||
|
||||
new_pollutant = cls(
|
||||
id=id, name=name,
|
||||
status=status
|
||||
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()
|
||||
|
||||
new_data = []
|
||||
table = execute(
|
||||
"SELECT * " +
|
||||
"FROM Pollutants_characteristics " +
|
||||
f"WHERE pollutant = {id}"
|
||||
)
|
||||
loaded.add(pid)
|
||||
new.append(pc)
|
||||
|
||||
if table is not None:
|
||||
for t in table:
|
||||
new_data = t[1:-1]
|
||||
|
||||
new_pollutant._data.append(new_data)
|
||||
|
||||
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}")
|
||||
execute(
|
||||
"DELETE FROM pollutants_characteristics " +
|
||||
f"WHERE pamhyr_id = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"Pollutants(id, name) " +
|
||||
"pollutants_characteristics(pamhyr_id, deleted, " +
|
||||
"type, diametre, rho, porosity, " +
|
||||
"cdc_riv, cdc_cas, apd, ac, bc, " +
|
||||
"pollutant, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, " +
|
||||
f"'{self._db_format(self._name)}'" +
|
||||
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}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
return True
|
||||
|
||||
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})"
|
||||
|
||||
class Pollutants(SQLSubModel):
|
||||
_sub_classes = [
|
||||
PollutantCharacteristics,
|
||||
# BoundaryConditionAdisTS
|
||||
]
|
||||
|
||||
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 = []
|
||||
self._boundary_conditions_adists = []
|
||||
|
||||
@property
|
||||
def boundary_conditions_adists(self):
|
||||
return self._boundary_conditions_adists
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
self._data = [
|
||||
PollutantCharacteristics(
|
||||
type=int(data[0]),
|
||||
diametre=float(data[1]),
|
||||
rho=float(data[2]),
|
||||
porosity=float(data[3]),
|
||||
cdc_riv=float(data[4]),
|
||||
cdc_cas=float(data[5]),
|
||||
apd=float(data[6]),
|
||||
ac=float(data[7]),
|
||||
bc=float(data[8]),
|
||||
pollutant=self,
|
||||
status=self._status
|
||||
)
|
||||
execute(sql)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE pollutants{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
name TEXT 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, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
created = False
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
if cls.is_table_exists(execute, "Pollutants"):
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
else:
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
if major == "0" and minor == "2":
|
||||
if int(release) < 6:
|
||||
execute(f"ALTER TABLE pollutants " +
|
||||
f"ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT TRUE")
|
||||
|
||||
# cls._db_update_to_0_2_5(execute, data)
|
||||
# # created = True
|
||||
|
||||
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 pamhyr_id, deleted, enabled, name, scenario " +
|
||||
"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:
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
enabled = (next(it) == 1)
|
||||
name = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
new_pollutant = cls(
|
||||
id=pid, name=name,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
new_pollutant.set_as_deleted()
|
||||
if not enabled:
|
||||
new_pollutant.enabled = False
|
||||
|
||||
data["pollutant"] = new_pollutant
|
||||
new_pollutant._data = PollutantCharacteristics._db_load(
|
||||
execute, data=data
|
||||
)
|
||||
|
||||
new_pollutant._boundary_conditions_adists = (
|
||||
BoundaryConditionAdisTS._db_load(
|
||||
execute,
|
||||
data=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
|
||||
|
||||
ok = True
|
||||
|
||||
execute(
|
||||
"DELETE FROM pollutants " +
|
||||
f"WHERE pamhyr_id = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM pollutants_characteristics " +
|
||||
f"WHERE pollutant = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"pollutants(pamhyr_id, deleted, enabled, name, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"{self._db_format(self._enabled)}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
for d in self._data:
|
||||
ok &= d._db_save(execute, data)
|
||||
|
||||
for bc in self._boundary_conditions_adists:
|
||||
ok &= bc._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:
|
||||
d._data_traversal(predicate, modifier, data)
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
@@ -178,8 +525,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 +532,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
|
||||
@@ -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.Pollutants.Pollutants import Pollutants
|
||||
|
||||
|
||||
@@ -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
|
||||
Loaded 100 of 422 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user